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        // SELECT
928        ctx.keyword("SELECT");
929
930        // DISTINCT
931        if let Some(distinct) = &stmt.distinct {
932            match distinct {
933                DistinctDef::Distinct => {
934                    ctx.keyword("DISTINCT");
935                }
936                DistinctDef::DistinctOn(_) => {
937                    return Err(RenderError::unsupported(
938                        "DISTINCT ON",
939                        "not supported in SQLite",
940                    ));
941                }
942            }
943        }
944
945        // Columns
946        self.render_select_columns(&stmt.columns, ctx)?;
947
948        // FROM
949        if let Some(from) = &stmt.from {
950            ctx.keyword("FROM");
951            for (i, item) in from.iter().enumerate() {
952                if i > 0 {
953                    ctx.comma();
954                }
955                self.sqlite_render_from_item(item, ctx)?;
956            }
957        }
958
959        // JOINs
960        if let Some(joins) = &stmt.joins {
961            self.render_joins(joins, ctx)?;
962        }
963
964        // WHERE
965        if let Some(cond) = &stmt.where_clause {
966            self.render_where(cond, ctx)?;
967        }
968
969        // GROUP BY
970        if let Some(group_by) = &stmt.group_by {
971            self.sqlite_render_group_by(group_by, ctx)?;
972        }
973
974        // HAVING
975        if let Some(having) = &stmt.having {
976            ctx.keyword("HAVING");
977            self.render_condition(having, ctx)?;
978        }
979
980        // WINDOW
981        if let Some(windows) = &stmt.window {
982            self.sqlite_render_window_clause(windows, ctx)?;
983        }
984
985        // ORDER BY
986        if let Some(order_by) = &stmt.order_by {
987            self.render_order_by(order_by, ctx)?;
988        }
989
990        // LIMIT / OFFSET
991        if let Some(limit) = &stmt.limit {
992            self.render_limit(limit, ctx)?;
993        }
994
995        // FOR UPDATE — not supported in SQLite
996        if let Some(locks) = &stmt.lock {
997            if !locks.is_empty() {
998                return Err(RenderError::unsupported(
999                    "FOR UPDATE/SHARE",
1000                    "row locking not supported in SQLite",
1001                ));
1002            }
1003        }
1004
1005        Ok(())
1006    }
1007
1008    fn render_select_columns(
1009        &self,
1010        cols: &[SelectColumn],
1011        ctx: &mut RenderCtx,
1012    ) -> RenderResult<()> {
1013        for (i, col) in cols.iter().enumerate() {
1014            if i > 0 {
1015                ctx.comma();
1016            }
1017            match col {
1018                SelectColumn::Star(None) => {
1019                    ctx.keyword("*");
1020                }
1021                SelectColumn::Star(Some(table)) => {
1022                    ctx.ident(table).operator(".").keyword("*");
1023                }
1024                SelectColumn::Expr { expr, alias } => {
1025                    self.render_expr(expr, ctx)?;
1026                    if let Some(a) = alias {
1027                        ctx.keyword("AS").ident(a);
1028                    }
1029                }
1030                SelectColumn::Field { field, alias } => {
1031                    self.sqlite_field_ref(field, ctx);
1032                    if let Some(a) = alias {
1033                        ctx.keyword("AS").ident(a);
1034                    }
1035                }
1036            }
1037        }
1038        Ok(())
1039    }
1040    fn render_from(&self, source: &TableSource, ctx: &mut RenderCtx) -> RenderResult<()> {
1041        match source {
1042            TableSource::Table(schema_ref) => {
1043                self.sqlite_schema_ref(schema_ref, ctx);
1044                if let Some(alias) = &schema_ref.alias {
1045                    ctx.keyword("AS").ident(alias);
1046                }
1047            }
1048            TableSource::SubQuery(sq) => {
1049                ctx.paren_open();
1050                self.render_query(&sq.query, ctx)?;
1051                ctx.paren_close().keyword("AS").ident(&sq.alias);
1052            }
1053            TableSource::SetOp(set_op) => {
1054                ctx.paren_open();
1055                self.sqlite_render_set_op(set_op, ctx)?;
1056                ctx.paren_close();
1057            }
1058            TableSource::Lateral(_) => {
1059                return Err(RenderError::unsupported(
1060                    "LATERAL",
1061                    "LATERAL subqueries not supported in SQLite",
1062                ));
1063            }
1064            TableSource::Function { name, args, alias } => {
1065                ctx.keyword(name).write("(");
1066                for (i, arg) in args.iter().enumerate() {
1067                    if i > 0 {
1068                        ctx.comma();
1069                    }
1070                    self.render_expr(arg, ctx)?;
1071                }
1072                ctx.paren_close();
1073                if let Some(a) = alias {
1074                    ctx.keyword("AS").ident(a);
1075                }
1076            }
1077            TableSource::Values {
1078                rows,
1079                alias,
1080                column_aliases,
1081            } => {
1082                ctx.paren_open().keyword("VALUES");
1083                for (i, row) in rows.iter().enumerate() {
1084                    if i > 0 {
1085                        ctx.comma();
1086                    }
1087                    ctx.paren_open();
1088                    for (j, val) in row.iter().enumerate() {
1089                        if j > 0 {
1090                            ctx.comma();
1091                        }
1092                        self.render_expr(val, ctx)?;
1093                    }
1094                    ctx.paren_close();
1095                }
1096                ctx.paren_close().keyword("AS").ident(alias);
1097                if let Some(cols) = column_aliases {
1098                    ctx.paren_open();
1099                    for (i, c) in cols.iter().enumerate() {
1100                        if i > 0 {
1101                            ctx.comma();
1102                        }
1103                        ctx.ident(c);
1104                    }
1105                    ctx.paren_close();
1106                }
1107            }
1108            TableSource::Custom(_) => {
1109                return Err(RenderError::unsupported(
1110                    "CustomTableSource",
1111                    "custom table source must be handled by a wrapping renderer",
1112                ));
1113            }
1114        }
1115        Ok(())
1116    }
1117    fn render_joins(&self, joins: &[JoinDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1118        for join in joins {
1119            if join.natural {
1120                ctx.keyword("NATURAL");
1121            }
1122            ctx.keyword(match join.join_type {
1123                JoinType::Inner => "INNER JOIN",
1124                JoinType::Left => "LEFT JOIN",
1125                JoinType::Right => "RIGHT JOIN",
1126                JoinType::Full => "FULL JOIN",
1127                JoinType::Cross => "CROSS JOIN",
1128                JoinType::CrossApply | JoinType::OuterApply => {
1129                    return Err(RenderError::unsupported(
1130                        "APPLY",
1131                        "CROSS/OUTER APPLY not supported in SQLite",
1132                    ));
1133                }
1134            });
1135            self.sqlite_render_from_item(&join.source, ctx)?;
1136            if !matches!(join.join_type, JoinType::Cross) {
1137                if let Some(condition) = &join.condition {
1138                    match condition {
1139                        JoinCondition::On(cond) => {
1140                            ctx.keyword("ON");
1141                            self.render_condition(cond, ctx)?;
1142                        }
1143                        JoinCondition::Using(cols) => {
1144                            ctx.keyword("USING").paren_open();
1145                            self.sqlite_comma_idents(cols, ctx);
1146                            ctx.paren_close();
1147                        }
1148                    }
1149                }
1150            }
1151        }
1152        Ok(())
1153    }
1154    fn render_where(&self, cond: &Conditions, ctx: &mut RenderCtx) -> RenderResult<()> {
1155        ctx.keyword("WHERE");
1156        self.render_condition(cond, ctx)
1157    }
1158    fn render_order_by(&self, order: &[OrderByDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1159        ctx.keyword("ORDER BY");
1160        self.sqlite_order_by_list(order, ctx)
1161    }
1162    fn render_limit(&self, limit: &LimitDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1163        match &limit.kind {
1164            LimitKind::Limit(n) => {
1165                ctx.keyword("LIMIT");
1166                if ctx.parameterize() {
1167                    ctx.param(Value::BigInt(*n as i64));
1168                } else {
1169                    ctx.space().write(&n.to_string());
1170                }
1171            }
1172            LimitKind::FetchFirst {
1173                count, with_ties, ..
1174            } => {
1175                if *with_ties {
1176                    return Err(RenderError::unsupported(
1177                        "FETCH FIRST WITH TIES",
1178                        "not supported in SQLite",
1179                    ));
1180                }
1181                // Convert FETCH FIRST to LIMIT
1182                ctx.keyword("LIMIT");
1183                if ctx.parameterize() {
1184                    ctx.param(Value::BigInt(*count as i64));
1185                } else {
1186                    ctx.space().write(&count.to_string());
1187                }
1188            }
1189            LimitKind::Top {
1190                count, with_ties, ..
1191            } => {
1192                if *with_ties {
1193                    return Err(RenderError::unsupported(
1194                        "TOP WITH TIES",
1195                        "not supported in SQLite",
1196                    ));
1197                }
1198                // Convert TOP to LIMIT
1199                ctx.keyword("LIMIT");
1200                if ctx.parameterize() {
1201                    ctx.param(Value::BigInt(*count as i64));
1202                } else {
1203                    ctx.space().write(&count.to_string());
1204                }
1205            }
1206        }
1207        if let Some(offset) = limit.offset {
1208            ctx.keyword("OFFSET");
1209            if ctx.parameterize() {
1210                ctx.param(Value::BigInt(offset as i64));
1211            } else {
1212                ctx.space().write(&offset.to_string());
1213            }
1214        }
1215        Ok(())
1216    }
1217    fn render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1218        let any_recursive = ctes.iter().any(|c| c.recursive);
1219        ctx.keyword("WITH");
1220        if any_recursive {
1221            ctx.keyword("RECURSIVE");
1222        }
1223        for (i, cte) in ctes.iter().enumerate() {
1224            if i > 0 {
1225                ctx.comma();
1226            }
1227            ctx.ident(&cte.name);
1228            if let Some(col_names) = &cte.column_names {
1229                ctx.paren_open();
1230                self.sqlite_comma_idents(col_names, ctx);
1231                ctx.paren_close();
1232            }
1233            // SQLite ignores MATERIALIZED hints
1234            ctx.keyword("AS").paren_open();
1235            self.render_query(&cte.query, ctx)?;
1236            ctx.paren_close();
1237        }
1238        Ok(())
1239    }
1240    fn render_lock(&self, _lock: &SelectLockDef, _ctx: &mut RenderCtx) -> RenderResult<()> {
1241        Err(RenderError::unsupported(
1242            "FOR UPDATE/SHARE",
1243            "row locking not supported in SQLite",
1244        ))
1245    }
1246
1247    // ── DML ──────────────────────────────────────────────────────────────
1248
1249    fn render_mutation(&self, stmt: &MutationStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1250        match stmt {
1251            MutationStmt::Insert(s) => self.render_insert(s, ctx),
1252            MutationStmt::Update(s) => self.render_update(s, ctx),
1253            MutationStmt::Delete(s) => self.render_delete(s, ctx),
1254            MutationStmt::Custom(_) => Err(RenderError::unsupported(
1255                "CustomMutation",
1256                "custom DML must be handled by a wrapping renderer",
1257            )),
1258        }
1259    }
1260
1261    fn render_insert(&self, stmt: &InsertStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1262        // CTEs
1263        if let Some(ctes) = &stmt.ctes {
1264            self.sqlite_render_ctes(ctes, ctx)?;
1265        }
1266
1267        // INSERT OR REPLACE / OR IGNORE / etc.
1268        if let Some(cr) = &stmt.conflict_resolution {
1269            ctx.keyword("INSERT OR");
1270            ctx.keyword(match cr {
1271                ConflictResolution::Rollback => "ROLLBACK",
1272                ConflictResolution::Abort => "ABORT",
1273                ConflictResolution::Fail => "FAIL",
1274                ConflictResolution::Ignore => "IGNORE",
1275                ConflictResolution::Replace => "REPLACE",
1276            });
1277            ctx.keyword("INTO");
1278        } else {
1279            ctx.keyword("INSERT INTO");
1280        }
1281
1282        self.sqlite_schema_ref(&stmt.table, ctx);
1283
1284        // Alias
1285        if let Some(alias) = &stmt.table.alias {
1286            ctx.keyword("AS").ident(alias);
1287        }
1288
1289        // Column list
1290        if let Some(cols) = &stmt.columns {
1291            ctx.paren_open();
1292            self.sqlite_comma_idents(cols, ctx);
1293            ctx.paren_close();
1294        }
1295
1296        // Source
1297        match &stmt.source {
1298            InsertSource::Values(rows) => {
1299                ctx.keyword("VALUES");
1300                for (i, row) in rows.iter().enumerate() {
1301                    if i > 0 {
1302                        ctx.comma();
1303                    }
1304                    ctx.paren_open();
1305                    for (j, expr) in row.iter().enumerate() {
1306                        if j > 0 {
1307                            ctx.comma();
1308                        }
1309                        self.render_expr(expr, ctx)?;
1310                    }
1311                    ctx.paren_close();
1312                }
1313            }
1314            InsertSource::Select(query) => {
1315                self.render_query(query, ctx)?;
1316            }
1317            InsertSource::DefaultValues => {
1318                ctx.keyword("DEFAULT VALUES");
1319            }
1320        }
1321
1322        // ON CONFLICT
1323        if let Some(conflicts) = &stmt.on_conflict {
1324            for oc in conflicts {
1325                self.render_on_conflict(oc, ctx)?;
1326            }
1327        }
1328
1329        // RETURNING
1330        if let Some(returning) = &stmt.returning {
1331            self.render_returning(returning, ctx)?;
1332        }
1333
1334        Ok(())
1335    }
1336
1337    fn render_update(&self, stmt: &UpdateStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1338        // CTEs
1339        if let Some(ctes) = &stmt.ctes {
1340            self.sqlite_render_ctes(ctes, ctx)?;
1341        }
1342
1343        // UPDATE OR REPLACE / OR IGNORE / etc.
1344        if let Some(cr) = &stmt.conflict_resolution {
1345            ctx.keyword("UPDATE OR");
1346            ctx.keyword(match cr {
1347                ConflictResolution::Rollback => "ROLLBACK",
1348                ConflictResolution::Abort => "ABORT",
1349                ConflictResolution::Fail => "FAIL",
1350                ConflictResolution::Ignore => "IGNORE",
1351                ConflictResolution::Replace => "REPLACE",
1352            });
1353        } else {
1354            ctx.keyword("UPDATE");
1355        }
1356
1357        self.sqlite_schema_ref(&stmt.table, ctx);
1358
1359        // Alias
1360        if let Some(alias) = &stmt.table.alias {
1361            ctx.keyword("AS").ident(alias);
1362        }
1363
1364        // SET
1365        ctx.keyword("SET");
1366        for (i, (col, expr)) in stmt.assignments.iter().enumerate() {
1367            if i > 0 {
1368                ctx.comma();
1369            }
1370            ctx.ident(col).write(" = ");
1371            self.render_expr(expr, ctx)?;
1372        }
1373
1374        // FROM (SQLite 3.33+)
1375        if let Some(from) = &stmt.from {
1376            ctx.keyword("FROM");
1377            for (i, source) in from.iter().enumerate() {
1378                if i > 0 {
1379                    ctx.comma();
1380                }
1381                self.render_from(source, ctx)?;
1382            }
1383        }
1384
1385        // WHERE
1386        if let Some(cond) = &stmt.where_clause {
1387            ctx.keyword("WHERE");
1388            self.render_condition(cond, ctx)?;
1389        }
1390
1391        // RETURNING
1392        if let Some(returning) = &stmt.returning {
1393            self.render_returning(returning, ctx)?;
1394        }
1395
1396        // ORDER BY
1397        if let Some(order_by) = &stmt.order_by {
1398            ctx.keyword("ORDER BY");
1399            self.sqlite_order_by_list(order_by, ctx)?;
1400        }
1401
1402        // LIMIT / OFFSET
1403        if let Some(limit) = stmt.limit {
1404            ctx.keyword("LIMIT").keyword(&limit.to_string());
1405            if let Some(offset) = stmt.offset {
1406                ctx.keyword("OFFSET").keyword(&offset.to_string());
1407            }
1408        }
1409
1410        Ok(())
1411    }
1412
1413    fn render_delete(&self, stmt: &DeleteStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1414        // CTEs
1415        if let Some(ctes) = &stmt.ctes {
1416            self.sqlite_render_ctes(ctes, ctx)?;
1417        }
1418
1419        ctx.keyword("DELETE FROM");
1420
1421        self.sqlite_schema_ref(&stmt.table, ctx);
1422
1423        // Alias
1424        if let Some(alias) = &stmt.table.alias {
1425            ctx.keyword("AS").ident(alias);
1426        }
1427
1428        // SQLite doesn't support USING — ignore
1429        // (SQLite has no JOIN syntax in DELETE; use subqueries in WHERE)
1430
1431        // WHERE
1432        if let Some(cond) = &stmt.where_clause {
1433            ctx.keyword("WHERE");
1434            self.render_condition(cond, ctx)?;
1435        }
1436
1437        // RETURNING
1438        if let Some(returning) = &stmt.returning {
1439            self.render_returning(returning, ctx)?;
1440        }
1441
1442        // ORDER BY
1443        if let Some(order_by) = &stmt.order_by {
1444            ctx.keyword("ORDER BY");
1445            self.sqlite_order_by_list(order_by, ctx)?;
1446        }
1447
1448        // LIMIT / OFFSET
1449        if let Some(limit) = stmt.limit {
1450            ctx.keyword("LIMIT").keyword(&limit.to_string());
1451            if let Some(offset) = stmt.offset {
1452                ctx.keyword("OFFSET").keyword(&offset.to_string());
1453            }
1454        }
1455
1456        Ok(())
1457    }
1458
1459    fn render_on_conflict(&self, oc: &OnConflictDef, ctx: &mut RenderCtx) -> RenderResult<()> {
1460        ctx.keyword("ON CONFLICT");
1461
1462        // Target
1463        if let Some(target) = &oc.target {
1464            match target {
1465                ConflictTarget::Columns {
1466                    columns,
1467                    where_clause,
1468                } => {
1469                    ctx.paren_open();
1470                    self.sqlite_comma_idents(columns, ctx);
1471                    ctx.paren_close();
1472                    if let Some(cond) = where_clause {
1473                        ctx.keyword("WHERE");
1474                        self.render_condition(cond, ctx)?;
1475                    }
1476                }
1477                ConflictTarget::Constraint(_) => {
1478                    return Err(RenderError::unsupported(
1479                        "OnConstraint",
1480                        "SQLite does not support ON CONFLICT ON CONSTRAINT. Use column list instead.",
1481                    ));
1482                }
1483            }
1484        }
1485
1486        // Action
1487        match &oc.action {
1488            ConflictAction::DoNothing => {
1489                ctx.keyword("DO NOTHING");
1490            }
1491            ConflictAction::DoUpdate {
1492                assignments,
1493                where_clause,
1494            } => {
1495                ctx.keyword("DO UPDATE SET");
1496                for (i, (col, expr)) in assignments.iter().enumerate() {
1497                    if i > 0 {
1498                        ctx.comma();
1499                    }
1500                    ctx.ident(col).write(" = ");
1501                    self.render_expr(expr, ctx)?;
1502                }
1503                if let Some(cond) = where_clause {
1504                    ctx.keyword("WHERE");
1505                    self.render_condition(cond, ctx)?;
1506                }
1507            }
1508        }
1509
1510        Ok(())
1511    }
1512
1513    fn render_returning(&self, cols: &[SelectColumn], ctx: &mut RenderCtx) -> RenderResult<()> {
1514        ctx.keyword("RETURNING");
1515        for (i, col) in cols.iter().enumerate() {
1516            if i > 0 {
1517                ctx.comma();
1518            }
1519            match col {
1520                SelectColumn::Star(None) => {
1521                    ctx.keyword("*");
1522                }
1523                SelectColumn::Star(Some(table)) => {
1524                    ctx.ident(table).operator(".").keyword("*");
1525                }
1526                SelectColumn::Expr { expr, alias } => {
1527                    self.render_expr(expr, ctx)?;
1528                    if let Some(a) = alias {
1529                        ctx.keyword("AS").ident(a);
1530                    }
1531                }
1532                SelectColumn::Field { field, alias } => {
1533                    self.sqlite_field_ref(field, ctx);
1534                    if let Some(a) = alias {
1535                        ctx.keyword("AS").ident(a);
1536                    }
1537                }
1538            }
1539        }
1540        Ok(())
1541    }
1542
1543    // ── TCL ──────────────────────────────────────────────────────────────
1544
1545    fn render_transaction(&self, stmt: &TransactionStmt, ctx: &mut RenderCtx) -> RenderResult<()> {
1546        match stmt {
1547            TransactionStmt::Begin(s) => {
1548                ctx.keyword("BEGIN");
1549                if let Some(lock_type) = &s.lock_type {
1550                    ctx.keyword(match lock_type {
1551                        SqliteLockType::Deferred => "DEFERRED",
1552                        SqliteLockType::Immediate => "IMMEDIATE",
1553                        SqliteLockType::Exclusive => "EXCLUSIVE",
1554                    });
1555                }
1556                ctx.keyword("TRANSACTION");
1557                Ok(())
1558            }
1559            TransactionStmt::Commit(_) => {
1560                ctx.keyword("COMMIT");
1561                Ok(())
1562            }
1563            TransactionStmt::Rollback(s) => {
1564                ctx.keyword("ROLLBACK");
1565                if let Some(sp) = &s.to_savepoint {
1566                    ctx.keyword("TO").keyword("SAVEPOINT").ident(sp);
1567                }
1568                Ok(())
1569            }
1570            TransactionStmt::Savepoint(s) => {
1571                ctx.keyword("SAVEPOINT").ident(&s.name);
1572                Ok(())
1573            }
1574            TransactionStmt::ReleaseSavepoint(s) => {
1575                ctx.keyword("RELEASE").keyword("SAVEPOINT").ident(&s.name);
1576                Ok(())
1577            }
1578            TransactionStmt::SetTransaction(_) => Err(RenderError::unsupported(
1579                "SET TRANSACTION",
1580                "not supported in SQLite",
1581            )),
1582            TransactionStmt::LockTable(_) => Err(RenderError::unsupported(
1583                "LOCK TABLE",
1584                "not supported in SQLite (use BEGIN EXCLUSIVE)",
1585            )),
1586            TransactionStmt::PrepareTransaction(_) => Err(RenderError::unsupported(
1587                "PREPARE TRANSACTION",
1588                "not supported in SQLite",
1589            )),
1590            TransactionStmt::CommitPrepared(_) => Err(RenderError::unsupported(
1591                "COMMIT PREPARED",
1592                "not supported in SQLite",
1593            )),
1594            TransactionStmt::RollbackPrepared(_) => Err(RenderError::unsupported(
1595                "ROLLBACK PREPARED",
1596                "not supported in SQLite",
1597            )),
1598            TransactionStmt::Custom(_) => Err(RenderError::unsupported(
1599                "Custom TCL",
1600                "not supported by SqliteRenderer",
1601            )),
1602        }
1603    }
1604}
1605
1606// ==========================================================================
1607// SQLite-specific helpers
1608// ==========================================================================
1609
1610impl SqliteRenderer {
1611    fn sqlite_schema_ref(
1612        &self,
1613        schema_ref: &qcraft_core::ast::common::SchemaRef,
1614        ctx: &mut RenderCtx,
1615    ) {
1616        if let Some(ns) = &schema_ref.namespace {
1617            ctx.ident(ns).operator(".");
1618        }
1619        ctx.ident(&schema_ref.name);
1620    }
1621
1622    /// Render an expression with all FieldRef table names stripped.
1623    /// Used for SQLite generated column expressions which only allow
1624    /// unqualified column references.
1625    fn render_expr_unqualified(&self, expr: &Expr, ctx: &mut RenderCtx) -> RenderResult<()> {
1626        match expr {
1627            Expr::Field(field_ref) => {
1628                ctx.ident(&field_ref.field.name);
1629                let mut child = &field_ref.field.child;
1630                while let Some(c) = child {
1631                    ctx.operator("->'")
1632                        .write(&c.name.replace('\'', "''"))
1633                        .write("'");
1634                    child = &c.child;
1635                }
1636                Ok(())
1637            }
1638            // For any other expr, delegate to normal render_expr
1639            other => self.render_expr(other, ctx),
1640        }
1641    }
1642
1643    fn sqlite_field_ref(&self, field_ref: &FieldRef, ctx: &mut RenderCtx) {
1644        if let Some(ns) = &field_ref.namespace {
1645            ctx.ident(ns).operator(".");
1646        }
1647        if !field_ref.table_name.is_empty() {
1648            ctx.ident(&field_ref.table_name).operator(".");
1649        }
1650        ctx.ident(&field_ref.field.name);
1651        let mut child = &field_ref.field.child;
1652        while let Some(c) = child {
1653            ctx.operator("->'")
1654                .write(&c.name.replace('\'', "''"))
1655                .write("'");
1656            child = &c.child;
1657        }
1658    }
1659
1660    fn sqlite_comma_idents(&self, names: &[String], ctx: &mut RenderCtx) {
1661        for (i, name) in names.iter().enumerate() {
1662            if i > 0 {
1663                ctx.comma();
1664            }
1665            ctx.ident(name);
1666        }
1667    }
1668
1669    fn sqlite_value(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
1670        if matches!(val, Value::Null) && !ctx.parameterize() {
1671            ctx.keyword("NULL");
1672            return Ok(());
1673        }
1674
1675        // Convert Array to JSON string for SQLite (no native array type).
1676        if let Value::Array(items) = val {
1677            let json = Self::array_to_json(items);
1678            if ctx.parameterize() {
1679                ctx.param(Value::Str(json));
1680            } else {
1681                ctx.string_literal(&json);
1682            }
1683            return Ok(());
1684        }
1685
1686        // Unsupported types always error, regardless of parameterize mode.
1687        match val {
1688            Value::Vector(_) => {
1689                return Err(RenderError::unsupported(
1690                    "VectorValue",
1691                    "SQLite does not support vector type.",
1692                ));
1693            }
1694            Value::TimeDelta { .. } => {
1695                return Err(RenderError::unsupported(
1696                    "TimeDeltaValue",
1697                    "SQLite does not support INTERVAL type. Use string expressions with datetime functions.",
1698                ));
1699            }
1700            _ => {}
1701        }
1702
1703        // In parameterized mode, send values as bind parameters.
1704        if ctx.parameterize() {
1705            ctx.param(val.clone());
1706            return Ok(());
1707        }
1708
1709        // Inline literal mode (DDL defaults, etc.)
1710        self.sqlite_value_literal(val, ctx)
1711    }
1712
1713    fn sqlite_value_literal(&self, val: &Value, ctx: &mut RenderCtx) -> RenderResult<()> {
1714        match val {
1715            Value::Null => {
1716                ctx.keyword("NULL");
1717            }
1718            Value::Bool(b) => {
1719                ctx.keyword(if *b { "1" } else { "0" });
1720            }
1721            Value::Int(n) | Value::BigInt(n) => {
1722                ctx.keyword(&n.to_string());
1723            }
1724            Value::Float(f) => {
1725                ctx.keyword(&f.to_string());
1726            }
1727            Value::Str(s) => {
1728                ctx.string_literal(s);
1729            }
1730            Value::Bytes(b) => {
1731                ctx.write("X'");
1732                for byte in b {
1733                    ctx.write(&format!("{byte:02x}"));
1734                }
1735                ctx.write("'");
1736            }
1737            Value::Date(s) | Value::DateTime(s) | Value::Time(s) => {
1738                ctx.string_literal(s);
1739            }
1740            Value::Decimal(s) => {
1741                ctx.keyword(s);
1742            }
1743            Value::Uuid(s) => {
1744                ctx.string_literal(s);
1745            }
1746            Value::Json(s) | Value::Jsonb(s) => {
1747                ctx.string_literal(s);
1748            }
1749            Value::IpNetwork(s) => {
1750                ctx.string_literal(s);
1751            }
1752            _ => {
1753                // Vector, TimeDelta — already caught in sqlite_value
1754                unreachable!()
1755            }
1756        }
1757        Ok(())
1758    }
1759
1760    fn array_to_json(items: &[Value]) -> String {
1761        let mut s = String::from("[");
1762        for (i, item) in items.iter().enumerate() {
1763            if i > 0 {
1764                s.push_str(", ");
1765            }
1766            Self::value_to_json(item, &mut s);
1767        }
1768        s.push(']');
1769        s
1770    }
1771
1772    fn value_to_json(val: &Value, s: &mut String) {
1773        match val {
1774            Value::Null => s.push_str("null"),
1775            Value::Bool(b) => s.push_str(if *b { "true" } else { "false" }),
1776            Value::Int(n) | Value::BigInt(n) => s.push_str(&n.to_string()),
1777            Value::Float(f) => s.push_str(&f.to_string()),
1778            Value::Str(v) => {
1779                s.push('"');
1780                for ch in v.chars() {
1781                    match ch {
1782                        '"' => s.push_str("\\\""),
1783                        '\\' => s.push_str("\\\\"),
1784                        '\n' => s.push_str("\\n"),
1785                        '\r' => s.push_str("\\r"),
1786                        '\t' => s.push_str("\\t"),
1787                        c => s.push(c),
1788                    }
1789                }
1790                s.push('"');
1791            }
1792            Value::Array(items) => {
1793                s.push('[');
1794                for (i, item) in items.iter().enumerate() {
1795                    if i > 0 {
1796                        s.push_str(", ");
1797                    }
1798                    Self::value_to_json(item, s);
1799                }
1800                s.push(']');
1801            }
1802            // Date, DateTime, Time, Uuid, Json, Jsonb, etc. → string
1803            Value::Date(v)
1804            | Value::DateTime(v)
1805            | Value::Time(v)
1806            | Value::Uuid(v)
1807            | Value::Decimal(v)
1808            | Value::IpNetwork(v) => {
1809                s.push('"');
1810                s.push_str(v);
1811                s.push('"');
1812            }
1813            Value::Json(v) | Value::Jsonb(v) => {
1814                // Already JSON — embed directly
1815                s.push_str(v);
1816            }
1817            _ => s.push_str("null"),
1818        }
1819    }
1820
1821    fn sqlite_referential_action(
1822        &self,
1823        action: &ReferentialAction,
1824        ctx: &mut RenderCtx,
1825    ) -> RenderResult<()> {
1826        match action {
1827            ReferentialAction::NoAction => {
1828                ctx.keyword("NO ACTION");
1829            }
1830            ReferentialAction::Restrict => {
1831                ctx.keyword("RESTRICT");
1832            }
1833            ReferentialAction::Cascade => {
1834                ctx.keyword("CASCADE");
1835            }
1836            ReferentialAction::SetNull(cols) => {
1837                ctx.keyword("SET NULL");
1838                if cols.is_some() {
1839                    return Err(RenderError::unsupported(
1840                        "SetNullColumns",
1841                        "SQLite does not support SET NULL with column list.",
1842                    ));
1843                }
1844            }
1845            ReferentialAction::SetDefault(cols) => {
1846                ctx.keyword("SET DEFAULT");
1847                if cols.is_some() {
1848                    return Err(RenderError::unsupported(
1849                        "SetDefaultColumns",
1850                        "SQLite does not support SET DEFAULT with column list.",
1851                    ));
1852                }
1853            }
1854        }
1855        Ok(())
1856    }
1857
1858    fn sqlite_deferrable(&self, def: &DeferrableConstraint, ctx: &mut RenderCtx) {
1859        if def.deferrable {
1860            ctx.keyword("DEFERRABLE");
1861        } else {
1862            ctx.keyword("NOT DEFERRABLE");
1863        }
1864        if def.initially_deferred {
1865            ctx.keyword("INITIALLY DEFERRED");
1866        } else {
1867            ctx.keyword("INITIALLY IMMEDIATE");
1868        }
1869    }
1870
1871    fn sqlite_render_ctes(&self, ctes: &[CteDef], ctx: &mut RenderCtx) -> RenderResult<()> {
1872        self.render_ctes(ctes, ctx)
1873    }
1874
1875    fn sqlite_render_from_item(&self, item: &FromItem, ctx: &mut RenderCtx) -> RenderResult<()> {
1876        // SQLite ignores ONLY (PG-specific)
1877        self.render_from(&item.source, ctx)?;
1878        // Index hints
1879        if let Some(hint) = &item.index_hint {
1880            match hint {
1881                SqliteIndexHint::IndexedBy(name) => {
1882                    ctx.keyword("INDEXED BY").ident(name);
1883                }
1884                SqliteIndexHint::NotIndexed => {
1885                    ctx.keyword("NOT INDEXED");
1886                }
1887            }
1888        }
1889        // TABLESAMPLE
1890        if item.sample.is_some() {
1891            return Err(RenderError::unsupported(
1892                "TABLESAMPLE",
1893                "not supported in SQLite",
1894            ));
1895        }
1896        Ok(())
1897    }
1898
1899    fn sqlite_render_group_by(
1900        &self,
1901        items: &[GroupByItem],
1902        ctx: &mut RenderCtx,
1903    ) -> RenderResult<()> {
1904        ctx.keyword("GROUP BY");
1905        for (i, item) in items.iter().enumerate() {
1906            if i > 0 {
1907                ctx.comma();
1908            }
1909            match item {
1910                GroupByItem::Expr(expr) => {
1911                    self.render_expr(expr, ctx)?;
1912                }
1913                GroupByItem::Rollup(_) => {
1914                    return Err(RenderError::unsupported(
1915                        "ROLLUP",
1916                        "not supported in SQLite",
1917                    ));
1918                }
1919                GroupByItem::Cube(_) => {
1920                    return Err(RenderError::unsupported("CUBE", "not supported in SQLite"));
1921                }
1922                GroupByItem::GroupingSets(_) => {
1923                    return Err(RenderError::unsupported(
1924                        "GROUPING SETS",
1925                        "not supported in SQLite",
1926                    ));
1927                }
1928            }
1929        }
1930        Ok(())
1931    }
1932
1933    fn sqlite_render_window_clause(
1934        &self,
1935        windows: &[WindowNameDef],
1936        ctx: &mut RenderCtx,
1937    ) -> RenderResult<()> {
1938        ctx.keyword("WINDOW");
1939        for (i, win) in windows.iter().enumerate() {
1940            if i > 0 {
1941                ctx.comma();
1942            }
1943            ctx.ident(&win.name).keyword("AS").paren_open();
1944            if let Some(base) = &win.base_window {
1945                ctx.ident(base);
1946            }
1947            if let Some(partition_by) = &win.partition_by {
1948                ctx.keyword("PARTITION BY");
1949                for (j, expr) in partition_by.iter().enumerate() {
1950                    if j > 0 {
1951                        ctx.comma();
1952                    }
1953                    self.render_expr(expr, ctx)?;
1954                }
1955            }
1956            if let Some(order_by) = &win.order_by {
1957                ctx.keyword("ORDER BY");
1958                self.sqlite_order_by_list(order_by, ctx)?;
1959            }
1960            if let Some(frame) = &win.frame {
1961                self.sqlite_window_frame(frame, ctx);
1962            }
1963            ctx.paren_close();
1964        }
1965        Ok(())
1966    }
1967
1968    fn sqlite_window_frame(&self, frame: &WindowFrameDef, ctx: &mut RenderCtx) {
1969        ctx.keyword(match frame.frame_type {
1970            WindowFrameType::Rows => "ROWS",
1971            WindowFrameType::Range => "RANGE",
1972            WindowFrameType::Groups => "GROUPS",
1973        });
1974        if let Some(end) = &frame.end {
1975            ctx.keyword("BETWEEN");
1976            self.sqlite_frame_bound(&frame.start, ctx);
1977            ctx.keyword("AND");
1978            self.sqlite_frame_bound(end, ctx);
1979        } else {
1980            self.sqlite_frame_bound(&frame.start, ctx);
1981        }
1982    }
1983
1984    fn sqlite_frame_bound(&self, bound: &WindowFrameBound, ctx: &mut RenderCtx) {
1985        match bound {
1986            WindowFrameBound::CurrentRow => {
1987                ctx.keyword("CURRENT ROW");
1988            }
1989            WindowFrameBound::Preceding(None) => {
1990                ctx.keyword("UNBOUNDED PRECEDING");
1991            }
1992            WindowFrameBound::Preceding(Some(n)) => {
1993                ctx.keyword(&n.to_string()).keyword("PRECEDING");
1994            }
1995            WindowFrameBound::Following(None) => {
1996                ctx.keyword("UNBOUNDED FOLLOWING");
1997            }
1998            WindowFrameBound::Following(Some(n)) => {
1999                ctx.keyword(&n.to_string()).keyword("FOLLOWING");
2000            }
2001        }
2002    }
2003
2004    fn sqlite_render_set_op(&self, set_op: &SetOpDef, ctx: &mut RenderCtx) -> RenderResult<()> {
2005        self.render_query(&set_op.left, ctx)?;
2006        ctx.keyword(match set_op.operation {
2007            SetOperationType::Union => "UNION",
2008            SetOperationType::UnionAll => "UNION ALL",
2009            SetOperationType::Intersect => "INTERSECT",
2010            SetOperationType::Except => "EXCEPT",
2011            SetOperationType::IntersectAll => {
2012                return Err(RenderError::unsupported(
2013                    "INTERSECT ALL",
2014                    "not supported in SQLite",
2015                ));
2016            }
2017            SetOperationType::ExceptAll => {
2018                return Err(RenderError::unsupported(
2019                    "EXCEPT ALL",
2020                    "not supported in SQLite",
2021                ));
2022            }
2023        });
2024        self.render_query(&set_op.right, ctx)
2025    }
2026
2027    fn sqlite_create_table(
2028        &self,
2029        schema: &SchemaDef,
2030        if_not_exists: bool,
2031        temporary: bool,
2032        without_rowid: bool,
2033        strict: bool,
2034        ctx: &mut RenderCtx,
2035    ) -> RenderResult<()> {
2036        ctx.keyword("CREATE");
2037        if temporary {
2038            ctx.keyword("TEMP");
2039        }
2040        ctx.keyword("TABLE");
2041        if if_not_exists {
2042            ctx.keyword("IF NOT EXISTS");
2043        }
2044        if let Some(ns) = &schema.namespace {
2045            ctx.ident(ns).operator(".");
2046        }
2047        ctx.ident(&schema.name);
2048
2049        // Collect PK column names for identity detection
2050        let pk_columns: Vec<&str> = schema
2051            .constraints
2052            .as_ref()
2053            .and_then(|cs| {
2054                cs.iter().find_map(|c| {
2055                    if let ConstraintDef::PrimaryKey { columns, .. } = c {
2056                        Some(columns.iter().map(|s| s.as_str()).collect::<Vec<_>>())
2057                    } else {
2058                        None
2059                    }
2060                })
2061            })
2062            .unwrap_or_default();
2063
2064        // Find identity column that should be rendered as PRIMARY KEY AUTOINCREMENT
2065        let identity_pk_col = schema.columns.iter().find_map(|col| {
2066            if col.identity.is_some() {
2067                if pk_columns.contains(&col.name.as_str()) {
2068                    Some(col.name.as_str())
2069                } else {
2070                    None
2071                }
2072            } else {
2073                None
2074            }
2075        });
2076
2077        // Validate: identity without PK is an error in SQLite
2078        for col in &schema.columns {
2079            if col.identity.is_some() && !pk_columns.contains(&col.name.as_str()) {
2080                return Err(RenderError::unsupported(
2081                    "IdentityColumn",
2082                    "SQLite requires identity columns to be PRIMARY KEY. Add a PrimaryKey constraint for this column.",
2083                ));
2084            }
2085        }
2086
2087        ctx.paren_open();
2088        let mut first = true;
2089        for col in &schema.columns {
2090            if !first {
2091                ctx.comma();
2092            }
2093            first = false;
2094            self.render_column_def(col, ctx)?;
2095            // Inline PRIMARY KEY AUTOINCREMENT on the identity column
2096            if identity_pk_col == Some(col.name.as_str()) {
2097                ctx.keyword("PRIMARY KEY AUTOINCREMENT");
2098            }
2099        }
2100        if let Some(constraints) = &schema.constraints {
2101            for constraint in constraints {
2102                // Skip single-column PK if it was inlined with AUTOINCREMENT
2103                if let ConstraintDef::PrimaryKey { columns, .. } = constraint {
2104                    if columns.len() == 1 && identity_pk_col == Some(columns[0].as_str()) {
2105                        continue;
2106                    }
2107                }
2108                if !first {
2109                    ctx.comma();
2110                }
2111                first = false;
2112                self.render_constraint(constraint, ctx)?;
2113            }
2114        }
2115        ctx.paren_close();
2116
2117        // SQLite table modifiers
2118        let mut modifiers = Vec::new();
2119        if without_rowid {
2120            modifiers.push("WITHOUT ROWID");
2121        }
2122        if strict {
2123            modifiers.push("STRICT");
2124        }
2125        if !modifiers.is_empty() {
2126            for (i, m) in modifiers.iter().enumerate() {
2127                if i > 0 {
2128                    ctx.comma();
2129                }
2130                ctx.keyword(m);
2131            }
2132        }
2133
2134        Ok(())
2135    }
2136
2137    fn sqlite_create_index(
2138        &self,
2139        schema_ref: &qcraft_core::ast::common::SchemaRef,
2140        index: &IndexDef,
2141        if_not_exists: bool,
2142        ctx: &mut RenderCtx,
2143    ) -> RenderResult<()> {
2144        ctx.keyword("CREATE");
2145        if index.unique {
2146            ctx.keyword("UNIQUE");
2147        }
2148        ctx.keyword("INDEX");
2149        if if_not_exists {
2150            ctx.keyword("IF NOT EXISTS");
2151        }
2152        ctx.ident(&index.name).keyword("ON");
2153        self.sqlite_schema_ref(schema_ref, ctx);
2154
2155        // SQLite doesn't support USING method — Ignore index_type
2156
2157        ctx.paren_open();
2158        self.sqlite_index_columns(&index.columns, ctx)?;
2159        ctx.paren_close();
2160
2161        // SQLite doesn't support INCLUDE, NULLS DISTINCT, WITH params, TABLESPACE — Ignore
2162
2163        if let Some(condition) = &index.condition {
2164            ctx.keyword("WHERE");
2165            self.render_condition(condition, ctx)?;
2166        }
2167
2168        Ok(())
2169    }
2170
2171    fn sqlite_index_columns(
2172        &self,
2173        columns: &[IndexColumnDef],
2174        ctx: &mut RenderCtx,
2175    ) -> RenderResult<()> {
2176        for (i, col) in columns.iter().enumerate() {
2177            if i > 0 {
2178                ctx.comma();
2179            }
2180            match &col.expr {
2181                IndexExpr::Column(name) => {
2182                    ctx.ident(name);
2183                }
2184                IndexExpr::Expression(expr) => {
2185                    ctx.paren_open();
2186                    self.render_expr(expr, ctx)?;
2187                    ctx.paren_close();
2188                }
2189            }
2190            if let Some(collation) = &col.collation {
2191                ctx.keyword("COLLATE").ident(collation);
2192            }
2193            // SQLite doesn't support operator classes — Ignore opclass
2194            if let Some(dir) = col.direction {
2195                ctx.keyword(match dir {
2196                    OrderDir::Asc => "ASC",
2197                    OrderDir::Desc => "DESC",
2198                });
2199            }
2200            // SQLite doesn't support NULLS FIRST/LAST — Ignore
2201        }
2202        Ok(())
2203    }
2204
2205    fn sqlite_order_by_list(
2206        &self,
2207        order_by: &[OrderByDef],
2208        ctx: &mut RenderCtx,
2209    ) -> RenderResult<()> {
2210        for (i, ob) in order_by.iter().enumerate() {
2211            if i > 0 {
2212                ctx.comma();
2213            }
2214            self.render_expr(&ob.expr, ctx)?;
2215            ctx.keyword(match ob.direction {
2216                OrderDir::Asc => "ASC",
2217                OrderDir::Desc => "DESC",
2218            });
2219            if let Some(nulls) = &ob.nulls {
2220                ctx.keyword(match nulls {
2221                    NullsOrder::First => "NULLS FIRST",
2222                    NullsOrder::Last => "NULLS LAST",
2223                });
2224            }
2225        }
2226        Ok(())
2227    }
2228}