1#![warn(missing_docs)]
4#![forbid(unsafe_code)]
5
6macro_rules! render_returning {
15 ($ctx:expr, $returning:expr, $quote:expr) => {{
16 if !$returning.is_empty() {
17 $ctx.sql.push_str(" RETURNING ");
18 for (i, col) in $returning.iter().enumerate() {
19 if i > 0 {
20 $ctx.sql.push_str(", ");
21 }
22 crate::push_qualified_identifier(&mut $ctx.sql, &col.table, &col.name, $quote);
23 $ctx.sql.push_str(" AS ");
24 crate::push_column_alias(&mut $ctx.sql, col, $quote);
25 }
26 }
27 }};
28}
29
30macro_rules! render_insert_body {
34 ($ctx:expr, $insert:expr, $quote:expr, $supports_returning:expr, $supports_enum_cast:expr) => {{
35 $ctx.sql.push_str("INSERT INTO ");
36 crate::push_quoted_identifier(&mut $ctx.sql, &$insert.table, $quote);
37
38 $ctx.sql.push_str(" (");
39 for (i, col) in $insert.columns.iter().enumerate() {
40 if i > 0 {
41 $ctx.sql.push_str(", ");
42 }
43 crate::push_quoted_identifier(&mut $ctx.sql, &col.name, $quote);
44 }
45 $ctx.sql.push(')');
46
47 $ctx.sql.push_str(" VALUES ");
48 for (row_idx, row) in $insert.values.iter().enumerate() {
49 if row_idx > 0 {
50 $ctx.sql.push_str(", ");
51 }
52 $ctx.sql.push('(');
53 for (val_idx, value) in row.iter().enumerate() {
54 if val_idx > 0 {
55 $ctx.sql.push_str(", ");
56 }
57 if matches!(value, nautilus_core::Value::Null) {
58 $ctx.sql.push_str("NULL");
59 } else {
60 $ctx.push_param(value.clone());
61 if $supports_enum_cast {
62 if let nautilus_core::Value::Enum { type_name, .. } = value {
63 $ctx.sql.push_str("::");
64 $ctx.sql.push_str(type_name);
65 }
66 }
67 }
68 }
69 $ctx.sql.push(')');
70 }
71
72 if $supports_returning {
73 render_returning!($ctx, $insert.returning, $quote);
74 }
75 }};
76}
77
78macro_rules! render_update_body {
83 ($ctx:expr, $update:expr, $quote:expr, $render_expr:ident, $supports_returning:expr, $supports_enum_cast:expr) => {{
84 $ctx.sql.push_str("UPDATE ");
85 crate::push_quoted_identifier(&mut $ctx.sql, &$update.table, $quote);
86
87 $ctx.sql.push_str(" SET ");
88 for (i, (col, value)) in $update.assignments.iter().enumerate() {
89 if i > 0 {
90 $ctx.sql.push_str(", ");
91 }
92 crate::push_quoted_identifier(&mut $ctx.sql, &col.name, $quote);
93 $ctx.sql.push_str(" = ");
94 if matches!(value, nautilus_core::Value::Null) {
95 $ctx.sql.push_str("NULL");
96 } else {
97 $ctx.push_param(value.clone());
98 if $supports_enum_cast {
99 if let nautilus_core::Value::Enum { type_name, .. } = value {
100 $ctx.sql.push_str("::");
101 $ctx.sql.push_str(type_name);
102 }
103 }
104 }
105 }
106
107 if let Some(ref filter) = $update.filter {
108 $ctx.sql.push_str(" WHERE ");
109 $render_expr($ctx, filter);
110 }
111
112 if $supports_returning {
113 render_returning!($ctx, $update.returning, $quote);
114 }
115 }};
116}
117
118macro_rules! render_delete_body {
123 ($ctx:expr, $delete:expr, $quote:expr, $render_expr:ident, $supports_returning:expr) => {{
124 $ctx.sql.push_str("DELETE FROM ");
125 crate::push_quoted_identifier(&mut $ctx.sql, &$delete.table, $quote);
126
127 if let Some(ref filter) = $delete.filter {
128 $ctx.sql.push_str(" WHERE ");
129 $render_expr($ctx, filter);
130 }
131
132 if $supports_returning {
133 render_returning!($ctx, $delete.returning, $quote);
134 }
135 }};
136}
137
138macro_rules! render_select_body_core {
146 (
147 $ctx:expr, $select:expr,
148 $quote:expr, $render_expr:ident,
149 $distinct_on:expr, $mysql_limit_hack:expr
150 ) => {{
151 $ctx.sql.push_str("SELECT ");
152
153 if !$select.distinct.is_empty() {
156 if $distinct_on {
157 $ctx.sql.push_str("DISTINCT ON (");
158 for (i, col) in $select.distinct.iter().enumerate() {
159 if i > 0 {
160 $ctx.sql.push_str(", ");
161 }
162 crate::push_identifier_reference(&mut $ctx.sql, col, $quote);
163 }
164 $ctx.sql.push_str(") ");
165 } else {
166 $ctx.sql.push_str("DISTINCT ");
167 }
168 }
169
170 let has_items =
171 !$select.items.is_empty() || $select.joins.iter().any(|join| !join.items.is_empty());
172
173 if !has_items {
174 $ctx.sql.push('*');
175 } else {
176 let mut first = true;
177 for item in &$select.items {
178 if !first {
179 $ctx.sql.push_str(", ");
180 }
181 first = false;
182 match item {
183 nautilus_core::SelectItem::Column(col) => {
184 crate::push_qualified_identifier(
185 &mut $ctx.sql,
186 &col.table,
187 &col.name,
188 $quote,
189 );
190 $ctx.sql.push_str(" AS ");
191 crate::push_column_alias(&mut $ctx.sql, col, $quote);
192 }
193 nautilus_core::SelectItem::Computed { expr, alias } => {
194 $ctx.sql.push('(');
195 $render_expr($ctx, expr);
196 $ctx.sql.push(')');
197 $ctx.sql.push_str(" AS ");
198 crate::push_quoted_identifier(&mut $ctx.sql, alias, $quote);
199 }
200 }
201 }
202 for join in &$select.joins {
203 for item in &join.items {
204 if !first {
205 $ctx.sql.push_str(", ");
206 }
207 first = false;
208 match item {
209 nautilus_core::SelectItem::Column(col) => {
210 crate::push_qualified_identifier(
211 &mut $ctx.sql,
212 &col.table,
213 &col.name,
214 $quote,
215 );
216 $ctx.sql.push_str(" AS ");
217 crate::push_column_alias(&mut $ctx.sql, col, $quote);
218 }
219 nautilus_core::SelectItem::Computed { expr, alias } => {
220 $ctx.sql.push('(');
221 $render_expr($ctx, expr);
222 $ctx.sql.push(')');
223 $ctx.sql.push_str(" AS ");
224 crate::push_quoted_identifier(&mut $ctx.sql, alias, $quote);
225 }
226 }
227 }
228 }
229 }
230
231 $ctx.sql.push_str(" FROM ");
232 crate::push_quoted_identifier(&mut $ctx.sql, &$select.table, $quote);
233
234 for join in &$select.joins {
235 match join.join_type {
236 nautilus_core::JoinType::Inner => $ctx.sql.push_str(" INNER JOIN "),
237 nautilus_core::JoinType::Left => $ctx.sql.push_str(" LEFT JOIN "),
238 }
239 crate::push_quoted_identifier(&mut $ctx.sql, &join.table, $quote);
240 $ctx.sql.push_str(" ON ");
241 $render_expr($ctx, &join.on);
242 }
243
244 if let Some(ref filter) = $select.filter {
245 $ctx.sql.push_str(" WHERE ");
246 $render_expr($ctx, filter);
247 }
248
249 if !$select.group_by.is_empty() {
250 $ctx.sql.push_str(" GROUP BY ");
251 for (i, col) in $select.group_by.iter().enumerate() {
252 if i > 0 {
253 $ctx.sql.push_str(", ");
254 }
255 crate::push_qualified_identifier(&mut $ctx.sql, &col.table, &col.name, $quote);
256 }
257 }
258
259 if let Some(ref having) = $select.having {
260 $ctx.sql.push_str(" HAVING ");
261 $render_expr($ctx, having);
262 }
263
264 let has_order_items = !$select.order_by_items.is_empty();
265 let has_col_order = !$select.order_by.is_empty();
266 let has_expr_order = !$select.order_by_exprs.is_empty();
267 if has_order_items || has_col_order || has_expr_order {
268 $ctx.sql.push_str(" ORDER BY ");
269 let mut first = true;
270 if has_order_items {
271 for item in &$select.order_by_items {
272 if !first {
273 $ctx.sql.push_str(", ");
274 }
275 first = false;
276 match item {
277 nautilus_core::OrderByItem::Column(order) => {
278 crate::push_identifier_reference(&mut $ctx.sql, &order.column, $quote);
279 match order.direction {
280 nautilus_core::OrderDir::Asc => $ctx.sql.push_str(" ASC"),
281 nautilus_core::OrderDir::Desc => $ctx.sql.push_str(" DESC"),
282 }
283 }
284 nautilus_core::OrderByItem::Expr(expr, dir) => {
285 $render_expr($ctx, expr);
286 match dir {
287 nautilus_core::OrderDir::Asc => $ctx.sql.push_str(" ASC"),
288 nautilus_core::OrderDir::Desc => $ctx.sql.push_str(" DESC"),
289 }
290 }
291 }
292 }
293 } else {
294 for order in &$select.order_by {
295 if !first {
296 $ctx.sql.push_str(", ");
297 }
298 first = false;
299 crate::push_identifier_reference(&mut $ctx.sql, &order.column, $quote);
300 match order.direction {
301 nautilus_core::OrderDir::Asc => $ctx.sql.push_str(" ASC"),
302 nautilus_core::OrderDir::Desc => $ctx.sql.push_str(" DESC"),
303 }
304 }
305 for (expr, dir) in &$select.order_by_exprs {
306 if !first {
307 $ctx.sql.push_str(", ");
308 }
309 first = false;
310 $render_expr($ctx, expr);
311 match dir {
312 nautilus_core::OrderDir::Asc => $ctx.sql.push_str(" ASC"),
313 nautilus_core::OrderDir::Desc => $ctx.sql.push_str(" DESC"),
314 }
315 }
316 }
317 }
318
319 if let Some(take) = $select.take {
321 $ctx.sql.push_str(" LIMIT ");
322 crate::push_u32(&mut $ctx.sql, take.unsigned_abs());
323 } else if $mysql_limit_hack && $select.skip.is_some() {
324 $ctx.sql.push_str(" LIMIT 18446744073709551615");
325 }
326
327 if let Some(skip) = $select.skip {
328 $ctx.sql.push_str(" OFFSET ");
329 crate::push_u32(&mut $ctx.sql, skip);
330 }
331 }};
332}
333
334macro_rules! render_expr_common {
353 (
354 $ctx:expr, $expr:expr,
355 $quote:expr, $render_expr:ident, $render_select_body:ident,
356 { $($specific:tt)* }
357 ) => {
358 match $expr {
359 nautilus_core::Expr::Column(name) => {
362 crate::push_identifier_reference(&mut $ctx.sql, name, $quote);
363 }
364 nautilus_core::Expr::Not(inner) => {
365 $ctx.sql.push_str("NOT (");
366 $render_expr($ctx, inner);
367 $ctx.sql.push(')');
368 }
369 nautilus_core::Expr::Exists(subquery) => {
370 $ctx.sql.push_str("EXISTS (");
371 $render_select_body($ctx, subquery);
372 $ctx.sql.push(')');
373 }
374 nautilus_core::Expr::NotExists(subquery) => {
375 $ctx.sql.push_str("NOT EXISTS (");
376 $render_select_body($ctx, subquery);
377 $ctx.sql.push(')');
378 }
379 nautilus_core::Expr::Relation { op, relation } => {
380 let is_exists = matches!(op, nautilus_core::expr::RelationFilterOp::Some);
381 if is_exists {
382 $ctx.sql.push_str("EXISTS (SELECT * FROM ");
383 } else {
384 $ctx.sql.push_str("NOT EXISTS (SELECT * FROM ");
385 }
386 crate::push_quoted_identifier(&mut $ctx.sql, &relation.target_table, $quote);
387 $ctx.sql.push_str(" WHERE ");
388 crate::push_qualified_identifier(
389 &mut $ctx.sql,
390 &relation.target_table,
391 &relation.fk_db,
392 $quote,
393 );
394 $ctx.sql.push_str(" = ");
395 crate::push_qualified_identifier(
396 &mut $ctx.sql,
397 &relation.parent_table,
398 &relation.pk_db,
399 $quote,
400 );
401 $ctx.sql.push_str(" AND ");
402 if matches!(op, nautilus_core::expr::RelationFilterOp::Every) {
403 $ctx.sql.push_str("NOT (");
404 $render_expr($ctx, &relation.filter);
405 $ctx.sql.push(')');
406 } else {
407 $render_expr($ctx, &relation.filter);
408 }
409 $ctx.sql.push(')');
410 }
411 nautilus_core::Expr::ScalarSubquery(subquery) => {
412 $ctx.sql.push('(');
413 $render_select_body($ctx, subquery);
414 $ctx.sql.push(')');
415 }
416 nautilus_core::Expr::IsNull(inner) => {
417 $ctx.sql.push('(');
418 $render_expr($ctx, inner);
419 $ctx.sql.push_str(" IS NULL)");
420 }
421 nautilus_core::Expr::IsNotNull(inner) => {
422 $ctx.sql.push('(');
423 $render_expr($ctx, inner);
424 $ctx.sql.push_str(" IS NOT NULL)");
425 }
426 nautilus_core::Expr::Literal(s) => {
430 crate::push_sql_string_literal(&mut $ctx.sql, s);
431 }
432 nautilus_core::Expr::List(exprs) => {
433 for (i, e) in exprs.iter().enumerate() {
434 if i > 0 { $ctx.sql.push_str(", "); }
435 $render_expr($ctx, e);
436 }
437 }
438 nautilus_core::Expr::CaseWhen { condition, then } => {
439 $ctx.sql.push_str("CASE WHEN ");
440 $render_expr($ctx, condition);
441 $ctx.sql.push_str(" THEN ");
442 $render_expr($ctx, then);
443 $ctx.sql.push_str(" ELSE NULL END");
444 }
445 nautilus_core::Expr::Star => {
446 $ctx.sql.push('*');
447 }
448 $($specific)*
449 }
450 };
451}
452
453macro_rules! render_returning_mut {
455 ($ctx:expr, $returning:expr, $quote:expr) => {{
456 if !$returning.is_empty() {
457 $ctx.sql.push_str(" RETURNING ");
458 for (i, col) in $returning.iter().enumerate() {
459 if i > 0 {
460 $ctx.sql.push_str(", ");
461 }
462 crate::push_qualified_identifier(&mut $ctx.sql, &col.table, &col.name, $quote);
463 $ctx.sql.push_str(" AS ");
464 crate::push_column_alias(&mut $ctx.sql, col, $quote);
465 }
466 }
467 }};
468}
469
470macro_rules! render_insert_body_mut {
472 ($ctx:expr, $insert:expr, $quote:expr, $supports_returning:expr, $supports_enum_cast:expr) => {{
473 $ctx.sql.push_str("INSERT INTO ");
474 crate::push_quoted_identifier(&mut $ctx.sql, &$insert.table, $quote);
475
476 $ctx.sql.push_str(" (");
477 for (i, col) in $insert.columns.iter().enumerate() {
478 if i > 0 {
479 $ctx.sql.push_str(", ");
480 }
481 crate::push_quoted_identifier(&mut $ctx.sql, &col.name, $quote);
482 }
483 $ctx.sql.push(')');
484
485 $ctx.sql.push_str(" VALUES ");
486 for (row_idx, row) in $insert.values.iter_mut().enumerate() {
487 if row_idx > 0 {
488 $ctx.sql.push_str(", ");
489 }
490 $ctx.sql.push('(');
491 for (val_idx, value) in row.iter_mut().enumerate() {
492 if val_idx > 0 {
493 $ctx.sql.push_str(", ");
494 }
495 if matches!(value, nautilus_core::Value::Null) {
496 $ctx.sql.push_str("NULL");
497 } else {
498 let enum_type_name = if $supports_enum_cast {
499 if let nautilus_core::Value::Enum { type_name, .. } = value {
500 Some(type_name.clone())
501 } else {
502 None
503 }
504 } else {
505 None
506 };
507 $ctx.take_param(value);
508 if let Some(type_name) = enum_type_name.as_deref() {
509 $ctx.sql.push_str("::");
510 $ctx.sql.push_str(type_name);
511 }
512 }
513 }
514 $ctx.sql.push(')');
515 }
516
517 if $supports_returning {
518 render_returning_mut!($ctx, $insert.returning, $quote);
519 }
520 }};
521}
522
523macro_rules! render_update_body_mut {
525 ($ctx:expr, $update:expr, $quote:expr, $render_expr:ident, $supports_returning:expr, $supports_enum_cast:expr) => {{
526 $ctx.sql.push_str("UPDATE ");
527 crate::push_quoted_identifier(&mut $ctx.sql, &$update.table, $quote);
528
529 $ctx.sql.push_str(" SET ");
530 for (i, (col, value)) in $update.assignments.iter_mut().enumerate() {
531 if i > 0 {
532 $ctx.sql.push_str(", ");
533 }
534 crate::push_quoted_identifier(&mut $ctx.sql, &col.name, $quote);
535 $ctx.sql.push_str(" = ");
536 if matches!(value, nautilus_core::Value::Null) {
537 $ctx.sql.push_str("NULL");
538 } else {
539 let enum_type_name = if $supports_enum_cast {
540 if let nautilus_core::Value::Enum { type_name, .. } = value {
541 Some(type_name.clone())
542 } else {
543 None
544 }
545 } else {
546 None
547 };
548 $ctx.take_param(value);
549 if let Some(type_name) = enum_type_name.as_deref() {
550 $ctx.sql.push_str("::");
551 $ctx.sql.push_str(type_name);
552 }
553 }
554 }
555
556 if let Some(filter) = $update.filter.as_mut() {
557 $ctx.sql.push_str(" WHERE ");
558 $render_expr($ctx, filter);
559 }
560
561 if $supports_returning {
562 render_returning_mut!($ctx, $update.returning, $quote);
563 }
564 }};
565}
566
567macro_rules! render_delete_body_mut {
569 ($ctx:expr, $delete:expr, $quote:expr, $render_expr:ident, $supports_returning:expr) => {{
570 $ctx.sql.push_str("DELETE FROM ");
571 crate::push_quoted_identifier(&mut $ctx.sql, &$delete.table, $quote);
572
573 if let Some(filter) = $delete.filter.as_mut() {
574 $ctx.sql.push_str(" WHERE ");
575 $render_expr($ctx, filter);
576 }
577
578 if $supports_returning {
579 render_returning_mut!($ctx, $delete.returning, $quote);
580 }
581 }};
582}
583
584macro_rules! render_select_body_core_mut {
586 (
587 $ctx:expr, $select:expr,
588 $quote:expr, $render_expr:ident,
589 $distinct_on:expr, $mysql_limit_hack:expr
590 ) => {{
591 $ctx.sql.push_str("SELECT ");
592
593 if !$select.distinct.is_empty() {
594 if $distinct_on {
595 $ctx.sql.push_str("DISTINCT ON (");
596 for (i, col) in $select.distinct.iter().enumerate() {
597 if i > 0 {
598 $ctx.sql.push_str(", ");
599 }
600 crate::push_identifier_reference(&mut $ctx.sql, col, $quote);
601 }
602 $ctx.sql.push_str(") ");
603 } else {
604 $ctx.sql.push_str("DISTINCT ");
605 }
606 }
607
608 let has_items =
609 !$select.items.is_empty() || $select.joins.iter().any(|join| !join.items.is_empty());
610
611 if !has_items {
612 $ctx.sql.push('*');
613 } else {
614 let mut first = true;
615 for item in $select.items.iter_mut() {
616 if !first {
617 $ctx.sql.push_str(", ");
618 }
619 first = false;
620 match item {
621 nautilus_core::SelectItem::Column(col) => {
622 crate::push_qualified_identifier(
623 &mut $ctx.sql,
624 &col.table,
625 &col.name,
626 $quote,
627 );
628 $ctx.sql.push_str(" AS ");
629 crate::push_column_alias(&mut $ctx.sql, col, $quote);
630 }
631 nautilus_core::SelectItem::Computed { expr, alias } => {
632 $ctx.sql.push('(');
633 $render_expr($ctx, expr);
634 $ctx.sql.push(')');
635 $ctx.sql.push_str(" AS ");
636 crate::push_quoted_identifier(&mut $ctx.sql, alias, $quote);
637 }
638 }
639 }
640 for join in $select.joins.iter_mut() {
641 for item in join.items.iter_mut() {
642 if !first {
643 $ctx.sql.push_str(", ");
644 }
645 first = false;
646 match item {
647 nautilus_core::SelectItem::Column(col) => {
648 crate::push_qualified_identifier(
649 &mut $ctx.sql,
650 &col.table,
651 &col.name,
652 $quote,
653 );
654 $ctx.sql.push_str(" AS ");
655 crate::push_column_alias(&mut $ctx.sql, col, $quote);
656 }
657 nautilus_core::SelectItem::Computed { expr, alias } => {
658 $ctx.sql.push('(');
659 $render_expr($ctx, expr);
660 $ctx.sql.push(')');
661 $ctx.sql.push_str(" AS ");
662 crate::push_quoted_identifier(&mut $ctx.sql, alias, $quote);
663 }
664 }
665 }
666 }
667 }
668
669 $ctx.sql.push_str(" FROM ");
670 crate::push_quoted_identifier(&mut $ctx.sql, &$select.table, $quote);
671
672 for join in $select.joins.iter_mut() {
673 match join.join_type {
674 nautilus_core::JoinType::Inner => $ctx.sql.push_str(" INNER JOIN "),
675 nautilus_core::JoinType::Left => $ctx.sql.push_str(" LEFT JOIN "),
676 }
677 crate::push_quoted_identifier(&mut $ctx.sql, &join.table, $quote);
678 $ctx.sql.push_str(" ON ");
679 $render_expr($ctx, &mut join.on);
680 }
681
682 if let Some(filter) = $select.filter.as_mut() {
683 $ctx.sql.push_str(" WHERE ");
684 $render_expr($ctx, filter);
685 }
686
687 if !$select.group_by.is_empty() {
688 $ctx.sql.push_str(" GROUP BY ");
689 for (i, col) in $select.group_by.iter().enumerate() {
690 if i > 0 {
691 $ctx.sql.push_str(", ");
692 }
693 crate::push_qualified_identifier(&mut $ctx.sql, &col.table, &col.name, $quote);
694 }
695 }
696
697 if let Some(having) = $select.having.as_mut() {
698 $ctx.sql.push_str(" HAVING ");
699 $render_expr($ctx, having);
700 }
701
702 let has_order_items = !$select.order_by_items.is_empty();
703 let has_col_order = !$select.order_by.is_empty();
704 let has_expr_order = !$select.order_by_exprs.is_empty();
705 if has_order_items || has_col_order || has_expr_order {
706 $ctx.sql.push_str(" ORDER BY ");
707 let mut first = true;
708 if has_order_items {
709 for item in $select.order_by_items.iter_mut() {
710 if !first {
711 $ctx.sql.push_str(", ");
712 }
713 first = false;
714 match item {
715 nautilus_core::OrderByItem::Column(order) => {
716 crate::push_identifier_reference(&mut $ctx.sql, &order.column, $quote);
717 match order.direction {
718 nautilus_core::OrderDir::Asc => $ctx.sql.push_str(" ASC"),
719 nautilus_core::OrderDir::Desc => $ctx.sql.push_str(" DESC"),
720 }
721 }
722 nautilus_core::OrderByItem::Expr(expr, dir) => {
723 $render_expr($ctx, expr);
724 match *dir {
725 nautilus_core::OrderDir::Asc => $ctx.sql.push_str(" ASC"),
726 nautilus_core::OrderDir::Desc => $ctx.sql.push_str(" DESC"),
727 }
728 }
729 }
730 }
731 } else {
732 for order in $select.order_by.iter() {
733 if !first {
734 $ctx.sql.push_str(", ");
735 }
736 first = false;
737 crate::push_identifier_reference(&mut $ctx.sql, &order.column, $quote);
738 match order.direction {
739 nautilus_core::OrderDir::Asc => $ctx.sql.push_str(" ASC"),
740 nautilus_core::OrderDir::Desc => $ctx.sql.push_str(" DESC"),
741 }
742 }
743 for (expr, dir) in $select.order_by_exprs.iter_mut() {
744 if !first {
745 $ctx.sql.push_str(", ");
746 }
747 first = false;
748 $render_expr($ctx, expr);
749 match *dir {
750 nautilus_core::OrderDir::Asc => $ctx.sql.push_str(" ASC"),
751 nautilus_core::OrderDir::Desc => $ctx.sql.push_str(" DESC"),
752 }
753 }
754 }
755 }
756
757 if let Some(take) = $select.take {
758 $ctx.sql.push_str(" LIMIT ");
759 crate::push_u32(&mut $ctx.sql, take.unsigned_abs());
760 } else if $mysql_limit_hack && $select.skip.is_some() {
761 $ctx.sql.push_str(" LIMIT 18446744073709551615");
762 }
763
764 if let Some(skip) = $select.skip {
765 $ctx.sql.push_str(" OFFSET ");
766 crate::push_u32(&mut $ctx.sql, skip);
767 }
768 }};
769}
770
771macro_rules! render_expr_common_mut {
773 (
774 $ctx:expr, $expr:expr,
775 $quote:expr, $render_expr:ident, $render_select_body:ident,
776 { $($specific:tt)* }
777 ) => {
778 match $expr {
779 nautilus_core::Expr::Column(name) => {
780 crate::push_identifier_reference(&mut $ctx.sql, name, $quote);
781 }
782 nautilus_core::Expr::Not(inner) => {
783 $ctx.sql.push_str("NOT (");
784 $render_expr($ctx, inner.as_mut());
785 $ctx.sql.push(')');
786 }
787 nautilus_core::Expr::Exists(subquery) => {
788 $ctx.sql.push_str("EXISTS (");
789 $render_select_body($ctx, subquery.as_mut());
790 $ctx.sql.push(')');
791 }
792 nautilus_core::Expr::NotExists(subquery) => {
793 $ctx.sql.push_str("NOT EXISTS (");
794 $render_select_body($ctx, subquery.as_mut());
795 $ctx.sql.push(')');
796 }
797 nautilus_core::Expr::Relation { op, relation } => {
798 let is_exists = matches!(*op, nautilus_core::expr::RelationFilterOp::Some);
799 if is_exists {
800 $ctx.sql.push_str("EXISTS (SELECT * FROM ");
801 } else {
802 $ctx.sql.push_str("NOT EXISTS (SELECT * FROM ");
803 }
804 crate::push_quoted_identifier(&mut $ctx.sql, &relation.target_table, $quote);
805 $ctx.sql.push_str(" WHERE ");
806 crate::push_qualified_identifier(
807 &mut $ctx.sql,
808 &relation.target_table,
809 &relation.fk_db,
810 $quote,
811 );
812 $ctx.sql.push_str(" = ");
813 crate::push_qualified_identifier(
814 &mut $ctx.sql,
815 &relation.parent_table,
816 &relation.pk_db,
817 $quote,
818 );
819 $ctx.sql.push_str(" AND ");
820 if matches!(*op, nautilus_core::expr::RelationFilterOp::Every) {
821 $ctx.sql.push_str("NOT (");
822 $render_expr($ctx, relation.filter.as_mut());
823 $ctx.sql.push(')');
824 } else {
825 $render_expr($ctx, relation.filter.as_mut());
826 }
827 $ctx.sql.push(')');
828 }
829 nautilus_core::Expr::ScalarSubquery(subquery) => {
830 $ctx.sql.push('(');
831 $render_select_body($ctx, subquery.as_mut());
832 $ctx.sql.push(')');
833 }
834 nautilus_core::Expr::IsNull(inner) => {
835 $ctx.sql.push('(');
836 $render_expr($ctx, inner.as_mut());
837 $ctx.sql.push_str(" IS NULL)");
838 }
839 nautilus_core::Expr::IsNotNull(inner) => {
840 $ctx.sql.push('(');
841 $render_expr($ctx, inner.as_mut());
842 $ctx.sql.push_str(" IS NOT NULL)");
843 }
844 nautilus_core::Expr::Literal(s) => {
845 crate::push_sql_string_literal(&mut $ctx.sql, s);
846 }
847 nautilus_core::Expr::List(exprs) => {
848 for (i, e) in exprs.iter_mut().enumerate() {
849 if i > 0 {
850 $ctx.sql.push_str(", ");
851 }
852 $render_expr($ctx, e);
853 }
854 }
855 nautilus_core::Expr::CaseWhen { condition, then } => {
856 $ctx.sql.push_str("CASE WHEN ");
857 $render_expr($ctx, condition.as_mut());
858 $ctx.sql.push_str(" THEN ");
859 $render_expr($ctx, then.as_mut());
860 $ctx.sql.push_str(" ELSE NULL END");
861 }
862 nautilus_core::Expr::Star => {
863 $ctx.sql.push('*');
864 }
865 $($specific)*
866 }
867 };
868}
869
870mod mysql;
871mod postgres;
872mod render_estimate;
873mod sqlite;
874
875pub use mysql::MysqlDialect;
876pub use postgres::PostgresDialect;
877pub use sqlite::SqliteDialect;
878
879use nautilus_core::{Delete, Insert, Result, Select, Update, Value};
880pub(crate) use render_estimate::{
881 estimate_delete_render, estimate_insert_render, estimate_select_render, estimate_update_render,
882 RenderEstimate,
883};
884
885#[derive(Debug, Clone, PartialEq)]
889#[must_use]
890pub struct Sql {
891 pub text: String,
893 pub params: Vec<Value>,
895}
896
897pub trait Dialect {
901 fn supports_returning(&self) -> bool {
907 true
908 }
909
910 fn render_select(&self, select: &Select) -> Result<Sql>;
912
913 fn render_select_owned(&self, select: Select) -> Result<Sql> {
916 self.render_select(&select)
917 }
918
919 fn render_insert(&self, insert: &Insert) -> Result<Sql>;
921
922 fn render_insert_owned(&self, insert: Insert) -> Result<Sql> {
925 self.render_insert(&insert)
926 }
927
928 fn render_update(&self, update: &Update) -> Result<Sql>;
930
931 fn render_update_owned(&self, update: Update) -> Result<Sql> {
934 self.render_update(&update)
935 }
936
937 fn render_delete(&self, delete: &Delete) -> Result<Sql>;
939
940 fn render_delete_owned(&self, delete: Delete) -> Result<Sql> {
943 self.render_delete(&delete)
944 }
945}
946
947fn push_escaped_identifier(sql: &mut String, name: &str, quote: char) {
948 for ch in name.chars() {
949 if ch == quote {
950 sql.push(quote);
951 }
952 sql.push(ch);
953 }
954}
955
956pub(crate) fn push_quoted_identifier(sql: &mut String, name: &str, quote: char) {
958 sql.push(quote);
959 push_escaped_identifier(sql, name, quote);
960 sql.push(quote);
961}
962
963pub(crate) fn push_quoted_identifier_segments(sql: &mut String, segments: &[&str], quote: char) {
965 sql.push(quote);
966 for segment in segments {
967 push_escaped_identifier(sql, segment, quote);
968 }
969 sql.push(quote);
970}
971
972pub(crate) fn push_qualified_identifier(sql: &mut String, table: &str, column: &str, quote: char) {
974 push_quoted_identifier(sql, table, quote);
975 sql.push('.');
976 push_quoted_identifier(sql, column, quote);
977}
978
979pub(crate) fn push_column_alias(
981 sql: &mut String,
982 column: &nautilus_core::ColumnMarker,
983 quote: char,
984) {
985 push_quoted_identifier_segments(
986 sql,
987 &[column.table.as_ref(), "__", column.name.as_ref()],
988 quote,
989 );
990}
991
992pub(crate) fn push_identifier_reference(sql: &mut String, name: &str, quote: char) {
997 if let Some((table, column)) = name.split_once("__") {
998 push_qualified_identifier(sql, table, column, quote);
999 } else {
1000 push_quoted_identifier(sql, name, quote);
1001 }
1002}
1003
1004pub(crate) fn push_sql_string_literal(sql: &mut String, value: &str) {
1006 sql.push('\'');
1007 for ch in value.chars() {
1008 if ch == '\'' {
1009 sql.push('\'');
1010 }
1011 sql.push(ch);
1012 }
1013 sql.push('\'');
1014}
1015
1016fn push_u64(sql: &mut String, mut value: u64) {
1017 let mut digits = [0_u8; 20];
1018 let mut idx = digits.len();
1019
1020 loop {
1021 idx -= 1;
1022 digits[idx] = b'0' + (value % 10) as u8;
1023 value /= 10;
1024 if value == 0 {
1025 break;
1026 }
1027 }
1028
1029 for digit in &digits[idx..] {
1030 sql.push(char::from(*digit));
1031 }
1032}
1033
1034pub(crate) fn push_u32(sql: &mut String, value: u32) {
1036 push_u64(sql, u64::from(value));
1037}
1038
1039pub(crate) fn push_usize(sql: &mut String, value: usize) {
1041 push_u64(sql, value as u64);
1042}
1043
1044#[inline]
1050pub(crate) fn binary_op_sql(op: &nautilus_core::BinaryOp) -> &'static str {
1051 match op {
1052 nautilus_core::BinaryOp::Eq => "=",
1053 nautilus_core::BinaryOp::Ne => "!=",
1054 nautilus_core::BinaryOp::Lt => "<",
1055 nautilus_core::BinaryOp::Le => "<=",
1056 nautilus_core::BinaryOp::Gt => ">",
1057 nautilus_core::BinaryOp::Ge => ">=",
1058 nautilus_core::BinaryOp::And => "AND",
1059 nautilus_core::BinaryOp::Or => "OR",
1060 nautilus_core::BinaryOp::Like => "LIKE",
1061 nautilus_core::BinaryOp::ArrayContains
1062 | nautilus_core::BinaryOp::ArrayContainedBy
1063 | nautilus_core::BinaryOp::ArrayOverlaps
1064 | nautilus_core::BinaryOp::In
1065 | nautilus_core::BinaryOp::NotIn => {
1066 unreachable!(
1067 "binary_op_sql: operator {:?} must be handled by dialect-specific code",
1068 op
1069 )
1070 }
1071 }
1072}