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