Skip to main content

qcraft_sqlite/
lib.rs

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
70// ==========================================================================
71// Renderer trait implementation
72// ==========================================================================
73
74impl Renderer for SqliteRenderer {
75    // ── DDL ──────────────────────────────────────────────────────────────
76
77    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: _,  // SQLite doesn't support PARTITION BY
90                inherits: _,      // SQLite doesn't support INHERITS
91                using_method: _,  // SQLite doesn't support USING method
92                with_options: _,  // SQLite doesn't support WITH options
93                on_commit: _,     // SQLite doesn't support ON COMMIT
94                table_options: _, // SQLite doesn't support generic table options
95                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: _, // SQLite doesn't support CASCADE — Ignore
110            } => {
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: _, // SQLite doesn't have RESTART IDENTITY
132                cascade: _,          // SQLite doesn't support CASCADE
133            } => {
134                // SQLite has no TRUNCATE — use DELETE FROM (equivalent semantics)
135                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: _, // SQLite ADD COLUMN doesn't support IF NOT EXISTS
144                position: _,      // SQLite doesn't support FIRST/AFTER
145            } => {
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: _, // SQLite DROP COLUMN doesn't support IF EXISTS
156                cascade: _,   // SQLite doesn't support CASCADE
157            } => {
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            // SQLite does NOT support these ALTER operations — Error
179            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            // ── Index operations ──
209            SchemaMutationStmt::CreateIndex {
210                schema_ref,
211                index,
212                if_not_exists,
213                concurrently: _, // SQLite doesn't support CONCURRENTLY — Ignore
214            } => 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: _, // Ignore
221                cascade: _,      // Ignore
222            } => {
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            // SQLite doesn't have extensions
232            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        // SQLite doesn't support IDENTITY — use INTEGER PRIMARY KEY AUTOINCREMENT instead
266        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: _, // SQLite doesn't support INCLUDE — Ignore
330                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: _, // SQLite accepts MATCH but it's a no-op — Ignore
352            } => {
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: _,        // Ignore
380                nulls_distinct: _, // Ignore
381                condition: _,      // Ignore
382            } => {
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: _, // Ignore
395                enforced: _,   // Ignore
396            } => {
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    // ── Expressions (basic, needed for DDL) ──────────────────────────────
431
432    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    // ── Conditions ───────────────────────────────────────────────────────
593
594    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            // SQLite doesn't natively support these — Error
668            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    // ── Query (stub) ─────────────────────────────────────────────────────
702
703    fn render_query(&self, stmt: &QueryStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
704        // CTEs
705        if let Some(ctes) = &stmt.ctes {
706            self.render_ctes(ctes, ctx)?;
707        }
708
709        // SELECT
710        ctx.keyword("SELECT");
711
712        // DISTINCT
713        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        // Columns
728        self.render_select_columns(&stmt.columns, ctx)?;
729
730        // FROM
731        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        // JOINs
742        if let Some(joins) = &stmt.joins {
743            self.render_joins(joins, ctx)?;
744        }
745
746        // WHERE
747        if let Some(cond) = &stmt.where_clause {
748            self.render_where(cond, ctx)?;
749        }
750
751        // GROUP BY
752        if let Some(group_by) = &stmt.group_by {
753            self.sqlite_render_group_by(group_by, ctx)?;
754        }
755
756        // HAVING
757        if let Some(having) = &stmt.having {
758            ctx.keyword("HAVING");
759            self.render_condition(having, ctx)?;
760        }
761
762        // WINDOW
763        if let Some(windows) = &stmt.window {
764            self.sqlite_render_window_clause(windows, ctx)?;
765        }
766
767        // ORDER BY
768        if let Some(order_by) = &stmt.order_by {
769            self.render_order_by(order_by, ctx)?;
770        }
771
772        // LIMIT / OFFSET
773        if let Some(limit) = &stmt.limit {
774            self.render_limit(limit, ctx)?;
775        }
776
777        // FOR UPDATE — not supported in SQLite
778        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                // Convert FETCH FIRST to LIMIT
957                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                // Convert TOP to LIMIT
969                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            // SQLite ignores MATERIALIZED hints
994            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    // ── DML ──────────────────────────────────────────────────────────────
1008
1009    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        // CTEs
1023        if let Some(ctes) = &stmt.ctes {
1024            self.sqlite_render_ctes(ctes, ctx)?;
1025        }
1026
1027        // INSERT OR REPLACE / OR IGNORE / etc.
1028        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        // Alias
1045        if let Some(alias) = &stmt.table.alias {
1046            ctx.keyword("AS").ident(alias);
1047        }
1048
1049        // Column list
1050        if let Some(cols) = &stmt.columns {
1051            ctx.paren_open();
1052            self.sqlite_comma_idents(cols, ctx);
1053            ctx.paren_close();
1054        }
1055
1056        // Source
1057        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        // ON CONFLICT
1083        if let Some(conflicts) = &stmt.on_conflict {
1084            for oc in conflicts {
1085                self.render_on_conflict(oc, ctx)?;
1086            }
1087        }
1088
1089        // RETURNING
1090        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        // CTEs
1099        if let Some(ctes) = &stmt.ctes {
1100            self.sqlite_render_ctes(ctes, ctx)?;
1101        }
1102
1103        // UPDATE OR REPLACE / OR IGNORE / etc.
1104        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        // Alias
1120        if let Some(alias) = &stmt.table.alias {
1121            ctx.keyword("AS").ident(alias);
1122        }
1123
1124        // SET
1125        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        // FROM (SQLite 3.33+)
1135        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        // WHERE
1146        if let Some(cond) = &stmt.where_clause {
1147            ctx.keyword("WHERE");
1148            self.render_condition(cond, ctx)?;
1149        }
1150
1151        // RETURNING
1152        if let Some(returning) = &stmt.returning {
1153            self.render_returning(returning, ctx)?;
1154        }
1155
1156        // ORDER BY
1157        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        // LIMIT / OFFSET
1163        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        // CTEs
1175        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        // Alias
1184        if let Some(alias) = &stmt.table.alias {
1185            ctx.keyword("AS").ident(alias);
1186        }
1187
1188        // SQLite doesn't support USING — ignore
1189        // (SQLite has no JOIN syntax in DELETE; use subqueries in WHERE)
1190
1191        // WHERE
1192        if let Some(cond) = &stmt.where_clause {
1193            ctx.keyword("WHERE");
1194            self.render_condition(cond, ctx)?;
1195        }
1196
1197        // RETURNING
1198        if let Some(returning) = &stmt.returning {
1199            self.render_returning(returning, ctx)?;
1200        }
1201
1202        // ORDER BY
1203        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        // LIMIT / OFFSET
1209        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        // Target
1223        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        // Action
1247        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    // ── TCL ──────────────────────────────────────────────────────────────
1304
1305    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
1366// ==========================================================================
1367// SQLite-specific helpers
1368// ==========================================================================
1369
1370impl 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        // NULL is always rendered as keyword, never as parameter.
1399        if matches!(val, Value::Null) {
1400            ctx.keyword("NULL");
1401            return Ok(());
1402        }
1403
1404        // Unsupported types always error, regardless of parameterize mode.
1405        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        // In parameterized mode, send values as bind parameters.
1428        if ctx.parameterize() {
1429            ctx.param(val.clone());
1430            return Ok(());
1431        }
1432
1433        // Inline literal mode (DDL defaults, etc.)
1434        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                // Array, Vector, TimeDelta — already caught in sqlite_value
1478                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        // SQLite ignores ONLY (PG-specific)
1540        self.render_from(&item.source, ctx)?;
1541        // Index hints
1542        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        // TABLESAMPLE
1553        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        // Detect AUTOINCREMENT PK — must be rendered inline on the column in SQLite
1713        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            // Inline PRIMARY KEY AUTOINCREMENT on the column
1738            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                // Skip the AUTOINCREMENT PK — already rendered inline
1745                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        // SQLite table modifiers
1765        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        // SQLite doesn't support USING method — Ignore index_type
1803
1804        ctx.paren_open();
1805        self.sqlite_index_columns(&index.columns, ctx)?;
1806        ctx.paren_close();
1807
1808        // SQLite doesn't support INCLUDE, NULLS DISTINCT, WITH params, TABLESPACE — Ignore
1809
1810        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            // SQLite doesn't support operator classes — Ignore opclass
1841            if let Some(dir) = col.direction {
1842                ctx.keyword(match dir {
1843                    OrderDir::Asc => "ASC",
1844                    OrderDir::Desc => "DESC",
1845                });
1846            }
1847            // SQLite doesn't support NULLS FIRST/LAST — Ignore
1848        }
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}