1use qcraft_core::ast::common::{FieldRef, NullsOrder, OrderByDef, OrderDir};
2use qcraft_core::ast::conditions::{CompareOp, ConditionNode, Conditions, Connector};
3use qcraft_core::ast::ddl::{
4 ColumnDef, ConstraintDef, DeferrableConstraint, FieldType, IndexColumnDef, IndexDef, IndexExpr,
5 ReferentialAction, SchemaDef, SchemaMutationStmt,
6};
7use qcraft_core::ast::dml::{
8 ConflictAction, ConflictResolution, ConflictTarget, DeleteStmt, InsertSource, InsertStmt,
9 MutationStmt, OnConflictDef, UpdateStmt,
10};
11use qcraft_core::ast::expr::{
12 AggregationDef, BinaryOp, CaseDef, Expr, UnaryOp, WindowDef, WindowFrameBound, WindowFrameDef,
13 WindowFrameType,
14};
15use qcraft_core::ast::query::{
16 CteDef, DistinctDef, FromItem, GroupByItem, JoinCondition, JoinDef, JoinType, LimitDef,
17 LimitKind, QueryStmt, SelectColumn, SelectLockDef, SetOpDef, SetOperationType, SqliteIndexHint,
18 TableSource, WindowNameDef,
19};
20use qcraft_core::ast::tcl::{SqliteLockType, TransactionStmt};
21use qcraft_core::ast::value::Value;
22use qcraft_core::error::{RenderError, RenderResult};
23use qcraft_core::render::ctx::{ParamStyle, RenderCtx};
24use qcraft_core::render::escape_like_value;
25use qcraft_core::render::renderer::Renderer;
26
27fn render_like_pattern(op: &CompareOp, right: &Expr, ctx: &mut RenderCtx) -> RenderResult<()> {
28 let raw = match right {
29 Expr::Value(Value::Str(s)) => s.as_str(),
30 _ => {
31 return Err(RenderError::unsupported(
32 "CompareOp",
33 "Contains/StartsWith/EndsWith require a string value on the right side",
34 ));
35 }
36 };
37 let escaped = escape_like_value(raw);
38 let pattern = match op {
39 CompareOp::Contains | CompareOp::IContains => format!("%{escaped}%"),
40 CompareOp::StartsWith | CompareOp::IStartsWith => format!("{escaped}%"),
41 CompareOp::EndsWith | CompareOp::IEndsWith => format!("%{escaped}"),
42 _ => unreachable!(),
43 };
44 if ctx.parameterize() {
45 ctx.param(Value::Str(pattern));
46 } else {
47 ctx.string_literal(&pattern);
48 }
49 Ok(())
50}
51
52pub struct SqliteRenderer;
53
54impl SqliteRenderer {
55 pub fn new() -> Self {
56 Self
57 }
58
59 pub fn render_schema_stmt(
60 &self,
61 stmt: &SchemaMutationStmt,
62 ) -> RenderResult<Vec<(String, Vec<Value>)>> {
63 let mut ctx = RenderCtx::new(ParamStyle::QMark);
64 self.render_schema_mutation(stmt, &mut ctx)?;
65 Ok(vec![ctx.finish()])
66 }
67
68 pub fn render_transaction_stmt(
69 &self,
70 stmt: &TransactionStmt,
71 ) -> RenderResult<(String, Vec<Value>)> {
72 let mut ctx = RenderCtx::new(ParamStyle::QMark);
73 self.render_transaction(stmt, &mut ctx)?;
74 Ok(ctx.finish())
75 }
76
77 pub fn render_mutation_stmt(&self, stmt: &MutationStmt) -> RenderResult<(String, Vec<Value>)> {
78 let mut ctx = RenderCtx::new(ParamStyle::QMark).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>)> {
84 let mut ctx = RenderCtx::new(ParamStyle::QMark).with_parameterize(true);
85 self.render_query(stmt, &mut ctx)?;
86 Ok(ctx.finish())
87 }
88}
89
90impl Default for SqliteRenderer {
91 fn default() -> Self {
92 Self::new()
93 }
94}
95
96impl Renderer for SqliteRenderer {
101 fn render_schema_mutation(
104 &self,
105 stmt: &SchemaMutationStmt,
106 ctx: &mut RenderCtx,
107 ) -> RenderResult<()> {
108 match stmt {
109 SchemaMutationStmt::CreateTable {
110 schema,
111 if_not_exists,
112 temporary,
113 unlogged: _,
114 tablespace: _,
115 partition_by: _, inherits: _, using_method: _, with_options: _, on_commit: _, table_options: _, without_rowid,
122 strict,
123 } => self.sqlite_create_table(
124 schema,
125 *if_not_exists,
126 *temporary,
127 *without_rowid,
128 *strict,
129 ctx,
130 ),
131
132 SchemaMutationStmt::DropTable {
133 schema_ref,
134 if_exists,
135 cascade: _, } => {
137 ctx.keyword("DROP TABLE");
138 if *if_exists {
139 ctx.keyword("IF EXISTS");
140 }
141 self.sqlite_schema_ref(schema_ref, ctx);
142 Ok(())
143 }
144
145 SchemaMutationStmt::RenameTable {
146 schema_ref,
147 new_name,
148 } => {
149 ctx.keyword("ALTER TABLE");
150 self.sqlite_schema_ref(schema_ref, ctx);
151 ctx.keyword("RENAME TO").ident(new_name);
152 Ok(())
153 }
154
155 SchemaMutationStmt::TruncateTable {
156 schema_ref,
157 restart_identity: _, cascade: _, } => {
160 ctx.keyword("DELETE FROM");
162 self.sqlite_schema_ref(schema_ref, ctx);
163 Ok(())
164 }
165
166 SchemaMutationStmt::AddColumn {
167 schema_ref,
168 column,
169 if_not_exists: _, position: _, } => {
172 ctx.keyword("ALTER TABLE");
173 self.sqlite_schema_ref(schema_ref, ctx);
174 ctx.keyword("ADD COLUMN");
175 self.render_column_def(column, ctx)
176 }
177
178 SchemaMutationStmt::DropColumn {
179 schema_ref,
180 name,
181 if_exists: _, cascade: _, } => {
184 ctx.keyword("ALTER TABLE");
185 self.sqlite_schema_ref(schema_ref, ctx);
186 ctx.keyword("DROP COLUMN").ident(name);
187 Ok(())
188 }
189
190 SchemaMutationStmt::RenameColumn {
191 schema_ref,
192 old_name,
193 new_name,
194 } => {
195 ctx.keyword("ALTER TABLE");
196 self.sqlite_schema_ref(schema_ref, ctx);
197 ctx.keyword("RENAME COLUMN")
198 .ident(old_name)
199 .keyword("TO")
200 .ident(new_name);
201 Ok(())
202 }
203
204 SchemaMutationStmt::AlterColumnType { .. } => Err(RenderError::unsupported(
206 "AlterColumnType",
207 "SQLite does not support ALTER COLUMN TYPE. Use the 12-step table rebuild procedure.",
208 )),
209 SchemaMutationStmt::AlterColumnDefault { .. } => Err(RenderError::unsupported(
210 "AlterColumnDefault",
211 "SQLite does not support ALTER COLUMN DEFAULT. Use the 12-step table rebuild procedure.",
212 )),
213 SchemaMutationStmt::AlterColumnNullability { .. } => Err(RenderError::unsupported(
214 "AlterColumnNullability",
215 "SQLite does not support ALTER COLUMN NOT NULL. Use the 12-step table rebuild procedure.",
216 )),
217 SchemaMutationStmt::AddConstraint { .. } => Err(RenderError::unsupported(
218 "AddConstraint",
219 "SQLite does not support ADD CONSTRAINT. Use the 12-step table rebuild procedure.",
220 )),
221 SchemaMutationStmt::DropConstraint { .. } => Err(RenderError::unsupported(
222 "DropConstraint",
223 "SQLite does not support DROP CONSTRAINT. Use the 12-step table rebuild procedure.",
224 )),
225 SchemaMutationStmt::RenameConstraint { .. } => Err(RenderError::unsupported(
226 "RenameConstraint",
227 "SQLite does not support RENAME CONSTRAINT.",
228 )),
229 SchemaMutationStmt::ValidateConstraint { .. } => Err(RenderError::unsupported(
230 "ValidateConstraint",
231 "SQLite does not support VALIDATE CONSTRAINT.",
232 )),
233
234 SchemaMutationStmt::CreateIndex {
236 schema_ref,
237 index,
238 if_not_exists,
239 concurrently: _, } => self.sqlite_create_index(schema_ref, index, *if_not_exists, ctx),
241
242 SchemaMutationStmt::DropIndex {
243 schema_ref: _,
244 index_name,
245 if_exists,
246 concurrently: _, cascade: _, } => {
249 ctx.keyword("DROP INDEX");
250 if *if_exists {
251 ctx.keyword("IF EXISTS");
252 }
253 ctx.ident(index_name);
254 Ok(())
255 }
256
257 SchemaMutationStmt::CreateExtension { .. } => Err(RenderError::unsupported(
259 "CreateExtension",
260 "SQLite does not support extensions.",
261 )),
262 SchemaMutationStmt::DropExtension { .. } => Err(RenderError::unsupported(
263 "DropExtension",
264 "SQLite does not support extensions.",
265 )),
266
267 SchemaMutationStmt::CreateCollation { .. } => Err(RenderError::unsupported(
268 "CreateCollation",
269 "SQLite does not support CREATE COLLATION. Use sqlite3_create_collation() C API instead.",
270 )),
271 SchemaMutationStmt::DropCollation { .. } => Err(RenderError::unsupported(
272 "DropCollation",
273 "SQLite does not support DROP COLLATION.",
274 )),
275
276 SchemaMutationStmt::Custom(_) => Err(RenderError::unsupported(
277 "CustomSchemaMutation",
278 "custom DDL must be handled by a wrapping renderer",
279 )),
280 }
281 }
282
283 fn render_column_def(&self, col: &ColumnDef, ctx: &mut RenderCtx) -> RenderResult<()> {
284 ctx.ident(&col.name);
285 self.render_column_type(&col.field_type, ctx)?;
286
287 if let Some(collation) = &col.collation {
288 ctx.keyword("COLLATE").ident(collation);
289 }
290
291 if col.not_null {
292 ctx.keyword("NOT NULL");
293 }
294
295 if let Some(default) = &col.default {
296 ctx.keyword("DEFAULT");
297 self.render_expr(default, ctx)?;
298 }
299
300 if let Some(generated) = &col.generated {
304 ctx.keyword("GENERATED ALWAYS AS").space().paren_open();
305 self.render_expr_unqualified(&generated.expr, ctx)?;
307 ctx.paren_close();
308 if generated.stored {
309 ctx.keyword("STORED");
310 } else {
311 ctx.keyword("VIRTUAL");
312 }
313 }
314
315 Ok(())
316 }
317
318 fn render_column_type(&self, ty: &FieldType, ctx: &mut RenderCtx) -> RenderResult<()> {
319 match ty {
320 FieldType::Scalar(name) => {
321 ctx.keyword(name);
322 }
323 FieldType::Parameterized { name, params } => {
324 ctx.keyword(name).write("(");
325 for (i, p) in params.iter().enumerate() {
326 if i > 0 {
327 ctx.comma();
328 }
329 ctx.write(p);
330 }
331 ctx.paren_close();
332 }
333 FieldType::Array(_) => {
334 return Err(RenderError::unsupported(
335 "ArrayType",
336 "SQLite does not support array types.",
337 ));
338 }
339 FieldType::Vector(_) => {
340 return Err(RenderError::unsupported(
341 "VectorType",
342 "SQLite does not support vector types.",
343 ));
344 }
345 FieldType::Custom(_) => {
346 return Err(RenderError::unsupported(
347 "CustomFieldType",
348 "custom field type must be handled by a wrapping renderer",
349 ));
350 }
351 }
352 Ok(())
353 }
354
355 fn render_constraint(&self, c: &ConstraintDef, ctx: &mut RenderCtx) -> RenderResult<()> {
356 match c {
357 ConstraintDef::PrimaryKey {
358 name,
359 columns,
360 include: _, } => {
362 if let Some(n) = name {
363 ctx.keyword("CONSTRAINT").ident(n);
364 }
365 ctx.keyword("PRIMARY KEY").paren_open();
366 self.sqlite_comma_idents(columns, ctx);
367 ctx.paren_close();
368 }
369
370 ConstraintDef::ForeignKey {
371 name,
372 columns,
373 ref_table,
374 ref_columns,
375 on_delete,
376 on_update,
377 deferrable,
378 match_type: _, } => {
380 if let Some(n) = name {
381 ctx.keyword("CONSTRAINT").ident(n);
382 }
383 ctx.keyword("FOREIGN KEY").paren_open();
384 self.sqlite_comma_idents(columns, ctx);
385 ctx.paren_close().keyword("REFERENCES");
386 self.sqlite_schema_ref(ref_table, ctx);
387 ctx.paren_open();
388 self.sqlite_comma_idents(ref_columns, ctx);
389 ctx.paren_close();
390 if let Some(action) = on_delete {
391 ctx.keyword("ON DELETE");
392 self.sqlite_referential_action(action, ctx)?;
393 }
394 if let Some(action) = on_update {
395 ctx.keyword("ON UPDATE");
396 self.sqlite_referential_action(action, ctx)?;
397 }
398 if let Some(def) = deferrable {
399 self.sqlite_deferrable(def, ctx);
400 }
401 }
402
403 ConstraintDef::Unique {
404 name,
405 columns,
406 include: _, nulls_distinct: _, condition: _, } => {
410 if let Some(n) = name {
411 ctx.keyword("CONSTRAINT").ident(n);
412 }
413 ctx.keyword("UNIQUE").paren_open();
414 self.sqlite_comma_idents(columns, ctx);
415 ctx.paren_close();
416 }
417
418 ConstraintDef::Check {
419 name,
420 condition,
421 no_inherit: _, enforced: _, } => {
424 if let Some(n) = name {
425 ctx.keyword("CONSTRAINT").ident(n);
426 }
427 ctx.keyword("CHECK").paren_open();
428 self.render_condition(condition, ctx)?;
429 ctx.paren_close();
430 }
431
432 ConstraintDef::Exclusion { .. } => {
433 return Err(RenderError::unsupported(
434 "ExclusionConstraint",
435 "SQLite does not support EXCLUDE constraints.",
436 ));
437 }
438
439 ConstraintDef::Custom(_) => {
440 return Err(RenderError::unsupported(
441 "CustomConstraint",
442 "custom constraint must be handled by a wrapping renderer",
443 ));
444 }
445 }
446 Ok(())
447 }
448
449 fn render_index_def(&self, idx: &IndexDef, ctx: &mut RenderCtx) -> RenderResult<()> {
450 ctx.ident(&idx.name);
451 ctx.paren_open();
452 self.sqlite_index_columns(&idx.columns, ctx)?;
453 ctx.paren_close();
454 Ok(())
455 }
456
457 fn render_expr(&self, expr: &Expr, ctx: &mut RenderCtx) -> RenderResult<()> {
460 match expr {
461 Expr::Value(val) => self.sqlite_value(val, ctx),
462
463 Expr::Field(field_ref) => {
464 self.sqlite_field_ref(field_ref, ctx);
465 Ok(())
466 }
467
468 Expr::Binary { left, op, right } => {
469 self.render_expr(left, ctx)?;
470 match op {
471 BinaryOp::Custom(_) => {
472 return Err(RenderError::unsupported(
473 "CustomBinaryOp",
474 "SQLite does not support custom binary operators.",
475 ));
476 }
477 _ => {
478 ctx.keyword(match op {
479 BinaryOp::Add => "+",
480 BinaryOp::Sub => "-",
481 BinaryOp::Mul => "*",
482 BinaryOp::Div => "/",
483 BinaryOp::Mod => "%",
484 BinaryOp::BitwiseAnd => "&",
485 BinaryOp::BitwiseOr => "|",
486 BinaryOp::ShiftLeft => "<<",
487 BinaryOp::ShiftRight => ">>",
488 BinaryOp::Concat => "||",
489 BinaryOp::Custom(_) => unreachable!(),
490 });
491 }
492 };
493 self.render_expr(right, ctx)
494 }
495
496 Expr::Unary { op, expr: inner } => {
497 match op {
498 UnaryOp::Neg => ctx.write("-"),
499 UnaryOp::Not => ctx.keyword("NOT"),
500 UnaryOp::BitwiseNot => ctx.write("~"),
501 };
502 self.render_expr(inner, ctx)
503 }
504
505 Expr::Func { name, args } => {
506 ctx.keyword(name).write("(");
507 for (i, arg) in args.iter().enumerate() {
508 if i > 0 {
509 ctx.comma();
510 }
511 self.render_expr(arg, ctx)?;
512 }
513 ctx.paren_close();
514 Ok(())
515 }
516
517 Expr::Aggregate(agg) => self.render_aggregate(agg, ctx),
518
519 Expr::Cast {
520 expr: inner,
521 to_type,
522 } => {
523 ctx.keyword("CAST").write("(");
524 self.render_expr(inner, ctx)?;
525 ctx.keyword("AS").keyword(to_type).paren_close();
526 Ok(())
527 }
528
529 Expr::Case(case) => self.render_case(case, ctx),
530 Expr::Window(win) => self.render_window(win, ctx),
531
532 Expr::Exists(query) => {
533 ctx.keyword("EXISTS").write("(");
534 self.render_query(query, ctx)?;
535 ctx.paren_close();
536 Ok(())
537 }
538
539 Expr::SubQuery(query) => {
540 ctx.paren_open();
541 self.render_query(query, ctx)?;
542 ctx.paren_close();
543 Ok(())
544 }
545
546 Expr::ArraySubQuery(_) => Err(RenderError::unsupported(
547 "ArraySubQuery",
548 "SQLite does not support ARRAY subqueries.",
549 )),
550
551 Expr::Collate { expr, collation } => {
552 self.render_expr(expr, ctx)?;
553 ctx.keyword("COLLATE").keyword(collation);
554 Ok(())
555 }
556
557 Expr::JsonArray(items) => {
558 ctx.keyword("json_array").write("(");
559 for (i, item) in items.iter().enumerate() {
560 if i > 0 {
561 ctx.comma();
562 }
563 self.render_expr(item, ctx)?;
564 }
565 ctx.paren_close();
566 Ok(())
567 }
568
569 Expr::JsonObject(pairs) => {
570 ctx.keyword("json_object").write("(");
571 for (i, (key, val)) in pairs.iter().enumerate() {
572 if i > 0 {
573 ctx.comma();
574 }
575 ctx.string_literal(key).comma();
576 self.render_expr(val, ctx)?;
577 }
578 ctx.paren_close();
579 Ok(())
580 }
581
582 Expr::JsonAgg {
583 expr,
584 distinct,
585 filter,
586 order_by,
587 } => {
588 ctx.keyword("json_group_array").write("(");
589 if *distinct {
590 ctx.keyword("DISTINCT");
591 }
592 self.render_expr(expr, ctx)?;
593 if let Some(ob) = order_by {
594 ctx.keyword("ORDER BY");
595 self.sqlite_order_by_list(ob, ctx)?;
596 }
597 ctx.paren_close();
598 if let Some(f) = filter {
599 ctx.keyword("FILTER").paren_open().keyword("WHERE");
600 self.render_condition(f, ctx)?;
601 ctx.paren_close();
602 }
603 Ok(())
604 }
605
606 Expr::StringAgg {
607 expr,
608 delimiter,
609 distinct,
610 filter,
611 order_by,
612 } => {
613 ctx.keyword("group_concat").write("(");
614 if *distinct {
615 ctx.keyword("DISTINCT");
616 }
617 self.render_expr(expr, ctx)?;
618 ctx.comma().string_literal(delimiter);
619 if let Some(ob) = order_by {
620 ctx.keyword("ORDER BY");
621 self.sqlite_order_by_list(ob, ctx)?;
622 }
623 ctx.paren_close();
624 if let Some(f) = filter {
625 ctx.keyword("FILTER").paren_open().keyword("WHERE");
626 self.render_condition(f, ctx)?;
627 ctx.paren_close();
628 }
629 Ok(())
630 }
631
632 Expr::Now => {
633 ctx.keyword("datetime")
634 .write("(")
635 .string_literal("now")
636 .paren_close();
637 Ok(())
638 }
639
640 Expr::JsonPathText { expr, path } => {
641 self.render_expr(expr, ctx)?;
642 ctx.operator("->>'")
643 .write(&path.replace('\'', "''"))
644 .write("'");
645 Ok(())
646 }
647
648 Expr::Tuple(exprs) => {
649 ctx.paren_open();
650 for (i, expr) in exprs.iter().enumerate() {
651 if i > 0 {
652 ctx.comma();
653 }
654 self.render_expr(expr, ctx)?;
655 }
656 ctx.paren_close();
657 Ok(())
658 }
659
660 Expr::Param { type_hint: _ } => {
661 ctx.placeholder();
662 Ok(())
663 }
664
665 Expr::Raw { sql, params } => {
666 if params.is_empty() {
667 ctx.keyword(sql);
668 } else {
669 ctx.raw_with_params(sql, params);
670 }
671 Ok(())
672 }
673
674 Expr::Custom(_) => Err(RenderError::unsupported(
675 "CustomExpr",
676 "custom expression must be handled by a wrapping renderer",
677 )),
678 }
679 }
680
681 fn render_aggregate(&self, agg: &AggregationDef, ctx: &mut RenderCtx) -> RenderResult<()> {
682 ctx.keyword(&agg.name).write("(");
683 if agg.distinct {
684 ctx.keyword("DISTINCT");
685 }
686 if let Some(expr) = &agg.expression {
687 self.render_expr(expr, ctx)?;
688 } else {
689 ctx.write("*");
690 }
691 if let Some(args) = &agg.args {
692 for arg in args {
693 ctx.comma();
694 self.render_expr(arg, ctx)?;
695 }
696 }
697 if let Some(order_by) = &agg.order_by {
698 ctx.keyword("ORDER BY");
699 self.sqlite_order_by_list(order_by, ctx)?;
700 }
701 ctx.paren_close();
702 if let Some(filter) = &agg.filter {
703 ctx.keyword("FILTER").paren_open().keyword("WHERE");
704 self.render_condition(filter, ctx)?;
705 ctx.paren_close();
706 }
707 Ok(())
708 }
709
710 fn render_window(&self, win: &WindowDef, ctx: &mut RenderCtx) -> RenderResult<()> {
711 self.render_expr(&win.expression, ctx)?;
712 ctx.keyword("OVER").paren_open();
713 if let Some(partition_by) = &win.partition_by {
714 ctx.keyword("PARTITION BY");
715 for (i, expr) in partition_by.iter().enumerate() {
716 if i > 0 {
717 ctx.comma();
718 }
719 self.render_expr(expr, ctx)?;
720 }
721 }
722 if let Some(order_by) = &win.order_by {
723 ctx.keyword("ORDER BY");
724 self.sqlite_order_by_list(order_by, ctx)?;
725 }
726 ctx.paren_close();
727 Ok(())
728 }
729
730 fn render_case(&self, case: &CaseDef, ctx: &mut RenderCtx) -> RenderResult<()> {
731 ctx.keyword("CASE");
732 for clause in &case.cases {
733 ctx.keyword("WHEN");
734 self.render_condition(&clause.condition, ctx)?;
735 ctx.keyword("THEN");
736 self.render_expr(&clause.result, ctx)?;
737 }
738 if let Some(default) = &case.default {
739 ctx.keyword("ELSE");
740 self.render_expr(default, ctx)?;
741 }
742 ctx.keyword("END");
743 Ok(())
744 }
745
746 fn render_condition(&self, cond: &Conditions, ctx: &mut RenderCtx) -> RenderResult<()> {
749 if cond.negated
751 && cond.children.len() == 1
752 && matches!(cond.children[0], ConditionNode::Exists(_))
753 {
754 if let ConditionNode::Exists(query) = &cond.children[0] {
755 ctx.keyword("NOT EXISTS").write("(");
756 self.render_query(query, ctx)?;
757 ctx.paren_close();
758 return Ok(());
759 }
760 }
761
762 if cond.negated {
763 ctx.keyword("NOT").paren_open();
764 }
765 let connector = match cond.connector {
766 Connector::And => " AND ",
767 Connector::Or => " OR ",
768 };
769 for (i, child) in cond.children.iter().enumerate() {
770 if i > 0 {
771 ctx.write(connector);
772 }
773 match child {
774 ConditionNode::Comparison(comp) => {
775 if comp.negate {
776 ctx.keyword("NOT").paren_open();
777 }
778 self.render_compare_op(&comp.op, &comp.left, &comp.right, ctx)?;
779 if comp.negate {
780 ctx.paren_close();
781 }
782 }
783 ConditionNode::Group(group) => {
784 ctx.paren_open();
785 self.render_condition(group, ctx)?;
786 ctx.paren_close();
787 }
788 ConditionNode::Exists(query) => {
789 ctx.keyword("EXISTS").write("(");
790 self.render_query(query, ctx)?;
791 ctx.paren_close();
792 }
793 ConditionNode::Custom(_) => {
794 return Err(RenderError::unsupported(
795 "CustomCondition",
796 "custom condition must be handled by a wrapping renderer",
797 ));
798 }
799 }
800 }
801 if cond.negated {
802 ctx.paren_close();
803 }
804 Ok(())
805 }
806
807 fn render_compare_op(
808 &self,
809 op: &CompareOp,
810 left: &Expr,
811 right: &Expr,
812 ctx: &mut RenderCtx,
813 ) -> RenderResult<()> {
814 let needs_lower = matches!(
815 op,
816 CompareOp::ILike | CompareOp::IContains | CompareOp::IStartsWith | CompareOp::IEndsWith
817 );
818 if needs_lower {
819 ctx.keyword("LOWER").write("(");
820 }
821 self.render_expr(left, ctx)?;
822 if needs_lower {
823 ctx.paren_close();
824 }
825 match op {
826 CompareOp::Eq => ctx.write(" = "),
827 CompareOp::Neq => ctx.write(" <> "),
828 CompareOp::Gt => ctx.write(" > "),
829 CompareOp::Gte => ctx.write(" >= "),
830 CompareOp::Lt => ctx.write(" < "),
831 CompareOp::Lte => ctx.write(" <= "),
832 CompareOp::Like => ctx.keyword("LIKE"),
833 CompareOp::Contains | CompareOp::StartsWith | CompareOp::EndsWith => {
834 ctx.keyword("LIKE");
835 render_like_pattern(op, right, ctx)?;
836 ctx.keyword("ESCAPE").string_literal("\\");
837 return Ok(());
838 }
839 CompareOp::IContains | CompareOp::IStartsWith | CompareOp::IEndsWith => {
840 ctx.keyword("LIKE");
841 ctx.keyword("LOWER").write("(");
842 render_like_pattern(op, right, ctx)?;
843 ctx.paren_close();
844 ctx.keyword("ESCAPE").string_literal("\\");
845 return Ok(());
846 }
847 CompareOp::In => {
848 if let Expr::Value(Value::Array(items)) = right {
849 ctx.keyword("IN").paren_open();
850 for (i, item) in items.iter().enumerate() {
851 if i > 0 {
852 ctx.comma();
853 }
854 self.sqlite_value(item, ctx)?;
855 }
856 ctx.paren_close();
857 } else {
858 ctx.keyword("IN");
859 self.render_expr(right, ctx)?;
860 }
861 return Ok(());
862 }
863 CompareOp::Between => {
864 ctx.keyword("BETWEEN");
865 if let Expr::Value(Value::Array(items)) = right {
866 if items.len() == 2 {
867 self.sqlite_value(&items[0], ctx)?;
868 ctx.keyword("AND");
869 self.sqlite_value(&items[1], ctx)?;
870 } else {
871 return Err(RenderError::unsupported(
872 "Between",
873 "BETWEEN requires exactly 2 values",
874 ));
875 }
876 } else {
877 self.render_expr(right, ctx)?;
878 }
879 return Ok(());
880 }
881 CompareOp::IsNull => {
882 ctx.keyword("IS NULL");
883 return Ok(());
884 }
885 CompareOp::Regex => ctx.keyword("REGEXP"),
886 CompareOp::IRegex => {
887 ctx.keyword("REGEXP").string_literal("(?i)").keyword("||");
888 self.render_expr(right, ctx)?;
889 return Ok(());
890 }
891 CompareOp::ILike => {
892 ctx.keyword("LIKE").keyword("LOWER").write("(");
893 self.render_expr(right, ctx)?;
894 ctx.paren_close();
895 return Ok(());
896 }
897 CompareOp::Similar => {
899 return Err(RenderError::unsupported(
900 "CompareOp",
901 "SQLite does not support SIMILAR TO.",
902 ));
903 }
904 CompareOp::JsonbContains
905 | CompareOp::JsonbContainedBy
906 | CompareOp::JsonbHasKey
907 | CompareOp::JsonbHasAnyKey
908 | CompareOp::JsonbHasAllKeys
909 | CompareOp::FtsMatch
910 | CompareOp::TrigramSimilar
911 | CompareOp::TrigramWordSimilar
912 | CompareOp::TrigramStrictWordSimilar
913 | CompareOp::RangeContains
914 | CompareOp::RangeContainedBy
915 | CompareOp::RangeOverlap
916 | CompareOp::RangeStrictlyLeft
917 | CompareOp::RangeStrictlyRight
918 | CompareOp::RangeNotLeft
919 | CompareOp::RangeNotRight
920 | CompareOp::RangeAdjacent => {
921 return Err(RenderError::unsupported(
922 "CompareOp",
923 "SQLite does not support PostgreSQL-specific operators (JSONB, FTS, trigram, range).",
924 ));
925 }
926 CompareOp::Custom(_) => {
927 return Err(RenderError::unsupported(
928 "CustomCompareOp",
929 "custom compare op must be handled by a wrapping renderer",
930 ));
931 }
932 };
933 self.render_expr(right, ctx)
934 }
935
936 fn render_query(&self, stmt: &QueryStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
939 if let Some(ctes) = &stmt.ctes {
941 self.render_ctes(ctes, ctx)?;
942 }
943
944 if let Some(set_op) = &stmt.set_op {
946 return self.sqlite_render_set_op(set_op, ctx);
947 }
948
949 ctx.keyword("SELECT");
951
952 if let Some(distinct) = &stmt.distinct {
954 match distinct {
955 DistinctDef::Distinct => {
956 ctx.keyword("DISTINCT");
957 }
958 DistinctDef::DistinctOn(_) => {
959 return Err(RenderError::unsupported(
960 "DISTINCT ON",
961 "not supported in SQLite",
962 ));
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.sqlite_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.sqlite_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.sqlite_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 if !locks.is_empty() {
1020 return Err(RenderError::unsupported(
1021 "FOR UPDATE/SHARE",
1022 "row locking not supported in SQLite",
1023 ));
1024 }
1025 }
1026
1027 Ok(())
1028 }
1029
1030 fn render_select_columns(
1031 &self,
1032 cols: &[SelectColumn],
1033 ctx: &mut RenderCtx,
1034 ) -> RenderResult<()> {
1035 for (i, col) in cols.iter().enumerate() {
1036 if i > 0 {
1037 ctx.comma();
1038 }
1039 match col {
1040 SelectColumn::Star(None) => {
1041 ctx.keyword("*");
1042 }
1043 SelectColumn::Star(Some(table)) => {
1044 ctx.ident(table).operator(".").keyword("*");
1045 }
1046 SelectColumn::Expr { expr, alias } => {
1047 self.render_expr(expr, ctx)?;
1048 if let Some(a) = alias {
1049 ctx.keyword("AS").ident(a);
1050 }
1051 }
1052 SelectColumn::Field { field, alias } => {
1053 self.sqlite_field_ref(field, ctx);
1054 if let Some(a) = alias {
1055 ctx.keyword("AS").ident(a);
1056 }
1057 }
1058 }
1059 }
1060 Ok(())
1061 }
1062 fn render_from(&self, source: &TableSource, ctx: &mut RenderCtx) -> RenderResult<()> {
1063 match source {
1064 TableSource::Table(schema_ref) => {
1065 self.sqlite_schema_ref(schema_ref, ctx);
1066 if let Some(alias) = &schema_ref.alias {
1067 ctx.keyword("AS").ident(alias);
1068 }
1069 }
1070 TableSource::SubQuery(sq) => {
1071 ctx.paren_open();
1072 self.render_query(&sq.query, ctx)?;
1073 ctx.paren_close().keyword("AS").ident(&sq.alias);
1074 }
1075 TableSource::SetOp(set_op) => {
1076 ctx.paren_open();
1077 self.sqlite_render_set_op(set_op, ctx)?;
1078 ctx.paren_close();
1079 }
1080 TableSource::Lateral(_) => {
1081 return Err(RenderError::unsupported(
1082 "LATERAL",
1083 "LATERAL subqueries not supported in SQLite",
1084 ));
1085 }
1086 TableSource::Function { name, args, alias } => {
1087 ctx.keyword(name).write("(");
1088 for (i, arg) in args.iter().enumerate() {
1089 if i > 0 {
1090 ctx.comma();
1091 }
1092 self.render_expr(arg, ctx)?;
1093 }
1094 ctx.paren_close();
1095 if let Some(a) = alias {
1096 ctx.keyword("AS").ident(a);
1097 }
1098 }
1099 TableSource::Values {
1100 rows,
1101 alias,
1102 columns,
1103 } => {
1104 ctx.paren_open().keyword("SELECT");
1108 for (i, c) in columns.iter().enumerate() {
1109 if i > 0 {
1110 ctx.comma();
1111 }
1112 ctx.keyword(&format!("column{}", i + 1))
1113 .keyword("AS")
1114 .ident(c);
1115 }
1116 ctx.keyword("FROM").paren_open().keyword("VALUES");
1117 for (i, row) in rows.iter().enumerate() {
1118 if i > 0 {
1119 ctx.comma();
1120 }
1121 ctx.paren_open();
1122 for (j, val) in row.iter().enumerate() {
1123 if j > 0 {
1124 ctx.comma();
1125 }
1126 self.render_expr(val, ctx)?;
1127 }
1128 ctx.paren_close();
1129 }
1130 ctx.paren_close().paren_close().keyword("AS").ident(alias);
1131 }
1132 TableSource::Custom(_) => {
1133 return Err(RenderError::unsupported(
1134 "CustomTableSource",
1135 "custom table source must be handled by a wrapping renderer",
1136 ));
1137 }
1138 }
1139 Ok(())
1140 }
1141 fn render_joins(&self, joins: &[JoinDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1142 for join in joins {
1143 if join.natural {
1144 ctx.keyword("NATURAL");
1145 }
1146 ctx.keyword(match join.join_type {
1147 JoinType::Inner => "INNER JOIN",
1148 JoinType::Left => "LEFT JOIN",
1149 JoinType::Right => "RIGHT JOIN",
1150 JoinType::Full => "FULL JOIN",
1151 JoinType::Cross => "CROSS JOIN",
1152 JoinType::CrossApply | JoinType::OuterApply => {
1153 return Err(RenderError::unsupported(
1154 "APPLY",
1155 "CROSS/OUTER APPLY not supported in SQLite",
1156 ));
1157 }
1158 });
1159 self.sqlite_render_from_item(&join.source, ctx)?;
1160 if !matches!(join.join_type, JoinType::Cross) {
1161 if let Some(condition) = &join.condition {
1162 match condition {
1163 JoinCondition::On(cond) => {
1164 ctx.keyword("ON");
1165 self.render_condition(cond, ctx)?;
1166 }
1167 JoinCondition::Using(cols) => {
1168 ctx.keyword("USING").paren_open();
1169 self.sqlite_comma_idents(cols, ctx);
1170 ctx.paren_close();
1171 }
1172 }
1173 }
1174 }
1175 }
1176 Ok(())
1177 }
1178 fn render_where(&self, cond: &Conditions, ctx: &mut RenderCtx) -> RenderResult<()> {
1179 ctx.keyword("WHERE");
1180 self.render_condition(cond, ctx)
1181 }
1182 fn render_order_by(&self, order: &[OrderByDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1183 ctx.keyword("ORDER BY");
1184 self.sqlite_order_by_list(order, ctx)
1185 }
1186 fn render_limit(&self, limit: &LimitDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1187 match &limit.kind {
1188 LimitKind::Limit(n) => {
1189 ctx.keyword("LIMIT");
1190 if ctx.parameterize() {
1191 ctx.param(Value::BigInt(*n as i64));
1192 } else {
1193 ctx.space().write(&n.to_string());
1194 }
1195 }
1196 LimitKind::FetchFirst {
1197 count, with_ties, ..
1198 } => {
1199 if *with_ties {
1200 return Err(RenderError::unsupported(
1201 "FETCH FIRST WITH TIES",
1202 "not supported in SQLite",
1203 ));
1204 }
1205 ctx.keyword("LIMIT");
1207 if ctx.parameterize() {
1208 ctx.param(Value::BigInt(*count as i64));
1209 } else {
1210 ctx.space().write(&count.to_string());
1211 }
1212 }
1213 LimitKind::Top {
1214 count, with_ties, ..
1215 } => {
1216 if *with_ties {
1217 return Err(RenderError::unsupported(
1218 "TOP WITH TIES",
1219 "not supported in SQLite",
1220 ));
1221 }
1222 ctx.keyword("LIMIT");
1224 if ctx.parameterize() {
1225 ctx.param(Value::BigInt(*count as i64));
1226 } else {
1227 ctx.space().write(&count.to_string());
1228 }
1229 }
1230 }
1231 if let Some(offset) = limit.offset {
1232 ctx.keyword("OFFSET");
1233 if ctx.parameterize() {
1234 ctx.param(Value::BigInt(offset as i64));
1235 } else {
1236 ctx.space().write(&offset.to_string());
1237 }
1238 }
1239 Ok(())
1240 }
1241 fn render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1242 let any_recursive = ctes.iter().any(|c| c.recursive);
1243 ctx.keyword("WITH");
1244 if any_recursive {
1245 ctx.keyword("RECURSIVE");
1246 }
1247 for (i, cte) in ctes.iter().enumerate() {
1248 if i > 0 {
1249 ctx.comma();
1250 }
1251 ctx.ident(&cte.name);
1252 if let Some(col_names) = &cte.column_names {
1253 ctx.paren_open();
1254 self.sqlite_comma_idents(col_names, ctx);
1255 ctx.paren_close();
1256 }
1257 ctx.keyword("AS").paren_open();
1259 self.render_query(&cte.query, ctx)?;
1260 ctx.paren_close();
1261 }
1262 Ok(())
1263 }
1264 fn render_lock(&self, _lock: &SelectLockDef, _ctx: &mut RenderCtx) -> RenderResult<()> {
1265 Err(RenderError::unsupported(
1266 "FOR UPDATE/SHARE",
1267 "row locking not supported in SQLite",
1268 ))
1269 }
1270
1271 fn render_mutation(&self, stmt: &MutationStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1274 match stmt {
1275 MutationStmt::Insert(s) => self.render_insert(s, ctx),
1276 MutationStmt::Update(s) => self.render_update(s, ctx),
1277 MutationStmt::Delete(s) => self.render_delete(s, ctx),
1278 MutationStmt::Custom(_) => Err(RenderError::unsupported(
1279 "CustomMutation",
1280 "custom DML must be handled by a wrapping renderer",
1281 )),
1282 }
1283 }
1284
1285 fn render_insert(&self, stmt: &InsertStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1286 if let Some(ctes) = &stmt.ctes {
1288 self.sqlite_render_ctes(ctes, ctx)?;
1289 }
1290
1291 if let Some(cr) = &stmt.conflict_resolution {
1293 ctx.keyword("INSERT OR");
1294 ctx.keyword(match cr {
1295 ConflictResolution::Rollback => "ROLLBACK",
1296 ConflictResolution::Abort => "ABORT",
1297 ConflictResolution::Fail => "FAIL",
1298 ConflictResolution::Ignore => "IGNORE",
1299 ConflictResolution::Replace => "REPLACE",
1300 });
1301 ctx.keyword("INTO");
1302 } else {
1303 ctx.keyword("INSERT INTO");
1304 }
1305
1306 self.sqlite_schema_ref(&stmt.table, ctx);
1307
1308 if let Some(alias) = &stmt.table.alias {
1310 ctx.keyword("AS").ident(alias);
1311 }
1312
1313 if let Some(cols) = &stmt.columns {
1315 ctx.paren_open();
1316 self.sqlite_comma_idents(cols, ctx);
1317 ctx.paren_close();
1318 }
1319
1320 match &stmt.source {
1322 InsertSource::Values(rows) => {
1323 ctx.keyword("VALUES");
1324 for (i, row) in rows.iter().enumerate() {
1325 if i > 0 {
1326 ctx.comma();
1327 }
1328 ctx.paren_open();
1329 for (j, expr) in row.iter().enumerate() {
1330 if j > 0 {
1331 ctx.comma();
1332 }
1333 self.render_expr(expr, ctx)?;
1334 }
1335 ctx.paren_close();
1336 }
1337 }
1338 InsertSource::Select(query) => {
1339 self.render_query(query, ctx)?;
1340 }
1341 InsertSource::DefaultValues => {
1342 ctx.keyword("DEFAULT VALUES");
1343 }
1344 }
1345
1346 if let Some(conflicts) = &stmt.on_conflict {
1348 for oc in conflicts {
1349 self.render_on_conflict(oc, ctx)?;
1350 }
1351 }
1352
1353 if let Some(returning) = &stmt.returning {
1355 self.render_returning(returning, ctx)?;
1356 }
1357
1358 Ok(())
1359 }
1360
1361 fn render_update(&self, stmt: &UpdateStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1362 if let Some(ctes) = &stmt.ctes {
1364 self.sqlite_render_ctes(ctes, ctx)?;
1365 }
1366
1367 if let Some(cr) = &stmt.conflict_resolution {
1369 ctx.keyword("UPDATE OR");
1370 ctx.keyword(match cr {
1371 ConflictResolution::Rollback => "ROLLBACK",
1372 ConflictResolution::Abort => "ABORT",
1373 ConflictResolution::Fail => "FAIL",
1374 ConflictResolution::Ignore => "IGNORE",
1375 ConflictResolution::Replace => "REPLACE",
1376 });
1377 } else {
1378 ctx.keyword("UPDATE");
1379 }
1380
1381 self.sqlite_schema_ref(&stmt.table, ctx);
1382
1383 if let Some(alias) = &stmt.table.alias {
1385 ctx.keyword("AS").ident(alias);
1386 }
1387
1388 ctx.keyword("SET");
1390 for (i, (col, expr)) in stmt.assignments.iter().enumerate() {
1391 if i > 0 {
1392 ctx.comma();
1393 }
1394 ctx.ident(col).write(" = ");
1395 self.render_expr(expr, ctx)?;
1396 }
1397
1398 if let Some(from) = &stmt.from {
1400 ctx.keyword("FROM");
1401 for (i, source) in from.iter().enumerate() {
1402 if i > 0 {
1403 ctx.comma();
1404 }
1405 self.render_from(source, ctx)?;
1406 }
1407 }
1408
1409 if let Some(cond) = &stmt.where_clause {
1411 ctx.keyword("WHERE");
1412 self.render_condition(cond, ctx)?;
1413 }
1414
1415 if let Some(returning) = &stmt.returning {
1417 self.render_returning(returning, ctx)?;
1418 }
1419
1420 if let Some(order_by) = &stmt.order_by {
1422 ctx.keyword("ORDER BY");
1423 self.sqlite_order_by_list(order_by, ctx)?;
1424 }
1425
1426 if let Some(limit) = stmt.limit {
1428 ctx.keyword("LIMIT").keyword(&limit.to_string());
1429 if let Some(offset) = stmt.offset {
1430 ctx.keyword("OFFSET").keyword(&offset.to_string());
1431 }
1432 }
1433
1434 Ok(())
1435 }
1436
1437 fn render_delete(&self, stmt: &DeleteStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1438 if let Some(ctes) = &stmt.ctes {
1440 self.sqlite_render_ctes(ctes, ctx)?;
1441 }
1442
1443 ctx.keyword("DELETE FROM");
1444
1445 self.sqlite_schema_ref(&stmt.table, ctx);
1446
1447 if let Some(alias) = &stmt.table.alias {
1449 ctx.keyword("AS").ident(alias);
1450 }
1451
1452 if let Some(cond) = &stmt.where_clause {
1457 ctx.keyword("WHERE");
1458 self.render_condition(cond, ctx)?;
1459 }
1460
1461 if let Some(returning) = &stmt.returning {
1463 self.render_returning(returning, ctx)?;
1464 }
1465
1466 if let Some(order_by) = &stmt.order_by {
1468 ctx.keyword("ORDER BY");
1469 self.sqlite_order_by_list(order_by, ctx)?;
1470 }
1471
1472 if let Some(limit) = stmt.limit {
1474 ctx.keyword("LIMIT").keyword(&limit.to_string());
1475 if let Some(offset) = stmt.offset {
1476 ctx.keyword("OFFSET").keyword(&offset.to_string());
1477 }
1478 }
1479
1480 Ok(())
1481 }
1482
1483 fn render_on_conflict(&self, oc: &OnConflictDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1484 ctx.keyword("ON CONFLICT");
1485
1486 if let Some(target) = &oc.target {
1488 match target {
1489 ConflictTarget::Columns {
1490 columns,
1491 where_clause,
1492 } => {
1493 ctx.paren_open();
1494 self.sqlite_comma_idents(columns, ctx);
1495 ctx.paren_close();
1496 if let Some(cond) = where_clause {
1497 ctx.keyword("WHERE");
1498 self.render_condition(cond, ctx)?;
1499 }
1500 }
1501 ConflictTarget::Constraint(_) => {
1502 return Err(RenderError::unsupported(
1503 "OnConstraint",
1504 "SQLite does not support ON CONFLICT ON CONSTRAINT. Use column list instead.",
1505 ));
1506 }
1507 }
1508 }
1509
1510 match &oc.action {
1512 ConflictAction::DoNothing => {
1513 ctx.keyword("DO NOTHING");
1514 }
1515 ConflictAction::DoUpdate {
1516 assignments,
1517 where_clause,
1518 } => {
1519 ctx.keyword("DO UPDATE SET");
1520 for (i, (col, expr)) in assignments.iter().enumerate() {
1521 if i > 0 {
1522 ctx.comma();
1523 }
1524 ctx.ident(col).write(" = ");
1525 self.render_expr(expr, ctx)?;
1526 }
1527 if let Some(cond) = where_clause {
1528 ctx.keyword("WHERE");
1529 self.render_condition(cond, ctx)?;
1530 }
1531 }
1532 }
1533
1534 Ok(())
1535 }
1536
1537 fn render_returning(&self, cols: &[SelectColumn], ctx: &mut RenderCtx) -> RenderResult<()> {
1538 ctx.keyword("RETURNING");
1539 for (i, col) in cols.iter().enumerate() {
1540 if i > 0 {
1541 ctx.comma();
1542 }
1543 match col {
1544 SelectColumn::Star(None) => {
1545 ctx.keyword("*");
1546 }
1547 SelectColumn::Star(Some(table)) => {
1548 ctx.ident(table).operator(".").keyword("*");
1549 }
1550 SelectColumn::Expr { expr, alias } => {
1551 self.render_expr(expr, ctx)?;
1552 if let Some(a) = alias {
1553 ctx.keyword("AS").ident(a);
1554 }
1555 }
1556 SelectColumn::Field { field, alias } => {
1557 self.sqlite_field_ref(field, ctx);
1558 if let Some(a) = alias {
1559 ctx.keyword("AS").ident(a);
1560 }
1561 }
1562 }
1563 }
1564 Ok(())
1565 }
1566
1567 fn render_transaction(&self, stmt: &TransactionStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1570 match stmt {
1571 TransactionStmt::Begin(s) => {
1572 ctx.keyword("BEGIN");
1573 if let Some(lock_type) = &s.lock_type {
1574 ctx.keyword(match lock_type {
1575 SqliteLockType::Deferred => "DEFERRED",
1576 SqliteLockType::Immediate => "IMMEDIATE",
1577 SqliteLockType::Exclusive => "EXCLUSIVE",
1578 });
1579 }
1580 ctx.keyword("TRANSACTION");
1581 Ok(())
1582 }
1583 TransactionStmt::Commit(_) => {
1584 ctx.keyword("COMMIT");
1585 Ok(())
1586 }
1587 TransactionStmt::Rollback(s) => {
1588 ctx.keyword("ROLLBACK");
1589 if let Some(sp) = &s.to_savepoint {
1590 ctx.keyword("TO").keyword("SAVEPOINT").ident(sp);
1591 }
1592 Ok(())
1593 }
1594 TransactionStmt::Savepoint(s) => {
1595 ctx.keyword("SAVEPOINT").ident(&s.name);
1596 Ok(())
1597 }
1598 TransactionStmt::ReleaseSavepoint(s) => {
1599 ctx.keyword("RELEASE").keyword("SAVEPOINT").ident(&s.name);
1600 Ok(())
1601 }
1602 TransactionStmt::SetTransaction(_) => Err(RenderError::unsupported(
1603 "SET TRANSACTION",
1604 "not supported in SQLite",
1605 )),
1606 TransactionStmt::LockTable(_) => Err(RenderError::unsupported(
1607 "LOCK TABLE",
1608 "not supported in SQLite (use BEGIN EXCLUSIVE)",
1609 )),
1610 TransactionStmt::PrepareTransaction(_) => Err(RenderError::unsupported(
1611 "PREPARE TRANSACTION",
1612 "not supported in SQLite",
1613 )),
1614 TransactionStmt::CommitPrepared(_) => Err(RenderError::unsupported(
1615 "COMMIT PREPARED",
1616 "not supported in SQLite",
1617 )),
1618 TransactionStmt::RollbackPrepared(_) => Err(RenderError::unsupported(
1619 "ROLLBACK PREPARED",
1620 "not supported in SQLite",
1621 )),
1622 TransactionStmt::Custom(_) => Err(RenderError::unsupported(
1623 "Custom TCL",
1624 "not supported by SqliteRenderer",
1625 )),
1626 }
1627 }
1628}
1629
1630impl SqliteRenderer {
1635 fn sqlite_schema_ref(
1636 &self,
1637 schema_ref: &qcraft_core::ast::common::SchemaRef,
1638 ctx: &mut RenderCtx,
1639 ) {
1640 if let Some(ns) = &schema_ref.namespace {
1641 ctx.ident(ns).operator(".");
1642 }
1643 ctx.ident(&schema_ref.name);
1644 }
1645
1646 fn render_expr_unqualified(&self, expr: &Expr, ctx: &mut RenderCtx) -> RenderResult<()> {
1650 match expr {
1651 Expr::Field(field_ref) => {
1652 ctx.ident(&field_ref.field.name);
1653 let mut child = &field_ref.field.child;
1654 while let Some(c) = child {
1655 ctx.operator("->'")
1656 .write(&c.name.replace('\'', "''"))
1657 .write("'");
1658 child = &c.child;
1659 }
1660 Ok(())
1661 }
1662 other => self.render_expr(other, ctx),
1664 }
1665 }
1666
1667 fn sqlite_field_ref(&self, field_ref: &FieldRef, ctx: &mut RenderCtx) {
1668 if let Some(ns) = &field_ref.namespace {
1669 ctx.ident(ns).operator(".");
1670 }
1671 if !field_ref.table_name.is_empty() {
1672 ctx.ident(&field_ref.table_name).operator(".");
1673 }
1674 ctx.ident(&field_ref.field.name);
1675 let mut child = &field_ref.field.child;
1676 while let Some(c) = child {
1677 ctx.operator("->'")
1678 .write(&c.name.replace('\'', "''"))
1679 .write("'");
1680 child = &c.child;
1681 }
1682 }
1683
1684 fn sqlite_comma_idents(&self, names: &[String], ctx: &mut RenderCtx) {
1685 for (i, name) in names.iter().enumerate() {
1686 if i > 0 {
1687 ctx.comma();
1688 }
1689 ctx.ident(name);
1690 }
1691 }
1692
1693 fn sqlite_value(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
1694 if matches!(val, Value::Null) && !ctx.parameterize() {
1695 ctx.keyword("NULL");
1696 return Ok(());
1697 }
1698
1699 if let Value::Array(items) = val {
1701 if !ctx.parameterize() {
1702 let json = Self::array_to_json(items);
1703 ctx.string_literal(&json);
1704 return Ok(());
1705 }
1706 }
1707
1708 if let Value::Vector(_) = val {
1710 return Err(RenderError::unsupported(
1711 "VectorValue",
1712 "SQLite does not support vector type.",
1713 ));
1714 }
1715
1716 if ctx.parameterize() {
1718 ctx.param(val.clone());
1719 return Ok(());
1720 }
1721
1722 self.sqlite_value_literal(val, ctx)
1724 }
1725
1726 fn sqlite_value_literal(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
1727 match val {
1728 Value::Null => {
1729 ctx.keyword("NULL");
1730 }
1731 Value::Bool(b) => {
1732 ctx.keyword(if *b { "1" } else { "0" });
1733 }
1734 Value::Int(n) | Value::BigInt(n) => {
1735 ctx.keyword(&n.to_string());
1736 }
1737 Value::Float(f) => {
1738 ctx.keyword(&f.to_string());
1739 }
1740 Value::Str(s) => {
1741 ctx.string_literal(s);
1742 }
1743 Value::Bytes(b) => {
1744 ctx.write("X'");
1745 for byte in b {
1746 ctx.write(&format!("{byte:02x}"));
1747 }
1748 ctx.write("'");
1749 }
1750 Value::Date(s) | Value::DateTime(s) | Value::Time(s) => {
1751 ctx.string_literal(s);
1752 }
1753 Value::Decimal(s) => {
1754 ctx.keyword(s);
1755 }
1756 Value::Uuid(s) => {
1757 ctx.string_literal(s);
1758 }
1759 Value::Json(s) | Value::Jsonb(s) => {
1760 ctx.string_literal(s);
1761 }
1762 Value::IpNetwork(s) => {
1763 ctx.string_literal(s);
1764 }
1765 Value::TimeDelta {
1766 years,
1767 months,
1768 days,
1769 seconds,
1770 microseconds,
1771 } => {
1772 let mut parts = Vec::new();
1773 if *years != 0 {
1774 parts.push(format!("{years} years"));
1775 }
1776 if *months != 0 {
1777 parts.push(format!("{months} months"));
1778 }
1779 if *days != 0 {
1780 parts.push(format!("{days} days"));
1781 }
1782 if *seconds != 0 {
1783 parts.push(format!("{seconds} seconds"));
1784 }
1785 if *microseconds != 0 {
1786 parts.push(format!("{microseconds} microseconds"));
1787 }
1788 if parts.is_empty() {
1789 parts.push("0 seconds".into());
1790 }
1791 ctx.string_literal(&parts.join(" "));
1792 }
1793 _ => {
1794 unreachable!()
1796 }
1797 }
1798 Ok(())
1799 }
1800
1801 fn array_to_json(items: &[Value]) -> String {
1802 let mut s = String::from("[");
1803 for (i, item) in items.iter().enumerate() {
1804 if i > 0 {
1805 s.push_str(", ");
1806 }
1807 Self::value_to_json(item, &mut s);
1808 }
1809 s.push(']');
1810 s
1811 }
1812
1813 fn value_to_json(val: &Value, s: &mut String) {
1814 match val {
1815 Value::Null => s.push_str("null"),
1816 Value::Bool(b) => s.push_str(if *b { "true" } else { "false" }),
1817 Value::Int(n) | Value::BigInt(n) => s.push_str(&n.to_string()),
1818 Value::Float(f) => s.push_str(&f.to_string()),
1819 Value::Str(v) => {
1820 s.push('"');
1821 for ch in v.chars() {
1822 match ch {
1823 '"' => s.push_str("\\\""),
1824 '\\' => s.push_str("\\\\"),
1825 '\n' => s.push_str("\\n"),
1826 '\r' => s.push_str("\\r"),
1827 '\t' => s.push_str("\\t"),
1828 c => s.push(c),
1829 }
1830 }
1831 s.push('"');
1832 }
1833 Value::Array(items) => {
1834 s.push('[');
1835 for (i, item) in items.iter().enumerate() {
1836 if i > 0 {
1837 s.push_str(", ");
1838 }
1839 Self::value_to_json(item, s);
1840 }
1841 s.push(']');
1842 }
1843 Value::Date(v)
1845 | Value::DateTime(v)
1846 | Value::Time(v)
1847 | Value::Uuid(v)
1848 | Value::Decimal(v)
1849 | Value::IpNetwork(v) => {
1850 s.push('"');
1851 s.push_str(v);
1852 s.push('"');
1853 }
1854 Value::Json(v) | Value::Jsonb(v) => {
1855 s.push_str(v);
1857 }
1858 _ => s.push_str("null"),
1859 }
1860 }
1861
1862 fn sqlite_referential_action(
1863 &self,
1864 action: &ReferentialAction,
1865 ctx: &mut RenderCtx,
1866 ) -> RenderResult<()> {
1867 match action {
1868 ReferentialAction::NoAction => {
1869 ctx.keyword("NO ACTION");
1870 }
1871 ReferentialAction::Restrict => {
1872 ctx.keyword("RESTRICT");
1873 }
1874 ReferentialAction::Cascade => {
1875 ctx.keyword("CASCADE");
1876 }
1877 ReferentialAction::SetNull(cols) => {
1878 ctx.keyword("SET NULL");
1879 if cols.is_some() {
1880 return Err(RenderError::unsupported(
1881 "SetNullColumns",
1882 "SQLite does not support SET NULL with column list.",
1883 ));
1884 }
1885 }
1886 ReferentialAction::SetDefault(cols) => {
1887 ctx.keyword("SET DEFAULT");
1888 if cols.is_some() {
1889 return Err(RenderError::unsupported(
1890 "SetDefaultColumns",
1891 "SQLite does not support SET DEFAULT with column list.",
1892 ));
1893 }
1894 }
1895 }
1896 Ok(())
1897 }
1898
1899 fn sqlite_deferrable(&self, def: &DeferrableConstraint, ctx: &mut RenderCtx) {
1900 if def.deferrable {
1901 ctx.keyword("DEFERRABLE");
1902 } else {
1903 ctx.keyword("NOT DEFERRABLE");
1904 }
1905 if def.initially_deferred {
1906 ctx.keyword("INITIALLY DEFERRED");
1907 } else {
1908 ctx.keyword("INITIALLY IMMEDIATE");
1909 }
1910 }
1911
1912 fn sqlite_render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1913 self.render_ctes(ctes, ctx)
1914 }
1915
1916 fn sqlite_render_from_item(&self, item: &FromItem, ctx: &mut RenderCtx) -> RenderResult<()> {
1917 self.render_from(&item.source, ctx)?;
1919 if let Some(hint) = &item.index_hint {
1921 match hint {
1922 SqliteIndexHint::IndexedBy(name) => {
1923 ctx.keyword("INDEXED BY").ident(name);
1924 }
1925 SqliteIndexHint::NotIndexed => {
1926 ctx.keyword("NOT INDEXED");
1927 }
1928 }
1929 }
1930 if item.sample.is_some() {
1932 return Err(RenderError::unsupported(
1933 "TABLESAMPLE",
1934 "not supported in SQLite",
1935 ));
1936 }
1937 Ok(())
1938 }
1939
1940 fn sqlite_render_group_by(
1941 &self,
1942 items: &[GroupByItem],
1943 ctx: &mut RenderCtx,
1944 ) -> RenderResult<()> {
1945 ctx.keyword("GROUP BY");
1946 for (i, item) in items.iter().enumerate() {
1947 if i > 0 {
1948 ctx.comma();
1949 }
1950 match item {
1951 GroupByItem::Expr(expr) => {
1952 self.render_expr(expr, ctx)?;
1953 }
1954 GroupByItem::Rollup(_) => {
1955 return Err(RenderError::unsupported(
1956 "ROLLUP",
1957 "not supported in SQLite",
1958 ));
1959 }
1960 GroupByItem::Cube(_) => {
1961 return Err(RenderError::unsupported("CUBE", "not supported in SQLite"));
1962 }
1963 GroupByItem::GroupingSets(_) => {
1964 return Err(RenderError::unsupported(
1965 "GROUPING SETS",
1966 "not supported in SQLite",
1967 ));
1968 }
1969 }
1970 }
1971 Ok(())
1972 }
1973
1974 fn sqlite_render_window_clause(
1975 &self,
1976 windows: &[WindowNameDef],
1977 ctx: &mut RenderCtx,
1978 ) -> RenderResult<()> {
1979 ctx.keyword("WINDOW");
1980 for (i, win) in windows.iter().enumerate() {
1981 if i > 0 {
1982 ctx.comma();
1983 }
1984 ctx.ident(&win.name).keyword("AS").paren_open();
1985 if let Some(base) = &win.base_window {
1986 ctx.ident(base);
1987 }
1988 if let Some(partition_by) = &win.partition_by {
1989 ctx.keyword("PARTITION BY");
1990 for (j, expr) in partition_by.iter().enumerate() {
1991 if j > 0 {
1992 ctx.comma();
1993 }
1994 self.render_expr(expr, ctx)?;
1995 }
1996 }
1997 if let Some(order_by) = &win.order_by {
1998 ctx.keyword("ORDER BY");
1999 self.sqlite_order_by_list(order_by, ctx)?;
2000 }
2001 if let Some(frame) = &win.frame {
2002 self.sqlite_window_frame(frame, ctx);
2003 }
2004 ctx.paren_close();
2005 }
2006 Ok(())
2007 }
2008
2009 fn sqlite_window_frame(&self, frame: &WindowFrameDef, ctx: &mut RenderCtx) {
2010 ctx.keyword(match frame.frame_type {
2011 WindowFrameType::Rows => "ROWS",
2012 WindowFrameType::Range => "RANGE",
2013 WindowFrameType::Groups => "GROUPS",
2014 });
2015 if let Some(end) = &frame.end {
2016 ctx.keyword("BETWEEN");
2017 self.sqlite_frame_bound(&frame.start, ctx);
2018 ctx.keyword("AND");
2019 self.sqlite_frame_bound(end, ctx);
2020 } else {
2021 self.sqlite_frame_bound(&frame.start, ctx);
2022 }
2023 }
2024
2025 fn sqlite_frame_bound(&self, bound: &WindowFrameBound, ctx: &mut RenderCtx) {
2026 match bound {
2027 WindowFrameBound::CurrentRow => {
2028 ctx.keyword("CURRENT ROW");
2029 }
2030 WindowFrameBound::Preceding(None) => {
2031 ctx.keyword("UNBOUNDED PRECEDING");
2032 }
2033 WindowFrameBound::Preceding(Some(n)) => {
2034 ctx.keyword(&n.to_string()).keyword("PRECEDING");
2035 }
2036 WindowFrameBound::Following(None) => {
2037 ctx.keyword("UNBOUNDED FOLLOWING");
2038 }
2039 WindowFrameBound::Following(Some(n)) => {
2040 ctx.keyword(&n.to_string()).keyword("FOLLOWING");
2041 }
2042 }
2043 }
2044
2045 fn sqlite_render_set_op(&self, set_op: &SetOpDef, ctx: &mut RenderCtx) -> RenderResult<()> {
2046 self.render_query(&set_op.left, ctx)?;
2047 ctx.keyword(match set_op.operation {
2048 SetOperationType::Union => "UNION",
2049 SetOperationType::UnionAll => "UNION ALL",
2050 SetOperationType::Intersect => "INTERSECT",
2051 SetOperationType::Except => "EXCEPT",
2052 SetOperationType::IntersectAll => {
2053 return Err(RenderError::unsupported(
2054 "INTERSECT ALL",
2055 "not supported in SQLite",
2056 ));
2057 }
2058 SetOperationType::ExceptAll => {
2059 return Err(RenderError::unsupported(
2060 "EXCEPT ALL",
2061 "not supported in SQLite",
2062 ));
2063 }
2064 });
2065 self.render_query(&set_op.right, ctx)
2066 }
2067
2068 fn sqlite_create_table(
2069 &self,
2070 schema: &SchemaDef,
2071 if_not_exists: bool,
2072 temporary: bool,
2073 without_rowid: bool,
2074 strict: bool,
2075 ctx: &mut RenderCtx,
2076 ) -> RenderResult<()> {
2077 ctx.keyword("CREATE");
2078 if temporary {
2079 ctx.keyword("TEMP");
2080 }
2081 ctx.keyword("TABLE");
2082 if if_not_exists {
2083 ctx.keyword("IF NOT EXISTS");
2084 }
2085 if let Some(ns) = &schema.namespace {
2086 ctx.ident(ns).operator(".");
2087 }
2088 ctx.ident(&schema.name);
2089
2090 let pk_columns: Vec<&str> = schema
2092 .constraints
2093 .as_ref()
2094 .and_then(|cs| {
2095 cs.iter().find_map(|c| {
2096 if let ConstraintDef::PrimaryKey { columns, .. } = c {
2097 Some(columns.iter().map(|s| s.as_str()).collect::<Vec<_>>())
2098 } else {
2099 None
2100 }
2101 })
2102 })
2103 .unwrap_or_default();
2104
2105 let identity_pk_col = schema.columns.iter().find_map(|col| {
2107 if col.identity.is_some() {
2108 if pk_columns.contains(&col.name.as_str()) {
2109 Some(col.name.as_str())
2110 } else {
2111 None
2112 }
2113 } else {
2114 None
2115 }
2116 });
2117
2118 for col in &schema.columns {
2120 if col.identity.is_some() && !pk_columns.contains(&col.name.as_str()) {
2121 return Err(RenderError::unsupported(
2122 "IdentityColumn",
2123 "SQLite requires identity columns to be PRIMARY KEY. Add a PrimaryKey constraint for this column.",
2124 ));
2125 }
2126 }
2127
2128 ctx.paren_open();
2129 let mut first = true;
2130 for col in &schema.columns {
2131 if !first {
2132 ctx.comma();
2133 }
2134 first = false;
2135 self.render_column_def(col, ctx)?;
2136 if identity_pk_col == Some(col.name.as_str()) {
2138 ctx.keyword("PRIMARY KEY AUTOINCREMENT");
2139 }
2140 }
2141 if let Some(constraints) = &schema.constraints {
2142 for constraint in constraints {
2143 if let ConstraintDef::PrimaryKey { columns, .. } = constraint {
2145 if columns.len() == 1 && identity_pk_col == Some(columns[0].as_str()) {
2146 continue;
2147 }
2148 }
2149 if !first {
2150 ctx.comma();
2151 }
2152 first = false;
2153 self.render_constraint(constraint, ctx)?;
2154 }
2155 }
2156 ctx.paren_close();
2157
2158 let mut modifiers = Vec::new();
2160 if without_rowid {
2161 modifiers.push("WITHOUT ROWID");
2162 }
2163 if strict {
2164 modifiers.push("STRICT");
2165 }
2166 if !modifiers.is_empty() {
2167 for (i, m) in modifiers.iter().enumerate() {
2168 if i > 0 {
2169 ctx.comma();
2170 }
2171 ctx.keyword(m);
2172 }
2173 }
2174
2175 Ok(())
2176 }
2177
2178 fn sqlite_create_index(
2179 &self,
2180 schema_ref: &qcraft_core::ast::common::SchemaRef,
2181 index: &IndexDef,
2182 if_not_exists: bool,
2183 ctx: &mut RenderCtx,
2184 ) -> RenderResult<()> {
2185 ctx.keyword("CREATE");
2186 if index.unique {
2187 ctx.keyword("UNIQUE");
2188 }
2189 ctx.keyword("INDEX");
2190 if if_not_exists {
2191 ctx.keyword("IF NOT EXISTS");
2192 }
2193 ctx.ident(&index.name).keyword("ON");
2194 self.sqlite_schema_ref(schema_ref, ctx);
2195
2196 ctx.paren_open();
2199 self.sqlite_index_columns(&index.columns, ctx)?;
2200 ctx.paren_close();
2201
2202 if let Some(condition) = &index.condition {
2205 ctx.keyword("WHERE");
2206 self.render_condition(condition, ctx)?;
2207 }
2208
2209 Ok(())
2210 }
2211
2212 fn sqlite_index_columns(
2213 &self,
2214 columns: &[IndexColumnDef],
2215 ctx: &mut RenderCtx,
2216 ) -> RenderResult<()> {
2217 for (i, col) in columns.iter().enumerate() {
2218 if i > 0 {
2219 ctx.comma();
2220 }
2221 match &col.expr {
2222 IndexExpr::Column(name) => {
2223 ctx.ident(name);
2224 }
2225 IndexExpr::Expression(expr) => {
2226 ctx.paren_open();
2227 self.render_expr(expr, ctx)?;
2228 ctx.paren_close();
2229 }
2230 }
2231 if let Some(collation) = &col.collation {
2232 ctx.keyword("COLLATE").ident(collation);
2233 }
2234 if let Some(dir) = col.direction {
2236 ctx.keyword(match dir {
2237 OrderDir::Asc => "ASC",
2238 OrderDir::Desc => "DESC",
2239 });
2240 }
2241 }
2243 Ok(())
2244 }
2245
2246 fn sqlite_order_by_list(
2247 &self,
2248 order_by: &[OrderByDef],
2249 ctx: &mut RenderCtx,
2250 ) -> RenderResult<()> {
2251 for (i, ob) in order_by.iter().enumerate() {
2252 if i > 0 {
2253 ctx.comma();
2254 }
2255 self.render_expr(&ob.expr, ctx)?;
2256 ctx.keyword(match ob.direction {
2257 OrderDir::Asc => "ASC",
2258 OrderDir::Desc => "DESC",
2259 });
2260 if let Some(nulls) = &ob.nulls {
2261 ctx.keyword(match nulls {
2262 NullsOrder::First => "NULLS FIRST",
2263 NullsOrder::Last => "NULLS LAST",
2264 });
2265 }
2266 }
2267 Ok(())
2268 }
2269}