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