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