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