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