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