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