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