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::renderer::Renderer;
25
26pub struct SqliteRenderer;
27
28impl SqliteRenderer {
29 pub fn new() -> Self {
30 Self
31 }
32
33 pub fn render_schema_stmt(
34 &self,
35 stmt: &SchemaMutationStmt,
36 ) -> RenderResult<(String, Vec<Value>)> {
37 let mut ctx = RenderCtx::new(ParamStyle::QMark);
38 self.render_schema_mutation(stmt, &mut ctx)?;
39 Ok(ctx.finish())
40 }
41
42 pub fn render_transaction_stmt(
43 &self,
44 stmt: &TransactionStmt,
45 ) -> RenderResult<(String, Vec<Value>)> {
46 let mut ctx = RenderCtx::new(ParamStyle::QMark);
47 self.render_transaction(stmt, &mut ctx)?;
48 Ok(ctx.finish())
49 }
50
51 pub fn render_mutation_stmt(&self, stmt: &MutationStmt) -> RenderResult<(String, Vec<Value>)> {
52 let mut ctx = RenderCtx::new(ParamStyle::QMark).with_parameterize(true);
53 self.render_mutation(stmt, &mut ctx)?;
54 Ok(ctx.finish())
55 }
56
57 pub fn render_query_stmt(&self, stmt: &QueryStmt) -> RenderResult<(String, Vec<Value>)> {
58 let mut ctx = RenderCtx::new(ParamStyle::QMark).with_parameterize(true);
59 self.render_query(stmt, &mut ctx)?;
60 Ok(ctx.finish())
61 }
62}
63
64impl Default for SqliteRenderer {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl Renderer for SqliteRenderer {
75 fn render_schema_mutation(
78 &self,
79 stmt: &SchemaMutationStmt,
80 ctx: &mut RenderCtx,
81 ) -> RenderResult<()> {
82 match stmt {
83 SchemaMutationStmt::CreateTable {
84 schema,
85 if_not_exists,
86 temporary,
87 unlogged: _,
88 tablespace: _,
89 partition_by: _, inherits: _, using_method: _, with_options: _, on_commit: _, table_options: _, without_rowid,
96 strict,
97 } => self.sqlite_create_table(
98 schema,
99 *if_not_exists,
100 *temporary,
101 *without_rowid,
102 *strict,
103 ctx,
104 ),
105
106 SchemaMutationStmt::DropTable {
107 schema_ref,
108 if_exists,
109 cascade: _, } => {
111 ctx.keyword("DROP TABLE");
112 if *if_exists {
113 ctx.keyword("IF EXISTS");
114 }
115 self.sqlite_schema_ref(schema_ref, ctx);
116 Ok(())
117 }
118
119 SchemaMutationStmt::RenameTable {
120 schema_ref,
121 new_name,
122 } => {
123 ctx.keyword("ALTER TABLE");
124 self.sqlite_schema_ref(schema_ref, ctx);
125 ctx.keyword("RENAME TO").ident(new_name);
126 Ok(())
127 }
128
129 SchemaMutationStmt::TruncateTable {
130 schema_ref,
131 restart_identity: _, cascade: _, } => {
134 ctx.keyword("DELETE FROM");
136 self.sqlite_schema_ref(schema_ref, ctx);
137 Ok(())
138 }
139
140 SchemaMutationStmt::AddColumn {
141 schema_ref,
142 column,
143 if_not_exists: _, position: _, } => {
146 ctx.keyword("ALTER TABLE");
147 self.sqlite_schema_ref(schema_ref, ctx);
148 ctx.keyword("ADD COLUMN");
149 self.render_column_def(column, ctx)
150 }
151
152 SchemaMutationStmt::DropColumn {
153 schema_ref,
154 name,
155 if_exists: _, cascade: _, } => {
158 ctx.keyword("ALTER TABLE");
159 self.sqlite_schema_ref(schema_ref, ctx);
160 ctx.keyword("DROP COLUMN").ident(name);
161 Ok(())
162 }
163
164 SchemaMutationStmt::RenameColumn {
165 schema_ref,
166 old_name,
167 new_name,
168 } => {
169 ctx.keyword("ALTER TABLE");
170 self.sqlite_schema_ref(schema_ref, ctx);
171 ctx.keyword("RENAME COLUMN")
172 .ident(old_name)
173 .keyword("TO")
174 .ident(new_name);
175 Ok(())
176 }
177
178 SchemaMutationStmt::AlterColumnType { .. } => Err(RenderError::unsupported(
180 "AlterColumnType",
181 "SQLite does not support ALTER COLUMN TYPE. Use the 12-step table rebuild procedure.",
182 )),
183 SchemaMutationStmt::AlterColumnDefault { .. } => Err(RenderError::unsupported(
184 "AlterColumnDefault",
185 "SQLite does not support ALTER COLUMN DEFAULT. Use the 12-step table rebuild procedure.",
186 )),
187 SchemaMutationStmt::AlterColumnNullability { .. } => Err(RenderError::unsupported(
188 "AlterColumnNullability",
189 "SQLite does not support ALTER COLUMN NOT NULL. Use the 12-step table rebuild procedure.",
190 )),
191 SchemaMutationStmt::AddConstraint { .. } => Err(RenderError::unsupported(
192 "AddConstraint",
193 "SQLite does not support ADD CONSTRAINT. Use the 12-step table rebuild procedure.",
194 )),
195 SchemaMutationStmt::DropConstraint { .. } => Err(RenderError::unsupported(
196 "DropConstraint",
197 "SQLite does not support DROP CONSTRAINT. Use the 12-step table rebuild procedure.",
198 )),
199 SchemaMutationStmt::RenameConstraint { .. } => Err(RenderError::unsupported(
200 "RenameConstraint",
201 "SQLite does not support RENAME CONSTRAINT.",
202 )),
203 SchemaMutationStmt::ValidateConstraint { .. } => Err(RenderError::unsupported(
204 "ValidateConstraint",
205 "SQLite does not support VALIDATE CONSTRAINT.",
206 )),
207
208 SchemaMutationStmt::CreateIndex {
210 schema_ref,
211 index,
212 if_not_exists,
213 concurrently: _, } => self.sqlite_create_index(schema_ref, index, *if_not_exists, ctx),
215
216 SchemaMutationStmt::DropIndex {
217 schema_ref: _,
218 index_name,
219 if_exists,
220 concurrently: _, cascade: _, } => {
223 ctx.keyword("DROP INDEX");
224 if *if_exists {
225 ctx.keyword("IF EXISTS");
226 }
227 ctx.ident(index_name);
228 Ok(())
229 }
230
231 SchemaMutationStmt::CreateExtension { .. } => Err(RenderError::unsupported(
233 "CreateExtension",
234 "SQLite does not support extensions.",
235 )),
236 SchemaMutationStmt::DropExtension { .. } => Err(RenderError::unsupported(
237 "DropExtension",
238 "SQLite does not support extensions.",
239 )),
240
241 SchemaMutationStmt::Custom(_) => Err(RenderError::unsupported(
242 "CustomSchemaMutation",
243 "custom DDL must be handled by a wrapping renderer",
244 )),
245 }
246 }
247
248 fn render_column_def(&self, col: &ColumnDef, ctx: &mut RenderCtx) -> RenderResult<()> {
249 ctx.ident(&col.name);
250 self.render_column_type(&col.field_type, ctx)?;
251
252 if let Some(collation) = &col.collation {
253 ctx.keyword("COLLATE").ident(collation);
254 }
255
256 if col.not_null {
257 ctx.keyword("NOT NULL");
258 }
259
260 if let Some(default) = &col.default {
261 ctx.keyword("DEFAULT");
262 self.render_expr(default, ctx)?;
263 }
264
265 if col.identity.is_some() {
267 return Err(RenderError::unsupported(
268 "IdentityColumn",
269 "SQLite does not support GENERATED AS IDENTITY. Use INTEGER PRIMARY KEY AUTOINCREMENT.",
270 ));
271 }
272
273 if let Some(generated) = &col.generated {
274 ctx.keyword("GENERATED ALWAYS AS").space().paren_open();
275 self.render_expr(&generated.expr, ctx)?;
276 ctx.paren_close();
277 if generated.stored {
278 ctx.keyword("STORED");
279 } else {
280 ctx.keyword("VIRTUAL");
281 }
282 }
283
284 Ok(())
285 }
286
287 fn render_column_type(&self, ty: &FieldType, ctx: &mut RenderCtx) -> RenderResult<()> {
288 match ty {
289 FieldType::Scalar(name) => {
290 ctx.keyword(name);
291 }
292 FieldType::Parameterized { name, params } => {
293 ctx.keyword(name).write("(");
294 for (i, p) in params.iter().enumerate() {
295 if i > 0 {
296 ctx.comma();
297 }
298 ctx.write(p);
299 }
300 ctx.paren_close();
301 }
302 FieldType::Array(_) => {
303 return Err(RenderError::unsupported(
304 "ArrayType",
305 "SQLite does not support array types.",
306 ));
307 }
308 FieldType::Vector(_) => {
309 return Err(RenderError::unsupported(
310 "VectorType",
311 "SQLite does not support vector types.",
312 ));
313 }
314 FieldType::Custom(_) => {
315 return Err(RenderError::unsupported(
316 "CustomFieldType",
317 "custom field type must be handled by a wrapping renderer",
318 ));
319 }
320 }
321 Ok(())
322 }
323
324 fn render_constraint(&self, c: &ConstraintDef, ctx: &mut RenderCtx) -> RenderResult<()> {
325 match c {
326 ConstraintDef::PrimaryKey {
327 name,
328 columns,
329 include: _, autoincrement,
331 } => {
332 if let Some(n) = name {
333 ctx.keyword("CONSTRAINT").ident(n);
334 }
335 ctx.keyword("PRIMARY KEY").paren_open();
336 self.sqlite_comma_idents(columns, ctx);
337 ctx.paren_close();
338 if *autoincrement {
339 ctx.keyword("AUTOINCREMENT");
340 }
341 }
342
343 ConstraintDef::ForeignKey {
344 name,
345 columns,
346 ref_table,
347 ref_columns,
348 on_delete,
349 on_update,
350 deferrable,
351 match_type: _, } => {
353 if let Some(n) = name {
354 ctx.keyword("CONSTRAINT").ident(n);
355 }
356 ctx.keyword("FOREIGN KEY").paren_open();
357 self.sqlite_comma_idents(columns, ctx);
358 ctx.paren_close().keyword("REFERENCES");
359 self.sqlite_schema_ref(ref_table, ctx);
360 ctx.paren_open();
361 self.sqlite_comma_idents(ref_columns, ctx);
362 ctx.paren_close();
363 if let Some(action) = on_delete {
364 ctx.keyword("ON DELETE");
365 self.sqlite_referential_action(action, ctx)?;
366 }
367 if let Some(action) = on_update {
368 ctx.keyword("ON UPDATE");
369 self.sqlite_referential_action(action, ctx)?;
370 }
371 if let Some(def) = deferrable {
372 self.sqlite_deferrable(def, ctx);
373 }
374 }
375
376 ConstraintDef::Unique {
377 name,
378 columns,
379 include: _, nulls_distinct: _, condition: _, } => {
383 if let Some(n) = name {
384 ctx.keyword("CONSTRAINT").ident(n);
385 }
386 ctx.keyword("UNIQUE").paren_open();
387 self.sqlite_comma_idents(columns, ctx);
388 ctx.paren_close();
389 }
390
391 ConstraintDef::Check {
392 name,
393 condition,
394 no_inherit: _, enforced: _, } => {
397 if let Some(n) = name {
398 ctx.keyword("CONSTRAINT").ident(n);
399 }
400 ctx.keyword("CHECK").paren_open();
401 self.render_condition(condition, ctx)?;
402 ctx.paren_close();
403 }
404
405 ConstraintDef::Exclusion { .. } => {
406 return Err(RenderError::unsupported(
407 "ExclusionConstraint",
408 "SQLite does not support EXCLUDE constraints.",
409 ));
410 }
411
412 ConstraintDef::Custom(_) => {
413 return Err(RenderError::unsupported(
414 "CustomConstraint",
415 "custom constraint must be handled by a wrapping renderer",
416 ));
417 }
418 }
419 Ok(())
420 }
421
422 fn render_index_def(&self, idx: &IndexDef, ctx: &mut RenderCtx) -> RenderResult<()> {
423 ctx.ident(&idx.name);
424 ctx.paren_open();
425 self.sqlite_index_columns(&idx.columns, ctx)?;
426 ctx.paren_close();
427 Ok(())
428 }
429
430 fn render_expr(&self, expr: &Expr, ctx: &mut RenderCtx) -> RenderResult<()> {
433 match expr {
434 Expr::Value(val) => self.sqlite_value(val, ctx),
435
436 Expr::Field(field_ref) => {
437 self.sqlite_field_ref(field_ref, ctx);
438 Ok(())
439 }
440
441 Expr::Binary { left, op, right } => {
442 self.render_expr(left, ctx)?;
443 ctx.keyword(match op {
444 BinaryOp::Add => "+",
445 BinaryOp::Sub => "-",
446 BinaryOp::Mul => "*",
447 BinaryOp::Div => "/",
448 BinaryOp::Mod => "%",
449 BinaryOp::BitwiseAnd => "&",
450 BinaryOp::BitwiseOr => "|",
451 BinaryOp::ShiftLeft => "<<",
452 BinaryOp::ShiftRight => ">>",
453 BinaryOp::Concat => "||",
454 });
455 self.render_expr(right, ctx)
456 }
457
458 Expr::Unary { op, expr: inner } => {
459 match op {
460 UnaryOp::Neg => ctx.write("-"),
461 UnaryOp::Not => ctx.keyword("NOT"),
462 UnaryOp::BitwiseNot => ctx.write("~"),
463 };
464 self.render_expr(inner, ctx)
465 }
466
467 Expr::Func { name, args } => {
468 ctx.keyword(name).write("(");
469 for (i, arg) in args.iter().enumerate() {
470 if i > 0 {
471 ctx.comma();
472 }
473 self.render_expr(arg, ctx)?;
474 }
475 ctx.paren_close();
476 Ok(())
477 }
478
479 Expr::Aggregate(agg) => self.render_aggregate(agg, ctx),
480
481 Expr::Cast {
482 expr: inner,
483 to_type,
484 } => {
485 ctx.keyword("CAST").paren_open();
486 self.render_expr(inner, ctx)?;
487 ctx.keyword("AS").keyword(to_type);
488 ctx.paren_close();
489 Ok(())
490 }
491
492 Expr::Case(case) => self.render_case(case, ctx),
493 Expr::Window(win) => self.render_window(win, ctx),
494
495 Expr::Exists(query) => {
496 ctx.keyword("EXISTS").paren_open();
497 self.render_query(query, ctx)?;
498 ctx.paren_close();
499 Ok(())
500 }
501
502 Expr::SubQuery(query) => {
503 ctx.paren_open();
504 self.render_query(query, ctx)?;
505 ctx.paren_close();
506 Ok(())
507 }
508
509 Expr::ArraySubQuery(_) => Err(RenderError::unsupported(
510 "ArraySubQuery",
511 "SQLite does not support ARRAY subqueries.",
512 )),
513
514 Expr::Raw { sql, params } => {
515 ctx.keyword(sql);
516 let _ = params;
517 Ok(())
518 }
519
520 Expr::Custom(_) => Err(RenderError::unsupported(
521 "CustomExpr",
522 "custom expression must be handled by a wrapping renderer",
523 )),
524 }
525 }
526
527 fn render_aggregate(&self, agg: &AggregationDef, ctx: &mut RenderCtx) -> RenderResult<()> {
528 ctx.keyword(&agg.name).write("(");
529 if agg.distinct {
530 ctx.keyword("DISTINCT");
531 }
532 if let Some(expr) = &agg.expression {
533 self.render_expr(expr, ctx)?;
534 } else {
535 ctx.write("*");
536 }
537 if let Some(args) = &agg.args {
538 for arg in args {
539 ctx.comma();
540 self.render_expr(arg, ctx)?;
541 }
542 }
543 if let Some(order_by) = &agg.order_by {
544 ctx.keyword("ORDER BY");
545 self.sqlite_order_by_list(order_by, ctx)?;
546 }
547 ctx.paren_close();
548 if let Some(filter) = &agg.filter {
549 ctx.keyword("FILTER").paren_open().keyword("WHERE");
550 self.render_condition(filter, ctx)?;
551 ctx.paren_close();
552 }
553 Ok(())
554 }
555
556 fn render_window(&self, win: &WindowDef, ctx: &mut RenderCtx) -> RenderResult<()> {
557 self.render_expr(&win.expression, ctx)?;
558 ctx.keyword("OVER").paren_open();
559 if let Some(partition_by) = &win.partition_by {
560 ctx.keyword("PARTITION BY");
561 for (i, expr) in partition_by.iter().enumerate() {
562 if i > 0 {
563 ctx.comma();
564 }
565 self.render_expr(expr, ctx)?;
566 }
567 }
568 if let Some(order_by) = &win.order_by {
569 ctx.keyword("ORDER BY");
570 self.sqlite_order_by_list(order_by, ctx)?;
571 }
572 ctx.paren_close();
573 Ok(())
574 }
575
576 fn render_case(&self, case: &CaseDef, ctx: &mut RenderCtx) -> RenderResult<()> {
577 ctx.keyword("CASE");
578 for clause in &case.cases {
579 ctx.keyword("WHEN");
580 self.render_condition(&clause.condition, ctx)?;
581 ctx.keyword("THEN");
582 self.render_expr(&clause.result, ctx)?;
583 }
584 if let Some(default) = &case.default {
585 ctx.keyword("ELSE");
586 self.render_expr(default, ctx)?;
587 }
588 ctx.keyword("END");
589 Ok(())
590 }
591
592 fn render_condition(&self, cond: &Conditions, ctx: &mut RenderCtx) -> RenderResult<()> {
595 if cond.negated {
596 ctx.keyword("NOT").paren_open();
597 }
598 let connector = match cond.connector {
599 Connector::And => " AND ",
600 Connector::Or => " OR ",
601 };
602 for (i, child) in cond.children.iter().enumerate() {
603 if i > 0 {
604 ctx.write(connector);
605 }
606 match child {
607 ConditionNode::Comparison(comp) => {
608 if comp.negate {
609 ctx.keyword("NOT").paren_open();
610 }
611 self.render_compare_op(&comp.op, &comp.left, &comp.right, ctx)?;
612 if comp.negate {
613 ctx.paren_close();
614 }
615 }
616 ConditionNode::Group(group) => {
617 ctx.paren_open();
618 self.render_condition(group, ctx)?;
619 ctx.paren_close();
620 }
621 ConditionNode::Exists(query) => {
622 ctx.keyword("EXISTS").paren_open();
623 self.render_query(query, ctx)?;
624 ctx.paren_close();
625 }
626 ConditionNode::Custom(_) => {
627 return Err(RenderError::unsupported(
628 "CustomCondition",
629 "custom condition must be handled by a wrapping renderer",
630 ));
631 }
632 }
633 }
634 if cond.negated {
635 ctx.paren_close();
636 }
637 Ok(())
638 }
639
640 fn render_compare_op(
641 &self,
642 op: &CompareOp,
643 left: &Expr,
644 right: &Expr,
645 ctx: &mut RenderCtx,
646 ) -> RenderResult<()> {
647 self.render_expr(left, ctx)?;
648 match op {
649 CompareOp::Eq => ctx.write(" = "),
650 CompareOp::Neq => ctx.write(" <> "),
651 CompareOp::Gt => ctx.write(" > "),
652 CompareOp::Gte => ctx.write(" >= "),
653 CompareOp::Lt => ctx.write(" < "),
654 CompareOp::Lte => ctx.write(" <= "),
655 CompareOp::Like => ctx.keyword("LIKE"),
656 CompareOp::In => ctx.keyword("IN"),
657 CompareOp::Between => {
658 ctx.keyword("BETWEEN");
659 self.render_expr(right, ctx)?;
660 return Ok(());
661 }
662 CompareOp::IsNull => {
663 ctx.keyword("IS NULL");
664 return Ok(());
665 }
666 CompareOp::Regex => ctx.keyword("REGEXP"),
667 CompareOp::ILike | CompareOp::Similar | CompareOp::IRegex => {
669 return Err(RenderError::unsupported(
670 "CompareOp",
671 "SQLite does not support ILIKE, SIMILAR TO, or case-insensitive regex.",
672 ));
673 }
674 CompareOp::JsonbContains
675 | CompareOp::JsonbContainedBy
676 | CompareOp::JsonbHasKey
677 | CompareOp::JsonbHasAnyKey
678 | CompareOp::JsonbHasAllKeys
679 | CompareOp::FtsMatch
680 | CompareOp::TrigramSimilar
681 | CompareOp::TrigramWordSimilar
682 | CompareOp::TrigramStrictWordSimilar
683 | CompareOp::RangeContains
684 | CompareOp::RangeContainedBy
685 | CompareOp::RangeOverlap => {
686 return Err(RenderError::unsupported(
687 "CompareOp",
688 "SQLite does not support PostgreSQL-specific operators (JSONB, FTS, trigram, range).",
689 ));
690 }
691 CompareOp::Custom(_) => {
692 return Err(RenderError::unsupported(
693 "CustomCompareOp",
694 "custom compare op must be handled by a wrapping renderer",
695 ));
696 }
697 };
698 self.render_expr(right, ctx)
699 }
700
701 fn render_query(&self, stmt: &QueryStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
704 if let Some(ctes) = &stmt.ctes {
706 self.render_ctes(ctes, ctx)?;
707 }
708
709 ctx.keyword("SELECT");
711
712 if let Some(distinct) = &stmt.distinct {
714 match distinct {
715 DistinctDef::Distinct => {
716 ctx.keyword("DISTINCT");
717 }
718 DistinctDef::DistinctOn(_) => {
719 return Err(RenderError::unsupported(
720 "DISTINCT ON",
721 "not supported in SQLite",
722 ));
723 }
724 }
725 }
726
727 self.render_select_columns(&stmt.columns, ctx)?;
729
730 if let Some(from) = &stmt.from {
732 ctx.keyword("FROM");
733 for (i, item) in from.iter().enumerate() {
734 if i > 0 {
735 ctx.comma();
736 }
737 self.sqlite_render_from_item(item, ctx)?;
738 }
739 }
740
741 if let Some(joins) = &stmt.joins {
743 self.render_joins(joins, ctx)?;
744 }
745
746 if let Some(cond) = &stmt.where_clause {
748 self.render_where(cond, ctx)?;
749 }
750
751 if let Some(group_by) = &stmt.group_by {
753 self.sqlite_render_group_by(group_by, ctx)?;
754 }
755
756 if let Some(having) = &stmt.having {
758 ctx.keyword("HAVING");
759 self.render_condition(having, ctx)?;
760 }
761
762 if let Some(windows) = &stmt.window {
764 self.sqlite_render_window_clause(windows, ctx)?;
765 }
766
767 if let Some(order_by) = &stmt.order_by {
769 self.render_order_by(order_by, ctx)?;
770 }
771
772 if let Some(limit) = &stmt.limit {
774 self.render_limit(limit, ctx)?;
775 }
776
777 if let Some(locks) = &stmt.lock {
779 if !locks.is_empty() {
780 return Err(RenderError::unsupported(
781 "FOR UPDATE/SHARE",
782 "row locking not supported in SQLite",
783 ));
784 }
785 }
786
787 Ok(())
788 }
789
790 fn render_select_columns(
791 &self,
792 cols: &[SelectColumn],
793 ctx: &mut RenderCtx,
794 ) -> RenderResult<()> {
795 for (i, col) in cols.iter().enumerate() {
796 if i > 0 {
797 ctx.comma();
798 }
799 match col {
800 SelectColumn::Star(None) => {
801 ctx.keyword("*");
802 }
803 SelectColumn::Star(Some(table)) => {
804 ctx.ident(table).operator(".").keyword("*");
805 }
806 SelectColumn::Expr { expr, alias } => {
807 self.render_expr(expr, ctx)?;
808 if let Some(a) = alias {
809 ctx.keyword("AS").ident(a);
810 }
811 }
812 SelectColumn::Field { field, alias } => {
813 self.sqlite_field_ref(field, ctx);
814 if let Some(a) = alias {
815 ctx.keyword("AS").ident(a);
816 }
817 }
818 }
819 }
820 Ok(())
821 }
822 fn render_from(&self, source: &TableSource, ctx: &mut RenderCtx) -> RenderResult<()> {
823 match source {
824 TableSource::Table(schema_ref) => {
825 self.sqlite_schema_ref(schema_ref, ctx);
826 if let Some(alias) = &schema_ref.alias {
827 ctx.keyword("AS").ident(alias);
828 }
829 }
830 TableSource::SubQuery(sq) => {
831 ctx.paren_open();
832 self.render_query(&sq.query, ctx)?;
833 ctx.paren_close().keyword("AS").ident(&sq.alias);
834 }
835 TableSource::SetOp(set_op) => {
836 ctx.paren_open();
837 self.sqlite_render_set_op(set_op, ctx)?;
838 ctx.paren_close();
839 }
840 TableSource::Lateral(_) => {
841 return Err(RenderError::unsupported(
842 "LATERAL",
843 "LATERAL subqueries not supported in SQLite",
844 ));
845 }
846 TableSource::Function { name, args, alias } => {
847 ctx.keyword(name).write("(");
848 for (i, arg) in args.iter().enumerate() {
849 if i > 0 {
850 ctx.comma();
851 }
852 self.render_expr(arg, ctx)?;
853 }
854 ctx.paren_close();
855 if let Some(a) = alias {
856 ctx.keyword("AS").ident(a);
857 }
858 }
859 TableSource::Values {
860 rows,
861 alias,
862 column_aliases,
863 } => {
864 ctx.paren_open().keyword("VALUES");
865 for (i, row) in rows.iter().enumerate() {
866 if i > 0 {
867 ctx.comma();
868 }
869 ctx.paren_open();
870 for (j, val) in row.iter().enumerate() {
871 if j > 0 {
872 ctx.comma();
873 }
874 self.render_expr(val, ctx)?;
875 }
876 ctx.paren_close();
877 }
878 ctx.paren_close().keyword("AS").ident(alias);
879 if let Some(cols) = column_aliases {
880 ctx.paren_open();
881 for (i, c) in cols.iter().enumerate() {
882 if i > 0 {
883 ctx.comma();
884 }
885 ctx.ident(c);
886 }
887 ctx.paren_close();
888 }
889 }
890 TableSource::Custom(_) => {
891 return Err(RenderError::unsupported(
892 "CustomTableSource",
893 "custom table source must be handled by a wrapping renderer",
894 ));
895 }
896 }
897 Ok(())
898 }
899 fn render_joins(&self, joins: &[JoinDef], ctx: &mut RenderCtx) -> RenderResult<()> {
900 for join in joins {
901 if join.natural {
902 ctx.keyword("NATURAL");
903 }
904 ctx.keyword(match join.join_type {
905 JoinType::Inner => "INNER JOIN",
906 JoinType::Left => "LEFT JOIN",
907 JoinType::Right => "RIGHT JOIN",
908 JoinType::Full => "FULL JOIN",
909 JoinType::Cross => "CROSS JOIN",
910 JoinType::CrossApply | JoinType::OuterApply => {
911 return Err(RenderError::unsupported(
912 "APPLY",
913 "CROSS/OUTER APPLY not supported in SQLite",
914 ));
915 }
916 });
917 self.sqlite_render_from_item(&join.source, ctx)?;
918 if let Some(condition) = &join.condition {
919 match condition {
920 JoinCondition::On(cond) => {
921 ctx.keyword("ON");
922 self.render_condition(cond, ctx)?;
923 }
924 JoinCondition::Using(cols) => {
925 ctx.keyword("USING").paren_open();
926 self.sqlite_comma_idents(cols, ctx);
927 ctx.paren_close();
928 }
929 }
930 }
931 }
932 Ok(())
933 }
934 fn render_where(&self, cond: &Conditions, ctx: &mut RenderCtx) -> RenderResult<()> {
935 ctx.keyword("WHERE");
936 self.render_condition(cond, ctx)
937 }
938 fn render_order_by(&self, order: &[OrderByDef], ctx: &mut RenderCtx) -> RenderResult<()> {
939 ctx.keyword("ORDER BY");
940 self.sqlite_order_by_list(order, ctx)
941 }
942 fn render_limit(&self, limit: &LimitDef, ctx: &mut RenderCtx) -> RenderResult<()> {
943 match &limit.kind {
944 LimitKind::Limit(n) => {
945 ctx.keyword("LIMIT").space().write(&n.to_string());
946 }
947 LimitKind::FetchFirst {
948 count, with_ties, ..
949 } => {
950 if *with_ties {
951 return Err(RenderError::unsupported(
952 "FETCH FIRST WITH TIES",
953 "not supported in SQLite",
954 ));
955 }
956 ctx.keyword("LIMIT").space().write(&count.to_string());
958 }
959 LimitKind::Top {
960 count, with_ties, ..
961 } => {
962 if *with_ties {
963 return Err(RenderError::unsupported(
964 "TOP WITH TIES",
965 "not supported in SQLite",
966 ));
967 }
968 ctx.keyword("LIMIT").space().write(&count.to_string());
970 }
971 }
972 if let Some(offset) = limit.offset {
973 ctx.keyword("OFFSET").space().write(&offset.to_string());
974 }
975 Ok(())
976 }
977 fn render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
978 let any_recursive = ctes.iter().any(|c| c.recursive);
979 ctx.keyword("WITH");
980 if any_recursive {
981 ctx.keyword("RECURSIVE");
982 }
983 for (i, cte) in ctes.iter().enumerate() {
984 if i > 0 {
985 ctx.comma();
986 }
987 ctx.ident(&cte.name);
988 if let Some(col_names) = &cte.column_names {
989 ctx.paren_open();
990 self.sqlite_comma_idents(col_names, ctx);
991 ctx.paren_close();
992 }
993 ctx.keyword("AS").paren_open();
995 self.render_query(&cte.query, ctx)?;
996 ctx.paren_close();
997 }
998 Ok(())
999 }
1000 fn render_lock(&self, _lock: &SelectLockDef, _ctx: &mut RenderCtx) -> RenderResult<()> {
1001 Err(RenderError::unsupported(
1002 "FOR UPDATE/SHARE",
1003 "row locking not supported in SQLite",
1004 ))
1005 }
1006
1007 fn render_mutation(&self, stmt: &MutationStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1010 match stmt {
1011 MutationStmt::Insert(s) => self.render_insert(s, ctx),
1012 MutationStmt::Update(s) => self.render_update(s, ctx),
1013 MutationStmt::Delete(s) => self.render_delete(s, ctx),
1014 MutationStmt::Custom(_) => Err(RenderError::unsupported(
1015 "CustomMutation",
1016 "custom DML must be handled by a wrapping renderer",
1017 )),
1018 }
1019 }
1020
1021 fn render_insert(&self, stmt: &InsertStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1022 if let Some(ctes) = &stmt.ctes {
1024 self.sqlite_render_ctes(ctes, ctx)?;
1025 }
1026
1027 if let Some(cr) = &stmt.conflict_resolution {
1029 ctx.keyword("INSERT OR");
1030 ctx.keyword(match cr {
1031 ConflictResolution::Rollback => "ROLLBACK",
1032 ConflictResolution::Abort => "ABORT",
1033 ConflictResolution::Fail => "FAIL",
1034 ConflictResolution::Ignore => "IGNORE",
1035 ConflictResolution::Replace => "REPLACE",
1036 });
1037 ctx.keyword("INTO");
1038 } else {
1039 ctx.keyword("INSERT INTO");
1040 }
1041
1042 self.sqlite_schema_ref(&stmt.table, ctx);
1043
1044 if let Some(alias) = &stmt.table.alias {
1046 ctx.keyword("AS").ident(alias);
1047 }
1048
1049 if let Some(cols) = &stmt.columns {
1051 ctx.paren_open();
1052 self.sqlite_comma_idents(cols, ctx);
1053 ctx.paren_close();
1054 }
1055
1056 match &stmt.source {
1058 InsertSource::Values(rows) => {
1059 ctx.keyword("VALUES");
1060 for (i, row) in rows.iter().enumerate() {
1061 if i > 0 {
1062 ctx.comma();
1063 }
1064 ctx.paren_open();
1065 for (j, expr) in row.iter().enumerate() {
1066 if j > 0 {
1067 ctx.comma();
1068 }
1069 self.render_expr(expr, ctx)?;
1070 }
1071 ctx.paren_close();
1072 }
1073 }
1074 InsertSource::Select(query) => {
1075 self.render_query(query, ctx)?;
1076 }
1077 InsertSource::DefaultValues => {
1078 ctx.keyword("DEFAULT VALUES");
1079 }
1080 }
1081
1082 if let Some(conflicts) = &stmt.on_conflict {
1084 for oc in conflicts {
1085 self.render_on_conflict(oc, ctx)?;
1086 }
1087 }
1088
1089 if let Some(returning) = &stmt.returning {
1091 self.render_returning(returning, ctx)?;
1092 }
1093
1094 Ok(())
1095 }
1096
1097 fn render_update(&self, stmt: &UpdateStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1098 if let Some(ctes) = &stmt.ctes {
1100 self.sqlite_render_ctes(ctes, ctx)?;
1101 }
1102
1103 if let Some(cr) = &stmt.conflict_resolution {
1105 ctx.keyword("UPDATE OR");
1106 ctx.keyword(match cr {
1107 ConflictResolution::Rollback => "ROLLBACK",
1108 ConflictResolution::Abort => "ABORT",
1109 ConflictResolution::Fail => "FAIL",
1110 ConflictResolution::Ignore => "IGNORE",
1111 ConflictResolution::Replace => "REPLACE",
1112 });
1113 } else {
1114 ctx.keyword("UPDATE");
1115 }
1116
1117 self.sqlite_schema_ref(&stmt.table, ctx);
1118
1119 if let Some(alias) = &stmt.table.alias {
1121 ctx.keyword("AS").ident(alias);
1122 }
1123
1124 ctx.keyword("SET");
1126 for (i, (col, expr)) in stmt.assignments.iter().enumerate() {
1127 if i > 0 {
1128 ctx.comma();
1129 }
1130 ctx.ident(col).write(" = ");
1131 self.render_expr(expr, ctx)?;
1132 }
1133
1134 if let Some(from) = &stmt.from {
1136 ctx.keyword("FROM");
1137 for (i, source) in from.iter().enumerate() {
1138 if i > 0 {
1139 ctx.comma();
1140 }
1141 self.render_from(source, ctx)?;
1142 }
1143 }
1144
1145 if let Some(cond) = &stmt.where_clause {
1147 ctx.keyword("WHERE");
1148 self.render_condition(cond, ctx)?;
1149 }
1150
1151 if let Some(returning) = &stmt.returning {
1153 self.render_returning(returning, ctx)?;
1154 }
1155
1156 if let Some(order_by) = &stmt.order_by {
1158 ctx.keyword("ORDER BY");
1159 self.sqlite_order_by_list(order_by, ctx)?;
1160 }
1161
1162 if let Some(limit) = stmt.limit {
1164 ctx.keyword("LIMIT").keyword(&limit.to_string());
1165 if let Some(offset) = stmt.offset {
1166 ctx.keyword("OFFSET").keyword(&offset.to_string());
1167 }
1168 }
1169
1170 Ok(())
1171 }
1172
1173 fn render_delete(&self, stmt: &DeleteStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1174 if let Some(ctes) = &stmt.ctes {
1176 self.sqlite_render_ctes(ctes, ctx)?;
1177 }
1178
1179 ctx.keyword("DELETE FROM");
1180
1181 self.sqlite_schema_ref(&stmt.table, ctx);
1182
1183 if let Some(alias) = &stmt.table.alias {
1185 ctx.keyword("AS").ident(alias);
1186 }
1187
1188 if let Some(cond) = &stmt.where_clause {
1193 ctx.keyword("WHERE");
1194 self.render_condition(cond, ctx)?;
1195 }
1196
1197 if let Some(returning) = &stmt.returning {
1199 self.render_returning(returning, ctx)?;
1200 }
1201
1202 if let Some(order_by) = &stmt.order_by {
1204 ctx.keyword("ORDER BY");
1205 self.sqlite_order_by_list(order_by, ctx)?;
1206 }
1207
1208 if let Some(limit) = stmt.limit {
1210 ctx.keyword("LIMIT").keyword(&limit.to_string());
1211 if let Some(offset) = stmt.offset {
1212 ctx.keyword("OFFSET").keyword(&offset.to_string());
1213 }
1214 }
1215
1216 Ok(())
1217 }
1218
1219 fn render_on_conflict(&self, oc: &OnConflictDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1220 ctx.keyword("ON CONFLICT");
1221
1222 if let Some(target) = &oc.target {
1224 match target {
1225 ConflictTarget::Columns {
1226 columns,
1227 where_clause,
1228 } => {
1229 ctx.paren_open();
1230 self.sqlite_comma_idents(columns, ctx);
1231 ctx.paren_close();
1232 if let Some(cond) = where_clause {
1233 ctx.keyword("WHERE");
1234 self.render_condition(cond, ctx)?;
1235 }
1236 }
1237 ConflictTarget::Constraint(_) => {
1238 return Err(RenderError::unsupported(
1239 "OnConstraint",
1240 "SQLite does not support ON CONFLICT ON CONSTRAINT. Use column list instead.",
1241 ));
1242 }
1243 }
1244 }
1245
1246 match &oc.action {
1248 ConflictAction::DoNothing => {
1249 ctx.keyword("DO NOTHING");
1250 }
1251 ConflictAction::DoUpdate {
1252 assignments,
1253 where_clause,
1254 } => {
1255 ctx.keyword("DO UPDATE SET");
1256 for (i, (col, expr)) in assignments.iter().enumerate() {
1257 if i > 0 {
1258 ctx.comma();
1259 }
1260 ctx.ident(col).write(" = ");
1261 self.render_expr(expr, ctx)?;
1262 }
1263 if let Some(cond) = where_clause {
1264 ctx.keyword("WHERE");
1265 self.render_condition(cond, ctx)?;
1266 }
1267 }
1268 }
1269
1270 Ok(())
1271 }
1272
1273 fn render_returning(&self, cols: &[SelectColumn], ctx: &mut RenderCtx) -> RenderResult<()> {
1274 ctx.keyword("RETURNING");
1275 for (i, col) in cols.iter().enumerate() {
1276 if i > 0 {
1277 ctx.comma();
1278 }
1279 match col {
1280 SelectColumn::Star(None) => {
1281 ctx.keyword("*");
1282 }
1283 SelectColumn::Star(Some(table)) => {
1284 ctx.ident(table).operator(".").keyword("*");
1285 }
1286 SelectColumn::Expr { expr, alias } => {
1287 self.render_expr(expr, ctx)?;
1288 if let Some(a) = alias {
1289 ctx.keyword("AS").ident(a);
1290 }
1291 }
1292 SelectColumn::Field { field, alias } => {
1293 self.sqlite_field_ref(field, ctx);
1294 if let Some(a) = alias {
1295 ctx.keyword("AS").ident(a);
1296 }
1297 }
1298 }
1299 }
1300 Ok(())
1301 }
1302
1303 fn render_transaction(&self, stmt: &TransactionStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1306 match stmt {
1307 TransactionStmt::Begin(s) => {
1308 ctx.keyword("BEGIN");
1309 if let Some(lock_type) = &s.lock_type {
1310 ctx.keyword(match lock_type {
1311 SqliteLockType::Deferred => "DEFERRED",
1312 SqliteLockType::Immediate => "IMMEDIATE",
1313 SqliteLockType::Exclusive => "EXCLUSIVE",
1314 });
1315 }
1316 ctx.keyword("TRANSACTION");
1317 Ok(())
1318 }
1319 TransactionStmt::Commit(_) => {
1320 ctx.keyword("COMMIT");
1321 Ok(())
1322 }
1323 TransactionStmt::Rollback(s) => {
1324 ctx.keyword("ROLLBACK");
1325 if let Some(sp) = &s.to_savepoint {
1326 ctx.keyword("TO").keyword("SAVEPOINT").ident(sp);
1327 }
1328 Ok(())
1329 }
1330 TransactionStmt::Savepoint(s) => {
1331 ctx.keyword("SAVEPOINT").ident(&s.name);
1332 Ok(())
1333 }
1334 TransactionStmt::ReleaseSavepoint(s) => {
1335 ctx.keyword("RELEASE").keyword("SAVEPOINT").ident(&s.name);
1336 Ok(())
1337 }
1338 TransactionStmt::SetTransaction(_) => Err(RenderError::unsupported(
1339 "SET TRANSACTION",
1340 "not supported in SQLite",
1341 )),
1342 TransactionStmt::LockTable(_) => Err(RenderError::unsupported(
1343 "LOCK TABLE",
1344 "not supported in SQLite (use BEGIN EXCLUSIVE)",
1345 )),
1346 TransactionStmt::PrepareTransaction(_) => Err(RenderError::unsupported(
1347 "PREPARE TRANSACTION",
1348 "not supported in SQLite",
1349 )),
1350 TransactionStmt::CommitPrepared(_) => Err(RenderError::unsupported(
1351 "COMMIT PREPARED",
1352 "not supported in SQLite",
1353 )),
1354 TransactionStmt::RollbackPrepared(_) => Err(RenderError::unsupported(
1355 "ROLLBACK PREPARED",
1356 "not supported in SQLite",
1357 )),
1358 TransactionStmt::Custom(_) => Err(RenderError::unsupported(
1359 "Custom TCL",
1360 "not supported by SqliteRenderer",
1361 )),
1362 }
1363 }
1364}
1365
1366impl SqliteRenderer {
1371 fn sqlite_schema_ref(
1372 &self,
1373 schema_ref: &qcraft_core::ast::common::SchemaRef,
1374 ctx: &mut RenderCtx,
1375 ) {
1376 if let Some(ns) = &schema_ref.namespace {
1377 ctx.ident(ns).operator(".");
1378 }
1379 ctx.ident(&schema_ref.name);
1380 }
1381
1382 fn sqlite_field_ref(&self, field_ref: &FieldRef, ctx: &mut RenderCtx) {
1383 ctx.ident(&field_ref.table_name)
1384 .operator(".")
1385 .ident(&field_ref.field.name);
1386 }
1387
1388 fn sqlite_comma_idents(&self, names: &[String], ctx: &mut RenderCtx) {
1389 for (i, name) in names.iter().enumerate() {
1390 if i > 0 {
1391 ctx.comma();
1392 }
1393 ctx.ident(name);
1394 }
1395 }
1396
1397 fn sqlite_value(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
1398 if matches!(val, Value::Null) {
1400 ctx.keyword("NULL");
1401 return Ok(());
1402 }
1403
1404 match val {
1406 Value::Array(_) => {
1407 return Err(RenderError::unsupported(
1408 "ArrayValue",
1409 "SQLite does not support array literals.",
1410 ));
1411 }
1412 Value::Vector(_) => {
1413 return Err(RenderError::unsupported(
1414 "VectorValue",
1415 "SQLite does not support vector type.",
1416 ));
1417 }
1418 Value::TimeDelta { .. } => {
1419 return Err(RenderError::unsupported(
1420 "TimeDeltaValue",
1421 "SQLite does not support INTERVAL type. Use string expressions with datetime functions.",
1422 ));
1423 }
1424 _ => {}
1425 }
1426
1427 if ctx.parameterize() {
1429 ctx.param(val.clone());
1430 return Ok(());
1431 }
1432
1433 self.sqlite_value_literal(val, ctx)
1435 }
1436
1437 fn sqlite_value_literal(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
1438 match val {
1439 Value::Null => {
1440 ctx.keyword("NULL");
1441 }
1442 Value::Bool(b) => {
1443 ctx.keyword(if *b { "1" } else { "0" });
1444 }
1445 Value::Int(n) => {
1446 ctx.keyword(&n.to_string());
1447 }
1448 Value::Float(f) => {
1449 ctx.keyword(&f.to_string());
1450 }
1451 Value::Str(s) => {
1452 ctx.string_literal(s);
1453 }
1454 Value::Bytes(b) => {
1455 ctx.write("X'");
1456 for byte in b {
1457 ctx.write(&format!("{byte:02x}"));
1458 }
1459 ctx.write("'");
1460 }
1461 Value::Date(s) | Value::DateTime(s) | Value::Time(s) => {
1462 ctx.string_literal(s);
1463 }
1464 Value::Decimal(s) => {
1465 ctx.keyword(s);
1466 }
1467 Value::Uuid(s) => {
1468 ctx.string_literal(s);
1469 }
1470 Value::Json(s) | Value::Jsonb(s) => {
1471 ctx.string_literal(s);
1472 }
1473 Value::IpNetwork(s) => {
1474 ctx.string_literal(s);
1475 }
1476 _ => {
1477 unreachable!()
1479 }
1480 }
1481 Ok(())
1482 }
1483
1484 fn sqlite_referential_action(
1485 &self,
1486 action: &ReferentialAction,
1487 ctx: &mut RenderCtx,
1488 ) -> RenderResult<()> {
1489 match action {
1490 ReferentialAction::NoAction => {
1491 ctx.keyword("NO ACTION");
1492 }
1493 ReferentialAction::Restrict => {
1494 ctx.keyword("RESTRICT");
1495 }
1496 ReferentialAction::Cascade => {
1497 ctx.keyword("CASCADE");
1498 }
1499 ReferentialAction::SetNull(cols) => {
1500 ctx.keyword("SET NULL");
1501 if cols.is_some() {
1502 return Err(RenderError::unsupported(
1503 "SetNullColumns",
1504 "SQLite does not support SET NULL with column list.",
1505 ));
1506 }
1507 }
1508 ReferentialAction::SetDefault(cols) => {
1509 ctx.keyword("SET DEFAULT");
1510 if cols.is_some() {
1511 return Err(RenderError::unsupported(
1512 "SetDefaultColumns",
1513 "SQLite does not support SET DEFAULT with column list.",
1514 ));
1515 }
1516 }
1517 }
1518 Ok(())
1519 }
1520
1521 fn sqlite_deferrable(&self, def: &DeferrableConstraint, ctx: &mut RenderCtx) {
1522 if def.deferrable {
1523 ctx.keyword("DEFERRABLE");
1524 } else {
1525 ctx.keyword("NOT DEFERRABLE");
1526 }
1527 if def.initially_deferred {
1528 ctx.keyword("INITIALLY DEFERRED");
1529 } else {
1530 ctx.keyword("INITIALLY IMMEDIATE");
1531 }
1532 }
1533
1534 fn sqlite_render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1535 self.render_ctes(ctes, ctx)
1536 }
1537
1538 fn sqlite_render_from_item(&self, item: &FromItem, ctx: &mut RenderCtx) -> RenderResult<()> {
1539 self.render_from(&item.source, ctx)?;
1541 if let Some(hint) = &item.index_hint {
1543 match hint {
1544 SqliteIndexHint::IndexedBy(name) => {
1545 ctx.keyword("INDEXED BY").ident(name);
1546 }
1547 SqliteIndexHint::NotIndexed => {
1548 ctx.keyword("NOT INDEXED");
1549 }
1550 }
1551 }
1552 if item.sample.is_some() {
1554 return Err(RenderError::unsupported(
1555 "TABLESAMPLE",
1556 "not supported in SQLite",
1557 ));
1558 }
1559 Ok(())
1560 }
1561
1562 fn sqlite_render_group_by(
1563 &self,
1564 items: &[GroupByItem],
1565 ctx: &mut RenderCtx,
1566 ) -> RenderResult<()> {
1567 ctx.keyword("GROUP BY");
1568 for (i, item) in items.iter().enumerate() {
1569 if i > 0 {
1570 ctx.comma();
1571 }
1572 match item {
1573 GroupByItem::Expr(expr) => {
1574 self.render_expr(expr, ctx)?;
1575 }
1576 GroupByItem::Rollup(_) => {
1577 return Err(RenderError::unsupported(
1578 "ROLLUP",
1579 "not supported in SQLite",
1580 ));
1581 }
1582 GroupByItem::Cube(_) => {
1583 return Err(RenderError::unsupported("CUBE", "not supported in SQLite"));
1584 }
1585 GroupByItem::GroupingSets(_) => {
1586 return Err(RenderError::unsupported(
1587 "GROUPING SETS",
1588 "not supported in SQLite",
1589 ));
1590 }
1591 }
1592 }
1593 Ok(())
1594 }
1595
1596 fn sqlite_render_window_clause(
1597 &self,
1598 windows: &[WindowNameDef],
1599 ctx: &mut RenderCtx,
1600 ) -> RenderResult<()> {
1601 ctx.keyword("WINDOW");
1602 for (i, win) in windows.iter().enumerate() {
1603 if i > 0 {
1604 ctx.comma();
1605 }
1606 ctx.ident(&win.name).keyword("AS").paren_open();
1607 if let Some(base) = &win.base_window {
1608 ctx.ident(base);
1609 }
1610 if let Some(partition_by) = &win.partition_by {
1611 ctx.keyword("PARTITION BY");
1612 for (j, expr) in partition_by.iter().enumerate() {
1613 if j > 0 {
1614 ctx.comma();
1615 }
1616 self.render_expr(expr, ctx)?;
1617 }
1618 }
1619 if let Some(order_by) = &win.order_by {
1620 ctx.keyword("ORDER BY");
1621 self.sqlite_order_by_list(order_by, ctx)?;
1622 }
1623 if let Some(frame) = &win.frame {
1624 self.sqlite_window_frame(frame, ctx);
1625 }
1626 ctx.paren_close();
1627 }
1628 Ok(())
1629 }
1630
1631 fn sqlite_window_frame(&self, frame: &WindowFrameDef, ctx: &mut RenderCtx) {
1632 ctx.keyword(match frame.frame_type {
1633 WindowFrameType::Rows => "ROWS",
1634 WindowFrameType::Range => "RANGE",
1635 WindowFrameType::Groups => "GROUPS",
1636 });
1637 if let Some(end) = &frame.end {
1638 ctx.keyword("BETWEEN");
1639 self.sqlite_frame_bound(&frame.start, ctx);
1640 ctx.keyword("AND");
1641 self.sqlite_frame_bound(end, ctx);
1642 } else {
1643 self.sqlite_frame_bound(&frame.start, ctx);
1644 }
1645 }
1646
1647 fn sqlite_frame_bound(&self, bound: &WindowFrameBound, ctx: &mut RenderCtx) {
1648 match bound {
1649 WindowFrameBound::CurrentRow => {
1650 ctx.keyword("CURRENT ROW");
1651 }
1652 WindowFrameBound::Preceding(None) => {
1653 ctx.keyword("UNBOUNDED PRECEDING");
1654 }
1655 WindowFrameBound::Preceding(Some(n)) => {
1656 ctx.keyword(&n.to_string()).keyword("PRECEDING");
1657 }
1658 WindowFrameBound::Following(None) => {
1659 ctx.keyword("UNBOUNDED FOLLOWING");
1660 }
1661 WindowFrameBound::Following(Some(n)) => {
1662 ctx.keyword(&n.to_string()).keyword("FOLLOWING");
1663 }
1664 }
1665 }
1666
1667 fn sqlite_render_set_op(&self, set_op: &SetOpDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1668 self.render_query(&set_op.left, ctx)?;
1669 ctx.keyword(match set_op.operation {
1670 SetOperationType::Union => "UNION",
1671 SetOperationType::UnionAll => "UNION ALL",
1672 SetOperationType::Intersect => "INTERSECT",
1673 SetOperationType::Except => "EXCEPT",
1674 SetOperationType::IntersectAll => {
1675 return Err(RenderError::unsupported(
1676 "INTERSECT ALL",
1677 "not supported in SQLite",
1678 ));
1679 }
1680 SetOperationType::ExceptAll => {
1681 return Err(RenderError::unsupported(
1682 "EXCEPT ALL",
1683 "not supported in SQLite",
1684 ));
1685 }
1686 });
1687 self.render_query(&set_op.right, ctx)
1688 }
1689
1690 fn sqlite_create_table(
1691 &self,
1692 schema: &SchemaDef,
1693 if_not_exists: bool,
1694 temporary: bool,
1695 without_rowid: bool,
1696 strict: bool,
1697 ctx: &mut RenderCtx,
1698 ) -> RenderResult<()> {
1699 ctx.keyword("CREATE");
1700 if temporary {
1701 ctx.keyword("TEMP");
1702 }
1703 ctx.keyword("TABLE");
1704 if if_not_exists {
1705 ctx.keyword("IF NOT EXISTS");
1706 }
1707 if let Some(ns) = &schema.namespace {
1708 ctx.ident(ns).operator(".");
1709 }
1710 ctx.ident(&schema.name);
1711
1712 let autoincrement_pk_col = schema.constraints.as_ref().and_then(|cs| {
1714 cs.iter().find_map(|c| {
1715 if let ConstraintDef::PrimaryKey {
1716 columns,
1717 autoincrement: true,
1718 ..
1719 } = c
1720 {
1721 if columns.len() == 1 {
1722 return Some(columns[0].as_str());
1723 }
1724 }
1725 None
1726 })
1727 });
1728
1729 ctx.paren_open();
1730 let mut first = true;
1731 for col in &schema.columns {
1732 if !first {
1733 ctx.comma();
1734 }
1735 first = false;
1736 self.render_column_def(col, ctx)?;
1737 if autoincrement_pk_col == Some(col.name.as_str()) {
1739 ctx.keyword("PRIMARY KEY AUTOINCREMENT");
1740 }
1741 }
1742 if let Some(constraints) = &schema.constraints {
1743 for constraint in constraints {
1744 if let ConstraintDef::PrimaryKey {
1746 autoincrement: true,
1747 columns,
1748 ..
1749 } = constraint
1750 {
1751 if columns.len() == 1 {
1752 continue;
1753 }
1754 }
1755 if !first {
1756 ctx.comma();
1757 }
1758 first = false;
1759 self.render_constraint(constraint, ctx)?;
1760 }
1761 }
1762 ctx.paren_close();
1763
1764 let mut modifiers = Vec::new();
1766 if without_rowid {
1767 modifiers.push("WITHOUT ROWID");
1768 }
1769 if strict {
1770 modifiers.push("STRICT");
1771 }
1772 if !modifiers.is_empty() {
1773 for (i, m) in modifiers.iter().enumerate() {
1774 if i > 0 {
1775 ctx.comma();
1776 }
1777 ctx.keyword(m);
1778 }
1779 }
1780
1781 Ok(())
1782 }
1783
1784 fn sqlite_create_index(
1785 &self,
1786 schema_ref: &qcraft_core::ast::common::SchemaRef,
1787 index: &IndexDef,
1788 if_not_exists: bool,
1789 ctx: &mut RenderCtx,
1790 ) -> RenderResult<()> {
1791 ctx.keyword("CREATE");
1792 if index.unique {
1793 ctx.keyword("UNIQUE");
1794 }
1795 ctx.keyword("INDEX");
1796 if if_not_exists {
1797 ctx.keyword("IF NOT EXISTS");
1798 }
1799 ctx.ident(&index.name).keyword("ON");
1800 self.sqlite_schema_ref(schema_ref, ctx);
1801
1802 ctx.paren_open();
1805 self.sqlite_index_columns(&index.columns, ctx)?;
1806 ctx.paren_close();
1807
1808 if let Some(condition) = &index.condition {
1811 ctx.keyword("WHERE");
1812 self.render_condition(condition, ctx)?;
1813 }
1814
1815 Ok(())
1816 }
1817
1818 fn sqlite_index_columns(
1819 &self,
1820 columns: &[IndexColumnDef],
1821 ctx: &mut RenderCtx,
1822 ) -> RenderResult<()> {
1823 for (i, col) in columns.iter().enumerate() {
1824 if i > 0 {
1825 ctx.comma();
1826 }
1827 match &col.expr {
1828 IndexExpr::Column(name) => {
1829 ctx.ident(name);
1830 }
1831 IndexExpr::Expression(expr) => {
1832 ctx.paren_open();
1833 self.render_expr(expr, ctx)?;
1834 ctx.paren_close();
1835 }
1836 }
1837 if let Some(collation) = &col.collation {
1838 ctx.keyword("COLLATE").ident(collation);
1839 }
1840 if let Some(dir) = col.direction {
1842 ctx.keyword(match dir {
1843 OrderDir::Asc => "ASC",
1844 OrderDir::Desc => "DESC",
1845 });
1846 }
1847 }
1849 Ok(())
1850 }
1851
1852 fn sqlite_order_by_list(
1853 &self,
1854 order_by: &[OrderByDef],
1855 ctx: &mut RenderCtx,
1856 ) -> RenderResult<()> {
1857 for (i, ob) in order_by.iter().enumerate() {
1858 if i > 0 {
1859 ctx.comma();
1860 }
1861 self.render_expr(&ob.expr, ctx)?;
1862 ctx.keyword(match ob.direction {
1863 OrderDir::Asc => "ASC",
1864 OrderDir::Desc => "DESC",
1865 });
1866 if let Some(nulls) = &ob.nulls {
1867 ctx.keyword(match nulls {
1868 NullsOrder::First => "NULLS FIRST",
1869 NullsOrder::Last => "NULLS LAST",
1870 });
1871 }
1872 }
1873 Ok(())
1874 }
1875}