vespertide_core/
sql_escape.rs1use std::borrow::Cow;
10
11#[must_use]
26pub fn escape_sql_string_literal(s: &str) -> Cow<'_, str> {
27 if s.contains('\'') {
28 Cow::Owned(s.replace('\'', "''"))
29 } else {
30 Cow::Borrowed(s)
31 }
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37
38 #[test]
39 fn empty_string_borrows() {
40 let out = escape_sql_string_literal("");
41 assert!(matches!(out, Cow::Borrowed(_)));
42 assert_eq!(out, "");
43 }
44
45 #[test]
46 fn clean_string_borrows() {
47 let out = escape_sql_string_literal("hello world");
48 assert!(matches!(out, Cow::Borrowed(_)));
49 assert_eq!(out, "hello world");
50 }
51
52 #[test]
53 fn single_quote_in_middle_escapes() {
54 let out = escape_sql_string_literal("O'Brien");
55 assert!(matches!(out, Cow::Owned(_)));
56 assert_eq!(out, "O''Brien");
57 }
58
59 #[test]
60 fn leading_quote_escapes() {
61 let out = escape_sql_string_literal("'leading");
62 assert!(matches!(out, Cow::Owned(_)));
63 assert_eq!(out, "''leading");
64 }
65
66 #[test]
67 fn trailing_quote_escapes() {
68 let out = escape_sql_string_literal("trailing'");
69 assert!(matches!(out, Cow::Owned(_)));
70 assert_eq!(out, "trailing''");
71 }
72
73 #[test]
74 fn only_quote_escapes() {
75 let out = escape_sql_string_literal("'");
76 assert!(matches!(out, Cow::Owned(_)));
77 assert_eq!(out, "''");
78 }
79
80 #[test]
81 fn multiple_quotes_escape_each() {
82 let out = escape_sql_string_literal("a'b'c'");
83 assert!(matches!(out, Cow::Owned(_)));
84 assert_eq!(out, "a''b''c''");
85 }
86
87 #[test]
88 fn consecutive_quotes_escape_each() {
89 let out = escape_sql_string_literal("''");
90 assert!(matches!(out, Cow::Owned(_)));
91 assert_eq!(out, "''''");
92 }
93
94 #[test]
95 fn unicode_preserved_when_borrowed() {
96 let out = escape_sql_string_literal("héllo 世界");
97 assert!(matches!(out, Cow::Borrowed(_)));
98 assert_eq!(out, "héllo 世界");
99 }
100
101 #[test]
102 fn unicode_preserved_when_owned() {
103 let out = escape_sql_string_literal("héllo'世界");
104 assert!(matches!(out, Cow::Owned(_)));
105 assert_eq!(out, "héllo''世界");
106 }
107
108 #[test]
109 fn matches_naive_replace() {
110 for s in [
112 "",
113 "x",
114 "'",
115 "''",
116 "a'",
117 "'a",
118 "a'b",
119 "O'Brien's",
120 "no quotes here",
121 "héllo 世界",
122 ] {
123 assert_eq!(
124 escape_sql_string_literal(s).as_ref(),
125 s.replace('\'', "''"),
126 "mismatch for input {s:?}"
127 );
128 }
129 }
130}