1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq)]
9#[non_exhaustive]
10pub enum SqlValue {
11 Text(String),
12 Integer(i64),
13 Float(f64),
14 Bool(bool),
15 Null,
16}
17
18impl SqlValue {
19 pub fn to_sql_literal(&self) -> String {
21 match self {
22 Self::Text(s) => format!("'{}'", s.replace('\'', "''")),
23 Self::Integer(n) => n.to_string(),
24 Self::Float(f) => f.to_string(),
25 Self::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
26 Self::Null => "NULL".to_string(),
27 }
28 }
29}
30
31impl fmt::Display for SqlValue {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 f.write_str(&self.to_sql_literal())
34 }
35}
36
37impl From<&str> for SqlValue {
38 fn from(s: &str) -> Self {
39 Self::Text(s.to_string())
40 }
41}
42impl From<String> for SqlValue {
43 fn from(s: String) -> Self {
44 Self::Text(s)
45 }
46}
47impl From<i8> for SqlValue {
48 fn from(n: i8) -> Self {
49 Self::Integer(n as i64)
50 }
51}
52impl From<i16> for SqlValue {
53 fn from(n: i16) -> Self {
54 Self::Integer(n as i64)
55 }
56}
57impl From<i32> for SqlValue {
58 fn from(n: i32) -> Self {
59 Self::Integer(n as i64)
60 }
61}
62impl From<i64> for SqlValue {
63 fn from(n: i64) -> Self {
64 Self::Integer(n)
65 }
66}
67impl From<u32> for SqlValue {
68 fn from(n: u32) -> Self {
69 Self::Integer(n as i64)
70 }
71}
72impl From<u64> for SqlValue {
73 fn from(n: u64) -> Self {
74 Self::Integer(n as i64)
75 }
76}
77impl From<f32> for SqlValue {
78 fn from(f: f32) -> Self {
79 Self::Float(f as f64)
80 }
81}
82impl From<f64> for SqlValue {
83 fn from(f: f64) -> Self {
84 Self::Float(f)
85 }
86}
87impl From<bool> for SqlValue {
88 fn from(b: bool) -> Self {
89 Self::Bool(b)
90 }
91}
92impl<T: Into<SqlValue>> From<Option<T>> for SqlValue {
93 fn from(opt: Option<T>) -> Self {
94 match opt {
95 Some(v) => v.into(),
96 None => Self::Null,
97 }
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum JoinOp {
106 And,
107 Or,
108}
109
110impl fmt::Display for JoinOp {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 Self::And => write!(f, "AND"),
114 Self::Or => write!(f, "OR"),
115 }
116 }
117}
118
119#[derive(Debug, Clone)]
123#[non_exhaustive]
124pub enum Condition {
125 Eq(String, SqlValue),
126 Ne(String, SqlValue),
127 Gt(String, SqlValue),
128 Gte(String, SqlValue),
129 Lt(String, SqlValue),
130 Lte(String, SqlValue),
131 Like(String, String),
132 NotLike(String, String),
133 ILike(String, String),
134 IsNull(String),
135 IsNotNull(String),
136 In(String, Vec<SqlValue>),
137 NotIn(String, Vec<SqlValue>),
138 Between(String, SqlValue, SqlValue),
139 NotBetween(String, SqlValue, SqlValue),
140 Raw(String),
141 Group(Vec<(JoinOp, Condition)>),
143 JsonGet(String, String, SqlValue),
145 Subquery {
148 exists: bool,
149 table: String,
150 fk_expr: String,
151 inner: Vec<(JoinOp, Condition)>,
152 },
153}
154
155impl Condition {
156 pub fn to_param_sql(&self, offset: usize) -> (String, Vec<SqlValue>) {
163 match self {
164 Self::Eq(col, v) => (format!("{col} = ${offset}"), vec![v.clone()]),
165 Self::Ne(col, v) => (format!("{col} != ${offset}"), vec![v.clone()]),
166 Self::Gt(col, v) => (format!("{col} > ${offset}"), vec![v.clone()]),
167 Self::Gte(col, v) => (format!("{col} >= ${offset}"), vec![v.clone()]),
168 Self::Lt(col, v) => (format!("{col} < ${offset}"), vec![v.clone()]),
169 Self::Lte(col, v) => (format!("{col} <= ${offset}"), vec![v.clone()]),
170 Self::Like(col, pat) => (
171 format!("{col} LIKE ${offset}"),
172 vec![SqlValue::Text(pat.clone())],
173 ),
174 Self::NotLike(col, pat) => (
175 format!("{col} NOT LIKE ${offset}"),
176 vec![SqlValue::Text(pat.clone())],
177 ),
178 Self::ILike(col, pat) => (
179 format!("{col} ILIKE ${offset}"),
180 vec![SqlValue::Text(pat.clone())],
181 ),
182 Self::IsNull(col) => (format!("{col} IS NULL"), vec![]),
183 Self::IsNotNull(col) => (format!("{col} IS NOT NULL"), vec![]),
184 Self::In(col, vals) => {
185 let placeholders: Vec<String> = vals
186 .iter()
187 .enumerate()
188 .map(|(i, _)| format!("${}", offset + i))
189 .collect();
190 (
191 format!("{col} IN ({})", placeholders.join(", ")),
192 vals.clone(),
193 )
194 }
195 Self::NotIn(col, vals) => {
196 let placeholders: Vec<String> = vals
197 .iter()
198 .enumerate()
199 .map(|(i, _)| format!("${}", offset + i))
200 .collect();
201 (
202 format!("{col} NOT IN ({})", placeholders.join(", ")),
203 vals.clone(),
204 )
205 }
206 Self::Between(col, lo, hi) => (
207 format!("{col} BETWEEN ${offset} AND ${}", offset + 1),
208 vec![lo.clone(), hi.clone()],
209 ),
210 Self::NotBetween(col, lo, hi) => (
211 format!("{col} NOT BETWEEN ${offset} AND ${}", offset + 1),
212 vec![lo.clone(), hi.clone()],
213 ),
214 Self::Raw(sql) => (sql.clone(), vec![]),
215 Self::Group(inner) => {
216 let mut parts: Vec<String> = Vec::new();
217 let mut group_params: Vec<SqlValue> = Vec::new();
218 for (idx, (op, cond)) in inner.iter().enumerate() {
219 let (frag, ps) = cond.to_param_sql(offset + group_params.len());
220 group_params.extend(ps);
221 if idx > 0 {
222 parts.push(format!("{op} {frag}"));
223 } else {
224 parts.push(frag);
225 }
226 }
227 (format!("({})", parts.join(" ")), group_params)
228 }
229 Self::JsonGet(col, key, val) => (
230 format!("{col}->>'{}' = ${offset}", key.replace('\'', "''")),
231 vec![val.clone()],
232 ),
233 Self::Subquery {
234 exists,
235 table,
236 fk_expr,
237 inner,
238 } => {
239 let kw = if *exists { "EXISTS" } else { "NOT EXISTS" };
240 let mut sub_params: Vec<SqlValue> = Vec::new();
241 let mut parts: Vec<String> = Vec::new();
242 for (i, (op, cond)) in inner.iter().enumerate() {
243 let (frag, ps) = cond.to_param_sql(offset + sub_params.len());
244 sub_params.extend(ps);
245 parts.push(if i == 0 {
246 format!("AND {frag}")
247 } else {
248 format!("{op} {frag}")
249 });
250 }
251 let extra = if parts.is_empty() {
252 String::new()
253 } else {
254 format!(" {}", parts.join(" "))
255 };
256 (
257 format!("{kw} (SELECT 1 FROM {table} WHERE {fk_expr}{extra})"),
258 sub_params,
259 )
260 }
261 }
262 }
263
264 pub fn to_param_sql_sqlite(&self) -> (String, Vec<SqlValue>) {
268 match self {
269 Self::Eq(col, v) => (format!("{col} = ?"), vec![v.clone()]),
270 Self::Ne(col, v) => (format!("{col} != ?"), vec![v.clone()]),
271 Self::Gt(col, v) => (format!("{col} > ?"), vec![v.clone()]),
272 Self::Gte(col, v) => (format!("{col} >= ?"), vec![v.clone()]),
273 Self::Lt(col, v) => (format!("{col} < ?"), vec![v.clone()]),
274 Self::Lte(col, v) => (format!("{col} <= ?"), vec![v.clone()]),
275 Self::Like(col, pat) => (format!("{col} LIKE ?"), vec![SqlValue::Text(pat.clone())]),
276 Self::NotLike(col, pat) => (
277 format!("{col} NOT LIKE ?"),
278 vec![SqlValue::Text(pat.clone())],
279 ),
280 Self::ILike(col, pat) => (format!("{col} ILIKE ?"), vec![SqlValue::Text(pat.clone())]),
281 Self::IsNull(col) => (format!("{col} IS NULL"), vec![]),
282 Self::IsNotNull(col) => (format!("{col} IS NOT NULL"), vec![]),
283 Self::In(col, vals) => {
284 let ph = vals.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
285 (format!("{col} IN ({ph})"), vals.clone())
286 }
287 Self::NotIn(col, vals) => {
288 let ph = vals.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
289 (format!("{col} NOT IN ({ph})"), vals.clone())
290 }
291 Self::Between(col, lo, hi) => (
292 format!("{col} BETWEEN ? AND ?"),
293 vec![lo.clone(), hi.clone()],
294 ),
295 Self::NotBetween(col, lo, hi) => (
296 format!("{col} NOT BETWEEN ? AND ?"),
297 vec![lo.clone(), hi.clone()],
298 ),
299 Self::Raw(sql) => (sql.clone(), vec![]),
300 Self::Group(inner) => {
301 let mut parts: Vec<String> = Vec::new();
302 let mut group_params: Vec<SqlValue> = Vec::new();
303 for (idx, (op, cond)) in inner.iter().enumerate() {
304 let (frag, ps) = cond.to_param_sql_sqlite();
305 group_params.extend(ps);
306 if idx > 0 {
307 parts.push(format!("{op} {frag}"));
308 } else {
309 parts.push(frag);
310 }
311 }
312 (format!("({})", parts.join(" ")), group_params)
313 }
314 Self::JsonGet(col, key, _val) => (
315 format!("json_extract({col}, '$.{}') = ?", key.replace('\'', "''")),
316 vec![_val.clone()],
317 ),
318 Self::Subquery {
319 exists,
320 table,
321 fk_expr,
322 inner,
323 } => {
324 let kw = if *exists { "EXISTS" } else { "NOT EXISTS" };
325 let mut sub_params: Vec<SqlValue> = Vec::new();
326 let mut parts: Vec<String> = Vec::new();
327 for (i, (op, cond)) in inner.iter().enumerate() {
328 let (frag, ps) = cond.to_param_sql_sqlite();
329 sub_params.extend(ps);
330 parts.push(if i == 0 {
331 format!("AND {frag}")
332 } else {
333 format!("{op} {frag}")
334 });
335 }
336 let extra = if parts.is_empty() {
337 String::new()
338 } else {
339 format!(" {}", parts.join(" "))
340 };
341 (
342 format!("{kw} (SELECT 1 FROM {table} WHERE {fk_expr}{extra})"),
343 sub_params,
344 )
345 }
346 }
347 }
348
349 pub fn to_literal_sql(&self) -> String {
351 match self {
352 Self::Eq(col, v) => format!("{col} = {v}"),
353 Self::Ne(col, v) => format!("{col} != {v}"),
354 Self::Gt(col, v) => format!("{col} > {v}"),
355 Self::Gte(col, v) => format!("{col} >= {v}"),
356 Self::Lt(col, v) => format!("{col} < {v}"),
357 Self::Lte(col, v) => format!("{col} <= {v}"),
358 Self::Like(col, p) => format!("{col} LIKE '{p}'"),
359 Self::NotLike(col, p) => format!("{col} NOT LIKE '{p}'"),
360 Self::ILike(col, p) => format!("{col} ILIKE '{p}'"),
361 Self::IsNull(col) => format!("{col} IS NULL"),
362 Self::IsNotNull(col) => format!("{col} IS NOT NULL"),
363 Self::In(col, vals) => {
364 let lits: Vec<String> = vals.iter().map(|v| v.to_sql_literal()).collect();
365 format!("{col} IN ({})", lits.join(", "))
366 }
367 Self::NotIn(col, vals) => {
368 let lits: Vec<String> = vals.iter().map(|v| v.to_sql_literal()).collect();
369 format!("{col} NOT IN ({})", lits.join(", "))
370 }
371 Self::Between(col, lo, hi) => format!("{col} BETWEEN {lo} AND {hi}"),
372 Self::NotBetween(col, lo, hi) => format!("{col} NOT BETWEEN {lo} AND {hi}"),
373 Self::Raw(sql) => sql.clone(),
374 Self::Group(inner) => {
375 let parts: Vec<String> = inner
376 .iter()
377 .enumerate()
378 .map(|(idx, (op, cond))| {
379 let frag = cond.to_literal_sql();
380 if idx > 0 {
381 format!("{op} {frag}")
382 } else {
383 frag
384 }
385 })
386 .collect();
387 format!("({})", parts.join(" "))
388 }
389 Self::JsonGet(col, key, val) => {
390 format!("{col}->>'{}' = {val}", key.replace('\'', "''"))
391 }
392 Self::Subquery {
393 exists,
394 table,
395 fk_expr,
396 inner,
397 } => {
398 let kw = if *exists { "EXISTS" } else { "NOT EXISTS" };
399 let parts: Vec<String> = inner
400 .iter()
401 .enumerate()
402 .map(|(i, (op, cond))| {
403 let frag = cond.to_literal_sql();
404 if i == 0 {
405 format!("AND {frag}")
406 } else {
407 format!("{op} {frag}")
408 }
409 })
410 .collect();
411 let extra = if parts.is_empty() {
412 String::new()
413 } else {
414 format!(" {}", parts.join(" "))
415 };
416 format!("{kw} (SELECT 1 FROM {table} WHERE {fk_expr}{extra})")
417 }
418 }
419 }
420}
421
422#[derive(Debug, Clone, Copy, PartialEq, Eq)]
425pub enum OrderDir {
426 Asc,
427 Desc,
428}
429
430impl fmt::Display for OrderDir {
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432 match self {
433 Self::Asc => write!(f, "ASC"),
434 Self::Desc => write!(f, "DESC"),
435 }
436 }
437}