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