1pub const RESERVED_WORDS: &[&str] = &[
5 "order",
6 "group",
7 "user",
8 "table",
9 "select",
10 "from",
11 "where",
12 "join",
13 "left",
14 "right",
15 "inner",
16 "outer",
17 "on",
18 "and",
19 "or",
20 "not",
21 "null",
22 "true",
23 "false",
24 "limit",
25 "offset",
26 "as",
27 "in",
28 "is",
29 "like",
30 "between",
31 "having",
32 "union",
33 "all",
34 "distinct",
35 "case",
36 "when",
37 "then",
38 "else",
39 "end",
40 "create",
41 "alter",
42 "drop",
43 "insert",
44 "update",
45 "delete",
46 "index",
47 "key",
48 "primary",
49 "foreign",
50 "references",
51 "default",
52 "constraint",
53 "check",
54];
55
56pub fn escape_identifier(name: &str) -> String {
59 if name.contains('.') {
60 return name
61 .split('.')
62 .map(escape_single_identifier)
63 .collect::<Vec<_>>()
64 .join(".");
65 }
66 escape_single_identifier(name)
67}
68
69fn escape_single_identifier(name: &str) -> String {
71 let lower = name.to_lowercase();
72 let needs_escaping = RESERVED_WORDS.contains(&lower.as_str())
73 || name.chars().any(|c| !c.is_alphanumeric() && c != '_')
74 || name.chars().next().map(|c| c.is_numeric()).unwrap_or(false);
75
76 if needs_escaping {
77 format!("\"{}\"", name.replace('"', "\"\""))
78 } else {
79 name.to_string()
80 }
81}
82
83pub trait SqlGenerator {
85 fn quote_identifier(&self, name: &str) -> String;
87 fn placeholder(&self, index: usize) -> String;
89 fn fuzzy_operator(&self) -> &str;
91 fn bool_literal(&self, val: bool) -> String;
93 fn string_concat(&self, parts: &[&str]) -> String;
95 fn limit_offset(&self, limit: Option<usize>, offset: Option<usize>) -> String;
96 fn json_access(&self, col: &str, path: &[&str]) -> String {
100 let mut parts = vec![self.quote_identifier(col)];
101 for key in path {
102 parts.push(self.quote_identifier(key));
103 }
104 parts.join(".")
105 }
106 fn json_contains(&self, col: &str, value: &str) -> String {
109 format!("{} @> {}", col, value)
110 }
111 fn json_key_exists(&self, col: &str, key: &str) -> String {
114 format!("{} ? {}", col, key)
115 }
116
117 fn json_exists(&self, col: &str, path: &str) -> String {
119 format!("JSON_EXISTS({}, '{}')", col, path)
120 }
121
122 fn json_query(&self, col: &str, path: &str) -> String {
124 format!("JSON_QUERY({}, '{}')", col, path)
125 }
126
127 fn json_value(&self, col: &str, path: &str) -> String {
129 format!("JSON_VALUE({}, '{}')", col, path)
130 }
131
132 fn in_array(&self, col: &str, value: &str) -> String {
135 format!("{} = ANY({})", col, value)
136 }
137
138 fn not_in_array(&self, col: &str, value: &str) -> String {
141 format!("{} != ALL({})", col, value)
142 }
143}