1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum Literal {
13 Null,
14 Bool(bool),
15 Int(i64),
16 Float(f64),
17 Text(String),
18 Json(serde_json::Value),
19}
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum Expr {
25 Column(String),
27 Literal(Literal),
29 And(Vec<Expr>),
31 Or(Vec<Expr>),
33 Not(Box<Expr>),
35 Eq(Box<Expr>, Box<Expr>),
37 Ne(Box<Expr>, Box<Expr>),
39 Gt(Box<Expr>, Box<Expr>),
41 Gte(Box<Expr>, Box<Expr>),
43 Lt(Box<Expr>, Box<Expr>),
45 Lte(Box<Expr>, Box<Expr>),
47 In(Box<Expr>, Vec<Literal>),
49 NotIn(Box<Expr>, Vec<Literal>),
51 IsNull(Box<Expr>),
53 IsNotNull(Box<Expr>),
55 Like(Box<Expr>, String),
57 Contains(Box<Expr>, String),
60 BytesPrefix(Box<Expr>, String),
66 InSubquery(Box<Expr>, Box<Select>),
70 Exists(Box<Select>),
72 NotExists(Box<Select>),
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
78#[serde(rename_all = "snake_case")]
79pub enum Direction {
80 #[default]
81 Asc,
82 Desc,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87pub struct OrderBy {
88 pub expr: Expr,
89 pub direction: Direction,
90}
91
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94pub struct Select {
95 pub table: String,
96 pub columns: Vec<Expr>,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub filter: Option<Expr>,
99 #[serde(default, skip_serializing_if = "Vec::is_empty")]
100 pub order_by: Vec<OrderBy>,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub limit: Option<usize>,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub offset: Option<usize>,
105}
106
107#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct Insert {
110 pub table: String,
111 pub values: serde_json::Map<String, serde_json::Value>,
112 #[serde(default, skip_serializing_if = "Vec::is_empty")]
113 pub returning: Vec<String>,
114}
115
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub struct Update {
119 pub table: String,
120 pub set: serde_json::Map<String, serde_json::Value>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub filter: Option<Expr>,
123 #[serde(default, skip_serializing_if = "Vec::is_empty")]
124 pub returning: Vec<String>,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
128 pub pk: Option<serde_json::Value>,
129}
130
131#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133pub struct Delete {
134 pub table: String,
135 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub filter: Option<Expr>,
137 #[serde(default, skip_serializing_if = "Vec::is_empty")]
138 pub returning: Vec<String>,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub pk: Option<serde_json::Value>,
143}
144
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147pub struct Upsert {
148 pub table: String,
149 pub values: serde_json::Map<String, serde_json::Value>,
150 pub on_conflict: OnConflict,
151 #[serde(default, skip_serializing_if = "Vec::is_empty")]
152 pub returning: Vec<String>,
153}
154
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157#[serde(rename_all = "snake_case")]
158pub enum OnConflict {
159 DoNothing,
160 DoUpdate(serde_json::Map<String, serde_json::Value>),
161}
162
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165#[serde(rename_all = "snake_case")]
166pub enum Query {
167 Select(Select),
168 Insert(Insert),
169 Update(Update),
170 Delete(Delete),
171 Upsert(Upsert),
172 Aggregate(AggregateQuery),
173 Join(JoinQuery),
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
178#[serde(rename_all = "snake_case")]
179pub enum AggFunc {
180 Count,
181 Sum,
182 Min,
183 Max,
184 Avg,
185}
186
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
189pub struct Aggregate {
190 pub func: AggFunc,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub column: Option<String>,
194 pub alias: String,
196 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
200 pub distinct: bool,
201}
202
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub struct AggregateQuery {
208 pub table: String,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub filter: Option<Expr>,
211 #[serde(default, skip_serializing_if = "Vec::is_empty")]
212 pub group_by: Vec<String>,
213 pub aggregates: Vec<Aggregate>,
214 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub having: Option<Expr>,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
222#[serde(rename_all = "snake_case")]
223pub enum JoinKind {
224 #[default]
225 Inner,
226 Left,
227 Cross,
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
232pub struct Join {
233 #[serde(default)]
234 pub kind: JoinKind,
235 pub table: String,
236 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub alias: Option<String>,
239 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub on: Option<Expr>,
243}
244
245#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250pub struct JoinQuery {
251 pub table: String,
252 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub alias: Option<String>,
254 #[serde(default, skip_serializing_if = "Vec::is_empty")]
255 pub joins: Vec<Join>,
256 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub filter: Option<Expr>,
258 #[serde(default, skip_serializing_if = "Vec::is_empty")]
259 pub order_by: Vec<OrderBy>,
260 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub limit: Option<usize>,
262 #[serde(default, skip_serializing_if = "Option::is_none")]
263 pub offset: Option<usize>,
264}
265
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269pub struct Cte {
270 pub name: String,
271 pub query: Box<Select>,
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn select_serializes_to_expected_shape() {
280 let q = Query::Select(Select {
281 table: "users".into(),
282 columns: vec![Expr::Column("id".into()), Expr::Column("email".into())],
283 filter: Some(Expr::Eq(
284 Box::new(Expr::Column("id".into())),
285 Box::new(Expr::Literal(Literal::Int(1))),
286 )),
287 order_by: vec![OrderBy {
288 expr: Expr::Column("id".into()),
289 direction: Direction::Desc,
290 }],
291 limit: Some(10),
292 offset: Some(5),
293 });
294
295 let json = serde_json::to_value(&q).unwrap();
296 assert_eq!(json["select"]["table"], "users");
297 assert_eq!(json["select"]["columns"][0]["column"], "id");
298 assert_eq!(json["select"]["filter"]["eq"][0]["column"], "id");
299 assert_eq!(json["select"]["filter"]["eq"][1]["literal"]["int"], 1);
300 assert_eq!(json["select"]["order_by"][0]["direction"], "desc");
301 assert_eq!(json["select"]["limit"], 10);
302 }
303
304 #[test]
305 fn query_roundtrips() {
306 let q = Query::Select(Select {
307 table: "t".into(),
308 columns: vec![Expr::Column("x".into())],
309 filter: Some(Expr::And(vec![
310 Expr::Not(Box::new(Expr::IsNull(Box::new(Expr::Column("y".into()))))),
311 Expr::Like(Box::new(Expr::Column("z".into())), "%test%".into()),
312 ])),
313 order_by: vec![],
314 limit: None,
315 offset: None,
316 });
317 let json = serde_json::to_string(&q).unwrap();
318 let back: Query = serde_json::from_str(&json).unwrap();
319 assert_eq!(q, back);
320 }
321
322 #[test]
323 fn extended_exprs_and_queries_roundtrip() {
324 let sub = Select {
325 table: "orders".into(),
326 columns: vec![Expr::Column("user_id".into())],
327 filter: Some(Expr::Gt(
328 Box::new(Expr::Column("total".into())),
329 Box::new(Expr::Literal(Literal::Int(100))),
330 )),
331 order_by: vec![],
332 limit: None,
333 offset: None,
334 };
335 let select = Select {
336 table: "users".into(),
337 columns: vec![],
338 filter: Some(Expr::And(vec![
339 Expr::Contains(Box::new(Expr::Column("email".into())), "@x".into()),
340 Expr::InSubquery(Box::new(Expr::Column("id".into())), Box::new(sub.clone())),
341 Expr::Exists(Box::new(sub.clone())),
342 Expr::Not(Box::new(Expr::NotExists(Box::new(sub.clone())))),
343 ])),
344 order_by: vec![],
345 limit: None,
346 offset: None,
347 };
348 let q = Query::Select(select);
349 let back: Query = serde_json::from_str(&serde_json::to_string(&q).unwrap()).unwrap();
350 assert_eq!(q, back);
351
352 let agg = Query::Aggregate(AggregateQuery {
353 table: "orders".into(),
354 filter: None,
355 group_by: vec!["user_id".into()],
356 aggregates: vec![
357 Aggregate {
358 func: AggFunc::Count,
359 column: None,
360 alias: "n".into(),
361 distinct: false,
362 },
363 Aggregate {
364 func: AggFunc::Sum,
365 column: Some("total".into()),
366 alias: "total_sum".into(),
367 distinct: false,
368 },
369 ],
370 having: Some(Expr::Gt(
371 Box::new(Expr::Column("n".into())),
372 Box::new(Expr::Literal(Literal::Int(1))),
373 )),
374 });
375 let back: Query = serde_json::from_str(&serde_json::to_string(&agg).unwrap()).unwrap();
376 assert_eq!(agg, back);
377
378 let join = Query::Join(JoinQuery {
379 table: "users".into(),
380 alias: None,
381 joins: vec![Join {
382 kind: JoinKind::Left,
383 table: "orders".into(),
384 alias: None,
385 on: Some(Expr::Eq(
386 Box::new(Expr::Column("users.id".into())),
387 Box::new(Expr::Column("orders.user_id".into())),
388 )),
389 }],
390 filter: None,
391 order_by: vec![],
392 limit: None,
393 offset: None,
394 });
395 let back: Query = serde_json::from_str(&serde_json::to_string(&join).unwrap()).unwrap();
396 assert_eq!(join, back);
397
398 let cte = Cte {
399 name: "big".into(),
400 query: Box::new(sub),
401 };
402 let back: Cte = serde_json::from_str(&serde_json::to_string(&cte).unwrap()).unwrap();
403 assert_eq!(cte, back);
404 }
405
406 #[test]
407 fn insert_and_delete_roundtrip() {
408 let mut values = serde_json::Map::new();
409 values.insert("name".into(), serde_json::Value::String("alice".into()));
410
411 let q = Query::Insert(Insert {
412 table: "users".into(),
413 values,
414 returning: vec![],
415 });
416 let back: Query = serde_json::from_str(&serde_json::to_string(&q).unwrap()).unwrap();
417 assert_eq!(q, back);
418
419 let d = Query::Delete(Delete {
420 table: "users".into(),
421 filter: Some(Expr::In(
422 Box::new(Expr::Column("id".into())),
423 vec![Literal::Int(1), Literal::Int(2)],
424 )),
425 returning: vec![],
426 pk: None,
427 });
428 let back: Query = serde_json::from_str(&serde_json::to_string(&d).unwrap()).unwrap();
429 assert_eq!(d, back);
430 }
431}