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