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