1use qcraft_core::ast::common::{FieldRef, NullsOrder, OrderByDef, OrderDir, SchemaRef};
2use qcraft_core::ast::conditions::{CompareOp, ConditionNode, Conditions, Connector};
3use qcraft_core::ast::custom::CustomBinaryOp;
4use qcraft_core::ast::ddl::{
5 ColumnDef, ConstraintDef, DeferrableConstraint, FieldType, IdentityColumn, IndexColumnDef,
6 IndexDef, IndexExpr, LikeTableDef, MatchType, OnCommitAction, PartitionByDef,
7 PartitionStrategy, ReferentialAction, SchemaDef, SchemaMutationStmt,
8};
9use qcraft_core::ast::dml::{
10 ConflictAction, ConflictTarget, DeleteStmt, InsertSource, InsertStmt, MutationStmt,
11 OnConflictDef, OverridingKind, UpdateStmt,
12};
13use qcraft_core::ast::expr::{
14 AggregationDef, BinaryOp, CaseDef, Expr, UnaryOp, WindowDef, WindowFrameBound, WindowFrameDef,
15 WindowFrameType,
16};
17use qcraft_core::ast::query::{
18 CteDef, CteMaterialized, DistinctDef, FromItem, GroupByItem, JoinCondition, JoinDef, JoinType,
19 LimitDef, LimitKind, LockStrength, QueryStmt, SampleMethod, SelectColumn, SelectLockDef,
20 SetOpDef, SetOperationType, TableSource, WindowNameDef,
21};
22use qcraft_core::ast::tcl::{
23 BeginStmt, CommitStmt, IsolationLevel, LockMode, LockTableStmt, RollbackStmt,
24 SetTransactionStmt, TransactionMode, TransactionScope, TransactionStmt,
25};
26use qcraft_core::ast::value::Value;
27use qcraft_core::error::{RenderError, RenderResult};
28use qcraft_core::render::ctx::{ParamStyle, RenderCtx};
29use qcraft_core::render::escape_like_value;
30use qcraft_core::render::renderer::Renderer;
31
32use std::any::Any;
33
34#[derive(Debug, Clone, Copy)]
36pub enum PgVectorOp {
37 L2Distance,
39 InnerProduct,
41 CosineDistance,
43 L1Distance,
45}
46
47impl CustomBinaryOp for PgVectorOp {
48 fn as_any(&self) -> &dyn Any {
49 self
50 }
51 fn clone_box(&self) -> Box<dyn CustomBinaryOp> {
52 Box::new(*self)
53 }
54}
55
56impl From<PgVectorOp> for BinaryOp {
57 fn from(op: PgVectorOp) -> Self {
58 BinaryOp::Custom(Box::new(op))
59 }
60}
61
62fn render_custom_binary_op(custom: &dyn CustomBinaryOp, ctx: &mut RenderCtx) -> RenderResult<()> {
63 if let Some(op) = custom.as_any().downcast_ref::<PgVectorOp>() {
64 ctx.write(match op {
65 PgVectorOp::L2Distance => " <-> ",
66 PgVectorOp::InnerProduct => " <#> ",
67 PgVectorOp::CosineDistance => " <=> ",
68 PgVectorOp::L1Distance => " <+> ",
69 });
70 Ok(())
71 } else {
72 Err(RenderError::unsupported(
73 "CustomBinaryOp",
74 "unknown custom binary operator; use a wrapping renderer to handle it",
75 ))
76 }
77}
78
79fn render_like_pattern(op: &CompareOp, right: &Expr, ctx: &mut RenderCtx) -> RenderResult<()> {
80 let raw = match right {
81 Expr::Value(Value::Str(s)) => s.as_str(),
82 _ => {
83 return Err(RenderError::unsupported(
84 "CompareOp",
85 "Contains/StartsWith/EndsWith require a string value on the right side",
86 ));
87 }
88 };
89 let escaped = escape_like_value(raw);
90 let pattern = match op {
91 CompareOp::Contains | CompareOp::IContains => format!("%{escaped}%"),
92 CompareOp::StartsWith | CompareOp::IStartsWith => format!("{escaped}%"),
93 CompareOp::EndsWith | CompareOp::IEndsWith => format!("%{escaped}"),
94 _ => unreachable!(),
95 };
96 if ctx.parameterize() {
97 ctx.param(Value::Str(pattern));
98 } else {
99 ctx.string_literal(&pattern);
100 }
101 Ok(())
102}
103
104struct PgCreateTableOpts<'a> {
105 tablespace: Option<&'a str>,
106 partition_by: Option<&'a PartitionByDef>,
107 inherits: Option<&'a [SchemaRef]>,
108 using_method: Option<&'a str>,
109 with_options: Option<&'a [(String, String)]>,
110 on_commit: Option<&'a OnCommitAction>,
111}
112
113pub struct PostgresRenderer {
114 param_style: ParamStyle,
115}
116
117impl PostgresRenderer {
118 pub fn new() -> Self {
119 Self {
120 param_style: ParamStyle::Dollar,
121 }
122 }
123
124 pub fn with_param_style(mut self, style: ParamStyle) -> Self {
126 self.param_style = style;
127 self
128 }
129
130 pub fn render_schema_stmt(
132 &self,
133 stmt: &SchemaMutationStmt,
134 ) -> RenderResult<(String, Vec<Value>)> {
135 let mut ctx = RenderCtx::new(self.param_style);
136 self.render_schema_mutation(stmt, &mut ctx)?;
137 Ok(ctx.finish())
138 }
139
140 pub fn render_transaction_stmt(
142 &self,
143 stmt: &TransactionStmt,
144 ) -> RenderResult<(String, Vec<Value>)> {
145 let mut ctx = RenderCtx::new(self.param_style);
146 self.render_transaction(stmt, &mut ctx)?;
147 Ok(ctx.finish())
148 }
149
150 pub fn render_mutation_stmt(&self, stmt: &MutationStmt) -> RenderResult<(String, Vec<Value>)> {
152 let mut ctx = RenderCtx::new(self.param_style).with_parameterize(true);
153 self.render_mutation(stmt, &mut ctx)?;
154 Ok(ctx.finish())
155 }
156
157 pub fn render_query_stmt(&self, stmt: &QueryStmt) -> RenderResult<(String, Vec<Value>)> {
159 let mut ctx = RenderCtx::new(self.param_style).with_parameterize(true);
160 self.render_query(stmt, &mut ctx)?;
161 Ok(ctx.finish())
162 }
163}
164
165impl Default for PostgresRenderer {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171impl Renderer for PostgresRenderer {
176 fn render_schema_mutation(
179 &self,
180 stmt: &SchemaMutationStmt,
181 ctx: &mut RenderCtx,
182 ) -> RenderResult<()> {
183 match stmt {
184 SchemaMutationStmt::CreateTable {
185 schema,
186 if_not_exists,
187 temporary,
188 unlogged,
189 tablespace,
190 partition_by,
191 inherits,
192 using_method,
193 with_options,
194 on_commit,
195 table_options: _, without_rowid: _, strict: _, } => self.pg_create_table(
199 schema,
200 *if_not_exists,
201 *temporary,
202 *unlogged,
203 &PgCreateTableOpts {
204 tablespace: tablespace.as_deref(),
205 partition_by: partition_by.as_ref(),
206 inherits: inherits.as_deref(),
207 using_method: using_method.as_deref(),
208 with_options: with_options.as_deref(),
209 on_commit: on_commit.as_ref(),
210 },
211 ctx,
212 ),
213
214 SchemaMutationStmt::DropTable {
215 schema_ref,
216 if_exists,
217 cascade,
218 } => {
219 ctx.keyword("DROP TABLE");
220 if *if_exists {
221 ctx.keyword("IF EXISTS");
222 }
223 self.pg_schema_ref(schema_ref, ctx);
224 if *cascade {
225 ctx.keyword("CASCADE");
226 }
227 Ok(())
228 }
229
230 SchemaMutationStmt::RenameTable {
231 schema_ref,
232 new_name,
233 } => {
234 ctx.keyword("ALTER TABLE");
235 self.pg_schema_ref(schema_ref, ctx);
236 ctx.keyword("RENAME TO").ident(new_name);
237 Ok(())
238 }
239
240 SchemaMutationStmt::TruncateTable {
241 schema_ref,
242 restart_identity,
243 cascade,
244 } => {
245 ctx.keyword("TRUNCATE TABLE");
246 self.pg_schema_ref(schema_ref, ctx);
247 if *restart_identity {
248 ctx.keyword("RESTART IDENTITY");
249 }
250 if *cascade {
251 ctx.keyword("CASCADE");
252 }
253 Ok(())
254 }
255
256 SchemaMutationStmt::AddColumn {
257 schema_ref,
258 column,
259 if_not_exists,
260 position: _, } => {
262 ctx.keyword("ALTER TABLE");
263 self.pg_schema_ref(schema_ref, ctx);
264 ctx.keyword("ADD COLUMN");
265 if *if_not_exists {
266 ctx.keyword("IF NOT EXISTS");
267 }
268 self.render_column_def(column, ctx)
269 }
270
271 SchemaMutationStmt::DropColumn {
272 schema_ref,
273 name,
274 if_exists,
275 cascade,
276 } => {
277 ctx.keyword("ALTER TABLE");
278 self.pg_schema_ref(schema_ref, ctx);
279 ctx.keyword("DROP COLUMN");
280 if *if_exists {
281 ctx.keyword("IF EXISTS");
282 }
283 ctx.ident(name);
284 if *cascade {
285 ctx.keyword("CASCADE");
286 }
287 Ok(())
288 }
289
290 SchemaMutationStmt::RenameColumn {
291 schema_ref,
292 old_name,
293 new_name,
294 } => {
295 ctx.keyword("ALTER TABLE");
296 self.pg_schema_ref(schema_ref, ctx);
297 ctx.keyword("RENAME COLUMN")
298 .ident(old_name)
299 .keyword("TO")
300 .ident(new_name);
301 Ok(())
302 }
303
304 SchemaMutationStmt::AlterColumnType {
305 schema_ref,
306 column_name,
307 new_type,
308 using_expr,
309 } => {
310 ctx.keyword("ALTER TABLE");
311 self.pg_schema_ref(schema_ref, ctx);
312 ctx.keyword("ALTER COLUMN")
313 .ident(column_name)
314 .keyword("SET DATA TYPE");
315 self.render_column_type(new_type, ctx)?;
316 if let Some(expr) = using_expr {
317 ctx.keyword("USING");
318 self.render_expr(expr, ctx)?;
319 }
320 Ok(())
321 }
322
323 SchemaMutationStmt::AlterColumnDefault {
324 schema_ref,
325 column_name,
326 default,
327 } => {
328 ctx.keyword("ALTER TABLE");
329 self.pg_schema_ref(schema_ref, ctx);
330 ctx.keyword("ALTER COLUMN").ident(column_name);
331 match default {
332 Some(expr) => {
333 ctx.keyword("SET DEFAULT");
334 self.render_expr(expr, ctx)?;
335 }
336 None => {
337 ctx.keyword("DROP DEFAULT");
338 }
339 }
340 Ok(())
341 }
342
343 SchemaMutationStmt::AlterColumnNullability {
344 schema_ref,
345 column_name,
346 not_null,
347 } => {
348 ctx.keyword("ALTER TABLE");
349 self.pg_schema_ref(schema_ref, ctx);
350 ctx.keyword("ALTER COLUMN").ident(column_name);
351 if *not_null {
352 ctx.keyword("SET NOT NULL");
353 } else {
354 ctx.keyword("DROP NOT NULL");
355 }
356 Ok(())
357 }
358
359 SchemaMutationStmt::AddConstraint {
360 schema_ref,
361 constraint,
362 not_valid,
363 } => {
364 ctx.keyword("ALTER TABLE");
365 self.pg_schema_ref(schema_ref, ctx);
366 ctx.keyword("ADD");
367 self.render_constraint(constraint, ctx)?;
368 if *not_valid {
369 ctx.keyword("NOT VALID");
370 }
371 Ok(())
372 }
373
374 SchemaMutationStmt::DropConstraint {
375 schema_ref,
376 constraint_name,
377 if_exists,
378 cascade,
379 } => {
380 ctx.keyword("ALTER TABLE");
381 self.pg_schema_ref(schema_ref, ctx);
382 ctx.keyword("DROP CONSTRAINT");
383 if *if_exists {
384 ctx.keyword("IF EXISTS");
385 }
386 ctx.ident(constraint_name);
387 if *cascade {
388 ctx.keyword("CASCADE");
389 }
390 Ok(())
391 }
392
393 SchemaMutationStmt::RenameConstraint {
394 schema_ref,
395 old_name,
396 new_name,
397 } => {
398 ctx.keyword("ALTER TABLE");
399 self.pg_schema_ref(schema_ref, ctx);
400 ctx.keyword("RENAME CONSTRAINT")
401 .ident(old_name)
402 .keyword("TO")
403 .ident(new_name);
404 Ok(())
405 }
406
407 SchemaMutationStmt::ValidateConstraint {
408 schema_ref,
409 constraint_name,
410 } => {
411 ctx.keyword("ALTER TABLE");
412 self.pg_schema_ref(schema_ref, ctx);
413 ctx.keyword("VALIDATE CONSTRAINT").ident(constraint_name);
414 Ok(())
415 }
416
417 SchemaMutationStmt::CreateIndex {
418 schema_ref,
419 index,
420 if_not_exists,
421 concurrently,
422 } => self.pg_create_index(schema_ref, index, *if_not_exists, *concurrently, ctx),
423
424 SchemaMutationStmt::DropIndex {
425 schema_ref: _,
426 index_name,
427 if_exists,
428 concurrently,
429 cascade,
430 } => {
431 ctx.keyword("DROP INDEX");
432 if *concurrently {
433 ctx.keyword("CONCURRENTLY");
434 }
435 if *if_exists {
436 ctx.keyword("IF EXISTS");
437 }
438 ctx.ident(index_name);
439 if *cascade {
440 ctx.keyword("CASCADE");
441 }
442 Ok(())
443 }
444
445 SchemaMutationStmt::CreateExtension {
446 name,
447 if_not_exists,
448 schema,
449 version,
450 cascade,
451 } => {
452 ctx.keyword("CREATE EXTENSION");
453 if *if_not_exists {
454 ctx.keyword("IF NOT EXISTS");
455 }
456 ctx.ident(name);
457 if let Some(s) = schema {
458 ctx.keyword("SCHEMA").ident(s);
459 }
460 if let Some(v) = version {
461 ctx.keyword("VERSION").string_literal(v);
462 }
463 if *cascade {
464 ctx.keyword("CASCADE");
465 }
466 Ok(())
467 }
468
469 SchemaMutationStmt::DropExtension {
470 name,
471 if_exists,
472 cascade,
473 } => {
474 ctx.keyword("DROP EXTENSION");
475 if *if_exists {
476 ctx.keyword("IF EXISTS");
477 }
478 ctx.ident(name);
479 if *cascade {
480 ctx.keyword("CASCADE");
481 }
482 Ok(())
483 }
484
485 SchemaMutationStmt::CreateCollation {
486 name,
487 if_not_exists,
488 locale,
489 lc_collate,
490 lc_ctype,
491 provider,
492 deterministic,
493 from_collation,
494 } => {
495 ctx.keyword("CREATE COLLATION");
496 if *if_not_exists {
497 ctx.keyword("IF NOT EXISTS");
498 }
499 ctx.ident(name);
500 if let Some(from) = from_collation {
501 ctx.keyword("FROM").ident(from);
502 } else {
503 ctx.write(" (");
504 let mut first = true;
505 if let Some(loc) = locale {
506 ctx.keyword("LOCALE").write(" = ").string_literal(loc);
507 first = false;
508 }
509 if let Some(lc) = lc_collate {
510 if !first {
511 ctx.write(", ");
512 }
513 ctx.keyword("LC_COLLATE").write(" = ").string_literal(lc);
514 first = false;
515 }
516 if let Some(lc) = lc_ctype {
517 if !first {
518 ctx.write(", ");
519 }
520 ctx.keyword("LC_CTYPE").write(" = ").string_literal(lc);
521 first = false;
522 }
523 if let Some(prov) = provider {
524 if !first {
525 ctx.write(", ");
526 }
527 ctx.keyword("PROVIDER").write(" = ").keyword(prov);
528 first = false;
529 }
530 if let Some(det) = deterministic {
531 if !first {
532 ctx.write(", ");
533 }
534 ctx.keyword("DETERMINISTIC").write(" = ").keyword(if *det {
535 "TRUE"
536 } else {
537 "FALSE"
538 });
539 }
540 ctx.write(")");
541 }
542 Ok(())
543 }
544
545 SchemaMutationStmt::DropCollation {
546 name,
547 if_exists,
548 cascade,
549 } => {
550 ctx.keyword("DROP COLLATION");
551 if *if_exists {
552 ctx.keyword("IF EXISTS");
553 }
554 ctx.ident(name);
555 if *cascade {
556 ctx.keyword("CASCADE");
557 }
558 Ok(())
559 }
560
561 SchemaMutationStmt::Custom(_) => Err(RenderError::unsupported(
562 "CustomSchemaMutation",
563 "custom DDL must be handled by a wrapping renderer",
564 )),
565 }
566 }
567
568 fn render_column_def(&self, col: &ColumnDef, ctx: &mut RenderCtx) -> RenderResult<()> {
569 ctx.ident(&col.name);
570 self.render_column_type(&col.field_type, ctx)?;
571
572 if let Some(storage) = &col.storage {
573 ctx.keyword("STORAGE").keyword(storage);
574 }
575
576 if let Some(compression) = &col.compression {
577 ctx.keyword("COMPRESSION").keyword(compression);
578 }
579
580 if let Some(collation) = &col.collation {
581 ctx.keyword("COLLATE").ident(collation);
582 }
583
584 if col.not_null {
585 ctx.keyword("NOT NULL");
586 }
587
588 if let Some(default) = &col.default {
589 ctx.keyword("DEFAULT");
590 self.render_expr(default, ctx)?;
591 }
592
593 if let Some(identity) = &col.identity {
594 self.pg_identity(identity, ctx);
595 }
596
597 if let Some(generated) = &col.generated {
598 ctx.keyword("GENERATED ALWAYS AS").space().paren_open();
599 self.render_expr(&generated.expr, ctx)?;
600 ctx.paren_close().keyword("STORED");
601 }
602
603 Ok(())
604 }
605
606 fn render_column_type(&self, ty: &FieldType, ctx: &mut RenderCtx) -> RenderResult<()> {
607 match ty {
608 FieldType::Scalar(name) => {
609 ctx.keyword(name);
610 }
611 FieldType::Parameterized { name, params } => {
612 ctx.keyword(name).write("(");
613 for (i, p) in params.iter().enumerate() {
614 if i > 0 {
615 ctx.comma();
616 }
617 ctx.write(p);
618 }
619 ctx.paren_close();
620 }
621 FieldType::Array(inner) => {
622 self.render_column_type(inner, ctx)?;
623 ctx.write("[]");
624 }
625 FieldType::Vector(dim) => {
626 ctx.keyword("VECTOR")
627 .write("(")
628 .write(&dim.to_string())
629 .paren_close();
630 }
631 FieldType::Custom(_) => {
632 return Err(RenderError::unsupported(
633 "CustomFieldType",
634 "custom field type must be handled by a wrapping renderer",
635 ));
636 }
637 }
638 Ok(())
639 }
640
641 fn render_constraint(&self, c: &ConstraintDef, ctx: &mut RenderCtx) -> RenderResult<()> {
642 match c {
643 ConstraintDef::PrimaryKey {
644 name,
645 columns,
646 include,
647 } => {
648 if let Some(n) = name {
649 ctx.keyword("CONSTRAINT").ident(n);
650 }
651 ctx.keyword("PRIMARY KEY").paren_open();
652 self.pg_comma_idents(columns, ctx);
653 ctx.paren_close();
654 if let Some(inc) = include {
655 ctx.keyword("INCLUDE").paren_open();
656 self.pg_comma_idents(inc, ctx);
657 ctx.paren_close();
658 }
659 }
660
661 ConstraintDef::ForeignKey {
662 name,
663 columns,
664 ref_table,
665 ref_columns,
666 on_delete,
667 on_update,
668 deferrable,
669 match_type,
670 } => {
671 if let Some(n) = name {
672 ctx.keyword("CONSTRAINT").ident(n);
673 }
674 ctx.keyword("FOREIGN KEY").paren_open();
675 self.pg_comma_idents(columns, ctx);
676 ctx.paren_close().keyword("REFERENCES");
677 self.pg_schema_ref(ref_table, ctx);
678 ctx.paren_open();
679 self.pg_comma_idents(ref_columns, ctx);
680 ctx.paren_close();
681 if let Some(mt) = match_type {
682 ctx.keyword(match mt {
683 MatchType::Full => "MATCH FULL",
684 MatchType::Partial => "MATCH PARTIAL",
685 MatchType::Simple => "MATCH SIMPLE",
686 });
687 }
688 if let Some(action) = on_delete {
689 ctx.keyword("ON DELETE");
690 self.pg_referential_action(action, ctx);
691 }
692 if let Some(action) = on_update {
693 ctx.keyword("ON UPDATE");
694 self.pg_referential_action(action, ctx);
695 }
696 if let Some(def) = deferrable {
697 self.pg_deferrable(def, ctx);
698 }
699 }
700
701 ConstraintDef::Unique {
702 name,
703 columns,
704 include,
705 nulls_distinct,
706 condition: _, } => {
708 if let Some(n) = name {
709 ctx.keyword("CONSTRAINT").ident(n);
710 }
711 ctx.keyword("UNIQUE");
712 if let Some(false) = nulls_distinct {
713 ctx.keyword("NULLS NOT DISTINCT");
714 }
715 ctx.paren_open();
716 self.pg_comma_idents(columns, ctx);
717 ctx.paren_close();
718 if let Some(inc) = include {
719 ctx.keyword("INCLUDE").paren_open();
720 self.pg_comma_idents(inc, ctx);
721 ctx.paren_close();
722 }
723 }
724
725 ConstraintDef::Check {
726 name,
727 condition,
728 no_inherit,
729 enforced: _, } => {
731 if let Some(n) = name {
732 ctx.keyword("CONSTRAINT").ident(n);
733 }
734 ctx.keyword("CHECK").paren_open();
735 self.render_condition(condition, ctx)?;
736 ctx.paren_close();
737 if *no_inherit {
738 ctx.keyword("NO INHERIT");
739 }
740 }
741
742 ConstraintDef::Exclusion {
743 name,
744 elements,
745 index_method,
746 condition,
747 } => {
748 if let Some(n) = name {
749 ctx.keyword("CONSTRAINT").ident(n);
750 }
751 ctx.keyword("EXCLUDE USING")
752 .keyword(index_method)
753 .paren_open();
754 for (i, elem) in elements.iter().enumerate() {
755 if i > 0 {
756 ctx.comma();
757 }
758 ctx.ident(&elem.column)
759 .keyword("WITH")
760 .keyword(&elem.operator);
761 }
762 ctx.paren_close();
763 if let Some(cond) = condition {
764 ctx.keyword("WHERE").paren_open();
765 self.render_condition(cond, ctx)?;
766 ctx.paren_close();
767 }
768 }
769
770 ConstraintDef::Custom(_) => {
771 return Err(RenderError::unsupported(
772 "CustomConstraint",
773 "custom constraint must be handled by a wrapping renderer",
774 ));
775 }
776 }
777 Ok(())
778 }
779
780 fn render_index_def(&self, idx: &IndexDef, ctx: &mut RenderCtx) -> RenderResult<()> {
781 ctx.ident(&idx.name);
784 if let Some(index_type) = &idx.index_type {
785 ctx.keyword("USING").keyword(index_type);
786 }
787 ctx.paren_open();
788 self.pg_index_columns(&idx.columns, ctx)?;
789 ctx.paren_close();
790 Ok(())
791 }
792
793 fn render_expr(&self, expr: &Expr, ctx: &mut RenderCtx) -> RenderResult<()> {
796 match expr {
797 Expr::Value(val) => self.pg_value(val, ctx),
798
799 Expr::Field(field_ref) => {
800 self.pg_field_ref(field_ref, ctx);
801 Ok(())
802 }
803
804 Expr::Binary { left, op, right } => {
805 self.render_expr(left, ctx)?;
806 let mod_op = if self.param_style == ParamStyle::Percent {
809 "%%"
810 } else {
811 "%"
812 };
813 match op {
814 BinaryOp::Custom(custom) => {
815 render_custom_binary_op(custom.as_ref(), ctx)?;
816 }
817 _ => {
818 ctx.keyword(match op {
819 BinaryOp::Add => "+",
820 BinaryOp::Sub => "-",
821 BinaryOp::Mul => "*",
822 BinaryOp::Div => "/",
823 BinaryOp::Mod => mod_op,
824 BinaryOp::BitwiseAnd => "&",
825 BinaryOp::BitwiseOr => "|",
826 BinaryOp::ShiftLeft => "<<",
827 BinaryOp::ShiftRight => ">>",
828 BinaryOp::Concat => "||",
829 BinaryOp::Custom(_) => unreachable!(),
830 });
831 }
832 };
833 self.render_expr(right, ctx)
834 }
835
836 Expr::Unary { op, expr: inner } => {
837 match op {
838 UnaryOp::Neg => ctx.write("-"),
839 UnaryOp::Not => ctx.keyword("NOT"),
840 UnaryOp::BitwiseNot => ctx.write("~"),
841 };
842 self.render_expr(inner, ctx)
843 }
844
845 Expr::Func { name, args } => {
846 ctx.keyword(name).write("(");
847 for (i, arg) in args.iter().enumerate() {
848 if i > 0 {
849 ctx.comma();
850 }
851 self.render_expr(arg, ctx)?;
852 }
853 ctx.paren_close();
854 Ok(())
855 }
856
857 Expr::Aggregate(agg) => self.render_aggregate(agg, ctx),
858
859 Expr::Cast {
860 expr: inner,
861 to_type,
862 } => {
863 self.render_expr(inner, ctx)?;
864 ctx.operator("::");
865 ctx.write(to_type);
866 Ok(())
867 }
868
869 Expr::Case(case) => self.render_case(case, ctx),
870
871 Expr::Window(win) => self.render_window(win, ctx),
872
873 Expr::Exists(query) => {
874 ctx.keyword("EXISTS").write("(");
875 self.render_query(query, ctx)?;
876 ctx.paren_close();
877 Ok(())
878 }
879
880 Expr::SubQuery(query) => {
881 ctx.paren_open();
882 self.render_query(query, ctx)?;
883 ctx.paren_close();
884 Ok(())
885 }
886
887 Expr::ArraySubQuery(query) => {
888 ctx.keyword("ARRAY").paren_open();
889 self.render_query(query, ctx)?;
890 ctx.paren_close();
891 Ok(())
892 }
893
894 Expr::Collate { expr, collation } => {
895 self.render_expr(expr, ctx)?;
896 ctx.keyword("COLLATE").ident(collation);
897 Ok(())
898 }
899
900 Expr::JsonArray(items) => {
901 ctx.keyword("jsonb_build_array").write("(");
902 for (i, item) in items.iter().enumerate() {
903 if i > 0 {
904 ctx.comma();
905 }
906 self.render_expr(item, ctx)?;
907 }
908 ctx.paren_close();
909 Ok(())
910 }
911
912 Expr::JsonObject(pairs) => {
913 ctx.keyword("jsonb_build_object").write("(");
914 for (i, (key, val)) in pairs.iter().enumerate() {
915 if i > 0 {
916 ctx.comma();
917 }
918 ctx.string_literal(key).comma();
919 self.render_expr(val, ctx)?;
920 }
921 ctx.paren_close();
922 Ok(())
923 }
924
925 Expr::JsonAgg {
926 expr,
927 distinct,
928 filter,
929 order_by,
930 } => {
931 ctx.keyword("jsonb_agg").write("(");
932 if *distinct {
933 ctx.keyword("DISTINCT");
934 }
935 self.render_expr(expr, ctx)?;
936 if let Some(ob) = order_by {
937 ctx.keyword("ORDER BY");
938 self.pg_order_by_list(ob, ctx)?;
939 }
940 ctx.paren_close();
941 if let Some(f) = filter {
942 ctx.keyword("FILTER").paren_open().keyword("WHERE");
943 self.render_condition(f, ctx)?;
944 ctx.paren_close();
945 }
946 Ok(())
947 }
948
949 Expr::StringAgg {
950 expr,
951 delimiter,
952 distinct,
953 filter,
954 order_by,
955 } => {
956 ctx.keyword("string_agg").write("(");
957 if *distinct {
958 ctx.keyword("DISTINCT");
959 }
960 self.render_expr(expr, ctx)?;
961 ctx.comma().string_literal(delimiter);
962 if let Some(ob) = order_by {
963 ctx.keyword("ORDER BY");
964 self.pg_order_by_list(ob, ctx)?;
965 }
966 ctx.paren_close();
967 if let Some(f) = filter {
968 ctx.keyword("FILTER").paren_open().keyword("WHERE");
969 self.render_condition(f, ctx)?;
970 ctx.paren_close();
971 }
972 Ok(())
973 }
974
975 Expr::Now => {
976 ctx.keyword("now()");
977 Ok(())
978 }
979
980 Expr::Raw { sql, params } => {
981 if params.is_empty() {
982 ctx.keyword(sql);
983 } else {
984 ctx.raw_with_params(sql, params);
985 }
986 Ok(())
987 }
988
989 Expr::Custom(_) => Err(RenderError::unsupported(
990 "CustomExpr",
991 "custom expression must be handled by a wrapping renderer",
992 )),
993 }
994 }
995
996 fn render_aggregate(&self, agg: &AggregationDef, ctx: &mut RenderCtx) -> RenderResult<()> {
997 ctx.keyword(&agg.name).write("(");
998 if agg.distinct {
999 ctx.keyword("DISTINCT");
1000 }
1001 if let Some(expr) = &agg.expression {
1002 self.render_expr(expr, ctx)?;
1003 } else {
1004 ctx.write("*");
1005 }
1006 if let Some(args) = &agg.args {
1007 for arg in args {
1008 ctx.comma();
1009 self.render_expr(arg, ctx)?;
1010 }
1011 }
1012 if let Some(order_by) = &agg.order_by {
1013 ctx.keyword("ORDER BY");
1014 self.pg_order_by_list(order_by, ctx)?;
1015 }
1016 ctx.paren_close();
1017 if let Some(filter) = &agg.filter {
1018 ctx.keyword("FILTER").paren_open().keyword("WHERE");
1019 self.render_condition(filter, ctx)?;
1020 ctx.paren_close();
1021 }
1022 Ok(())
1023 }
1024
1025 fn render_window(&self, win: &WindowDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1026 self.render_expr(&win.expression, ctx)?;
1027 ctx.keyword("OVER").paren_open();
1028 if let Some(partition_by) = &win.partition_by {
1029 ctx.keyword("PARTITION BY");
1030 for (i, expr) in partition_by.iter().enumerate() {
1031 if i > 0 {
1032 ctx.comma();
1033 }
1034 self.render_expr(expr, ctx)?;
1035 }
1036 }
1037 if let Some(order_by) = &win.order_by {
1038 ctx.keyword("ORDER BY");
1039 self.pg_order_by_list(order_by, ctx)?;
1040 }
1041 if let Some(frame) = &win.frame {
1042 self.pg_window_frame(frame, ctx);
1043 }
1044 ctx.paren_close();
1045 Ok(())
1046 }
1047
1048 fn render_case(&self, case: &CaseDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1049 ctx.keyword("CASE");
1050 for clause in &case.cases {
1051 ctx.keyword("WHEN");
1052 self.render_condition(&clause.condition, ctx)?;
1053 ctx.keyword("THEN");
1054 self.render_expr(&clause.result, ctx)?;
1055 }
1056 if let Some(default) = &case.default {
1057 ctx.keyword("ELSE");
1058 self.render_expr(default, ctx)?;
1059 }
1060 ctx.keyword("END");
1061 Ok(())
1062 }
1063
1064 fn render_condition(&self, cond: &Conditions, ctx: &mut RenderCtx) -> RenderResult<()> {
1067 if cond.negated
1069 && cond.children.len() == 1
1070 && matches!(cond.children[0], ConditionNode::Exists(_))
1071 {
1072 if let ConditionNode::Exists(query) = &cond.children[0] {
1073 ctx.keyword("NOT EXISTS").write("(");
1074 self.render_query(query, ctx)?;
1075 ctx.paren_close();
1076 return Ok(());
1077 }
1078 }
1079
1080 if cond.negated {
1081 ctx.keyword("NOT").paren_open();
1082 }
1083 let connector = match cond.connector {
1084 Connector::And => " AND ",
1085 Connector::Or => " OR ",
1086 };
1087 for (i, child) in cond.children.iter().enumerate() {
1088 if i > 0 {
1089 ctx.write(connector);
1090 }
1091 match child {
1092 ConditionNode::Comparison(comp) => {
1093 if comp.negate {
1094 ctx.keyword("NOT").paren_open();
1095 }
1096 self.render_compare_op(&comp.op, &comp.left, &comp.right, ctx)?;
1097 if comp.negate {
1098 ctx.paren_close();
1099 }
1100 }
1101 ConditionNode::Group(group) => {
1102 ctx.paren_open();
1103 self.render_condition(group, ctx)?;
1104 ctx.paren_close();
1105 }
1106 ConditionNode::Exists(query) => {
1107 ctx.keyword("EXISTS").write("(");
1108 self.render_query(query, ctx)?;
1109 ctx.paren_close();
1110 }
1111 ConditionNode::Custom(_) => {
1112 return Err(RenderError::unsupported(
1113 "CustomCondition",
1114 "custom condition must be handled by a wrapping renderer",
1115 ));
1116 }
1117 }
1118 }
1119 if cond.negated {
1120 ctx.paren_close();
1121 }
1122 Ok(())
1123 }
1124
1125 fn render_compare_op(
1126 &self,
1127 op: &CompareOp,
1128 left: &Expr,
1129 right: &Expr,
1130 ctx: &mut RenderCtx,
1131 ) -> RenderResult<()> {
1132 self.render_expr(left, ctx)?;
1133 match op {
1134 CompareOp::Eq => ctx.write(" = "),
1135 CompareOp::Neq => ctx.write(" <> "),
1136 CompareOp::Gt => ctx.write(" > "),
1137 CompareOp::Gte => ctx.write(" >= "),
1138 CompareOp::Lt => ctx.write(" < "),
1139 CompareOp::Lte => ctx.write(" <= "),
1140 CompareOp::Like => ctx.keyword("LIKE"),
1141 CompareOp::ILike => ctx.keyword("ILIKE"),
1142 CompareOp::Contains | CompareOp::StartsWith | CompareOp::EndsWith => {
1143 ctx.keyword("LIKE");
1144 render_like_pattern(op, right, ctx)?;
1145 return Ok(());
1146 }
1147 CompareOp::IContains | CompareOp::IStartsWith | CompareOp::IEndsWith => {
1148 ctx.keyword("ILIKE");
1149 render_like_pattern(op, right, ctx)?;
1150 return Ok(());
1151 }
1152 CompareOp::In => {
1153 if let Expr::Value(Value::Array(items)) = right {
1154 ctx.keyword("IN").paren_open();
1155 for (i, item) in items.iter().enumerate() {
1156 if i > 0 {
1157 ctx.comma();
1158 }
1159 self.pg_value(item, ctx)?;
1160 }
1161 ctx.paren_close();
1162 } else {
1163 ctx.keyword("IN");
1164 self.render_expr(right, ctx)?;
1165 }
1166 return Ok(());
1167 }
1168 CompareOp::Between => {
1169 ctx.keyword("BETWEEN");
1170 if let Expr::Value(Value::Array(items)) = right {
1171 if items.len() == 2 {
1172 self.pg_value(&items[0], ctx)?;
1173 ctx.keyword("AND");
1174 self.pg_value(&items[1], ctx)?;
1175 } else {
1176 return Err(RenderError::unsupported(
1177 "Between",
1178 "BETWEEN requires exactly 2 values",
1179 ));
1180 }
1181 } else {
1182 self.render_expr(right, ctx)?;
1183 }
1184 return Ok(());
1185 }
1186 CompareOp::IsNull => {
1187 ctx.keyword("IS NULL");
1188 return Ok(());
1189 }
1190 CompareOp::Similar => ctx.keyword("SIMILAR TO"),
1191 CompareOp::Regex => ctx.write(" ~ "),
1192 CompareOp::IRegex => ctx.write(" ~* "),
1193 CompareOp::JsonbContains => ctx.write(" @> "),
1194 CompareOp::JsonbContainedBy => ctx.write(" <@ "),
1195 CompareOp::JsonbHasKey => ctx.write(" ? "),
1196 CompareOp::JsonbHasAnyKey => {
1197 ctx.write(" ?| ");
1198 self.render_expr(right, ctx)?;
1199 ctx.write("::text[]");
1200 return Ok(());
1201 }
1202 CompareOp::JsonbHasAllKeys => {
1203 ctx.write(" ?& ");
1204 self.render_expr(right, ctx)?;
1205 ctx.write("::text[]");
1206 return Ok(());
1207 }
1208 CompareOp::FtsMatch => ctx.write(" @@ "),
1209 CompareOp::TrigramSimilar => {
1210 if self.param_style == ParamStyle::Percent {
1211 ctx.write(" %% ")
1212 } else {
1213 ctx.write(" % ")
1214 }
1215 }
1216 CompareOp::TrigramWordSimilar => {
1217 if self.param_style == ParamStyle::Percent {
1218 ctx.write(" <%% ")
1219 } else {
1220 ctx.write(" <% ")
1221 }
1222 }
1223 CompareOp::TrigramStrictWordSimilar => {
1224 if self.param_style == ParamStyle::Percent {
1225 ctx.write(" <<%% ")
1226 } else {
1227 ctx.write(" <<% ")
1228 }
1229 }
1230 CompareOp::RangeContains => ctx.write(" @> "),
1231 CompareOp::RangeContainedBy => ctx.write(" <@ "),
1232 CompareOp::RangeOverlap => ctx.write(" && "),
1233 CompareOp::RangeStrictlyLeft => ctx.write(" << "),
1234 CompareOp::RangeStrictlyRight => ctx.write(" >> "),
1235 CompareOp::RangeNotLeft => ctx.write(" &> "),
1236 CompareOp::RangeNotRight => ctx.write(" &< "),
1237 CompareOp::RangeAdjacent => ctx.write(" -|- "),
1238 CompareOp::Custom(_) => {
1239 return Err(RenderError::unsupported(
1240 "CustomCompareOp",
1241 "custom compare op must be handled by a wrapping renderer",
1242 ));
1243 }
1244 };
1245 self.render_expr(right, ctx)
1246 }
1247
1248 fn render_query(&self, stmt: &QueryStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1251 if let Some(ctes) = &stmt.ctes {
1253 self.render_ctes(ctes, ctx)?;
1254 }
1255
1256 ctx.keyword("SELECT");
1258
1259 if let Some(distinct) = &stmt.distinct {
1261 match distinct {
1262 DistinctDef::Distinct => {
1263 ctx.keyword("DISTINCT");
1264 }
1265 DistinctDef::DistinctOn(exprs) => {
1266 ctx.keyword("DISTINCT ON").paren_open();
1267 for (i, expr) in exprs.iter().enumerate() {
1268 if i > 0 {
1269 ctx.comma();
1270 }
1271 self.render_expr(expr, ctx)?;
1272 }
1273 ctx.paren_close();
1274 }
1275 }
1276 }
1277
1278 self.render_select_columns(&stmt.columns, ctx)?;
1280
1281 if let Some(from) = &stmt.from {
1283 ctx.keyword("FROM");
1284 for (i, item) in from.iter().enumerate() {
1285 if i > 0 {
1286 ctx.comma();
1287 }
1288 self.pg_render_from_item(item, ctx)?;
1289 }
1290 }
1291
1292 if let Some(joins) = &stmt.joins {
1294 self.render_joins(joins, ctx)?;
1295 }
1296
1297 if let Some(cond) = &stmt.where_clause {
1299 self.render_where(cond, ctx)?;
1300 }
1301
1302 if let Some(group_by) = &stmt.group_by {
1304 self.pg_render_group_by(group_by, ctx)?;
1305 }
1306
1307 if let Some(having) = &stmt.having {
1309 ctx.keyword("HAVING");
1310 self.render_condition(having, ctx)?;
1311 }
1312
1313 if let Some(windows) = &stmt.window {
1315 self.pg_render_window_clause(windows, ctx)?;
1316 }
1317
1318 if let Some(order_by) = &stmt.order_by {
1320 self.render_order_by(order_by, ctx)?;
1321 }
1322
1323 if let Some(limit) = &stmt.limit {
1325 self.render_limit(limit, ctx)?;
1326 }
1327
1328 if let Some(locks) = &stmt.lock {
1330 for lock in locks {
1331 self.render_lock(lock, ctx)?;
1332 }
1333 }
1334
1335 Ok(())
1336 }
1337
1338 fn render_select_columns(
1339 &self,
1340 cols: &[SelectColumn],
1341 ctx: &mut RenderCtx,
1342 ) -> RenderResult<()> {
1343 for (i, col) in cols.iter().enumerate() {
1344 if i > 0 {
1345 ctx.comma();
1346 }
1347 match col {
1348 SelectColumn::Star(None) => {
1349 ctx.keyword("*");
1350 }
1351 SelectColumn::Star(Some(table)) => {
1352 ctx.ident(table).operator(".").keyword("*");
1353 }
1354 SelectColumn::Expr { expr, alias } => {
1355 self.render_expr(expr, ctx)?;
1356 if let Some(a) = alias {
1357 ctx.keyword("AS").ident(a);
1358 }
1359 }
1360 SelectColumn::Field { field, alias } => {
1361 self.pg_field_ref(field, ctx);
1362 if let Some(a) = alias {
1363 ctx.keyword("AS").ident(a);
1364 }
1365 }
1366 }
1367 }
1368 Ok(())
1369 }
1370 fn render_from(&self, source: &TableSource, ctx: &mut RenderCtx) -> RenderResult<()> {
1371 match source {
1372 TableSource::Table(schema_ref) => {
1373 self.pg_schema_ref(schema_ref, ctx);
1374 if let Some(alias) = &schema_ref.alias {
1375 ctx.keyword("AS").ident(alias);
1376 }
1377 }
1378 TableSource::SubQuery(sq) => {
1379 ctx.paren_open();
1380 self.render_query(&sq.query, ctx)?;
1381 ctx.paren_close().keyword("AS").ident(&sq.alias);
1382 }
1383 TableSource::SetOp(set_op) => {
1384 ctx.paren_open();
1385 self.pg_render_set_op(set_op, ctx)?;
1386 ctx.paren_close();
1387 }
1388 TableSource::Lateral(inner) => {
1389 ctx.keyword("LATERAL");
1390 self.render_from(&inner.source, ctx)?;
1391 }
1392 TableSource::Function { name, args, alias } => {
1393 ctx.keyword(name).write("(");
1394 for (i, arg) in args.iter().enumerate() {
1395 if i > 0 {
1396 ctx.comma();
1397 }
1398 self.render_expr(arg, ctx)?;
1399 }
1400 ctx.paren_close();
1401 if let Some(a) = alias {
1402 ctx.keyword("AS").ident(a);
1403 }
1404 }
1405 TableSource::Values {
1406 rows,
1407 alias,
1408 column_aliases,
1409 } => {
1410 ctx.paren_open().keyword("VALUES");
1411 for (i, row) in rows.iter().enumerate() {
1412 if i > 0 {
1413 ctx.comma();
1414 }
1415 ctx.paren_open();
1416 for (j, val) in row.iter().enumerate() {
1417 if j > 0 {
1418 ctx.comma();
1419 }
1420 self.render_expr(val, ctx)?;
1421 }
1422 ctx.paren_close();
1423 }
1424 ctx.paren_close().keyword("AS").ident(alias);
1425 if let Some(cols) = column_aliases {
1426 ctx.paren_open();
1427 for (i, c) in cols.iter().enumerate() {
1428 if i > 0 {
1429 ctx.comma();
1430 }
1431 ctx.ident(c);
1432 }
1433 ctx.paren_close();
1434 }
1435 }
1436 TableSource::Custom(_) => {
1437 return Err(RenderError::unsupported(
1438 "CustomTableSource",
1439 "custom table source must be handled by a wrapping renderer",
1440 ));
1441 }
1442 }
1443 Ok(())
1444 }
1445 fn render_joins(&self, joins: &[JoinDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1446 for join in joins {
1447 if join.natural {
1448 ctx.keyword("NATURAL");
1449 }
1450 ctx.keyword(match join.join_type {
1451 JoinType::Inner => "INNER JOIN",
1452 JoinType::Left => "LEFT JOIN",
1453 JoinType::Right => "RIGHT JOIN",
1454 JoinType::Full => "FULL JOIN",
1455 JoinType::Cross => "CROSS JOIN",
1456 JoinType::CrossApply => "CROSS JOIN LATERAL",
1457 JoinType::OuterApply => "LEFT JOIN LATERAL",
1458 });
1459 self.pg_render_from_item(&join.source, ctx)?;
1460 if !matches!(join.join_type, JoinType::Cross) {
1461 if let Some(condition) = &join.condition {
1462 match condition {
1463 JoinCondition::On(cond) => {
1464 ctx.keyword("ON");
1465 self.render_condition(cond, ctx)?;
1466 }
1467 JoinCondition::Using(cols) => {
1468 ctx.keyword("USING").paren_open();
1469 self.pg_comma_idents(cols, ctx);
1470 ctx.paren_close();
1471 }
1472 }
1473 }
1474 }
1475 if matches!(join.join_type, JoinType::OuterApply) && join.condition.is_none() {
1477 ctx.keyword("ON TRUE");
1478 }
1479 }
1480 Ok(())
1481 }
1482 fn render_where(&self, cond: &Conditions, ctx: &mut RenderCtx) -> RenderResult<()> {
1483 ctx.keyword("WHERE");
1484 self.render_condition(cond, ctx)
1485 }
1486 fn render_order_by(&self, order: &[OrderByDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1487 ctx.keyword("ORDER BY");
1488 self.pg_order_by_list(order, ctx)
1489 }
1490 fn render_limit(&self, limit: &LimitDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1491 match &limit.kind {
1492 LimitKind::Limit(n) => {
1493 ctx.keyword("LIMIT");
1494 if ctx.parameterize() {
1495 ctx.param(Value::BigInt(*n as i64));
1496 } else {
1497 ctx.space().write(&n.to_string());
1498 }
1499 }
1500 LimitKind::FetchFirst {
1501 count,
1502 with_ties,
1503 percent,
1504 } => {
1505 if let Some(offset) = limit.offset {
1506 ctx.keyword("OFFSET")
1507 .space()
1508 .write(&offset.to_string())
1509 .keyword("ROWS");
1510 }
1511 ctx.keyword("FETCH FIRST");
1512 if *percent {
1513 ctx.space().write(&count.to_string()).keyword("PERCENT");
1514 } else {
1515 ctx.space().write(&count.to_string());
1516 }
1517 if *with_ties {
1518 ctx.keyword("ROWS WITH TIES");
1519 } else {
1520 ctx.keyword("ROWS ONLY");
1521 }
1522 return Ok(());
1523 }
1524 LimitKind::Top { count, .. } => {
1525 ctx.keyword("LIMIT");
1527 if ctx.parameterize() {
1528 ctx.param(Value::BigInt(*count as i64));
1529 } else {
1530 ctx.space().write(&count.to_string());
1531 }
1532 }
1533 }
1534 if let Some(offset) = limit.offset {
1535 ctx.keyword("OFFSET");
1536 if ctx.parameterize() {
1537 ctx.param(Value::BigInt(offset as i64));
1538 } else {
1539 ctx.space().write(&offset.to_string());
1540 }
1541 }
1542 Ok(())
1543 }
1544 fn render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1545 let any_recursive = ctes.iter().any(|c| c.recursive);
1547 ctx.keyword("WITH");
1548 if any_recursive {
1549 ctx.keyword("RECURSIVE");
1550 }
1551 for (i, cte) in ctes.iter().enumerate() {
1552 if i > 0 {
1553 ctx.comma();
1554 }
1555 ctx.ident(&cte.name);
1556 if let Some(col_names) = &cte.column_names {
1557 ctx.paren_open();
1558 self.pg_comma_idents(col_names, ctx);
1559 ctx.paren_close();
1560 }
1561 ctx.keyword("AS");
1562 if let Some(mat) = &cte.materialized {
1563 match mat {
1564 CteMaterialized::Materialized => {
1565 ctx.keyword("MATERIALIZED");
1566 }
1567 CteMaterialized::NotMaterialized => {
1568 ctx.keyword("NOT MATERIALIZED");
1569 }
1570 }
1571 }
1572 ctx.paren_open();
1573 self.render_query(&cte.query, ctx)?;
1574 ctx.paren_close();
1575 }
1576 Ok(())
1577 }
1578 fn render_lock(&self, lock: &SelectLockDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1579 ctx.keyword("FOR");
1580 ctx.keyword(match lock.strength {
1581 LockStrength::Update => "UPDATE",
1582 LockStrength::NoKeyUpdate => "NO KEY UPDATE",
1583 LockStrength::Share => "SHARE",
1584 LockStrength::KeyShare => "KEY SHARE",
1585 });
1586 if let Some(of) = &lock.of {
1587 ctx.keyword("OF");
1588 for (i, table) in of.iter().enumerate() {
1589 if i > 0 {
1590 ctx.comma();
1591 }
1592 self.pg_schema_ref(table, ctx);
1593 }
1594 }
1595 if lock.nowait {
1596 ctx.keyword("NOWAIT");
1597 }
1598 if lock.skip_locked {
1599 ctx.keyword("SKIP LOCKED");
1600 }
1601 Ok(())
1602 }
1603
1604 fn render_mutation(&self, stmt: &MutationStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1607 match stmt {
1608 MutationStmt::Insert(s) => self.render_insert(s, ctx),
1609 MutationStmt::Update(s) => self.render_update(s, ctx),
1610 MutationStmt::Delete(s) => self.render_delete(s, ctx),
1611 MutationStmt::Custom(_) => Err(RenderError::unsupported(
1612 "CustomMutation",
1613 "custom DML must be handled by a wrapping renderer",
1614 )),
1615 }
1616 }
1617
1618 fn render_insert(&self, stmt: &InsertStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1619 if let Some(ctes) = &stmt.ctes {
1621 self.pg_render_ctes(ctes, ctx)?;
1622 }
1623
1624 ctx.keyword("INSERT INTO");
1625 self.pg_schema_ref(&stmt.table, ctx);
1626
1627 if let Some(cols) = &stmt.columns {
1629 ctx.paren_open();
1630 self.pg_comma_idents(cols, ctx);
1631 ctx.paren_close();
1632 }
1633
1634 if let Some(overriding) = &stmt.overriding {
1636 ctx.keyword(match overriding {
1637 OverridingKind::System => "OVERRIDING SYSTEM VALUE",
1638 OverridingKind::User => "OVERRIDING USER VALUE",
1639 });
1640 }
1641
1642 match &stmt.source {
1644 InsertSource::Values(rows) => {
1645 ctx.keyword("VALUES");
1646 for (i, row) in rows.iter().enumerate() {
1647 if i > 0 {
1648 ctx.comma();
1649 }
1650 ctx.paren_open();
1651 for (j, expr) in row.iter().enumerate() {
1652 if j > 0 {
1653 ctx.comma();
1654 }
1655 self.render_expr(expr, ctx)?;
1656 }
1657 ctx.paren_close();
1658 }
1659 }
1660 InsertSource::Select(query) => {
1661 self.render_query(query, ctx)?;
1662 }
1663 InsertSource::DefaultValues => {
1664 ctx.keyword("DEFAULT VALUES");
1665 }
1666 }
1667
1668 if let Some(conflicts) = &stmt.on_conflict {
1670 for oc in conflicts {
1671 self.render_on_conflict(oc, ctx)?;
1672 }
1673 }
1674
1675 if let Some(returning) = &stmt.returning {
1677 self.render_returning(returning, ctx)?;
1678 }
1679
1680 Ok(())
1681 }
1682
1683 fn render_update(&self, stmt: &UpdateStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1684 if let Some(ctes) = &stmt.ctes {
1686 self.pg_render_ctes(ctes, ctx)?;
1687 }
1688
1689 ctx.keyword("UPDATE");
1690
1691 if stmt.only {
1693 ctx.keyword("ONLY");
1694 }
1695
1696 self.pg_schema_ref(&stmt.table, ctx);
1697
1698 if let Some(alias) = &stmt.table.alias {
1700 ctx.keyword("AS").ident(alias);
1701 }
1702
1703 ctx.keyword("SET");
1705 for (i, (col, expr)) in stmt.assignments.iter().enumerate() {
1706 if i > 0 {
1707 ctx.comma();
1708 }
1709 ctx.ident(col).write(" = ");
1710 self.render_expr(expr, ctx)?;
1711 }
1712
1713 if let Some(from) = &stmt.from {
1715 ctx.keyword("FROM");
1716 for (i, source) in from.iter().enumerate() {
1717 if i > 0 {
1718 ctx.comma();
1719 }
1720 self.render_from(source, ctx)?;
1721 }
1722 }
1723
1724 if let Some(cond) = &stmt.where_clause {
1726 ctx.keyword("WHERE");
1727 self.render_condition(cond, ctx)?;
1728 }
1729
1730 if let Some(returning) = &stmt.returning {
1732 self.render_returning(returning, ctx)?;
1733 }
1734
1735 Ok(())
1736 }
1737
1738 fn render_delete(&self, stmt: &DeleteStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1739 if let Some(ctes) = &stmt.ctes {
1741 self.pg_render_ctes(ctes, ctx)?;
1742 }
1743
1744 ctx.keyword("DELETE FROM");
1745
1746 if stmt.only {
1748 ctx.keyword("ONLY");
1749 }
1750
1751 self.pg_schema_ref(&stmt.table, ctx);
1752
1753 if let Some(alias) = &stmt.table.alias {
1755 ctx.keyword("AS").ident(alias);
1756 }
1757
1758 if let Some(using) = &stmt.using {
1760 ctx.keyword("USING");
1761 for (i, source) in using.iter().enumerate() {
1762 if i > 0 {
1763 ctx.comma();
1764 }
1765 self.render_from(source, ctx)?;
1766 }
1767 }
1768
1769 if let Some(cond) = &stmt.where_clause {
1771 ctx.keyword("WHERE");
1772 self.render_condition(cond, ctx)?;
1773 }
1774
1775 if let Some(returning) = &stmt.returning {
1777 self.render_returning(returning, ctx)?;
1778 }
1779
1780 Ok(())
1781 }
1782
1783 fn render_on_conflict(&self, oc: &OnConflictDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1784 ctx.keyword("ON CONFLICT");
1785
1786 if let Some(target) = &oc.target {
1788 match target {
1789 ConflictTarget::Columns {
1790 columns,
1791 where_clause,
1792 } => {
1793 ctx.paren_open();
1794 self.pg_comma_idents(columns, ctx);
1795 ctx.paren_close();
1796 if let Some(cond) = where_clause {
1797 ctx.keyword("WHERE");
1798 self.render_condition(cond, ctx)?;
1799 }
1800 }
1801 ConflictTarget::Constraint(name) => {
1802 ctx.keyword("ON CONSTRAINT").ident(name);
1803 }
1804 }
1805 }
1806
1807 match &oc.action {
1809 ConflictAction::DoNothing => {
1810 ctx.keyword("DO NOTHING");
1811 }
1812 ConflictAction::DoUpdate {
1813 assignments,
1814 where_clause,
1815 } => {
1816 ctx.keyword("DO UPDATE SET");
1817 for (i, (col, expr)) in assignments.iter().enumerate() {
1818 if i > 0 {
1819 ctx.comma();
1820 }
1821 ctx.ident(col).write(" = ");
1822 self.render_expr(expr, ctx)?;
1823 }
1824 if let Some(cond) = where_clause {
1825 ctx.keyword("WHERE");
1826 self.render_condition(cond, ctx)?;
1827 }
1828 }
1829 }
1830
1831 Ok(())
1832 }
1833
1834 fn render_returning(&self, cols: &[SelectColumn], ctx: &mut RenderCtx) -> RenderResult<()> {
1835 ctx.keyword("RETURNING");
1836 for (i, col) in cols.iter().enumerate() {
1837 if i > 0 {
1838 ctx.comma();
1839 }
1840 match col {
1841 SelectColumn::Star(None) => {
1842 ctx.keyword("*");
1843 }
1844 SelectColumn::Star(Some(table)) => {
1845 ctx.ident(table).operator(".").keyword("*");
1846 }
1847 SelectColumn::Expr { expr, alias } => {
1848 self.render_expr(expr, ctx)?;
1849 if let Some(a) = alias {
1850 ctx.keyword("AS").ident(a);
1851 }
1852 }
1853 SelectColumn::Field { field, alias } => {
1854 self.pg_field_ref(field, ctx);
1855 if let Some(a) = alias {
1856 ctx.keyword("AS").ident(a);
1857 }
1858 }
1859 }
1860 }
1861 Ok(())
1862 }
1863
1864 fn render_transaction(&self, stmt: &TransactionStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1867 match stmt {
1868 TransactionStmt::Begin(s) => self.pg_begin(s, ctx),
1869 TransactionStmt::Commit(s) => self.pg_commit(s, ctx),
1870 TransactionStmt::Rollback(s) => self.pg_rollback(s, ctx),
1871 TransactionStmt::Savepoint(s) => {
1872 ctx.keyword("SAVEPOINT").ident(&s.name);
1873 Ok(())
1874 }
1875 TransactionStmt::ReleaseSavepoint(s) => {
1876 ctx.keyword("RELEASE").keyword("SAVEPOINT").ident(&s.name);
1877 Ok(())
1878 }
1879 TransactionStmt::SetTransaction(s) => self.pg_set_transaction(s, ctx),
1880 TransactionStmt::LockTable(s) => self.pg_lock_table(s, ctx),
1881 TransactionStmt::PrepareTransaction(s) => {
1882 ctx.keyword("PREPARE")
1883 .keyword("TRANSACTION")
1884 .string_literal(&s.transaction_id);
1885 Ok(())
1886 }
1887 TransactionStmt::CommitPrepared(s) => {
1888 ctx.keyword("COMMIT")
1889 .keyword("PREPARED")
1890 .string_literal(&s.transaction_id);
1891 Ok(())
1892 }
1893 TransactionStmt::RollbackPrepared(s) => {
1894 ctx.keyword("ROLLBACK")
1895 .keyword("PREPARED")
1896 .string_literal(&s.transaction_id);
1897 Ok(())
1898 }
1899 TransactionStmt::Custom(_) => Err(RenderError::unsupported(
1900 "Custom TCL",
1901 "not supported by PostgresRenderer",
1902 )),
1903 }
1904 }
1905}
1906
1907impl PostgresRenderer {
1912 fn pg_begin(&self, stmt: &BeginStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1915 ctx.keyword("BEGIN");
1916 if let Some(modes) = &stmt.modes {
1917 self.pg_transaction_modes(modes, ctx);
1918 }
1919 Ok(())
1920 }
1921
1922 fn pg_commit(&self, stmt: &CommitStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1923 ctx.keyword("COMMIT");
1924 if stmt.and_chain {
1925 ctx.keyword("AND").keyword("CHAIN");
1926 }
1927 Ok(())
1928 }
1929
1930 fn pg_rollback(&self, stmt: &RollbackStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1931 ctx.keyword("ROLLBACK");
1932 if let Some(sp) = &stmt.to_savepoint {
1933 ctx.keyword("TO").keyword("SAVEPOINT").ident(sp);
1934 }
1935 if stmt.and_chain {
1936 ctx.keyword("AND").keyword("CHAIN");
1937 }
1938 Ok(())
1939 }
1940
1941 fn pg_set_transaction(
1942 &self,
1943 stmt: &SetTransactionStmt,
1944 ctx: &mut RenderCtx,
1945 ) -> RenderResult<()> {
1946 ctx.keyword("SET");
1947 match &stmt.scope {
1948 Some(TransactionScope::Session) => {
1949 ctx.keyword("SESSION")
1950 .keyword("CHARACTERISTICS")
1951 .keyword("AS")
1952 .keyword("TRANSACTION");
1953 }
1954 _ => {
1955 ctx.keyword("TRANSACTION");
1956 }
1957 }
1958 if let Some(snap_id) = &stmt.snapshot_id {
1959 ctx.keyword("SNAPSHOT").string_literal(snap_id);
1960 } else {
1961 self.pg_transaction_modes(&stmt.modes, ctx);
1962 }
1963 Ok(())
1964 }
1965
1966 fn pg_transaction_modes(&self, modes: &[TransactionMode], ctx: &mut RenderCtx) {
1967 for (i, mode) in modes.iter().enumerate() {
1968 if i > 0 {
1969 ctx.comma();
1970 }
1971 match mode {
1972 TransactionMode::IsolationLevel(lvl) => {
1973 ctx.keyword("ISOLATION").keyword("LEVEL");
1974 ctx.keyword(match lvl {
1975 IsolationLevel::ReadUncommitted => "READ UNCOMMITTED",
1976 IsolationLevel::ReadCommitted => "READ COMMITTED",
1977 IsolationLevel::RepeatableRead => "REPEATABLE READ",
1978 IsolationLevel::Serializable => "SERIALIZABLE",
1979 IsolationLevel::Snapshot => "SERIALIZABLE", });
1981 }
1982 TransactionMode::ReadOnly => {
1983 ctx.keyword("READ ONLY");
1984 }
1985 TransactionMode::ReadWrite => {
1986 ctx.keyword("READ WRITE");
1987 }
1988 TransactionMode::Deferrable => {
1989 ctx.keyword("DEFERRABLE");
1990 }
1991 TransactionMode::NotDeferrable => {
1992 ctx.keyword("NOT DEFERRABLE");
1993 }
1994 TransactionMode::WithConsistentSnapshot => {} }
1996 }
1997 }
1998
1999 fn pg_lock_table(&self, stmt: &LockTableStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
2000 ctx.keyword("LOCK").keyword("TABLE");
2001 for (i, def) in stmt.tables.iter().enumerate() {
2002 if i > 0 {
2003 ctx.comma();
2004 }
2005 if def.only {
2006 ctx.keyword("ONLY");
2007 }
2008 if let Some(schema) = &def.schema {
2009 ctx.ident(schema).operator(".");
2010 }
2011 ctx.ident(&def.table);
2012 }
2013 if let Some(first) = stmt.tables.first() {
2015 ctx.keyword("IN");
2016 ctx.keyword(match first.mode {
2017 LockMode::AccessShare => "ACCESS SHARE",
2018 LockMode::RowShare => "ROW SHARE",
2019 LockMode::RowExclusive => "ROW EXCLUSIVE",
2020 LockMode::ShareUpdateExclusive => "SHARE UPDATE EXCLUSIVE",
2021 LockMode::Share => "SHARE",
2022 LockMode::ShareRowExclusive => "SHARE ROW EXCLUSIVE",
2023 LockMode::Exclusive => "EXCLUSIVE",
2024 LockMode::AccessExclusive => "ACCESS EXCLUSIVE",
2025 _ => "ACCESS EXCLUSIVE", });
2027 ctx.keyword("MODE");
2028 }
2029 if stmt.nowait {
2030 ctx.keyword("NOWAIT");
2031 }
2032 Ok(())
2033 }
2034
2035 fn pg_schema_ref(&self, schema_ref: &qcraft_core::ast::common::SchemaRef, ctx: &mut RenderCtx) {
2038 if let Some(ns) = &schema_ref.namespace {
2039 ctx.ident(ns).operator(".");
2040 }
2041 ctx.ident(&schema_ref.name);
2042 }
2043
2044 fn pg_field_ref(&self, field_ref: &FieldRef, ctx: &mut RenderCtx) {
2045 if let Some(ns) = &field_ref.namespace {
2046 ctx.ident(ns).operator(".");
2047 }
2048 ctx.ident(&field_ref.table_name)
2049 .operator(".")
2050 .ident(&field_ref.field.name);
2051 let mut child = &field_ref.field.child;
2052 while let Some(c) = child {
2053 ctx.operator("->'")
2054 .write(&c.name.replace('\'', "''"))
2055 .write("'");
2056 child = &c.child;
2057 }
2058 }
2059
2060 fn pg_comma_idents(&self, names: &[String], ctx: &mut RenderCtx) {
2061 for (i, name) in names.iter().enumerate() {
2062 if i > 0 {
2063 ctx.comma();
2064 }
2065 ctx.ident(name);
2066 }
2067 }
2068
2069 fn pg_value(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
2070 if matches!(val, Value::Null) && !ctx.parameterize() {
2075 ctx.keyword("NULL");
2076 return Ok(());
2077 }
2078
2079 if ctx.parameterize() {
2083 ctx.param(val.clone());
2084 return Ok(());
2085 }
2086
2087 self.pg_value_literal(val, ctx)
2089 }
2090
2091 fn pg_value_literal(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
2092 match val {
2093 Value::Null => {
2094 ctx.keyword("NULL");
2095 }
2096 Value::Bool(b) => {
2097 ctx.keyword(if *b { "TRUE" } else { "FALSE" });
2098 }
2099 Value::Int(n) | Value::BigInt(n) => {
2100 ctx.keyword(&n.to_string());
2101 }
2102 Value::Float(f) => {
2103 ctx.keyword(&f.to_string());
2104 }
2105 Value::Str(s) => {
2106 ctx.string_literal(s);
2107 }
2108 Value::Bytes(b) => {
2109 ctx.write("'\\x");
2110 for byte in b {
2111 ctx.write(&format!("{byte:02x}"));
2112 }
2113 ctx.write("'");
2114 }
2115 Value::Date(s) | Value::DateTime(s) | Value::Time(s) => {
2116 ctx.string_literal(s);
2117 }
2118 Value::Decimal(s) => {
2119 ctx.keyword(s);
2120 }
2121 Value::Uuid(s) => {
2122 ctx.string_literal(s);
2123 }
2124 Value::Json(s) => {
2125 ctx.string_literal(s);
2126 ctx.write("::json");
2127 }
2128 Value::Jsonb(s) => {
2129 ctx.string_literal(s);
2130 ctx.write("::jsonb");
2131 }
2132 Value::IpNetwork(s) => {
2133 ctx.string_literal(s);
2134 ctx.write("::inet");
2135 }
2136 Value::Array(items) => {
2137 ctx.keyword("ARRAY").write("[");
2138 for (i, item) in items.iter().enumerate() {
2139 if i > 0 {
2140 ctx.comma();
2141 }
2142 self.pg_value_literal(item, ctx)?;
2143 }
2144 ctx.write("]");
2145 }
2146 Value::Vector(values) => {
2147 let parts: Vec<String> = values.iter().map(|v| v.to_string()).collect();
2148 let literal = format!("[{}]", parts.join(","));
2149 ctx.string_literal(&literal);
2150 ctx.write("::vector");
2151 }
2152 Value::TimeDelta {
2153 years,
2154 months,
2155 days,
2156 seconds,
2157 microseconds,
2158 } => {
2159 ctx.keyword("INTERVAL");
2160 let mut parts = Vec::new();
2161 if *years != 0 {
2162 parts.push(format!("{years} years"));
2163 }
2164 if *months != 0 {
2165 parts.push(format!("{months} months"));
2166 }
2167 if *days != 0 {
2168 parts.push(format!("{days} days"));
2169 }
2170 if *seconds != 0 {
2171 parts.push(format!("{seconds} seconds"));
2172 }
2173 if *microseconds != 0 {
2174 parts.push(format!("{microseconds} microseconds"));
2175 }
2176 if parts.is_empty() {
2177 parts.push("0 seconds".into());
2178 }
2179 ctx.string_literal(&parts.join(" "));
2180 }
2181 }
2182 Ok(())
2183 }
2184
2185 fn pg_referential_action(&self, action: &ReferentialAction, ctx: &mut RenderCtx) {
2186 match action {
2187 ReferentialAction::NoAction => {
2188 ctx.keyword("NO ACTION");
2189 }
2190 ReferentialAction::Restrict => {
2191 ctx.keyword("RESTRICT");
2192 }
2193 ReferentialAction::Cascade => {
2194 ctx.keyword("CASCADE");
2195 }
2196 ReferentialAction::SetNull(cols) => {
2197 ctx.keyword("SET NULL");
2198 if let Some(cols) = cols {
2199 ctx.paren_open();
2200 self.pg_comma_idents(cols, ctx);
2201 ctx.paren_close();
2202 }
2203 }
2204 ReferentialAction::SetDefault(cols) => {
2205 ctx.keyword("SET DEFAULT");
2206 if let Some(cols) = cols {
2207 ctx.paren_open();
2208 self.pg_comma_idents(cols, ctx);
2209 ctx.paren_close();
2210 }
2211 }
2212 }
2213 }
2214
2215 fn pg_deferrable(&self, def: &DeferrableConstraint, ctx: &mut RenderCtx) {
2216 if def.deferrable {
2217 ctx.keyword("DEFERRABLE");
2218 } else {
2219 ctx.keyword("NOT DEFERRABLE");
2220 }
2221 if def.initially_deferred {
2222 ctx.keyword("INITIALLY DEFERRED");
2223 } else {
2224 ctx.keyword("INITIALLY IMMEDIATE");
2225 }
2226 }
2227
2228 fn pg_identity(&self, identity: &IdentityColumn, ctx: &mut RenderCtx) {
2229 if identity.always {
2230 ctx.keyword("GENERATED ALWAYS AS IDENTITY");
2231 } else {
2232 ctx.keyword("GENERATED BY DEFAULT AS IDENTITY");
2233 }
2234 let has_options = identity.start.is_some()
2235 || identity.increment.is_some()
2236 || identity.min_value.is_some()
2237 || identity.max_value.is_some()
2238 || identity.cycle
2239 || identity.cache.is_some();
2240 if has_options {
2241 ctx.paren_open();
2242 if let Some(start) = identity.start {
2243 ctx.keyword("START WITH").keyword(&start.to_string());
2244 }
2245 if let Some(inc) = identity.increment {
2246 ctx.keyword("INCREMENT BY").keyword(&inc.to_string());
2247 }
2248 if let Some(min) = identity.min_value {
2249 ctx.keyword("MINVALUE").keyword(&min.to_string());
2250 }
2251 if let Some(max) = identity.max_value {
2252 ctx.keyword("MAXVALUE").keyword(&max.to_string());
2253 }
2254 if identity.cycle {
2255 ctx.keyword("CYCLE");
2256 }
2257 if let Some(cache) = identity.cache {
2258 ctx.keyword("CACHE").write(&cache.to_string());
2259 }
2260 ctx.paren_close();
2261 }
2262 }
2263
2264 fn pg_render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
2265 self.render_ctes(ctes, ctx)
2267 }
2268
2269 fn pg_render_from_item(&self, item: &FromItem, ctx: &mut RenderCtx) -> RenderResult<()> {
2270 if item.only {
2271 ctx.keyword("ONLY");
2272 }
2273 self.render_from(&item.source, ctx)?;
2274 if let Some(sample) = &item.sample {
2275 ctx.keyword("TABLESAMPLE");
2276 ctx.keyword(match sample.method {
2277 SampleMethod::Bernoulli => "BERNOULLI",
2278 SampleMethod::System => "SYSTEM",
2279 SampleMethod::Block => "SYSTEM", });
2281 ctx.paren_open()
2282 .write(&sample.percentage.to_string())
2283 .paren_close();
2284 if let Some(seed) = sample.seed {
2285 ctx.keyword("REPEATABLE")
2286 .paren_open()
2287 .write(&seed.to_string())
2288 .paren_close();
2289 }
2290 }
2291 Ok(())
2292 }
2293
2294 fn pg_render_group_by(&self, items: &[GroupByItem], ctx: &mut RenderCtx) -> RenderResult<()> {
2295 ctx.keyword("GROUP BY");
2296 for (i, item) in items.iter().enumerate() {
2297 if i > 0 {
2298 ctx.comma();
2299 }
2300 match item {
2301 GroupByItem::Expr(expr) => {
2302 self.render_expr(expr, ctx)?;
2303 }
2304 GroupByItem::Rollup(exprs) => {
2305 ctx.keyword("ROLLUP").paren_open();
2306 for (j, expr) in exprs.iter().enumerate() {
2307 if j > 0 {
2308 ctx.comma();
2309 }
2310 self.render_expr(expr, ctx)?;
2311 }
2312 ctx.paren_close();
2313 }
2314 GroupByItem::Cube(exprs) => {
2315 ctx.keyword("CUBE").paren_open();
2316 for (j, expr) in exprs.iter().enumerate() {
2317 if j > 0 {
2318 ctx.comma();
2319 }
2320 self.render_expr(expr, ctx)?;
2321 }
2322 ctx.paren_close();
2323 }
2324 GroupByItem::GroupingSets(sets) => {
2325 ctx.keyword("GROUPING SETS").paren_open();
2326 for (j, set) in sets.iter().enumerate() {
2327 if j > 0 {
2328 ctx.comma();
2329 }
2330 ctx.paren_open();
2331 for (k, expr) in set.iter().enumerate() {
2332 if k > 0 {
2333 ctx.comma();
2334 }
2335 self.render_expr(expr, ctx)?;
2336 }
2337 ctx.paren_close();
2338 }
2339 ctx.paren_close();
2340 }
2341 }
2342 }
2343 Ok(())
2344 }
2345
2346 fn pg_render_window_clause(
2347 &self,
2348 windows: &[WindowNameDef],
2349 ctx: &mut RenderCtx,
2350 ) -> RenderResult<()> {
2351 ctx.keyword("WINDOW");
2352 for (i, win) in windows.iter().enumerate() {
2353 if i > 0 {
2354 ctx.comma();
2355 }
2356 ctx.ident(&win.name).keyword("AS").paren_open();
2357 if let Some(base) = &win.base_window {
2358 ctx.ident(base);
2359 }
2360 if let Some(partition_by) = &win.partition_by {
2361 ctx.keyword("PARTITION BY");
2362 for (j, expr) in partition_by.iter().enumerate() {
2363 if j > 0 {
2364 ctx.comma();
2365 }
2366 self.render_expr(expr, ctx)?;
2367 }
2368 }
2369 if let Some(order_by) = &win.order_by {
2370 ctx.keyword("ORDER BY");
2371 self.pg_order_by_list(order_by, ctx)?;
2372 }
2373 if let Some(frame) = &win.frame {
2374 self.pg_window_frame(frame, ctx);
2375 }
2376 ctx.paren_close();
2377 }
2378 Ok(())
2379 }
2380
2381 fn pg_render_set_op(&self, set_op: &SetOpDef, ctx: &mut RenderCtx) -> RenderResult<()> {
2382 self.render_query(&set_op.left, ctx)?;
2383 ctx.keyword(match set_op.operation {
2384 SetOperationType::Union => "UNION",
2385 SetOperationType::UnionAll => "UNION ALL",
2386 SetOperationType::Intersect => "INTERSECT",
2387 SetOperationType::IntersectAll => "INTERSECT ALL",
2388 SetOperationType::Except => "EXCEPT",
2389 SetOperationType::ExceptAll => "EXCEPT ALL",
2390 });
2391 self.render_query(&set_op.right, ctx)
2392 }
2393
2394 fn pg_create_table(
2395 &self,
2396 schema: &SchemaDef,
2397 if_not_exists: bool,
2398 temporary: bool,
2399 unlogged: bool,
2400 opts: &PgCreateTableOpts<'_>,
2401 ctx: &mut RenderCtx,
2402 ) -> RenderResult<()> {
2403 let PgCreateTableOpts {
2404 tablespace,
2405 partition_by,
2406 inherits,
2407 using_method,
2408 with_options,
2409 on_commit,
2410 } = opts;
2411 ctx.keyword("CREATE");
2412 if temporary {
2413 ctx.keyword("TEMPORARY");
2414 }
2415 if unlogged {
2416 ctx.keyword("UNLOGGED");
2417 }
2418 ctx.keyword("TABLE");
2419 if if_not_exists {
2420 ctx.keyword("IF NOT EXISTS");
2421 }
2422 if let Some(ns) = &schema.namespace {
2423 ctx.ident(ns).operator(".");
2424 }
2425 ctx.ident(&schema.name);
2426
2427 ctx.paren_open();
2429 let mut first = true;
2430 for col in &schema.columns {
2431 if !first {
2432 ctx.comma();
2433 }
2434 first = false;
2435 self.render_column_def(col, ctx)?;
2436 }
2437 if let Some(like_tables) = &schema.like_tables {
2438 for like in like_tables {
2439 if !first {
2440 ctx.comma();
2441 }
2442 first = false;
2443 self.pg_like_table(like, ctx);
2444 }
2445 }
2446 if let Some(constraints) = &schema.constraints {
2447 for constraint in constraints {
2448 if !first {
2449 ctx.comma();
2450 }
2451 first = false;
2452 self.render_constraint(constraint, ctx)?;
2453 }
2454 }
2455 ctx.paren_close();
2456
2457 if let Some(parents) = inherits {
2459 ctx.keyword("INHERITS").paren_open();
2460 for (i, parent) in parents.iter().enumerate() {
2461 if i > 0 {
2462 ctx.comma();
2463 }
2464 self.pg_schema_ref(parent, ctx);
2465 }
2466 ctx.paren_close();
2467 }
2468
2469 if let Some(part) = partition_by {
2471 ctx.keyword("PARTITION BY");
2472 ctx.keyword(match part.strategy {
2473 PartitionStrategy::Range => "RANGE",
2474 PartitionStrategy::List => "LIST",
2475 PartitionStrategy::Hash => "HASH",
2476 });
2477 ctx.paren_open();
2478 for (i, col) in part.columns.iter().enumerate() {
2479 if i > 0 {
2480 ctx.comma();
2481 }
2482 match &col.expr {
2483 IndexExpr::Column(name) => {
2484 ctx.ident(name);
2485 }
2486 IndexExpr::Expression(expr) => {
2487 ctx.paren_open();
2488 self.render_expr(expr, ctx)?;
2489 ctx.paren_close();
2490 }
2491 }
2492 if let Some(collation) = &col.collation {
2493 ctx.keyword("COLLATE").ident(collation);
2494 }
2495 if let Some(opclass) = &col.opclass {
2496 ctx.keyword(opclass);
2497 }
2498 }
2499 ctx.paren_close();
2500 }
2501
2502 if let Some(method) = using_method {
2504 ctx.keyword("USING").keyword(method);
2505 }
2506
2507 if let Some(opts) = with_options {
2509 ctx.keyword("WITH").paren_open();
2510 for (i, (key, value)) in opts.iter().enumerate() {
2511 if i > 0 {
2512 ctx.comma();
2513 }
2514 ctx.write(key).write(" = ").write(value);
2515 }
2516 ctx.paren_close();
2517 }
2518
2519 if let Some(action) = on_commit {
2521 ctx.keyword("ON COMMIT");
2522 ctx.keyword(match action {
2523 OnCommitAction::PreserveRows => "PRESERVE ROWS",
2524 OnCommitAction::DeleteRows => "DELETE ROWS",
2525 OnCommitAction::Drop => "DROP",
2526 });
2527 }
2528
2529 if let Some(ts) = tablespace {
2531 ctx.keyword("TABLESPACE").ident(ts);
2532 }
2533
2534 if let Some(constraints) = &schema.constraints {
2536 for constraint in constraints {
2537 if let ConstraintDef::Unique {
2538 name,
2539 columns,
2540 condition: Some(cond),
2541 ..
2542 } = constraint
2543 {
2544 ctx.write(";");
2545 ctx.keyword("CREATE UNIQUE INDEX");
2546 if let Some(n) = name {
2547 ctx.ident(n);
2548 }
2549 ctx.keyword("ON");
2550 if let Some(ns) = &schema.namespace {
2551 ctx.ident(ns).operator(".");
2552 }
2553 ctx.ident(&schema.name);
2554 ctx.paren_open();
2555 self.pg_comma_idents(columns, ctx);
2556 ctx.paren_close();
2557 ctx.keyword("WHERE");
2558 self.render_condition(cond, ctx)?;
2559 }
2560 }
2561 }
2562
2563 Ok(())
2564 }
2565
2566 fn pg_like_table(&self, like: &LikeTableDef, ctx: &mut RenderCtx) {
2567 ctx.keyword("LIKE");
2568 self.pg_schema_ref(&like.source_table, ctx);
2569 for opt in &like.options {
2570 if opt.include {
2571 ctx.keyword("INCLUDING");
2572 } else {
2573 ctx.keyword("EXCLUDING");
2574 }
2575 ctx.keyword(match opt.kind {
2576 qcraft_core::ast::ddl::LikeOptionKind::Comments => "COMMENTS",
2577 qcraft_core::ast::ddl::LikeOptionKind::Compression => "COMPRESSION",
2578 qcraft_core::ast::ddl::LikeOptionKind::Constraints => "CONSTRAINTS",
2579 qcraft_core::ast::ddl::LikeOptionKind::Defaults => "DEFAULTS",
2580 qcraft_core::ast::ddl::LikeOptionKind::Generated => "GENERATED",
2581 qcraft_core::ast::ddl::LikeOptionKind::Identity => "IDENTITY",
2582 qcraft_core::ast::ddl::LikeOptionKind::Indexes => "INDEXES",
2583 qcraft_core::ast::ddl::LikeOptionKind::Statistics => "STATISTICS",
2584 qcraft_core::ast::ddl::LikeOptionKind::Storage => "STORAGE",
2585 qcraft_core::ast::ddl::LikeOptionKind::All => "ALL",
2586 });
2587 }
2588 }
2589
2590 fn pg_create_index(
2591 &self,
2592 schema_ref: &qcraft_core::ast::common::SchemaRef,
2593 index: &IndexDef,
2594 if_not_exists: bool,
2595 concurrently: bool,
2596 ctx: &mut RenderCtx,
2597 ) -> RenderResult<()> {
2598 ctx.keyword("CREATE");
2599 if index.unique {
2600 ctx.keyword("UNIQUE");
2601 }
2602 ctx.keyword("INDEX");
2603 if concurrently {
2604 ctx.keyword("CONCURRENTLY");
2605 }
2606 if if_not_exists {
2607 ctx.keyword("IF NOT EXISTS");
2608 }
2609 ctx.ident(&index.name).keyword("ON");
2610 self.pg_schema_ref(schema_ref, ctx);
2611
2612 if let Some(index_type) = &index.index_type {
2613 ctx.keyword("USING").keyword(index_type);
2614 }
2615
2616 ctx.paren_open();
2617 self.pg_index_columns(&index.columns, ctx)?;
2618 ctx.paren_close();
2619
2620 if let Some(include) = &index.include {
2621 ctx.keyword("INCLUDE").paren_open();
2622 self.pg_comma_idents(include, ctx);
2623 ctx.paren_close();
2624 }
2625
2626 if let Some(nd) = index.nulls_distinct {
2627 if !nd {
2628 ctx.keyword("NULLS NOT DISTINCT");
2629 }
2630 }
2631
2632 if let Some(params) = &index.parameters {
2633 ctx.keyword("WITH").paren_open();
2634 for (i, (key, value)) in params.iter().enumerate() {
2635 if i > 0 {
2636 ctx.comma();
2637 }
2638 ctx.write(key).write(" = ").write(value);
2639 }
2640 ctx.paren_close();
2641 }
2642
2643 if let Some(ts) = &index.tablespace {
2644 ctx.keyword("TABLESPACE").ident(ts);
2645 }
2646
2647 if let Some(condition) = &index.condition {
2648 ctx.keyword("WHERE");
2649 self.render_condition(condition, ctx)?;
2650 }
2651
2652 Ok(())
2653 }
2654
2655 fn pg_index_columns(
2656 &self,
2657 columns: &[IndexColumnDef],
2658 ctx: &mut RenderCtx,
2659 ) -> RenderResult<()> {
2660 for (i, col) in columns.iter().enumerate() {
2661 if i > 0 {
2662 ctx.comma();
2663 }
2664 match &col.expr {
2665 IndexExpr::Column(name) => {
2666 ctx.ident(name);
2667 }
2668 IndexExpr::Expression(expr) => {
2669 ctx.paren_open();
2670 self.render_expr(expr, ctx)?;
2671 ctx.paren_close();
2672 }
2673 }
2674 if let Some(collation) = &col.collation {
2675 ctx.keyword("COLLATE").ident(collation);
2676 }
2677 if let Some(opclass) = &col.opclass {
2678 ctx.keyword(opclass);
2679 }
2680 if let Some(dir) = col.direction {
2681 ctx.keyword(match dir {
2682 OrderDir::Asc => "ASC",
2683 OrderDir::Desc => "DESC",
2684 });
2685 }
2686 if let Some(nulls) = col.nulls {
2687 ctx.keyword(match nulls {
2688 NullsOrder::First => "NULLS FIRST",
2689 NullsOrder::Last => "NULLS LAST",
2690 });
2691 }
2692 }
2693 Ok(())
2694 }
2695
2696 fn pg_order_by_list(&self, order_by: &[OrderByDef], ctx: &mut RenderCtx) -> RenderResult<()> {
2697 for (i, ob) in order_by.iter().enumerate() {
2698 if i > 0 {
2699 ctx.comma();
2700 }
2701 self.render_expr(&ob.expr, ctx)?;
2702 ctx.keyword(match ob.direction {
2703 OrderDir::Asc => "ASC",
2704 OrderDir::Desc => "DESC",
2705 });
2706 if let Some(nulls) = &ob.nulls {
2707 ctx.keyword(match nulls {
2708 NullsOrder::First => "NULLS FIRST",
2709 NullsOrder::Last => "NULLS LAST",
2710 });
2711 }
2712 }
2713 Ok(())
2714 }
2715
2716 fn pg_window_frame(&self, frame: &WindowFrameDef, ctx: &mut RenderCtx) {
2717 ctx.keyword(match frame.frame_type {
2718 WindowFrameType::Rows => "ROWS",
2719 WindowFrameType::Range => "RANGE",
2720 WindowFrameType::Groups => "GROUPS",
2721 });
2722 if let Some(end) = &frame.end {
2723 ctx.keyword("BETWEEN");
2724 self.pg_frame_bound(&frame.start, ctx);
2725 ctx.keyword("AND");
2726 self.pg_frame_bound(end, ctx);
2727 } else {
2728 self.pg_frame_bound(&frame.start, ctx);
2729 }
2730 }
2731
2732 fn pg_frame_bound(&self, bound: &WindowFrameBound, ctx: &mut RenderCtx) {
2733 match bound {
2734 WindowFrameBound::CurrentRow => {
2735 ctx.keyword("CURRENT ROW");
2736 }
2737 WindowFrameBound::Preceding(None) => {
2738 ctx.keyword("UNBOUNDED PRECEDING");
2739 }
2740 WindowFrameBound::Preceding(Some(n)) => {
2741 ctx.keyword(&n.to_string()).keyword("PRECEDING");
2742 }
2743 WindowFrameBound::Following(None) => {
2744 ctx.keyword("UNBOUNDED FOLLOWING");
2745 }
2746 WindowFrameBound::Following(Some(n)) => {
2747 ctx.keyword(&n.to_string()).keyword("FOLLOWING");
2748 }
2749 }
2750 }
2751}