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<(String, Vec<Value>)> {
63 let mut ctx = RenderCtx::new(ParamStyle::QMark);
64 self.render_schema_mutation(stmt, &mut ctx)?;
65 Ok(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::Custom(_) => Err(RenderError::unsupported(
268 "CustomSchemaMutation",
269 "custom DDL must be handled by a wrapping renderer",
270 )),
271 }
272 }
273
274 fn render_column_def(&self, col: &ColumnDef, ctx: &mut RenderCtx) -> RenderResult<()> {
275 ctx.ident(&col.name);
276 self.render_column_type(&col.field_type, ctx)?;
277
278 if let Some(collation) = &col.collation {
279 ctx.keyword("COLLATE").ident(collation);
280 }
281
282 if col.not_null {
283 ctx.keyword("NOT NULL");
284 }
285
286 if let Some(default) = &col.default {
287 ctx.keyword("DEFAULT");
288 self.render_expr(default, ctx)?;
289 }
290
291 if col.identity.is_some() {
293 return Err(RenderError::unsupported(
294 "IdentityColumn",
295 "SQLite does not support GENERATED AS IDENTITY. Use INTEGER PRIMARY KEY AUTOINCREMENT.",
296 ));
297 }
298
299 if let Some(generated) = &col.generated {
300 ctx.keyword("GENERATED ALWAYS AS").space().paren_open();
301 self.render_expr(&generated.expr, ctx)?;
302 ctx.paren_close();
303 if generated.stored {
304 ctx.keyword("STORED");
305 } else {
306 ctx.keyword("VIRTUAL");
307 }
308 }
309
310 Ok(())
311 }
312
313 fn render_column_type(&self, ty: &FieldType, ctx: &mut RenderCtx) -> RenderResult<()> {
314 match ty {
315 FieldType::Scalar(name) => {
316 ctx.keyword(name);
317 }
318 FieldType::Parameterized { name, params } => {
319 ctx.keyword(name).write("(");
320 for (i, p) in params.iter().enumerate() {
321 if i > 0 {
322 ctx.comma();
323 }
324 ctx.write(p);
325 }
326 ctx.paren_close();
327 }
328 FieldType::Array(_) => {
329 return Err(RenderError::unsupported(
330 "ArrayType",
331 "SQLite does not support array types.",
332 ));
333 }
334 FieldType::Vector(_) => {
335 return Err(RenderError::unsupported(
336 "VectorType",
337 "SQLite does not support vector types.",
338 ));
339 }
340 FieldType::Custom(_) => {
341 return Err(RenderError::unsupported(
342 "CustomFieldType",
343 "custom field type must be handled by a wrapping renderer",
344 ));
345 }
346 }
347 Ok(())
348 }
349
350 fn render_constraint(&self, c: &ConstraintDef, ctx: &mut RenderCtx) -> RenderResult<()> {
351 match c {
352 ConstraintDef::PrimaryKey {
353 name,
354 columns,
355 include: _, autoincrement,
357 } => {
358 if let Some(n) = name {
359 ctx.keyword("CONSTRAINT").ident(n);
360 }
361 ctx.keyword("PRIMARY KEY").paren_open();
362 self.sqlite_comma_idents(columns, ctx);
363 ctx.paren_close();
364 if *autoincrement {
365 ctx.keyword("AUTOINCREMENT");
366 }
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 ctx.keyword(match op {
470 BinaryOp::Add => "+",
471 BinaryOp::Sub => "-",
472 BinaryOp::Mul => "*",
473 BinaryOp::Div => "/",
474 BinaryOp::Mod => "%",
475 BinaryOp::BitwiseAnd => "&",
476 BinaryOp::BitwiseOr => "|",
477 BinaryOp::ShiftLeft => "<<",
478 BinaryOp::ShiftRight => ">>",
479 BinaryOp::Concat => "||",
480 });
481 self.render_expr(right, ctx)
482 }
483
484 Expr::Unary { op, expr: inner } => {
485 match op {
486 UnaryOp::Neg => ctx.write("-"),
487 UnaryOp::Not => ctx.keyword("NOT"),
488 UnaryOp::BitwiseNot => ctx.write("~"),
489 };
490 self.render_expr(inner, ctx)
491 }
492
493 Expr::Func { name, args } => {
494 ctx.keyword(name).write("(");
495 for (i, arg) in args.iter().enumerate() {
496 if i > 0 {
497 ctx.comma();
498 }
499 self.render_expr(arg, ctx)?;
500 }
501 ctx.paren_close();
502 Ok(())
503 }
504
505 Expr::Aggregate(agg) => self.render_aggregate(agg, ctx),
506
507 Expr::Cast {
508 expr: inner,
509 to_type,
510 } => {
511 ctx.keyword("CAST").paren_open();
512 self.render_expr(inner, ctx)?;
513 ctx.keyword("AS").keyword(to_type);
514 ctx.paren_close();
515 Ok(())
516 }
517
518 Expr::Case(case) => self.render_case(case, ctx),
519 Expr::Window(win) => self.render_window(win, ctx),
520
521 Expr::Exists(query) => {
522 ctx.keyword("EXISTS").paren_open();
523 self.render_query(query, ctx)?;
524 ctx.paren_close();
525 Ok(())
526 }
527
528 Expr::SubQuery(query) => {
529 ctx.paren_open();
530 self.render_query(query, ctx)?;
531 ctx.paren_close();
532 Ok(())
533 }
534
535 Expr::ArraySubQuery(_) => Err(RenderError::unsupported(
536 "ArraySubQuery",
537 "SQLite does not support ARRAY subqueries.",
538 )),
539
540 Expr::Collate { expr, collation } => {
541 self.render_expr(expr, ctx)?;
542 ctx.keyword("COLLATE").keyword(collation);
543 Ok(())
544 }
545
546 Expr::Raw { sql, params } => {
547 ctx.keyword(sql);
548 let _ = params;
549 Ok(())
550 }
551
552 Expr::Custom(_) => Err(RenderError::unsupported(
553 "CustomExpr",
554 "custom expression must be handled by a wrapping renderer",
555 )),
556 }
557 }
558
559 fn render_aggregate(&self, agg: &AggregationDef, ctx: &mut RenderCtx) -> RenderResult<()> {
560 ctx.keyword(&agg.name).write("(");
561 if agg.distinct {
562 ctx.keyword("DISTINCT");
563 }
564 if let Some(expr) = &agg.expression {
565 self.render_expr(expr, ctx)?;
566 } else {
567 ctx.write("*");
568 }
569 if let Some(args) = &agg.args {
570 for arg in args {
571 ctx.comma();
572 self.render_expr(arg, ctx)?;
573 }
574 }
575 if let Some(order_by) = &agg.order_by {
576 ctx.keyword("ORDER BY");
577 self.sqlite_order_by_list(order_by, ctx)?;
578 }
579 ctx.paren_close();
580 if let Some(filter) = &agg.filter {
581 ctx.keyword("FILTER").paren_open().keyword("WHERE");
582 self.render_condition(filter, ctx)?;
583 ctx.paren_close();
584 }
585 Ok(())
586 }
587
588 fn render_window(&self, win: &WindowDef, ctx: &mut RenderCtx) -> RenderResult<()> {
589 self.render_expr(&win.expression, ctx)?;
590 ctx.keyword("OVER").paren_open();
591 if let Some(partition_by) = &win.partition_by {
592 ctx.keyword("PARTITION BY");
593 for (i, expr) in partition_by.iter().enumerate() {
594 if i > 0 {
595 ctx.comma();
596 }
597 self.render_expr(expr, ctx)?;
598 }
599 }
600 if let Some(order_by) = &win.order_by {
601 ctx.keyword("ORDER BY");
602 self.sqlite_order_by_list(order_by, ctx)?;
603 }
604 ctx.paren_close();
605 Ok(())
606 }
607
608 fn render_case(&self, case: &CaseDef, ctx: &mut RenderCtx) -> RenderResult<()> {
609 ctx.keyword("CASE");
610 for clause in &case.cases {
611 ctx.keyword("WHEN");
612 self.render_condition(&clause.condition, ctx)?;
613 ctx.keyword("THEN");
614 self.render_expr(&clause.result, ctx)?;
615 }
616 if let Some(default) = &case.default {
617 ctx.keyword("ELSE");
618 self.render_expr(default, ctx)?;
619 }
620 ctx.keyword("END");
621 Ok(())
622 }
623
624 fn render_condition(&self, cond: &Conditions, ctx: &mut RenderCtx) -> RenderResult<()> {
627 if cond.negated {
628 ctx.keyword("NOT").paren_open();
629 }
630 let connector = match cond.connector {
631 Connector::And => " AND ",
632 Connector::Or => " OR ",
633 };
634 for (i, child) in cond.children.iter().enumerate() {
635 if i > 0 {
636 ctx.write(connector);
637 }
638 match child {
639 ConditionNode::Comparison(comp) => {
640 if comp.negate {
641 ctx.keyword("NOT").paren_open();
642 }
643 self.render_compare_op(&comp.op, &comp.left, &comp.right, ctx)?;
644 if comp.negate {
645 ctx.paren_close();
646 }
647 }
648 ConditionNode::Group(group) => {
649 ctx.paren_open();
650 self.render_condition(group, ctx)?;
651 ctx.paren_close();
652 }
653 ConditionNode::Exists(query) => {
654 ctx.keyword("EXISTS").paren_open();
655 self.render_query(query, ctx)?;
656 ctx.paren_close();
657 }
658 ConditionNode::Custom(_) => {
659 return Err(RenderError::unsupported(
660 "CustomCondition",
661 "custom condition must be handled by a wrapping renderer",
662 ));
663 }
664 }
665 }
666 if cond.negated {
667 ctx.paren_close();
668 }
669 Ok(())
670 }
671
672 fn render_compare_op(
673 &self,
674 op: &CompareOp,
675 left: &Expr,
676 right: &Expr,
677 ctx: &mut RenderCtx,
678 ) -> RenderResult<()> {
679 let needs_lower = matches!(
680 op,
681 CompareOp::IContains | CompareOp::IStartsWith | CompareOp::IEndsWith
682 );
683 if needs_lower {
684 ctx.keyword("LOWER").paren_open();
685 }
686 self.render_expr(left, ctx)?;
687 if needs_lower {
688 ctx.paren_close();
689 }
690 match op {
691 CompareOp::Eq => ctx.write(" = "),
692 CompareOp::Neq => ctx.write(" <> "),
693 CompareOp::Gt => ctx.write(" > "),
694 CompareOp::Gte => ctx.write(" >= "),
695 CompareOp::Lt => ctx.write(" < "),
696 CompareOp::Lte => ctx.write(" <= "),
697 CompareOp::Like => ctx.keyword("LIKE"),
698 CompareOp::Contains | CompareOp::StartsWith | CompareOp::EndsWith => {
699 ctx.keyword("LIKE");
700 render_like_pattern(op, right, ctx)?;
701 ctx.keyword("ESCAPE").string_literal("\\");
702 return Ok(());
703 }
704 CompareOp::IContains | CompareOp::IStartsWith | CompareOp::IEndsWith => {
705 ctx.keyword("LIKE");
706 ctx.keyword("LOWER").paren_open();
707 render_like_pattern(op, right, ctx)?;
708 ctx.paren_close();
709 ctx.keyword("ESCAPE").string_literal("\\");
710 return Ok(());
711 }
712 CompareOp::In => ctx.keyword("IN"),
713 CompareOp::Between => {
714 ctx.keyword("BETWEEN");
715 self.render_expr(right, ctx)?;
716 return Ok(());
717 }
718 CompareOp::IsNull => {
719 ctx.keyword("IS NULL");
720 return Ok(());
721 }
722 CompareOp::Regex => ctx.keyword("REGEXP"),
723 CompareOp::ILike | CompareOp::Similar | CompareOp::IRegex => {
725 return Err(RenderError::unsupported(
726 "CompareOp",
727 "SQLite does not support ILIKE, SIMILAR TO, or case-insensitive regex.",
728 ));
729 }
730 CompareOp::JsonbContains
731 | CompareOp::JsonbContainedBy
732 | CompareOp::JsonbHasKey
733 | CompareOp::JsonbHasAnyKey
734 | CompareOp::JsonbHasAllKeys
735 | CompareOp::FtsMatch
736 | CompareOp::TrigramSimilar
737 | CompareOp::TrigramWordSimilar
738 | CompareOp::TrigramStrictWordSimilar
739 | CompareOp::RangeContains
740 | CompareOp::RangeContainedBy
741 | CompareOp::RangeOverlap => {
742 return Err(RenderError::unsupported(
743 "CompareOp",
744 "SQLite does not support PostgreSQL-specific operators (JSONB, FTS, trigram, range).",
745 ));
746 }
747 CompareOp::Custom(_) => {
748 return Err(RenderError::unsupported(
749 "CustomCompareOp",
750 "custom compare op must be handled by a wrapping renderer",
751 ));
752 }
753 };
754 self.render_expr(right, ctx)
755 }
756
757 fn render_query(&self, stmt: &QueryStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
760 if let Some(ctes) = &stmt.ctes {
762 self.render_ctes(ctes, ctx)?;
763 }
764
765 ctx.keyword("SELECT");
767
768 if let Some(distinct) = &stmt.distinct {
770 match distinct {
771 DistinctDef::Distinct => {
772 ctx.keyword("DISTINCT");
773 }
774 DistinctDef::DistinctOn(_) => {
775 return Err(RenderError::unsupported(
776 "DISTINCT ON",
777 "not supported in SQLite",
778 ));
779 }
780 }
781 }
782
783 self.render_select_columns(&stmt.columns, ctx)?;
785
786 if let Some(from) = &stmt.from {
788 ctx.keyword("FROM");
789 for (i, item) in from.iter().enumerate() {
790 if i > 0 {
791 ctx.comma();
792 }
793 self.sqlite_render_from_item(item, ctx)?;
794 }
795 }
796
797 if let Some(joins) = &stmt.joins {
799 self.render_joins(joins, ctx)?;
800 }
801
802 if let Some(cond) = &stmt.where_clause {
804 self.render_where(cond, ctx)?;
805 }
806
807 if let Some(group_by) = &stmt.group_by {
809 self.sqlite_render_group_by(group_by, ctx)?;
810 }
811
812 if let Some(having) = &stmt.having {
814 ctx.keyword("HAVING");
815 self.render_condition(having, ctx)?;
816 }
817
818 if let Some(windows) = &stmt.window {
820 self.sqlite_render_window_clause(windows, ctx)?;
821 }
822
823 if let Some(order_by) = &stmt.order_by {
825 self.render_order_by(order_by, ctx)?;
826 }
827
828 if let Some(limit) = &stmt.limit {
830 self.render_limit(limit, ctx)?;
831 }
832
833 if let Some(locks) = &stmt.lock {
835 if !locks.is_empty() {
836 return Err(RenderError::unsupported(
837 "FOR UPDATE/SHARE",
838 "row locking not supported in SQLite",
839 ));
840 }
841 }
842
843 Ok(())
844 }
845
846 fn render_select_columns(
847 &self,
848 cols: &[SelectColumn],
849 ctx: &mut RenderCtx,
850 ) -> RenderResult<()> {
851 for (i, col) in cols.iter().enumerate() {
852 if i > 0 {
853 ctx.comma();
854 }
855 match col {
856 SelectColumn::Star(None) => {
857 ctx.keyword("*");
858 }
859 SelectColumn::Star(Some(table)) => {
860 ctx.ident(table).operator(".").keyword("*");
861 }
862 SelectColumn::Expr { expr, alias } => {
863 self.render_expr(expr, ctx)?;
864 if let Some(a) = alias {
865 ctx.keyword("AS").ident(a);
866 }
867 }
868 SelectColumn::Field { field, alias } => {
869 self.sqlite_field_ref(field, ctx);
870 if let Some(a) = alias {
871 ctx.keyword("AS").ident(a);
872 }
873 }
874 }
875 }
876 Ok(())
877 }
878 fn render_from(&self, source: &TableSource, ctx: &mut RenderCtx) -> RenderResult<()> {
879 match source {
880 TableSource::Table(schema_ref) => {
881 self.sqlite_schema_ref(schema_ref, ctx);
882 if let Some(alias) = &schema_ref.alias {
883 ctx.keyword("AS").ident(alias);
884 }
885 }
886 TableSource::SubQuery(sq) => {
887 ctx.paren_open();
888 self.render_query(&sq.query, ctx)?;
889 ctx.paren_close().keyword("AS").ident(&sq.alias);
890 }
891 TableSource::SetOp(set_op) => {
892 ctx.paren_open();
893 self.sqlite_render_set_op(set_op, ctx)?;
894 ctx.paren_close();
895 }
896 TableSource::Lateral(_) => {
897 return Err(RenderError::unsupported(
898 "LATERAL",
899 "LATERAL subqueries not supported in SQLite",
900 ));
901 }
902 TableSource::Function { name, args, alias } => {
903 ctx.keyword(name).write("(");
904 for (i, arg) in args.iter().enumerate() {
905 if i > 0 {
906 ctx.comma();
907 }
908 self.render_expr(arg, ctx)?;
909 }
910 ctx.paren_close();
911 if let Some(a) = alias {
912 ctx.keyword("AS").ident(a);
913 }
914 }
915 TableSource::Values {
916 rows,
917 alias,
918 column_aliases,
919 } => {
920 ctx.paren_open().keyword("VALUES");
921 for (i, row) in rows.iter().enumerate() {
922 if i > 0 {
923 ctx.comma();
924 }
925 ctx.paren_open();
926 for (j, val) in row.iter().enumerate() {
927 if j > 0 {
928 ctx.comma();
929 }
930 self.render_expr(val, ctx)?;
931 }
932 ctx.paren_close();
933 }
934 ctx.paren_close().keyword("AS").ident(alias);
935 if let Some(cols) = column_aliases {
936 ctx.paren_open();
937 for (i, c) in cols.iter().enumerate() {
938 if i > 0 {
939 ctx.comma();
940 }
941 ctx.ident(c);
942 }
943 ctx.paren_close();
944 }
945 }
946 TableSource::Custom(_) => {
947 return Err(RenderError::unsupported(
948 "CustomTableSource",
949 "custom table source must be handled by a wrapping renderer",
950 ));
951 }
952 }
953 Ok(())
954 }
955 fn render_joins(&self, joins: &[JoinDef], ctx: &mut RenderCtx) -> RenderResult<()> {
956 for join in joins {
957 if join.natural {
958 ctx.keyword("NATURAL");
959 }
960 ctx.keyword(match join.join_type {
961 JoinType::Inner => "INNER JOIN",
962 JoinType::Left => "LEFT JOIN",
963 JoinType::Right => "RIGHT JOIN",
964 JoinType::Full => "FULL JOIN",
965 JoinType::Cross => "CROSS JOIN",
966 JoinType::CrossApply | JoinType::OuterApply => {
967 return Err(RenderError::unsupported(
968 "APPLY",
969 "CROSS/OUTER APPLY not supported in SQLite",
970 ));
971 }
972 });
973 self.sqlite_render_from_item(&join.source, ctx)?;
974 if let Some(condition) = &join.condition {
975 match condition {
976 JoinCondition::On(cond) => {
977 ctx.keyword("ON");
978 self.render_condition(cond, ctx)?;
979 }
980 JoinCondition::Using(cols) => {
981 ctx.keyword("USING").paren_open();
982 self.sqlite_comma_idents(cols, ctx);
983 ctx.paren_close();
984 }
985 }
986 }
987 }
988 Ok(())
989 }
990 fn render_where(&self, cond: &Conditions, ctx: &mut RenderCtx) -> RenderResult<()> {
991 ctx.keyword("WHERE");
992 self.render_condition(cond, ctx)
993 }
994 fn render_order_by(&self, order: &[OrderByDef], ctx: &mut RenderCtx) -> RenderResult<()> {
995 ctx.keyword("ORDER BY");
996 self.sqlite_order_by_list(order, ctx)
997 }
998 fn render_limit(&self, limit: &LimitDef, ctx: &mut RenderCtx) -> RenderResult<()> {
999 match &limit.kind {
1000 LimitKind::Limit(n) => {
1001 ctx.keyword("LIMIT").space().write(&n.to_string());
1002 }
1003 LimitKind::FetchFirst {
1004 count, with_ties, ..
1005 } => {
1006 if *with_ties {
1007 return Err(RenderError::unsupported(
1008 "FETCH FIRST WITH TIES",
1009 "not supported in SQLite",
1010 ));
1011 }
1012 ctx.keyword("LIMIT").space().write(&count.to_string());
1014 }
1015 LimitKind::Top {
1016 count, with_ties, ..
1017 } => {
1018 if *with_ties {
1019 return Err(RenderError::unsupported(
1020 "TOP WITH TIES",
1021 "not supported in SQLite",
1022 ));
1023 }
1024 ctx.keyword("LIMIT").space().write(&count.to_string());
1026 }
1027 }
1028 if let Some(offset) = limit.offset {
1029 ctx.keyword("OFFSET").space().write(&offset.to_string());
1030 }
1031 Ok(())
1032 }
1033 fn render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1034 let any_recursive = ctes.iter().any(|c| c.recursive);
1035 ctx.keyword("WITH");
1036 if any_recursive {
1037 ctx.keyword("RECURSIVE");
1038 }
1039 for (i, cte) in ctes.iter().enumerate() {
1040 if i > 0 {
1041 ctx.comma();
1042 }
1043 ctx.ident(&cte.name);
1044 if let Some(col_names) = &cte.column_names {
1045 ctx.paren_open();
1046 self.sqlite_comma_idents(col_names, ctx);
1047 ctx.paren_close();
1048 }
1049 ctx.keyword("AS").paren_open();
1051 self.render_query(&cte.query, ctx)?;
1052 ctx.paren_close();
1053 }
1054 Ok(())
1055 }
1056 fn render_lock(&self, _lock: &SelectLockDef, _ctx: &mut RenderCtx) -> RenderResult<()> {
1057 Err(RenderError::unsupported(
1058 "FOR UPDATE/SHARE",
1059 "row locking not supported in SQLite",
1060 ))
1061 }
1062
1063 fn render_mutation(&self, stmt: &MutationStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1066 match stmt {
1067 MutationStmt::Insert(s) => self.render_insert(s, ctx),
1068 MutationStmt::Update(s) => self.render_update(s, ctx),
1069 MutationStmt::Delete(s) => self.render_delete(s, ctx),
1070 MutationStmt::Custom(_) => Err(RenderError::unsupported(
1071 "CustomMutation",
1072 "custom DML must be handled by a wrapping renderer",
1073 )),
1074 }
1075 }
1076
1077 fn render_insert(&self, stmt: &InsertStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1078 if let Some(ctes) = &stmt.ctes {
1080 self.sqlite_render_ctes(ctes, ctx)?;
1081 }
1082
1083 if let Some(cr) = &stmt.conflict_resolution {
1085 ctx.keyword("INSERT OR");
1086 ctx.keyword(match cr {
1087 ConflictResolution::Rollback => "ROLLBACK",
1088 ConflictResolution::Abort => "ABORT",
1089 ConflictResolution::Fail => "FAIL",
1090 ConflictResolution::Ignore => "IGNORE",
1091 ConflictResolution::Replace => "REPLACE",
1092 });
1093 ctx.keyword("INTO");
1094 } else {
1095 ctx.keyword("INSERT INTO");
1096 }
1097
1098 self.sqlite_schema_ref(&stmt.table, ctx);
1099
1100 if let Some(alias) = &stmt.table.alias {
1102 ctx.keyword("AS").ident(alias);
1103 }
1104
1105 if let Some(cols) = &stmt.columns {
1107 ctx.paren_open();
1108 self.sqlite_comma_idents(cols, ctx);
1109 ctx.paren_close();
1110 }
1111
1112 match &stmt.source {
1114 InsertSource::Values(rows) => {
1115 ctx.keyword("VALUES");
1116 for (i, row) in rows.iter().enumerate() {
1117 if i > 0 {
1118 ctx.comma();
1119 }
1120 ctx.paren_open();
1121 for (j, expr) in row.iter().enumerate() {
1122 if j > 0 {
1123 ctx.comma();
1124 }
1125 self.render_expr(expr, ctx)?;
1126 }
1127 ctx.paren_close();
1128 }
1129 }
1130 InsertSource::Select(query) => {
1131 self.render_query(query, ctx)?;
1132 }
1133 InsertSource::DefaultValues => {
1134 ctx.keyword("DEFAULT VALUES");
1135 }
1136 }
1137
1138 if let Some(conflicts) = &stmt.on_conflict {
1140 for oc in conflicts {
1141 self.render_on_conflict(oc, ctx)?;
1142 }
1143 }
1144
1145 if let Some(returning) = &stmt.returning {
1147 self.render_returning(returning, ctx)?;
1148 }
1149
1150 Ok(())
1151 }
1152
1153 fn render_update(&self, stmt: &UpdateStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1154 if let Some(ctes) = &stmt.ctes {
1156 self.sqlite_render_ctes(ctes, ctx)?;
1157 }
1158
1159 if let Some(cr) = &stmt.conflict_resolution {
1161 ctx.keyword("UPDATE OR");
1162 ctx.keyword(match cr {
1163 ConflictResolution::Rollback => "ROLLBACK",
1164 ConflictResolution::Abort => "ABORT",
1165 ConflictResolution::Fail => "FAIL",
1166 ConflictResolution::Ignore => "IGNORE",
1167 ConflictResolution::Replace => "REPLACE",
1168 });
1169 } else {
1170 ctx.keyword("UPDATE");
1171 }
1172
1173 self.sqlite_schema_ref(&stmt.table, ctx);
1174
1175 if let Some(alias) = &stmt.table.alias {
1177 ctx.keyword("AS").ident(alias);
1178 }
1179
1180 ctx.keyword("SET");
1182 for (i, (col, expr)) in stmt.assignments.iter().enumerate() {
1183 if i > 0 {
1184 ctx.comma();
1185 }
1186 ctx.ident(col).write(" = ");
1187 self.render_expr(expr, ctx)?;
1188 }
1189
1190 if let Some(from) = &stmt.from {
1192 ctx.keyword("FROM");
1193 for (i, source) in from.iter().enumerate() {
1194 if i > 0 {
1195 ctx.comma();
1196 }
1197 self.render_from(source, ctx)?;
1198 }
1199 }
1200
1201 if let Some(cond) = &stmt.where_clause {
1203 ctx.keyword("WHERE");
1204 self.render_condition(cond, ctx)?;
1205 }
1206
1207 if let Some(returning) = &stmt.returning {
1209 self.render_returning(returning, ctx)?;
1210 }
1211
1212 if let Some(order_by) = &stmt.order_by {
1214 ctx.keyword("ORDER BY");
1215 self.sqlite_order_by_list(order_by, ctx)?;
1216 }
1217
1218 if let Some(limit) = stmt.limit {
1220 ctx.keyword("LIMIT").keyword(&limit.to_string());
1221 if let Some(offset) = stmt.offset {
1222 ctx.keyword("OFFSET").keyword(&offset.to_string());
1223 }
1224 }
1225
1226 Ok(())
1227 }
1228
1229 fn render_delete(&self, stmt: &DeleteStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1230 if let Some(ctes) = &stmt.ctes {
1232 self.sqlite_render_ctes(ctes, ctx)?;
1233 }
1234
1235 ctx.keyword("DELETE FROM");
1236
1237 self.sqlite_schema_ref(&stmt.table, ctx);
1238
1239 if let Some(alias) = &stmt.table.alias {
1241 ctx.keyword("AS").ident(alias);
1242 }
1243
1244 if let Some(cond) = &stmt.where_clause {
1249 ctx.keyword("WHERE");
1250 self.render_condition(cond, ctx)?;
1251 }
1252
1253 if let Some(returning) = &stmt.returning {
1255 self.render_returning(returning, ctx)?;
1256 }
1257
1258 if let Some(order_by) = &stmt.order_by {
1260 ctx.keyword("ORDER BY");
1261 self.sqlite_order_by_list(order_by, ctx)?;
1262 }
1263
1264 if let Some(limit) = stmt.limit {
1266 ctx.keyword("LIMIT").keyword(&limit.to_string());
1267 if let Some(offset) = stmt.offset {
1268 ctx.keyword("OFFSET").keyword(&offset.to_string());
1269 }
1270 }
1271
1272 Ok(())
1273 }
1274
1275 fn render_on_conflict(&self, oc: &OnConflictDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1276 ctx.keyword("ON CONFLICT");
1277
1278 if let Some(target) = &oc.target {
1280 match target {
1281 ConflictTarget::Columns {
1282 columns,
1283 where_clause,
1284 } => {
1285 ctx.paren_open();
1286 self.sqlite_comma_idents(columns, ctx);
1287 ctx.paren_close();
1288 if let Some(cond) = where_clause {
1289 ctx.keyword("WHERE");
1290 self.render_condition(cond, ctx)?;
1291 }
1292 }
1293 ConflictTarget::Constraint(_) => {
1294 return Err(RenderError::unsupported(
1295 "OnConstraint",
1296 "SQLite does not support ON CONFLICT ON CONSTRAINT. Use column list instead.",
1297 ));
1298 }
1299 }
1300 }
1301
1302 match &oc.action {
1304 ConflictAction::DoNothing => {
1305 ctx.keyword("DO NOTHING");
1306 }
1307 ConflictAction::DoUpdate {
1308 assignments,
1309 where_clause,
1310 } => {
1311 ctx.keyword("DO UPDATE SET");
1312 for (i, (col, expr)) in assignments.iter().enumerate() {
1313 if i > 0 {
1314 ctx.comma();
1315 }
1316 ctx.ident(col).write(" = ");
1317 self.render_expr(expr, ctx)?;
1318 }
1319 if let Some(cond) = where_clause {
1320 ctx.keyword("WHERE");
1321 self.render_condition(cond, ctx)?;
1322 }
1323 }
1324 }
1325
1326 Ok(())
1327 }
1328
1329 fn render_returning(&self, cols: &[SelectColumn], ctx: &mut RenderCtx) -> RenderResult<()> {
1330 ctx.keyword("RETURNING");
1331 for (i, col) in cols.iter().enumerate() {
1332 if i > 0 {
1333 ctx.comma();
1334 }
1335 match col {
1336 SelectColumn::Star(None) => {
1337 ctx.keyword("*");
1338 }
1339 SelectColumn::Star(Some(table)) => {
1340 ctx.ident(table).operator(".").keyword("*");
1341 }
1342 SelectColumn::Expr { expr, alias } => {
1343 self.render_expr(expr, ctx)?;
1344 if let Some(a) = alias {
1345 ctx.keyword("AS").ident(a);
1346 }
1347 }
1348 SelectColumn::Field { field, alias } => {
1349 self.sqlite_field_ref(field, ctx);
1350 if let Some(a) = alias {
1351 ctx.keyword("AS").ident(a);
1352 }
1353 }
1354 }
1355 }
1356 Ok(())
1357 }
1358
1359 fn render_transaction(&self, stmt: &TransactionStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1362 match stmt {
1363 TransactionStmt::Begin(s) => {
1364 ctx.keyword("BEGIN");
1365 if let Some(lock_type) = &s.lock_type {
1366 ctx.keyword(match lock_type {
1367 SqliteLockType::Deferred => "DEFERRED",
1368 SqliteLockType::Immediate => "IMMEDIATE",
1369 SqliteLockType::Exclusive => "EXCLUSIVE",
1370 });
1371 }
1372 ctx.keyword("TRANSACTION");
1373 Ok(())
1374 }
1375 TransactionStmt::Commit(_) => {
1376 ctx.keyword("COMMIT");
1377 Ok(())
1378 }
1379 TransactionStmt::Rollback(s) => {
1380 ctx.keyword("ROLLBACK");
1381 if let Some(sp) = &s.to_savepoint {
1382 ctx.keyword("TO").keyword("SAVEPOINT").ident(sp);
1383 }
1384 Ok(())
1385 }
1386 TransactionStmt::Savepoint(s) => {
1387 ctx.keyword("SAVEPOINT").ident(&s.name);
1388 Ok(())
1389 }
1390 TransactionStmt::ReleaseSavepoint(s) => {
1391 ctx.keyword("RELEASE").keyword("SAVEPOINT").ident(&s.name);
1392 Ok(())
1393 }
1394 TransactionStmt::SetTransaction(_) => Err(RenderError::unsupported(
1395 "SET TRANSACTION",
1396 "not supported in SQLite",
1397 )),
1398 TransactionStmt::LockTable(_) => Err(RenderError::unsupported(
1399 "LOCK TABLE",
1400 "not supported in SQLite (use BEGIN EXCLUSIVE)",
1401 )),
1402 TransactionStmt::PrepareTransaction(_) => Err(RenderError::unsupported(
1403 "PREPARE TRANSACTION",
1404 "not supported in SQLite",
1405 )),
1406 TransactionStmt::CommitPrepared(_) => Err(RenderError::unsupported(
1407 "COMMIT PREPARED",
1408 "not supported in SQLite",
1409 )),
1410 TransactionStmt::RollbackPrepared(_) => Err(RenderError::unsupported(
1411 "ROLLBACK PREPARED",
1412 "not supported in SQLite",
1413 )),
1414 TransactionStmt::Custom(_) => Err(RenderError::unsupported(
1415 "Custom TCL",
1416 "not supported by SqliteRenderer",
1417 )),
1418 }
1419 }
1420}
1421
1422impl SqliteRenderer {
1427 fn sqlite_schema_ref(
1428 &self,
1429 schema_ref: &qcraft_core::ast::common::SchemaRef,
1430 ctx: &mut RenderCtx,
1431 ) {
1432 if let Some(ns) = &schema_ref.namespace {
1433 ctx.ident(ns).operator(".");
1434 }
1435 ctx.ident(&schema_ref.name);
1436 }
1437
1438 fn sqlite_field_ref(&self, field_ref: &FieldRef, ctx: &mut RenderCtx) {
1439 ctx.ident(&field_ref.table_name)
1440 .operator(".")
1441 .ident(&field_ref.field.name);
1442 }
1443
1444 fn sqlite_comma_idents(&self, names: &[String], ctx: &mut RenderCtx) {
1445 for (i, name) in names.iter().enumerate() {
1446 if i > 0 {
1447 ctx.comma();
1448 }
1449 ctx.ident(name);
1450 }
1451 }
1452
1453 fn sqlite_value(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
1454 if matches!(val, Value::Null) {
1456 ctx.keyword("NULL");
1457 return Ok(());
1458 }
1459
1460 match val {
1462 Value::Array(_) => {
1463 return Err(RenderError::unsupported(
1464 "ArrayValue",
1465 "SQLite does not support array literals.",
1466 ));
1467 }
1468 Value::Vector(_) => {
1469 return Err(RenderError::unsupported(
1470 "VectorValue",
1471 "SQLite does not support vector type.",
1472 ));
1473 }
1474 Value::TimeDelta { .. } => {
1475 return Err(RenderError::unsupported(
1476 "TimeDeltaValue",
1477 "SQLite does not support INTERVAL type. Use string expressions with datetime functions.",
1478 ));
1479 }
1480 _ => {}
1481 }
1482
1483 if ctx.parameterize() {
1485 ctx.param(val.clone());
1486 return Ok(());
1487 }
1488
1489 self.sqlite_value_literal(val, ctx)
1491 }
1492
1493 fn sqlite_value_literal(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
1494 match val {
1495 Value::Null => {
1496 ctx.keyword("NULL");
1497 }
1498 Value::Bool(b) => {
1499 ctx.keyword(if *b { "1" } else { "0" });
1500 }
1501 Value::Int(n) => {
1502 ctx.keyword(&n.to_string());
1503 }
1504 Value::Float(f) => {
1505 ctx.keyword(&f.to_string());
1506 }
1507 Value::Str(s) => {
1508 ctx.string_literal(s);
1509 }
1510 Value::Bytes(b) => {
1511 ctx.write("X'");
1512 for byte in b {
1513 ctx.write(&format!("{byte:02x}"));
1514 }
1515 ctx.write("'");
1516 }
1517 Value::Date(s) | Value::DateTime(s) | Value::Time(s) => {
1518 ctx.string_literal(s);
1519 }
1520 Value::Decimal(s) => {
1521 ctx.keyword(s);
1522 }
1523 Value::Uuid(s) => {
1524 ctx.string_literal(s);
1525 }
1526 Value::Json(s) | Value::Jsonb(s) => {
1527 ctx.string_literal(s);
1528 }
1529 Value::IpNetwork(s) => {
1530 ctx.string_literal(s);
1531 }
1532 _ => {
1533 unreachable!()
1535 }
1536 }
1537 Ok(())
1538 }
1539
1540 fn sqlite_referential_action(
1541 &self,
1542 action: &ReferentialAction,
1543 ctx: &mut RenderCtx,
1544 ) -> RenderResult<()> {
1545 match action {
1546 ReferentialAction::NoAction => {
1547 ctx.keyword("NO ACTION");
1548 }
1549 ReferentialAction::Restrict => {
1550 ctx.keyword("RESTRICT");
1551 }
1552 ReferentialAction::Cascade => {
1553 ctx.keyword("CASCADE");
1554 }
1555 ReferentialAction::SetNull(cols) => {
1556 ctx.keyword("SET NULL");
1557 if cols.is_some() {
1558 return Err(RenderError::unsupported(
1559 "SetNullColumns",
1560 "SQLite does not support SET NULL with column list.",
1561 ));
1562 }
1563 }
1564 ReferentialAction::SetDefault(cols) => {
1565 ctx.keyword("SET DEFAULT");
1566 if cols.is_some() {
1567 return Err(RenderError::unsupported(
1568 "SetDefaultColumns",
1569 "SQLite does not support SET DEFAULT with column list.",
1570 ));
1571 }
1572 }
1573 }
1574 Ok(())
1575 }
1576
1577 fn sqlite_deferrable(&self, def: &DeferrableConstraint, ctx: &mut RenderCtx) {
1578 if def.deferrable {
1579 ctx.keyword("DEFERRABLE");
1580 } else {
1581 ctx.keyword("NOT DEFERRABLE");
1582 }
1583 if def.initially_deferred {
1584 ctx.keyword("INITIALLY DEFERRED");
1585 } else {
1586 ctx.keyword("INITIALLY IMMEDIATE");
1587 }
1588 }
1589
1590 fn sqlite_render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1591 self.render_ctes(ctes, ctx)
1592 }
1593
1594 fn sqlite_render_from_item(&self, item: &FromItem, ctx: &mut RenderCtx) -> RenderResult<()> {
1595 self.render_from(&item.source, ctx)?;
1597 if let Some(hint) = &item.index_hint {
1599 match hint {
1600 SqliteIndexHint::IndexedBy(name) => {
1601 ctx.keyword("INDEXED BY").ident(name);
1602 }
1603 SqliteIndexHint::NotIndexed => {
1604 ctx.keyword("NOT INDEXED");
1605 }
1606 }
1607 }
1608 if item.sample.is_some() {
1610 return Err(RenderError::unsupported(
1611 "TABLESAMPLE",
1612 "not supported in SQLite",
1613 ));
1614 }
1615 Ok(())
1616 }
1617
1618 fn sqlite_render_group_by(
1619 &self,
1620 items: &[GroupByItem],
1621 ctx: &mut RenderCtx,
1622 ) -> RenderResult<()> {
1623 ctx.keyword("GROUP BY");
1624 for (i, item) in items.iter().enumerate() {
1625 if i > 0 {
1626 ctx.comma();
1627 }
1628 match item {
1629 GroupByItem::Expr(expr) => {
1630 self.render_expr(expr, ctx)?;
1631 }
1632 GroupByItem::Rollup(_) => {
1633 return Err(RenderError::unsupported(
1634 "ROLLUP",
1635 "not supported in SQLite",
1636 ));
1637 }
1638 GroupByItem::Cube(_) => {
1639 return Err(RenderError::unsupported("CUBE", "not supported in SQLite"));
1640 }
1641 GroupByItem::GroupingSets(_) => {
1642 return Err(RenderError::unsupported(
1643 "GROUPING SETS",
1644 "not supported in SQLite",
1645 ));
1646 }
1647 }
1648 }
1649 Ok(())
1650 }
1651
1652 fn sqlite_render_window_clause(
1653 &self,
1654 windows: &[WindowNameDef],
1655 ctx: &mut RenderCtx,
1656 ) -> RenderResult<()> {
1657 ctx.keyword("WINDOW");
1658 for (i, win) in windows.iter().enumerate() {
1659 if i > 0 {
1660 ctx.comma();
1661 }
1662 ctx.ident(&win.name).keyword("AS").paren_open();
1663 if let Some(base) = &win.base_window {
1664 ctx.ident(base);
1665 }
1666 if let Some(partition_by) = &win.partition_by {
1667 ctx.keyword("PARTITION BY");
1668 for (j, expr) in partition_by.iter().enumerate() {
1669 if j > 0 {
1670 ctx.comma();
1671 }
1672 self.render_expr(expr, ctx)?;
1673 }
1674 }
1675 if let Some(order_by) = &win.order_by {
1676 ctx.keyword("ORDER BY");
1677 self.sqlite_order_by_list(order_by, ctx)?;
1678 }
1679 if let Some(frame) = &win.frame {
1680 self.sqlite_window_frame(frame, ctx);
1681 }
1682 ctx.paren_close();
1683 }
1684 Ok(())
1685 }
1686
1687 fn sqlite_window_frame(&self, frame: &WindowFrameDef, ctx: &mut RenderCtx) {
1688 ctx.keyword(match frame.frame_type {
1689 WindowFrameType::Rows => "ROWS",
1690 WindowFrameType::Range => "RANGE",
1691 WindowFrameType::Groups => "GROUPS",
1692 });
1693 if let Some(end) = &frame.end {
1694 ctx.keyword("BETWEEN");
1695 self.sqlite_frame_bound(&frame.start, ctx);
1696 ctx.keyword("AND");
1697 self.sqlite_frame_bound(end, ctx);
1698 } else {
1699 self.sqlite_frame_bound(&frame.start, ctx);
1700 }
1701 }
1702
1703 fn sqlite_frame_bound(&self, bound: &WindowFrameBound, ctx: &mut RenderCtx) {
1704 match bound {
1705 WindowFrameBound::CurrentRow => {
1706 ctx.keyword("CURRENT ROW");
1707 }
1708 WindowFrameBound::Preceding(None) => {
1709 ctx.keyword("UNBOUNDED PRECEDING");
1710 }
1711 WindowFrameBound::Preceding(Some(n)) => {
1712 ctx.keyword(&n.to_string()).keyword("PRECEDING");
1713 }
1714 WindowFrameBound::Following(None) => {
1715 ctx.keyword("UNBOUNDED FOLLOWING");
1716 }
1717 WindowFrameBound::Following(Some(n)) => {
1718 ctx.keyword(&n.to_string()).keyword("FOLLOWING");
1719 }
1720 }
1721 }
1722
1723 fn sqlite_render_set_op(&self, set_op: &SetOpDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1724 self.render_query(&set_op.left, ctx)?;
1725 ctx.keyword(match set_op.operation {
1726 SetOperationType::Union => "UNION",
1727 SetOperationType::UnionAll => "UNION ALL",
1728 SetOperationType::Intersect => "INTERSECT",
1729 SetOperationType::Except => "EXCEPT",
1730 SetOperationType::IntersectAll => {
1731 return Err(RenderError::unsupported(
1732 "INTERSECT ALL",
1733 "not supported in SQLite",
1734 ));
1735 }
1736 SetOperationType::ExceptAll => {
1737 return Err(RenderError::unsupported(
1738 "EXCEPT ALL",
1739 "not supported in SQLite",
1740 ));
1741 }
1742 });
1743 self.render_query(&set_op.right, ctx)
1744 }
1745
1746 fn sqlite_create_table(
1747 &self,
1748 schema: &SchemaDef,
1749 if_not_exists: bool,
1750 temporary: bool,
1751 without_rowid: bool,
1752 strict: bool,
1753 ctx: &mut RenderCtx,
1754 ) -> RenderResult<()> {
1755 ctx.keyword("CREATE");
1756 if temporary {
1757 ctx.keyword("TEMP");
1758 }
1759 ctx.keyword("TABLE");
1760 if if_not_exists {
1761 ctx.keyword("IF NOT EXISTS");
1762 }
1763 if let Some(ns) = &schema.namespace {
1764 ctx.ident(ns).operator(".");
1765 }
1766 ctx.ident(&schema.name);
1767
1768 let autoincrement_pk_col = schema.constraints.as_ref().and_then(|cs| {
1770 cs.iter().find_map(|c| {
1771 if let ConstraintDef::PrimaryKey {
1772 columns,
1773 autoincrement: true,
1774 ..
1775 } = c
1776 {
1777 if columns.len() == 1 {
1778 return Some(columns[0].as_str());
1779 }
1780 }
1781 None
1782 })
1783 });
1784
1785 ctx.paren_open();
1786 let mut first = true;
1787 for col in &schema.columns {
1788 if !first {
1789 ctx.comma();
1790 }
1791 first = false;
1792 self.render_column_def(col, ctx)?;
1793 if autoincrement_pk_col == Some(col.name.as_str()) {
1795 ctx.keyword("PRIMARY KEY AUTOINCREMENT");
1796 }
1797 }
1798 if let Some(constraints) = &schema.constraints {
1799 for constraint in constraints {
1800 if let ConstraintDef::PrimaryKey {
1802 autoincrement: true,
1803 columns,
1804 ..
1805 } = constraint
1806 {
1807 if columns.len() == 1 {
1808 continue;
1809 }
1810 }
1811 if !first {
1812 ctx.comma();
1813 }
1814 first = false;
1815 self.render_constraint(constraint, ctx)?;
1816 }
1817 }
1818 ctx.paren_close();
1819
1820 let mut modifiers = Vec::new();
1822 if without_rowid {
1823 modifiers.push("WITHOUT ROWID");
1824 }
1825 if strict {
1826 modifiers.push("STRICT");
1827 }
1828 if !modifiers.is_empty() {
1829 for (i, m) in modifiers.iter().enumerate() {
1830 if i > 0 {
1831 ctx.comma();
1832 }
1833 ctx.keyword(m);
1834 }
1835 }
1836
1837 Ok(())
1838 }
1839
1840 fn sqlite_create_index(
1841 &self,
1842 schema_ref: &qcraft_core::ast::common::SchemaRef,
1843 index: &IndexDef,
1844 if_not_exists: bool,
1845 ctx: &mut RenderCtx,
1846 ) -> RenderResult<()> {
1847 ctx.keyword("CREATE");
1848 if index.unique {
1849 ctx.keyword("UNIQUE");
1850 }
1851 ctx.keyword("INDEX");
1852 if if_not_exists {
1853 ctx.keyword("IF NOT EXISTS");
1854 }
1855 ctx.ident(&index.name).keyword("ON");
1856 self.sqlite_schema_ref(schema_ref, ctx);
1857
1858 ctx.paren_open();
1861 self.sqlite_index_columns(&index.columns, ctx)?;
1862 ctx.paren_close();
1863
1864 if let Some(condition) = &index.condition {
1867 ctx.keyword("WHERE");
1868 self.render_condition(condition, ctx)?;
1869 }
1870
1871 Ok(())
1872 }
1873
1874 fn sqlite_index_columns(
1875 &self,
1876 columns: &[IndexColumnDef],
1877 ctx: &mut RenderCtx,
1878 ) -> RenderResult<()> {
1879 for (i, col) in columns.iter().enumerate() {
1880 if i > 0 {
1881 ctx.comma();
1882 }
1883 match &col.expr {
1884 IndexExpr::Column(name) => {
1885 ctx.ident(name);
1886 }
1887 IndexExpr::Expression(expr) => {
1888 ctx.paren_open();
1889 self.render_expr(expr, ctx)?;
1890 ctx.paren_close();
1891 }
1892 }
1893 if let Some(collation) = &col.collation {
1894 ctx.keyword("COLLATE").ident(collation);
1895 }
1896 if let Some(dir) = col.direction {
1898 ctx.keyword(match dir {
1899 OrderDir::Asc => "ASC",
1900 OrderDir::Desc => "DESC",
1901 });
1902 }
1903 }
1905 Ok(())
1906 }
1907
1908 fn sqlite_order_by_list(
1909 &self,
1910 order_by: &[OrderByDef],
1911 ctx: &mut RenderCtx,
1912 ) -> RenderResult<()> {
1913 for (i, ob) in order_by.iter().enumerate() {
1914 if i > 0 {
1915 ctx.comma();
1916 }
1917 self.render_expr(&ob.expr, ctx)?;
1918 ctx.keyword(match ob.direction {
1919 OrderDir::Asc => "ASC",
1920 OrderDir::Desc => "DESC",
1921 });
1922 if let Some(nulls) = &ob.nulls {
1923 ctx.keyword(match nulls {
1924 NullsOrder::First => "NULLS FIRST",
1925 NullsOrder::Last => "NULLS LAST",
1926 });
1927 }
1928 }
1929 Ok(())
1930 }
1931}