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