Skip to main content

inillucent_sql/
directive.rs

1//! Statements the session carries out itself rather than compiling.
2//!
3//! Invariant: a directive is a decision, already resolved, with nothing left
4//! to look up. Binding a `DROP TABLE` resolves the name and refuses a missing
5//! one here; what reaches the session is "free this root page and remove this
6//! `sqlite_schema` row", not a name it has to resolve again.
7//!
8//! Transaction control and DDL are here rather than in the bytecode for a
9//! reason the TDD's own DDL protocol describes: their steps are catalog
10//! publication, cookie invalidation and lock transitions, none of which the
11//! machine's register-and-cursor model expresses. They still run inside the
12//! same transaction machinery as DML - the statement savepoint, the journal
13//! and the commit are identical - which is what the protocol actually
14//! requires. The row-touching part of DDL is ordinary storage work and goes
15//! through the same pager as everything else.
16
17use crate::ast::{self, ObjectKind, TransactionBehaviour};
18use crate::bind::{no_such_table, refused, schema_refused, unsupported, Binder, BoundExpr};
19use crate::catalog_view::CatalogView;
20use crate::catalog_view::TableKind;
21use crate::diagnostic::ParseError;
22use crate::lexer::Span;
23use inillucent_value::Collation;
24
25/// Returns the direct children of an expression node.
26///
27/// The arena has no walker of its own, and the only caller that needs one is
28/// the generated-column check, so it lives beside it rather than becoming a
29/// method every other reader would have to ignore.
30fn expression_children(ast: &crate::ast::Ast, expr: ast::ExprId) -> Vec<ast::ExprId> {
31    let mut out = Vec::new();
32    let Some(node) = ast.expr(expr) else {
33        return out;
34    };
35    match node {
36        ast::Expr::Unary { operand, .. } => out.push(*operand),
37        ast::Expr::Binary { left, right, .. } => {
38            out.push(*left);
39            out.push(*right);
40        }
41        ast::Expr::Collate { operand, .. } | ast::Expr::Cast { operand, .. } => out.push(*operand),
42        ast::Expr::IsNull { operand, .. } => out.push(*operand),
43        ast::Expr::Raise {
44            message: Some(message),
45            ..
46        } => out.push(*message),
47        ast::Expr::Is { left, right, .. } => {
48            out.push(*left);
49            out.push(*right);
50        }
51        ast::Expr::Between {
52            operand, low, high, ..
53        } => {
54            out.push(*operand);
55            out.push(*low);
56            out.push(*high);
57        }
58        ast::Expr::In { operand, rhs, .. } => {
59            out.push(*operand);
60            if let ast::InRhs::List(items) = rhs {
61                out.extend(items.iter().copied());
62            }
63        }
64        ast::Expr::Case {
65            operand,
66            branches,
67            otherwise,
68        } => {
69            if let Some(operand) = operand {
70                out.push(*operand);
71            }
72            for (when, then) in branches {
73                out.push(*when);
74                out.push(*then);
75            }
76            if let Some(otherwise) = otherwise {
77                out.push(*otherwise);
78            }
79        }
80        ast::Expr::Pattern {
81            operand,
82            pattern,
83            escape,
84            ..
85        } => {
86            out.push(*operand);
87            out.push(*pattern);
88            if let Some(escape) = escape {
89                out.push(*escape);
90            }
91        }
92        ast::Expr::Function {
93            arguments: Some(arguments),
94            ..
95        } => out.extend(arguments.iter().copied()),
96        _ => {}
97    }
98    out
99}
100
101/// Returns whether a stored expression names an identifier.
102///
103/// It lexes rather than searches, so a column called `a` is not found inside
104/// `abc` or inside the text of a string literal.
105fn mentions_name(sql: &[u8], folded: &[u8]) -> bool {
106    let mut lexer = crate::lexer::Lexer::at(sql, 0);
107    loop {
108        let Ok(token) = lexer.next_token() else {
109            return false;
110        };
111        match token.kind {
112            crate::lexer::TokenKind::EndOfInput => return false,
113            crate::lexer::TokenKind::Identifier { keyword: None, .. }
114                if token.span.slice(sql).to_ascii_lowercase() == folded =>
115            {
116                return true;
117            }
118            _ => {}
119        }
120    }
121}
122
123/// Returns the failure `REINDEX` gives for a name that is nothing it knows.
124fn no_such_collation_sequence(name: &[u8], span: Span) -> ParseError {
125    ParseError::new(
126        crate::diagnostic::ParseErrorKind::Unexpected {
127            found: format!(
128                "unable to identify the object to be reindexed: {}",
129                String::from_utf8_lossy(name)
130            ),
131            expected: Vec::new(),
132        },
133        span,
134    )
135}
136
137/// How an explicit `BEGIN` acquires its rights.
138#[derive(Clone, Copy, Debug, Eq, PartialEq)]
139pub enum BeginKind {
140    /// Take nothing until the first read or write needs it.
141    Deferred,
142    /// Take the writer's reservation now.
143    Immediate,
144    /// Take the write lock now, excluding readers too.
145    Exclusive,
146}
147
148impl BeginKind {
149    /// Returns the kind a `BEGIN` clause names, defaulting to DEFERRED.
150    pub fn of(behaviour: Option<TransactionBehaviour>) -> BeginKind {
151        match behaviour {
152            None | Some(TransactionBehaviour::Deferred) => BeginKind::Deferred,
153            Some(TransactionBehaviour::Immediate) => BeginKind::Immediate,
154            Some(TransactionBehaviour::Exclusive) => BeginKind::Exclusive,
155        }
156    }
157}
158
159/// What an added column would do to rows that already exist.
160///
161/// SQLite refuses `PRIMARY KEY` and `UNIQUE` while it is still compiling,
162/// because no table can take them however empty it is. The other three it
163/// defers: a `NOT NULL` column with no default, a non-constant default and a
164/// `STORED` generated column are refused *only when there is a row to break*,
165/// and are accepted on an empty table. That is not a quirk worth smoothing
166/// over - it is the difference between a migration that runs on a fresh
167/// database and one that runs on a populated one - so the binder records what
168/// it saw and the executor, which knows the row count, decides.
169#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
170pub struct AddedColumnRisk {
171    /// `NOT NULL` with nothing to fill the existing rows with.
172    pub null_without_default: bool,
173    /// A `DEFAULT` the existing rows cannot all be given one answer from.
174    pub non_constant_default: bool,
175    /// `GENERATED ALWAYS AS (...) STORED`, which needs a value in every record.
176    pub generated_stored: bool,
177}
178
179impl AddedColumnRisk {
180    /// Returns the refusal a table with rows in it owes, in SQLite's wording.
181    ///
182    /// The capitalisation is the reference's own and is inconsistent between
183    /// the three; it is reproduced rather than tidied, because a caller
184    /// matching on the message is matching on what SQLite prints.
185    pub fn refusal(&self) -> Option<&'static str> {
186        if self.null_without_default {
187            return Some("Cannot add a NOT NULL column with default value NULL");
188        }
189        if self.non_constant_default {
190            return Some("Cannot add a column with non-constant default");
191        }
192        if self.generated_stored {
193            return Some("cannot add a STORED column");
194        }
195        None
196    }
197}
198
199/// What an `ALTER TABLE` does, with every name already resolved.
200#[derive(Clone, Debug, PartialEq, Eq)]
201pub enum AlterKind {
202    /// `RENAME TO`.
203    RenameTable {
204        /// The new name, as written.
205        to: Vec<u8>,
206    },
207    /// `RENAME COLUMN a TO b`.
208    RenameColumn {
209        /// The column's current name, as stored.
210        from: Vec<u8>,
211        /// Its new name, as written.
212        to: Vec<u8>,
213    },
214    /// `ADD COLUMN`.
215    AddColumn {
216        /// Where the definition starts in the statement's own source.
217        ///
218        /// The offsets rather than the text, for the same reason `CREATE TABLE`
219        /// carries an offset: the executor has the statement's source and
220        /// slicing it there keeps the *written* definition - its spacing, its
221        /// case and its comments - rather than something re-rendered from the
222        /// parse.
223        start: u32,
224        /// Where it ends.
225        end: u32,
226        /// What it would do to rows that already exist.
227        risk: AddedColumnRisk,
228    },
229    /// `DROP COLUMN`.
230    DropColumn {
231        /// The column's name, as stored.
232        name: Vec<u8>,
233        /// Its declared position, which is the record slot to remove.
234        position: u16,
235    },
236}
237
238/// One key column of an index being created.
239#[derive(Clone, Debug, PartialEq, Eq)]
240pub struct IndexKeyColumn {
241    /// The table column, when the key is a bare column.
242    ///
243    /// `None` for a key that is an expression. It was a bare `u16` while
244    /// `CREATE INDEX ix ON t(lower(a))` was refused in the binder; the field is
245    /// an `Option` now so that a reader which needs a column - a module-backed
246    /// index, say - has to say what it does when there is not one, rather than
247    /// reading a position that was invented to fill the slot.
248    pub column: Option<u16>,
249    /// The key expression, as written, when the key is one.
250    pub expr_sql: Option<Vec<u8>>,
251    /// The folded collation name.
252    pub collation: Vec<u8>,
253    /// Whether the key is stored descending.
254    pub descending: bool,
255}
256
257/// A statement the session carries out.
258#[derive(Clone, Debug, PartialEq)]
259pub enum Directive {
260    /// `BEGIN`.
261    Begin(BeginKind),
262    /// `COMMIT` or `END`.
263    Commit,
264    /// `ROLLBACK`, or `ROLLBACK TO savepoint`.
265    Rollback {
266        /// The savepoint to roll back to, when one was named.
267        savepoint: Option<Vec<u8>>,
268    },
269    /// `SAVEPOINT name`.
270    Savepoint(Vec<u8>),
271    /// `RELEASE name`.
272    Release(Vec<u8>),
273    /// `CREATE TABLE`.
274    CreateTable {
275        /// Whether `IF NOT EXISTS` was written.
276        if_not_exists: bool,
277        /// Which attached database.
278        database: usize,
279        /// The table name as written.
280        name: Vec<u8>,
281        /// The byte the name starts at in the statement's source.
282        name_offset: u32,
283        /// Whether the table already exists.
284        exists: bool,
285    },
286    /// `CREATE TABLE ... AS SELECT`.
287    ///
288    /// A `CREATE` whose column list comes from a plan, which is why it is a
289    /// directive of its own rather than a flag on the one above: everything
290    /// about the table - its column names, and the declared types it inherits
291    /// from the query's origin columns - is decided by binding the query, and
292    /// the `CREATE` text that is stored is *synthesised* rather than being a
293    /// slice of what was typed.
294    CreateTableAsSelect {
295        /// Whether `IF NOT EXISTS` was written.
296        if_not_exists: bool,
297        /// Which attached database.
298        database: usize,
299        /// The table name as written.
300        name: Vec<u8>,
301        /// Whether the table already exists.
302        exists: bool,
303        /// The `CREATE TABLE name(...)` text to store, built from the query.
304        create_sql: Vec<u8>,
305        /// The `SELECT` that fills it, as the source text it was written as.
306        ///
307        /// The text rather than the bound query, because the rows are inserted
308        /// by an ordinary `INSERT INTO name <select>` compiled against the
309        /// schema *after* the table exists - which is one implementation of
310        /// what an insert means rather than a second one written here.
311        select_sql: Vec<u8>,
312    },
313    /// `CREATE VIRTUAL TABLE`.
314    CreateVirtualTable {
315        /// Whether `IF NOT EXISTS` was written.
316        if_not_exists: bool,
317        /// Which attached database.
318        database: usize,
319        /// The table name as written.
320        name: Vec<u8>,
321        /// The module name as written.
322        module: Vec<u8>,
323        /// The arguments inside the parentheses, as written.
324        arguments: Vec<Vec<u8>>,
325        /// The byte the name starts at in the statement's source.
326        name_offset: u32,
327        /// Whether the table already exists.
328        exists: bool,
329    },
330    /// `ALTER TABLE`.
331    Alter {
332        /// Which attached database.
333        database: usize,
334        /// The table being altered, by its stored name.
335        table: Vec<u8>,
336        /// What to do to it.
337        action: AlterKind,
338    },
339    /// `REINDEX`, over one index, one table's indexes, or everything.
340    Reindex {
341        /// Which attached database.
342        database: usize,
343        /// The indexes to rebuild, by name.
344        indexes: Vec<Vec<u8>>,
345    },
346    /// `VACUUM`, which rebuilds the database into a fresh file.
347    Vacuum {
348        /// Which attached database.
349        database: usize,
350        /// The file `VACUUM INTO` writes the rebuilt copy to.
351        ///
352        /// A string literal, as SQLite's grammar has it. `INTO` leaves the
353        /// database it was run on completely alone, which is the difference
354        /// between the two forms and the reason the path is carried rather
355        /// than resolved here.
356        into: Option<Vec<u8>>,
357    },
358    /// `ATTACH`, which adds a database file to this connection.
359    Attach {
360        /// The file to open, as the literal it was written as.
361        file: Vec<u8>,
362        /// The name it will be known by.
363        schema: Vec<u8>,
364        /// The `KEY` clause's text: the file's encryption key, or empty for a
365        /// plaintext file. `None` when there was no `KEY` clause, which means
366        /// the connection's own key.
367        key: Option<Vec<u8>>,
368    },
369    /// `DETACH`, which removes one.
370    Detach {
371        /// The name it was attached under.
372        schema: Vec<u8>,
373    },
374    /// `ANALYZE`, over one object or the whole schema.
375    Analyze {
376        /// Which attached database.
377        database: usize,
378        /// The one table or index to measure, or nothing for all of them.
379        table: Option<Vec<u8>>,
380        /// Whether the statement was a bare `ANALYZE`, which measures every
381        /// database but `temp` rather than `database` alone.
382        every_schema: bool,
383    },
384    /// `CREATE VIEW`.
385    CreateView {
386        /// Whether `IF NOT EXISTS` was written.
387        if_not_exists: bool,
388        /// Which attached database.
389        database: usize,
390        /// The view name as written.
391        name: Vec<u8>,
392        /// The byte the name starts at in the statement's source.
393        name_offset: u32,
394        /// Whether the view already exists.
395        exists: bool,
396    },
397    /// `CREATE TRIGGER`.
398    CreateTrigger {
399        /// Which attached database.
400        database: usize,
401        /// The trigger name as written.
402        name: Vec<u8>,
403        /// The byte the name starts at in the statement's source.
404        name_offset: u32,
405        /// The table or view the trigger is attached to.
406        table: Vec<u8>,
407        /// Whether the trigger already exists.
408        exists: bool,
409    },
410    /// `CREATE INDEX`.
411    CreateIndex {
412        /// Whether `UNIQUE` was written.
413        unique: bool,
414        /// Whether `IF NOT EXISTS` was written.
415        if_not_exists: bool,
416        /// Which attached database.
417        database: usize,
418        /// The index name as written.
419        name: Vec<u8>,
420        /// The byte the name starts at in the statement's source.
421        name_offset: u32,
422        /// The table it indexes.
423        table: Vec<u8>,
424        /// The root page of that table.
425        table_root: u32,
426        /// The module named by `USING`, folded, when one was.
427        using: Option<Vec<u8>>,
428        /// The key columns.
429        columns: Vec<IndexKeyColumn>,
430        /// The storage parameters `WITH ( ... )` named, checked against the
431        /// module that will read them.
432        settings: Vec<(Vec<u8>, Vec<u8>)>,
433        /// Whether the index already exists.
434        exists: bool,
435    },
436    /// `DROP TABLE` or `DROP INDEX`.
437    Drop {
438        /// Which kind of object.
439        kind: ObjectKind,
440        /// Whether `IF EXISTS` was written.
441        if_exists: bool,
442        /// Which attached database.
443        database: usize,
444        /// The object name.
445        name: Vec<u8>,
446        /// The root page to free, or zero when the object has none.
447        root: u32,
448        /// The root pages of the indexes a `DROP TABLE` takes with it.
449        index_roots: Vec<u32>,
450        /// Whether the object exists.
451        exists: bool,
452    },
453    /// `PRAGMA`.
454    Pragma {
455        /// The schema the pragma was qualified with, when one was written.
456        ///
457        /// `PRAGMA aux.table_info(t)` asks about the attached database rather
458        /// than about `main`, and a pragma that dropped the qualifier would
459        /// answer confidently about the wrong file.
460        database: Option<usize>,
461        /// The pragma name, folded.
462        name: Vec<u8>,
463        /// The argument, when one was written.
464        argument: Option<PragmaArgument>,
465    },
466}
467
468/// What a `PRAGMA` was given.
469#[derive(Clone, Debug, PartialEq)]
470pub enum PragmaArgument {
471    /// A bare word, such as `PRAGMA journal_mode = WAL`.
472    Name(Vec<u8>),
473    /// An expression, such as `PRAGMA user_version = 4`.
474    Value(BoundExpr),
475}
476
477/// Whether a `CREATE INDEX` declared `UNIQUE`.
478///
479/// **An enum rather than a `bool` beside another `bool` (task-1962, A9).**
480/// `bind_create_index` took `unique` and `if_not_exists` adjacent and
481/// positional; swapping them compiles and declares a unique index where the
482/// statement asked for `IF NOT EXISTS`.
483#[derive(Clone, Copy, Debug, Eq, PartialEq)]
484pub enum Uniqueness {
485    /// `CREATE UNIQUE INDEX`: two rows may not share a key.
486    Unique,
487    /// `CREATE INDEX`: a key may repeat.
488    Duplicates,
489}
490
491/// Whether a `CREATE` declared `IF NOT EXISTS`.
492#[derive(Clone, Copy, Debug, Eq, PartialEq)]
493pub enum IfNotExists {
494    /// The statement is a no-op when the object is already there.
495    Skip,
496    /// The statement fails when the object is already there.
497    Refuse,
498}
499
500/// Everything a `CREATE INDEX` statement names.
501///
502/// The grammar's own fields, gathered rather than passed as nine positional
503/// arguments of which two were adjacent booleans.
504pub struct CreateIndexSpec<'a> {
505    /// Whether the index refuses a repeated key.
506    pub unique: Uniqueness,
507    /// What to do when the index is already there.
508    pub if_not_exists: IfNotExists,
509    /// The schema the index is created in, when one was written.
510    pub database: Option<ast::NameId>,
511    /// The index's name.
512    pub name: ast::NameId,
513    /// The table it is over.
514    pub table: ast::NameId,
515    /// The module named by `USING`, for the extension index forms.
516    pub using: Option<ast::NameId>,
517    /// The indexed columns, in key order.
518    pub columns: &'a [ast::IndexedColumn],
519    /// The `WITH` settings, as written.
520    pub settings: &'a [Vec<u8>],
521    /// The `WHERE` of a partial index, as an expression of the statement.
522    pub filter: Option<ast::ExprId>,
523}
524
525/// The fields of a `CREATE TRIGGER`, passed as one argument.
526///
527/// Ten parameters is past the point where their order is checkable by reading,
528/// and every one of them is a field of the statement rather than something
529/// computed here.
530pub(crate) struct CreateTriggerParts<'p> {
531    /// Whether `TEMP` was written.
532    pub temporary: bool,
533    /// Whether `IF NOT EXISTS` was written.
534    pub if_not_exists: bool,
535    /// The schema qualifier.
536    pub database: Option<ast::NameId>,
537    /// The trigger name.
538    pub name: ast::NameId,
539    /// When it fires.
540    pub time: Option<ast::TriggerTime>,
541    /// The table it is attached to.
542    pub table: ast::NameId,
543    /// The schema qualifier on the table.
544    pub table_database: Option<ast::NameId>,
545    /// Whether `FOR EACH ROW` was written.
546    pub for_each_row: bool,
547    /// The `WHEN` guard.
548    pub when: Option<ast::ExprId>,
549    /// The body statements.
550    pub body: &'p [ast::Statement],
551}
552
553impl<'a> Binder<'a> {
554    /// Binds a statement the session carries out itself.
555    pub fn bind_directive(&mut self, statement: &ast::Statement) -> Result<Directive, ParseError> {
556        match statement {
557            ast::Statement::Begin { behaviour } => Ok(Directive::Begin(BeginKind::of(*behaviour))),
558            ast::Statement::Commit => Ok(Directive::Commit),
559            ast::Statement::Rollback { savepoint } => Ok(Directive::Rollback {
560                savepoint: savepoint.map(|id| self.ast.text(id).to_vec()),
561            }),
562            ast::Statement::Savepoint(name) => {
563                Ok(Directive::Savepoint(self.ast.text(*name).to_vec()))
564            }
565            ast::Statement::Release(name) => Ok(Directive::Release(self.ast.text(*name).to_vec())),
566            ast::Statement::CreateTable {
567                temporary,
568                if_not_exists,
569                database,
570                name,
571                body,
572            } => self.bind_create_table(*temporary, *if_not_exists, *database, *name, body),
573            ast::Statement::CreateVirtualTable {
574                if_not_exists,
575                database,
576                name,
577                module,
578                arguments,
579            } => {
580                self.bind_create_virtual_table(*if_not_exists, *database, *name, *module, arguments)
581            }
582            ast::Statement::CreateIndex {
583                unique,
584                if_not_exists,
585                database,
586                name,
587                table,
588                using,
589                columns,
590                settings,
591                filter,
592            } => self.bind_create_index(&CreateIndexSpec {
593                unique: if *unique {
594                    Uniqueness::Unique
595                } else {
596                    Uniqueness::Duplicates
597                },
598                if_not_exists: if *if_not_exists {
599                    IfNotExists::Skip
600                } else {
601                    IfNotExists::Refuse
602                },
603                database: *database,
604                name: *name,
605                table: *table,
606                using: *using,
607                columns,
608                settings,
609                filter: *filter,
610            }),
611            ast::Statement::Analyze { database, name } => self.bind_analyze(*database, *name),
612            ast::Statement::AlterTable {
613                database,
614                table,
615                action,
616            } => self.bind_alter(*database, *table, action),
617            ast::Statement::Reindex { database, name } => self.bind_reindex(*database, *name),
618            ast::Statement::Vacuum { database, into } => self.bind_vacuum(*database, *into),
619            ast::Statement::Attach { file, schema, key } => self.bind_attach(*file, *schema, *key),
620            ast::Statement::Detach { schema } => self.bind_detach(*schema),
621            ast::Statement::CreateView {
622                temporary,
623                if_not_exists,
624                database,
625                name,
626                columns,
627                select,
628            } => self.bind_create_view(
629                *temporary,
630                *if_not_exists,
631                *database,
632                *name,
633                columns,
634                *select,
635            ),
636            ast::Statement::CreateTrigger {
637                temporary,
638                if_not_exists,
639                database,
640                name,
641                time,
642                event: _,
643                table,
644                table_database,
645                for_each_row,
646                when,
647                body,
648            } => self.bind_create_trigger(CreateTriggerParts {
649                temporary: *temporary,
650                if_not_exists: *if_not_exists,
651                database: *database,
652                name: *name,
653                time: *time,
654                table: *table,
655                table_database: *table_database,
656                for_each_row: *for_each_row,
657                when: *when,
658                body,
659            }),
660            ast::Statement::Drop {
661                kind,
662                if_exists,
663                database,
664                name,
665            } => self.bind_drop(*kind, *if_exists, *database, *name),
666            ast::Statement::Pragma {
667                database,
668                name,
669                value,
670            } => self.bind_pragma(*database, *name, value),
671            _ => Err(unsupported(
672                "this statement is not implemented yet",
673                Span::default(),
674            )),
675        }
676    }
677
678    /// Binds a `CREATE TABLE`.
679    fn bind_create_virtual_table(
680        &mut self,
681        if_not_exists: bool,
682        database: Option<ast::NameId>,
683        name: ast::NameId,
684        module: ast::NameId,
685        arguments: &[Vec<u8>],
686    ) -> Result<Directive, ParseError> {
687        let index = self.resolve_database(database)?;
688        let written = self.ast.text(name).to_vec();
689        if written.to_ascii_lowercase().starts_with(b"sqlite_") {
690            return Err(refused(
691                format!(
692                    "object name reserved for internal use: {}",
693                    String::from_utf8_lossy(&written)
694                ),
695                Span::default(),
696            ));
697        }
698        let folded = self.ast.folded(name).to_vec();
699        let database_name = self.catalog.database_name(index).to_vec();
700        let exists = self
701            .catalog
702            .find_table(Some(database_name.as_slice()), &folded)
703            .is_some();
704        if exists && !if_not_exists {
705            return Err(refused(
706                format!("table {} already exists", String::from_utf8_lossy(&written)),
707                Span::default(),
708            ));
709        }
710        Ok(Directive::CreateVirtualTable {
711            if_not_exists,
712            database: index,
713            name: written,
714            module: self.ast.text(module).to_vec(),
715            arguments: arguments.to_vec(),
716            name_offset: self
717                .ast
718                .name(name)
719                .map(|entry| entry.span.start)
720                .unwrap_or_default(),
721            exists,
722        })
723    }
724
725    /// Binds `CREATE TABLE`, refusing what the file format cannot hold.
726    fn bind_create_table(
727        &mut self,
728        temporary: bool,
729        if_not_exists: bool,
730        database: Option<ast::NameId>,
731        name: ast::NameId,
732        body: &ast::CreateTableBody,
733    ) -> Result<Directive, ParseError> {
734        let temp = self.temporary_database(temporary, database, false)?;
735        // **Two bodies, and the second one is built.** This used to be written
736        // as two `let ... else` bindings, the inner one answering
737        // `unsupported("CREATE TABLE ... AS SELECT")` - an arm no statement
738        // could reach, because `CreateTableBody` has exactly these two
739        // variants, so a feature that works was described by a refusal
740        // (task-1979, section 8.3). A match over both says the same thing with
741        // nothing left over.
742        let (columns, constraints, without_rowid, strict) = match body {
743            ast::CreateTableBody::AsSelect(select) => {
744                return self.bind_create_table_as_select(
745                    temp,
746                    if_not_exists,
747                    database,
748                    name,
749                    *select,
750                )
751            }
752            ast::CreateTableBody::Columns {
753                columns,
754                constraints,
755                without_rowid,
756                strict,
757            } => (columns, constraints, without_rowid, strict),
758        };
759        if *without_rowid && !self.declares_primary_key(columns, constraints) {
760            return Err(schema_refused(
761                format!(
762                    "PRIMARY KEY missing on table {}",
763                    String::from_utf8_lossy(self.ast.text(name))
764                ),
765                Span::default(),
766            ));
767        }
768        self.check_autoincrement(columns, *without_rowid)?;
769        if *strict {
770            self.check_strict(columns)?;
771        }
772        self.check_generated(columns)?;
773        if columns.is_empty() {
774            return Err(refused(
775                "a table must have at least one column",
776                Span::default(),
777            ));
778        }
779        let index = match temp {
780            Some(index) => index,
781            None => self.resolve_database(database)?,
782        };
783        let written = self.ast.text(name).to_vec();
784        if written.to_ascii_lowercase().starts_with(b"sqlite_") {
785            return Err(refused(
786                format!(
787                    "object name reserved for internal use: {}",
788                    String::from_utf8_lossy(&written)
789                ),
790                Span::default(),
791            ));
792        }
793        let folded = self.ast.folded(name).to_vec();
794        let database_name = self.catalog.database_name(index).to_vec();
795        let exists = self
796            .catalog
797            .find_table(Some(database_name.as_slice()), &folded)
798            .is_some();
799        if exists && !if_not_exists {
800            return Err(refused(
801                format!("table {} already exists", String::from_utf8_lossy(&written)),
802                Span::default(),
803            ));
804        }
805        self.record_write_dependency(index);
806        Ok(Directive::CreateTable {
807            if_not_exists,
808            database: index,
809            name: written,
810            name_offset: self.name_offset(name),
811            exists,
812        })
813    }
814
815    /// Binds `CREATE TABLE ... AS SELECT`.
816    ///
817    /// **The column list comes from a plan**, which is the whole of why this is
818    /// a shape of its own. SQLite takes the table's columns from the query's
819    /// result columns: the name each one reports, and the declared type it
820    /// carries when it is a plain reference to a column that has one. So
821    /// `CREATE TABLE u AS SELECT a*2 AS d, b, c FROM t` on `t(a INTEGER, b TEXT,
822    /// c REAL)` stores `CREATE TABLE u(d,b TEXT,c REAL)` - `d` is an expression
823    /// and inherits nothing, and the other two inherit their origin's type.
824    ///
825    /// The rows are inserted afterwards by an ordinary `INSERT INTO name
826    /// <select>`, compiled against the schema once the table is in it. That is
827    /// one implementation of what an insert means rather than a second one
828    /// written into the DDL path, and it is what makes the affinity Part B4
829    /// applies reach these rows too.
830    ///
831    /// @param temp - the temporary database's index, when `TEMP` was written
832    /// @param if_not_exists - whether `IF NOT EXISTS` was written
833    /// @param database - the schema qualifier, when one was written
834    /// @param name - the table's name
835    /// @param select - the query the table is built from
836    fn bind_create_table_as_select(
837        &mut self,
838        temp: Option<usize>,
839        if_not_exists: bool,
840        database: Option<ast::NameId>,
841        name: ast::NameId,
842        select: ast::SelectId,
843    ) -> Result<Directive, ParseError> {
844        let index = match temp {
845            Some(index) => index,
846            None => self.resolve_database(database)?,
847        };
848        let written = self.ast.text(name).to_vec();
849        if written.to_ascii_lowercase().starts_with(b"sqlite_") {
850            return Err(refused(
851                format!(
852                    "object name reserved for internal use: {}",
853                    String::from_utf8_lossy(&written)
854                ),
855                Span::default(),
856            ));
857        }
858        let folded = self.ast.folded(name).to_vec();
859        let database_name = self.catalog.database_name(index).to_vec();
860        let exists = self
861            .catalog
862            .find_table(Some(database_name.as_slice()), &folded)
863            .is_some();
864        if exists && !if_not_exists {
865            return Err(refused(
866                format!("table {} already exists", String::from_utf8_lossy(&written)),
867                Span::default(),
868            ));
869        }
870        let span = self
871            .ast
872            .select(select)
873            .map(|held| held.span)
874            .ok_or_else(|| refused("the query could not be read", Span::default()))?;
875        let select_sql = self
876            .source
877            .get(span.start as usize..span.end as usize)
878            .ok_or_else(|| refused("the query could not be read", span))?
879            .to_vec();
880        // Bound rather than merely parsed, because binding is what resolves the
881        // result columns' names and origins - and because a query that does not
882        // bind has to be refused here rather than after the table exists.
883        let bound = self.bind_select(select)?;
884        if bound.columns.is_empty() {
885            return Err(refused(
886                "a table must have at least one column",
887                Span::default(),
888            ));
889        }
890        // **The declaration a `CREATE TABLE ... AS SELECT` stores is the
891        // *affinity*, not the source column's declared type.** SQLite writes
892        // `a INT` for a source column declared `INTEGER` and `b TEXT` for one
893        // declared `VARCHAR(3)`, because what survives a query is the affinity
894        // and nothing else - the width, the precision and the spelling are
895        // properties of the source table that the copy does not have. Storing
896        // `VARCHAR(3)` here claimed a constraint the new table does not
897        // enforce, and made the two schemas differ for every CTAS.
898        //
899        // The line break is SQLite's own rule too, so the stored text matches
900        // byte for byte: the name lengths are added up first, and a wide
901        // declaration is written one column per line.
902        let mut width = identifier_width(&written);
903        for column in &bound.columns {
904            width = width
905                .saturating_add(identifier_width(&column.name))
906                .saturating_add(5);
907        }
908        let (open, between, close): (&[u8], &[u8], &[u8]) = if width < 50 {
909            (b"", b",", b")")
910        } else {
911            (b"\n  ", b",\n  ", b"\n)")
912        };
913        let mut create_sql = Vec::new();
914        create_sql.extend_from_slice(b"CREATE TABLE ");
915        create_sql.extend_from_slice(&written);
916        create_sql.push(b'(');
917        let mut seen: Vec<Vec<u8>> = Vec::with_capacity(bound.columns.len());
918        for (position, column) in bound.columns.iter().enumerate() {
919            create_sql.extend_from_slice(if position > 0 { between } else { open });
920            let folded = column.name.to_ascii_lowercase();
921            if seen.contains(&folded) {
922                return Err(refused(
923                    format!(
924                        "duplicate column name: {}",
925                        String::from_utf8_lossy(&column.name)
926                    ),
927                    Span::default(),
928                ));
929            }
930            seen.push(folded);
931            create_sql.extend_from_slice(&quoted_name(&column.name));
932            create_sql.extend_from_slice(affinity_type(&column.declared_type));
933        }
934        create_sql.extend_from_slice(close);
935        self.record_write_dependency(index);
936        Ok(Directive::CreateTableAsSelect {
937            if_not_exists,
938            database: index,
939            name: written,
940            exists,
941            create_sql,
942            select_sql,
943        })
944    }
945
946    /// Returns whether a `CREATE TABLE` declares a primary key anywhere.
947    fn declares_primary_key(
948        &self,
949        columns: &[ast::ColumnDef],
950        constraints: &[(Option<ast::NameId>, ast::TableConstraint)],
951    ) -> bool {
952        let on_column = columns.iter().any(|column| {
953            column.constraints.iter().any(|(_, constraint)| {
954                matches!(constraint, ast::ColumnConstraint::PrimaryKey { .. })
955            })
956        });
957        on_column
958            || constraints.iter().any(|(_, constraint)| {
959                matches!(constraint, ast::TableConstraint::PrimaryKey { .. })
960            })
961    }
962
963    /// Checks the rules a generated column has to obey.
964    ///
965    /// A generated column may not carry a `DEFAULT` - it has no value of its
966    /// own to fall back to - may not be part of a rowid table's `PRIMARY KEY`,
967    /// and may not refer to a column that does not exist or to itself. The
968    /// cycle check is the one that matters: without it a `CREATE TABLE` that
969    /// describes one is accepted and every later insert recurses.
970    fn check_generated(&self, columns: &[ast::ColumnDef]) -> Result<(), ParseError> {
971        let names: Vec<Vec<u8>> = columns
972            .iter()
973            .map(|column| self.ast.folded(column.name).to_vec())
974            .collect();
975        let mut generated: Vec<(usize, Vec<usize>)> = Vec::new();
976        for (position, column) in columns.iter().enumerate() {
977            let mut expr = None;
978            let mut has_default = false;
979            let mut in_primary_key = false;
980            for (_, constraint) in &column.constraints {
981                match constraint {
982                    ast::ColumnConstraint::Generated { expr: body, .. } => expr = Some(*body),
983                    ast::ColumnConstraint::Default(_) => has_default = true,
984                    ast::ColumnConstraint::PrimaryKey { .. } => in_primary_key = true,
985                    _ => {}
986                }
987            }
988            let Some(expr) = expr else {
989                continue;
990            };
991            let written = String::from_utf8_lossy(self.ast.text(column.name)).into_owned();
992            if has_default {
993                return Err(refused(
994                    format!("cannot use DEFAULT on a generated column: {written}"),
995                    Span::default(),
996                ));
997            }
998            if in_primary_key {
999                return Err(refused(
1000                    format!("generated columns cannot be part of the PRIMARY KEY: {written}"),
1001                    Span::default(),
1002                ));
1003            }
1004            let mut reads = Vec::new();
1005            self.expression_names(expr, &mut reads);
1006            let mut resolved = Vec::new();
1007            for name in &reads {
1008                let Some(found) = names.iter().position(|candidate| candidate == name) else {
1009                    return Err(crate::bind::no_such_column(name, Span::default()));
1010                };
1011                resolved.push(found);
1012            }
1013            generated.push((position, resolved));
1014        }
1015        // A cycle is anything that never becomes computable: repeat the "every
1016        // dependency is settled" pass until it stops making progress, and if
1017        // anything is left it depends on itself, directly or through others.
1018        let mut settled: Vec<usize> = (0..columns.len())
1019            .filter(|position| !generated.iter().any(|(owner, _)| owner == position))
1020            .collect();
1021        let mut pending = generated;
1022        loop {
1023            let before = pending.len();
1024            let mut still = Vec::new();
1025            for (position, reads) in pending {
1026                if reads.iter().all(|read| settled.contains(read)) {
1027                    settled.push(position);
1028                } else {
1029                    still.push((position, reads));
1030                }
1031            }
1032            pending = still;
1033            if pending.is_empty() || pending.len() == before {
1034                break;
1035            }
1036        }
1037        if let Some((position, _)) = pending.first() {
1038            let written = columns
1039                .get(*position)
1040                .map(|column| String::from_utf8_lossy(self.ast.text(column.name)).into_owned())
1041                .unwrap_or_default();
1042            return Err(refused(
1043                format!("generated column loop on {written}"),
1044                Span::default(),
1045            ));
1046        }
1047        Ok(())
1048    }
1049
1050    /// Collects the folded column names an expression mentions.
1051    fn expression_names(&self, expr: ast::ExprId, into: &mut Vec<Vec<u8>>) {
1052        let Some(node) = self.ast.expr(expr) else {
1053            return;
1054        };
1055        if let ast::Expr::Column { column, .. } = node {
1056            let name = self.ast.folded(*column).to_vec();
1057            if !into.contains(&name) {
1058                into.push(name);
1059            }
1060        }
1061        for child in expression_children(self.ast, expr) {
1062            self.expression_names(child, into);
1063        }
1064    }
1065
1066    /// Checks the rules a `STRICT` table adds to its column list.
1067    ///
1068    /// Every column must name one of six types, and the check is on the
1069    /// declared text rather than on the affinity it maps to: `VARCHAR(10)` has
1070    /// TEXT affinity and is still refused, because STRICT is about what was
1071    /// written and not about what it means.
1072    fn check_strict(&self, columns: &[ast::ColumnDef]) -> Result<(), ParseError> {
1073        for column in columns {
1074            let Some(declared) = column.declared_type.as_ref() else {
1075                return Err(refused(
1076                    format!(
1077                        "missing datatype for {}",
1078                        String::from_utf8_lossy(self.ast.text(column.name))
1079                    ),
1080                    Span::default(),
1081                ));
1082            };
1083            let folded = declared.to_ascii_uppercase();
1084            let allowed = matches!(
1085                folded.as_slice(),
1086                b"INT" | b"INTEGER" | b"REAL" | b"TEXT" | b"BLOB" | b"ANY"
1087            );
1088            if !allowed {
1089                return Err(refused(
1090                    format!(
1091                        "unknown datatype for {}: \"{}\"",
1092                        String::from_utf8_lossy(self.ast.text(column.name)),
1093                        String::from_utf8_lossy(declared)
1094                    ),
1095                    Span::default(),
1096                ));
1097            }
1098        }
1099        Ok(())
1100    }
1101
1102    /// Binds an `ANALYZE`.
1103    ///
1104    /// A bare `ANALYZE` measures everything; one with a name measures that
1105    /// object. SQLite accepts a database name, an index name or a table name in
1106    /// the same position and works out which it is, and so does this: the name
1107    /// is resolved against the tables, then the indexes, and only then refused.
1108    fn bind_analyze(
1109        &mut self,
1110        database: Option<ast::NameId>,
1111        name: Option<ast::NameId>,
1112    ) -> Result<Directive, ParseError> {
1113        let Some(name) = name else {
1114            // A bare `ANALYZE` is every database but `temp`, which is SQLite's
1115            // `sqlite3Analyze`.
1116            let temp = self.catalog.database_index(b"temp");
1117            for index in 0..self.catalog.database_count() {
1118                if Some(index) != temp {
1119                    self.record_write_dependency(index);
1120                }
1121            }
1122            return Ok(Directive::Analyze {
1123                database: 0,
1124                table: None,
1125                every_schema: true,
1126            });
1127        };
1128        let folded = self.ast.folded(name).to_vec();
1129        // **An unqualified name may be a database's.** `ANALYZE aux` measures
1130        // every table in `aux`; resolving the name as a table in `main` first
1131        // made it "no such table: aux".
1132        if database.is_none() {
1133            if let Some(index) = self.catalog.database_index(&folded) {
1134                self.record_write_dependency(index);
1135                return Ok(Directive::Analyze {
1136                    database: index,
1137                    table: None,
1138                    every_schema: false,
1139                });
1140            }
1141        }
1142        // An unqualified table or index is searched for in every database, in
1143        // the usual order; a qualified one only in its own. An index is looked
1144        // for first, as SQLite does, and is passed on by its own name, because
1145        // `ANALYZE ix` measures that index alone.
1146        let schema_name = match database {
1147            Some(_) => {
1148                let index = self.resolve_database(database)?;
1149                Some(self.catalog.database_name(index).to_vec())
1150            }
1151            None => None,
1152        };
1153        let found = self
1154            .catalog
1155            .find_index(schema_name.as_deref(), &folded)
1156            .map(|(table, index)| (table.database, index.name.clone()))
1157            .or_else(|| {
1158                self.catalog
1159                    .find_table(schema_name.as_deref(), &folded)
1160                    .map(|table| (table.database, table.name.clone()))
1161            });
1162        let Some((index, table)) = found else {
1163            return Err(no_such_table(self.ast.text(name), Span::default()));
1164        };
1165        self.record_write_dependency(index);
1166        Ok(Directive::Analyze {
1167            database: index,
1168            table: Some(table),
1169            every_schema: false,
1170        })
1171    }
1172
1173    /// Binds an `ALTER TABLE`.
1174    ///
1175    /// Every refusal SQLite makes is made here, where the catalog is available,
1176    /// rather than half-way through rewriting the schema: a rename that is
1177    /// going to fail must fail before anything has been written.
1178    fn bind_alter(
1179        &mut self,
1180        database: Option<ast::NameId>,
1181        table: ast::NameId,
1182        action: &ast::AlterAction,
1183    ) -> Result<Directive, ParseError> {
1184        // **An unqualified `ALTER TABLE` searches `temp` before `main`
1185        // (task-2061).** This resolved every unqualified name through
1186        // `resolve_database(None)`, which answers `main` and nothing else, and
1187        // then looked the table up in `main` alone - so
1188        // `CREATE TEMP TABLE t (a, b); ALTER TABLE t ADD COLUMN c` was
1189        // `no such table: t` when nothing called `t` was in `main`, and altered
1190        // `main.t` when something was. SQLite searches `temp` first for an
1191        // unqualified name in `ALTER TABLE` exactly as it does in a `SELECT`,
1192        // and `find_table(None, ...)` is already that search - the same one
1193        // every query goes through - so the schema comes back from the table
1194        // that was found rather than being decided before the search.
1195        let written = match database {
1196            // A qualifier still has to name a database that exists, and it
1197            // still restricts the search to that one.
1198            Some(_) => Some(
1199                self.catalog
1200                    .database_name(self.resolve_database(database)?)
1201                    .to_vec(),
1202            ),
1203            None => None,
1204        };
1205        let folded = self.ast.folded(table).to_vec();
1206        let Some(target) = self
1207            .catalog
1208            .find_table(written.as_deref(), &folded)
1209            .cloned()
1210        else {
1211            return Err(no_such_table(self.ast.text(table), Span::default()));
1212        };
1213        let index = target.database;
1214        let database_name = self.catalog.database_name(index).to_vec();
1215        if target.kind != crate::catalog_view::TableKind::Table {
1216            return Err(refused(
1217                format!(
1218                    "cannot alter {}: not a table",
1219                    String::from_utf8_lossy(&target.name)
1220                ),
1221                Span::default(),
1222            ));
1223        }
1224        if target.folded.starts_with(b"sqlite_") {
1225            return Err(refused(
1226                format!(
1227                    "table {} may not be altered",
1228                    String::from_utf8_lossy(&target.name)
1229                ),
1230                Span::default(),
1231            ));
1232        }
1233        self.record_write_dependency(index);
1234        let kind = match action {
1235            ast::AlterAction::RenameTo(name) => {
1236                let to = self.ast.text(*name).to_vec();
1237                let to_folded = self.ast.folded(*name).to_vec();
1238                if self
1239                    .catalog
1240                    .find_table(Some(database_name.as_slice()), &to_folded)
1241                    .is_some()
1242                {
1243                    return Err(refused(
1244                        format!(
1245                            "there is already another table or index with this name: {}",
1246                            String::from_utf8_lossy(&to)
1247                        ),
1248                        Span::default(),
1249                    ));
1250                }
1251                AlterKind::RenameTable { to }
1252            }
1253            ast::AlterAction::RenameColumn { from, to } => {
1254                let from_folded = self.ast.folded(*from).to_vec();
1255                let Some(position) = target.column_position(&from_folded) else {
1256                    return Err(crate::bind::no_such_column(
1257                        self.ast.text(*from),
1258                        Span::default(),
1259                    ));
1260                };
1261                let to_folded = self.ast.folded(*to).to_vec();
1262                if target.column_position(&to_folded).is_some() {
1263                    return Err(refused(
1264                        format!(
1265                            "duplicate column name: {}",
1266                            String::from_utf8_lossy(self.ast.text(*to))
1267                        ),
1268                        Span::default(),
1269                    ));
1270                }
1271                let stored = target
1272                    .column(position)
1273                    .map(|column| column.name.clone())
1274                    .unwrap_or_default();
1275                AlterKind::RenameColumn {
1276                    from: stored,
1277                    to: self.ast.text(*to).to_vec(),
1278                }
1279            }
1280            ast::AlterAction::AddColumn(definition) => {
1281                let risk = self.check_added_column(&target, definition)?;
1282                AlterKind::AddColumn {
1283                    start: definition.span.start,
1284                    end: definition.span.end,
1285                    risk,
1286                }
1287            }
1288            ast::AlterAction::DropColumn(name) => {
1289                let folded = self.ast.folded(*name).to_vec();
1290                let Some(position) = target.column_position(&folded) else {
1291                    return Err(crate::bind::no_such_column(
1292                        self.ast.text(*name),
1293                        Span::default(),
1294                    ));
1295                };
1296                self.check_dropped_column(&target, position)?;
1297                let stored = target
1298                    .column(position)
1299                    .map(|column| column.name.clone())
1300                    .unwrap_or_default();
1301                AlterKind::DropColumn {
1302                    name: stored,
1303                    position,
1304                }
1305            }
1306        };
1307        Ok(Directive::Alter {
1308            database: index,
1309            table: target.name.clone(),
1310            action: kind,
1311        })
1312    }
1313
1314    /// Checks what `ADD COLUMN` may not add.
1315    ///
1316    /// Every one of these is refused because the existing rows have no value
1317    /// for the new column and cannot be given one: a `PRIMARY KEY` or `UNIQUE`
1318    /// column would need an index built over values that are all the same
1319    /// default, and a `NOT NULL` column with no default would make every
1320    /// existing row violate its own table.
1321    fn check_added_column(
1322        &self,
1323        table: &crate::catalog_view::TableInfo,
1324        definition: &ast::ColumnDef,
1325    ) -> Result<AddedColumnRisk, ParseError> {
1326        let folded = self.ast.folded(definition.name).to_vec();
1327        if table.column_position(&folded).is_some() {
1328            return Err(refused(
1329                format!(
1330                    "duplicate column name: {}",
1331                    String::from_utf8_lossy(self.ast.text(definition.name))
1332                ),
1333                Span::default(),
1334            ));
1335        }
1336        let mut not_null = false;
1337        let mut has_default = false;
1338        let mut constant = true;
1339        let mut generated_stored = false;
1340        for (_, constraint) in &definition.constraints {
1341            match constraint {
1342                ast::ColumnConstraint::PrimaryKey { .. } => {
1343                    return Err(schema_refused(
1344                        "Cannot add a PRIMARY KEY column",
1345                        Span::default(),
1346                    ))
1347                }
1348                ast::ColumnConstraint::Unique(_) => {
1349                    return Err(schema_refused(
1350                        "Cannot add a UNIQUE column",
1351                        Span::default(),
1352                    ))
1353                }
1354                ast::ColumnConstraint::NotNull(_) => not_null = true,
1355                ast::ColumnConstraint::Default(expr) => {
1356                    has_default = true;
1357                    if !self.constant_default(*expr) {
1358                        constant = false;
1359                    }
1360                }
1361                ast::ColumnConstraint::Generated { stored, .. } if *stored => {
1362                    generated_stored = true;
1363                }
1364                _ => {}
1365            }
1366        }
1367        Ok(AddedColumnRisk {
1368            null_without_default: not_null && !has_default,
1369            non_constant_default: !constant,
1370            generated_stored,
1371        })
1372    }
1373
1374    /// Returns whether a `DEFAULT` is a constant an existing row can be given.
1375    fn constant_default(&self, expr: ast::ExprId) -> bool {
1376        match self.ast.expr(expr) {
1377            Some(ast::Expr::Literal(_)) => true,
1378            Some(ast::Expr::Unary { operand, .. }) => self.constant_default(*operand),
1379            _ => false,
1380        }
1381    }
1382
1383    /// Checks what `DROP COLUMN` may not drop.
1384    fn check_dropped_column(
1385        &self,
1386        table: &crate::catalog_view::TableInfo,
1387        position: u16,
1388    ) -> Result<(), ParseError> {
1389        let named = table
1390            .column(position)
1391            .map(|column| String::from_utf8_lossy(&column.name).into_owned())
1392            .unwrap_or_default();
1393        if table.columns.len() <= 1 {
1394            return Err(refused(
1395                format!("cannot drop column \"{named}\": no other columns exist"),
1396                Span::default(),
1397            ));
1398        }
1399        if table.rowid_alias == Some(position)
1400            || table
1401                .column(position)
1402                .is_some_and(|column| column.primary_key_position.is_some())
1403        {
1404            return Err(refused(
1405                format!("cannot drop column \"{named}\": PRIMARY KEY"),
1406                Span::default(),
1407            ));
1408        }
1409        let indexed = table
1410            .indexes
1411            .iter()
1412            .any(|index| index.columns.iter().any(|key| key.column == Some(position)));
1413        if indexed {
1414            return Err(refused(
1415                format!("cannot drop column \"{named}\": indexed"),
1416                Span::default(),
1417            ));
1418        }
1419        // A CHECK or a generated column that reads it would be left naming a
1420        // column that is gone, and the table would stop loading.
1421        let folded = table
1422            .column(position)
1423            .map(|column| column.folded.clone())
1424            .unwrap_or_default();
1425        let referenced = table
1426            .checks
1427            .iter()
1428            .any(|check| mentions_name(&check.expr_sql, &folded))
1429            || table.columns.iter().enumerate().any(|(other, column)| {
1430                other != usize::from(position)
1431                    && column
1432                        .generated_sql
1433                        .as_ref()
1434                        .is_some_and(|sql| mentions_name(sql, &folded))
1435            });
1436        if referenced {
1437            return Err(refused(
1438                format!(
1439                    "error in table {}: cannot drop column \"{named}\"",
1440                    String::from_utf8_lossy(&table.name)
1441                ),
1442                Span::default(),
1443            ));
1444        }
1445        Ok(())
1446    }
1447
1448    /// Binds a `REINDEX`.
1449    ///
1450    /// The name is a collation, a table or an index, and SQLite works out which
1451    /// from what it finds - so the resolution order is the same here. A bare
1452    /// `REINDEX` rebuilds everything, which is the form that matters: it is what
1453    /// a person runs after a collation's definition has changed underneath an
1454    /// index that was built with the old one.
1455    fn bind_reindex(
1456        &mut self,
1457        database: Option<ast::NameId>,
1458        name: Option<ast::NameId>,
1459    ) -> Result<Directive, ParseError> {
1460        let index = self.resolve_database(database)?;
1461        self.record_write_dependency(index);
1462        // **An unqualified name means every database**, which is SQLite's
1463        // `sqlite3Reindex`: a bare `REINDEX` and a collation rebuild the
1464        // indexes of every database, and a table or index name is looked for
1465        // in all of them. Reading only `main` made `REINDEX ix` on a temporary
1466        // table's index "unable to identify the object to be reindexed".
1467        let schema_name = database.map(|_| self.catalog.database_name(index).to_vec());
1468        let qualified = database.is_some();
1469        let every_index = |catalog: &dyn CatalogView| -> Vec<Vec<u8>> {
1470            let tables = if qualified {
1471                catalog.tables_of(index)
1472            } else {
1473                catalog.every_table()
1474            };
1475            tables
1476                .into_iter()
1477                .flat_map(|table| table.indexes.iter())
1478                .map(|entry| entry.name.clone())
1479                .filter(|name| !name.is_empty())
1480                .collect()
1481        };
1482        let Some(name) = name else {
1483            return Ok(Directive::Reindex {
1484                database: index,
1485                indexes: every_index(self.catalog),
1486            });
1487        };
1488        let folded = self.ast.folded(name).to_vec();
1489        if let Some(table) = self.catalog.find_table(schema_name.as_deref(), &folded) {
1490            return Ok(Directive::Reindex {
1491                database: index,
1492                indexes: table
1493                    .indexes
1494                    .iter()
1495                    .map(|entry| entry.name.clone())
1496                    .collect(),
1497            });
1498        }
1499        if let Some((_, entry)) = self.catalog.find_index(schema_name.as_deref(), &folded) {
1500            return Ok(Directive::Reindex {
1501                database: index,
1502                indexes: vec![entry.name.clone()],
1503            });
1504        }
1505        // A collation name rebuilds every index ordered by it. An unknown name
1506        // is an error, and SQLite reports it against the collation because that
1507        // is the last thing it tried.
1508        if Collation::from_name(core::str::from_utf8(&folded).unwrap_or("")).is_some() {
1509            let wanted = folded.clone();
1510            let tables = if qualified {
1511                self.catalog.tables_of(index)
1512            } else {
1513                self.catalog.every_table()
1514            };
1515            let indexes = tables
1516                .into_iter()
1517                .flat_map(|table| table.indexes.iter())
1518                .filter(|entry| {
1519                    entry
1520                        .columns
1521                        .iter()
1522                        .any(|key| key.collation.eq_ignore_ascii_case(&wanted))
1523                })
1524                .map(|entry| entry.name.clone())
1525                .collect();
1526            return Ok(Directive::Reindex {
1527                database: index,
1528                indexes,
1529            });
1530        }
1531        Err(no_such_collation_sequence(
1532            self.ast.text(name),
1533            Span::default(),
1534        ))
1535    }
1536
1537    /// Binds a `VACUUM`.
1538    fn bind_vacuum(
1539        &mut self,
1540        database: Option<ast::NameId>,
1541        into: Option<ast::ExprId>,
1542    ) -> Result<Directive, ParseError> {
1543        let target = match into {
1544            Some(expr) => {
1545                Some(self.literal_path(expr, "VACUUM INTO with a file name that is not a literal")?)
1546            }
1547            None => None,
1548        };
1549        let index = self.resolve_database(database)?;
1550        self.record_write_dependency(index);
1551        Ok(Directive::Vacuum {
1552            database: index,
1553            into: target,
1554        })
1555    }
1556
1557    /// Binds an `ATTACH`.
1558    ///
1559    /// Every operand is a literal, the `KEY` included. SQLite evaluates them, and every other
1560    /// value they could produce is a file name computed at run time - a
1561    /// statement that decides which database to open from arithmetic is not a
1562    /// shape worth supporting before it is asked for, and it is one an
1563    /// authorizer could not check.
1564    pub(crate) fn bind_attach(
1565        &mut self,
1566        file: ast::ExprId,
1567        schema: ast::ExprId,
1568        key: Option<ast::ExprId>,
1569    ) -> Result<Directive, ParseError> {
1570        // SQLCipher's documentation writes a raw key as `KEY "x'...'"`, which
1571        // the grammar reads as a double quoted name, so a name is read as its
1572        // text the way `literal_or_name` reads a schema name.
1573        let key = match key.map(|expr| self.ast.expr(expr)) {
1574            None => None,
1575            Some(Some(ast::Expr::Literal(ast::Literal::String(text)))) => Some(text.clone()),
1576            Some(Some(ast::Expr::Column {
1577                table: None,
1578                column,
1579                ..
1580            })) => Some(self.ast.text(*column).to_vec()),
1581            Some(_) => {
1582                return Err(unsupported(
1583                    "an ATTACH KEY that is not a string literal",
1584                    Span::default(),
1585                ))
1586            }
1587        };
1588        Ok(Directive::Attach {
1589            file: self.literal_path(file, "ATTACH with a file name that is not a literal")?,
1590            schema: self.literal_or_name(schema)?,
1591            key,
1592        })
1593    }
1594
1595    /// Binds a `DETACH`.
1596    pub(crate) fn bind_detach(&mut self, schema: ast::ExprId) -> Result<Directive, ParseError> {
1597        Ok(Directive::Detach {
1598            schema: self.literal_or_name(schema)?,
1599        })
1600    }
1601
1602    /// Reads a name written either as a word or as a string.
1603    ///
1604    /// `ATTACH 'file.db' AS aux` and `ATTACH 'file.db' AS 'aux'` name the same
1605    /// schema. The grammar parses that position as an expression, so a bare
1606    /// word arrives as a reference to a column that does not exist - and what
1607    /// the statement meant is the word.
1608    fn literal_or_name(&mut self, expr: ast::ExprId) -> Result<Vec<u8>, ParseError> {
1609        match self.ast.expr(expr) {
1610            Some(ast::Expr::Literal(ast::Literal::String(text))) => Ok(text.clone()),
1611            Some(ast::Expr::Column {
1612                table: None,
1613                column,
1614                ..
1615            }) => Ok(self.ast.text(*column).to_vec()),
1616            _ => Err(unsupported(
1617                "a schema name that is not a word or a string",
1618                Span::default(),
1619            )),
1620        }
1621    }
1622
1623    /// Reads the file name a `VACUUM INTO` or an `ATTACH` was given.
1624    ///
1625    /// A literal only. SQLite evaluates the expression, but every other value
1626    /// it could produce is a file name computed at run time, and a statement
1627    /// that decides which file to open or where to write a copy of the
1628    /// database from arithmetic is not a shape worth supporting before it is
1629    /// asked for.
1630    ///
1631    /// @param expr - the file name operand
1632    /// @param refused - the construct the refusal names when it is not one
1633    fn literal_path(
1634        &mut self,
1635        expr: ast::ExprId,
1636        refused: &'static str,
1637    ) -> Result<Vec<u8>, ParseError> {
1638        match self.ast.expr(expr) {
1639            Some(ast::Expr::Literal(ast::Literal::String(text))) => Ok(text.clone()),
1640            _ => Err(unsupported(refused, Span::default())),
1641        }
1642    }
1643
1644    /// Binds a `CREATE VIEW`.
1645    ///
1646    /// The body is bound here, and thrown away, purely to refuse a view whose
1647    /// query does not resolve. SQLite does the same: the definition is checked
1648    /// when the view is created rather than when it is first read, so a typo
1649    /// fails at `CREATE VIEW` rather than in whatever statement happens to
1650    /// select from it next.
1651    fn bind_create_view(
1652        &mut self,
1653        temporary: bool,
1654        if_not_exists: bool,
1655        database: Option<ast::NameId>,
1656        name: ast::NameId,
1657        columns: &[ast::NameId],
1658        select: ast::SelectId,
1659    ) -> Result<Directive, ParseError> {
1660        let temp = self.temporary_database(temporary, database, false)?;
1661        let index = match temp {
1662            Some(index) => index,
1663            None => self.resolve_database(database)?,
1664        };
1665        let written = self.ast.text(name).to_vec();
1666        if written.to_ascii_lowercase().starts_with(b"sqlite_") {
1667            return Err(refused(
1668                format!(
1669                    "object name reserved for internal use: {}",
1670                    String::from_utf8_lossy(&written)
1671                ),
1672                Span::default(),
1673            ));
1674        }
1675        let folded = self.ast.folded(name).to_vec();
1676        let database_name = self.catalog.database_name(index).to_vec();
1677        let exists = self
1678            .catalog
1679            .find_table(Some(database_name.as_slice()), &folded)
1680            .is_some();
1681        if exists && !if_not_exists {
1682            return Err(refused(
1683                format!("table {} already exists", String::from_utf8_lossy(&written)),
1684                Span::default(),
1685            ));
1686        }
1687        if !exists {
1688            let saved = core::mem::take(&mut self.scopes);
1689            let bound = self.bind_select(select);
1690            self.scopes = saved;
1691            let bound = bound?;
1692            if !columns.is_empty() && columns.len() != bound.columns.len() {
1693                return Err(refused(
1694                    format!(
1695                        "expected {} columns for {} but got {}",
1696                        columns.len(),
1697                        String::from_utf8_lossy(&written),
1698                        bound.columns.len()
1699                    ),
1700                    Span::default(),
1701                ));
1702            }
1703        }
1704        self.record_write_dependency(index);
1705        Ok(Directive::CreateView {
1706            if_not_exists,
1707            database: index,
1708            name: written,
1709            name_offset: self.name_offset(name),
1710            exists,
1711        })
1712    }
1713
1714    /// Refuses the two places `AUTOINCREMENT` may not be written.
1715    ///
1716    /// It counts the rowid the table has handed out, so it needs a rowid to
1717    /// count: only an `INTEGER PRIMARY KEY` column, and never on a table that
1718    /// has no rowid at all. Both messages are the reference's own, because an
1719    /// application that reads them is reading SQLite's.
1720    fn check_autoincrement(
1721        &mut self,
1722        columns: &[ast::ColumnDef],
1723        without_rowid: bool,
1724    ) -> Result<(), ParseError> {
1725        for column in columns {
1726            let declared = column.declared_type.clone().unwrap_or_default();
1727            for (_, constraint) in &column.constraints {
1728                let ast::ColumnConstraint::PrimaryKey {
1729                    autoincrement: true,
1730                    ..
1731                } = constraint
1732                else {
1733                    continue;
1734                };
1735                if without_rowid {
1736                    return Err(refused(
1737                        "AUTOINCREMENT not allowed on WITHOUT ROWID tables",
1738                        Span::default(),
1739                    ));
1740                }
1741                if !declared.eq_ignore_ascii_case(b"integer") {
1742                    return Err(refused(
1743                        "AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY",
1744                        Span::default(),
1745                    ));
1746                }
1747            }
1748        }
1749        Ok(())
1750    }
1751
1752    /// Binds a `CREATE TRIGGER`.
1753    ///
1754    /// The body is bound here, against the table the trigger is attached to, so
1755    /// a trigger that reads a column that does not exist is refused when it is
1756    /// written rather than the first time somebody writes the table. SQLite
1757    /// makes the same promise, and the alternative is a schema that loads and
1758    /// then fails on an unrelated INSERT.
1759    fn bind_create_trigger(
1760        &mut self,
1761        parts: CreateTriggerParts<'_>,
1762    ) -> Result<Directive, ParseError> {
1763        let temp = self.temporary_database(parts.temporary, parts.database, true)?;
1764        // `for_each_row` records whether the words were written, not whether
1765        // the trigger is one: SQLite has only row triggers, an omitted clause
1766        // means FOR EACH ROW, and FOR EACH STATEMENT is a syntax error in the
1767        // parser. There is nothing to refuse here.
1768        let _ = parts.for_each_row;
1769        let index = match temp {
1770            Some(index) => index,
1771            None => self.resolve_database(parts.database)?,
1772        };
1773        let written = self.ast.text(parts.name).to_vec();
1774        if written.to_ascii_lowercase().starts_with(b"sqlite_") {
1775            return Err(refused(
1776                format!(
1777                    "object name reserved for internal use: {}",
1778                    String::from_utf8_lossy(&written)
1779                ),
1780                Span::default(),
1781            ));
1782        }
1783        let folded = self.ast.folded(parts.name).to_vec();
1784        let database_name = self.catalog.database_name(index).to_vec();
1785        let table_folded = self.ast.folded(parts.table).to_vec();
1786        // A trigger created in a named database fires for a table in that
1787        // database. A temporary one fires for whatever the name finds, which
1788        // is the whole point of `CREATE TEMP TRIGGER ... ON t`: the trigger is
1789        // the connection's and the table is everybody's. `ON main.t` names the
1790        // database: a temporary trigger may name any, and any other trigger
1791        // only its own, which is SQLite's `sqlite3FixSrcList`.
1792        let named = match parts.table_database {
1793            Some(id) => {
1794                let at = self.resolve_database(Some(id))?;
1795                if temp.is_none() && at != index {
1796                    return Err(refused(
1797                        format!(
1798                            "trigger {} cannot reference objects in database {}",
1799                            String::from_utf8_lossy(&written),
1800                            String::from_utf8_lossy(self.ast.text(id))
1801                        ),
1802                        Span::default(),
1803                    ));
1804                }
1805                Some(self.catalog.database_name(at).to_vec())
1806            }
1807            None => None,
1808        };
1809        let scope = match &named {
1810            Some(name) => Some(name.as_slice()),
1811            None => temp.map_or(Some(database_name.as_slice()), |_| None),
1812        };
1813        let Some(target) = self.catalog.find_table(scope, &table_folded).cloned() else {
1814            return Err(crate::bind::no_such_table(
1815                self.ast.text(parts.table),
1816                Span::default(),
1817            ));
1818        };
1819        let exists = self
1820            .catalog
1821            .find_trigger(Some(database_name.as_slice()), &folded)
1822            .is_some();
1823        if exists && !parts.if_not_exists {
1824            return Err(refused(
1825                format!(
1826                    "trigger {} already exists",
1827                    String::from_utf8_lossy(&written)
1828                ),
1829                Span::default(),
1830            ));
1831        }
1832        let instead_of = parts.time == Some(ast::TriggerTime::InsteadOf);
1833        match target.kind {
1834            TableKind::View if !instead_of => {
1835                return Err(refused(
1836                    format!(
1837                        "cannot create {} trigger on view: {}",
1838                        if parts.time == Some(ast::TriggerTime::After) {
1839                            "AFTER"
1840                        } else {
1841                            "BEFORE"
1842                        },
1843                        String::from_utf8_lossy(&target.name)
1844                    ),
1845                    Span::default(),
1846                ));
1847            }
1848            TableKind::Table if instead_of => {
1849                return Err(refused(
1850                    format!(
1851                        "cannot create INSTEAD OF trigger on table: {}",
1852                        String::from_utf8_lossy(&target.name)
1853                    ),
1854                    Span::default(),
1855                ));
1856            }
1857            TableKind::Virtual | TableKind::Subquery => {
1858                return Err(unsupported("a trigger on that object", Span::default()));
1859            }
1860            _ => {}
1861        }
1862        // `UPDATE OF a, b` is deliberately *not* checked against the table's
1863        // columns. The pinned build accepts `UPDATE OF nosuchcolumn` and simply
1864        // never fires the trigger, and refusing it here would make inillucent's
1865        // language smaller than the reference's - a schema SQLite wrote that
1866        // inillucent could not load.
1867        // The body is deliberately *not* bound here. SQLite stores a trigger
1868        // whose body names a column that does not exist and reports it on the
1869        // first write that fires it - measured against the pinned build, which
1870        // accepts both `UPDATE OF nosuchcolumn` and a body reading a column the
1871        // table has not got. Refusing either here would leave inillucent unable to
1872        // load a schema SQLite had written.
1873        let _ = (parts.time, parts.when, parts.body);
1874        self.record_write_dependency(index);
1875        Ok(Directive::CreateTrigger {
1876            database: index,
1877            name: written,
1878            name_offset: self.name_offset(parts.name),
1879            table: target.name.clone(),
1880            exists,
1881        })
1882    }
1883
1884    /// Binds a `CREATE INDEX`.
1885    ///
1886    /// @param spec - what the statement named
1887    fn bind_create_index(&mut self, spec: &CreateIndexSpec<'_>) -> Result<Directive, ParseError> {
1888        let CreateIndexSpec {
1889            database,
1890            name,
1891            table,
1892            using,
1893            columns,
1894            settings,
1895            ..
1896        } = *spec;
1897        let unique = spec.unique == Uniqueness::Unique;
1898        let if_not_exists = spec.if_not_exists == IfNotExists::Skip;
1899        // **A `WHERE` is carried in the statement text, not in this
1900        // directive.** The engine re-parses the canonical SQL it stores -
1901        // `index_from_create_sql` already puts the predicate on
1902        // `IndexInfo::partial_sql` - so a field here would be a second copy to
1903        // keep in step. A predicate that names a column the table has not got
1904        // is refused when the index is built, by the query that fills it.
1905        // Only one module can back an index, and naming another is refused here
1906        // rather than accepted and ignored - an index that silently was not the
1907        // structure it asked for is the shape of wrong answer this ticket keeps
1908        // finding.
1909        let using = match using {
1910            None => None,
1911            Some(named) => {
1912                let folded = self.ast.folded(named).to_vec();
1913                // Two structures, and both are real: `inillucent_hnsw` is the
1914                // graph the retrieval engine builds, and `ivfflat` is the
1915                // inverted file pgvector's other index type is - k-means
1916                // centroids and a list per centroid, probed `probes` deep.
1917                // Anything else is refused rather than accepted and ignored:
1918                // an index that silently was not the structure it asked for is
1919                // the shape of wrong answer this ticket keeps finding.
1920                if folded != b"inillucent_hnsw" && folded != b"ivfflat" {
1921                    return Err(unsupported(
1922                        "an index USING a module other than inillucent_hnsw or ivfflat",
1923                        Span::default(),
1924                    ));
1925                }
1926                Some(folded)
1927            }
1928        };
1929        let parsed_settings = index_settings(&using, settings)?;
1930        let table_folded = self.ast.folded(table).to_vec();
1931        // **An unqualified index goes where its table is.** SQLite looks the
1932        // table up in the usual order, `temp` first, and creates the index in
1933        // the schema it found the table in. Taking an unqualified index to mean
1934        // `main` made `CREATE TEMP TABLE t(a); CREATE INDEX i ON t(a)` report
1935        // "no such table: t".
1936        let index = match database {
1937            Some(_) => self.resolve_database(database)?,
1938            None => match self.catalog.find_table(None, &table_folded) {
1939                Some(found) => found.database,
1940                None => return Err(no_such_table(self.ast.text(table), Span::default())),
1941            },
1942        };
1943        let database_name = self.catalog.database_name(index).to_vec();
1944        let Some(target) = self
1945            .catalog
1946            .find_table(Some(database_name.as_slice()), &table_folded)
1947            .cloned()
1948        else {
1949            return Err(no_such_table(self.ast.text(table), Span::default()));
1950        };
1951        let written = self.ast.text(name).to_vec();
1952        let folded = self.ast.folded(name).to_vec();
1953        let exists = self
1954            .catalog
1955            .find_index(Some(database_name.as_slice()), &folded)
1956            .is_some();
1957        if exists && !if_not_exists {
1958            return Err(refused(
1959                format!("index {} already exists", String::from_utf8_lossy(&written)),
1960                Span::default(),
1961            ));
1962        }
1963        let mut keys = Vec::with_capacity(columns.len());
1964        for column in columns {
1965            // `CREATE INDEX x ON t(b COLLATE NOCASE DESC)` parses the collation
1966            // into the *expression*, because that is where the grammar puts a
1967            // `COLLATE` that follows a value. It is still an index on a bare
1968            // column, and treating it as one is the difference between
1969            // supporting the everyday form and refusing it as an expression.
1970            let (expr, written_collation) = match self.ast.expr(column.expr) {
1971                Some(ast::Expr::Collate { operand, collation }) => {
1972                    (self.ast.expr(*operand), Some(*collation))
1973                }
1974                other => (other, column.collation),
1975            };
1976            // A key that is not a bare column is an expression, and is carried
1977            // as the source text the engine re-parses. Its collation is BINARY
1978            // unless the statement named one: there is no column to inherit
1979            // from.
1980            let named = match expr {
1981                Some(ast::Expr::Column {
1982                    table: None,
1983                    column: name,
1984                    ..
1985                }) => Some(*name),
1986                _ => None,
1987            };
1988            let Some(name) = named else {
1989                let collation = match written_collation {
1990                    Some(collation) => self.ast.folded(collation).to_vec(),
1991                    None => b"binary".to_vec(),
1992                };
1993                keys.push(IndexKeyColumn {
1994                    column: None,
1995                    expr_sql: Some(self.ast.expr_span(column.expr).slice(self.source).to_vec()),
1996                    collation,
1997                    descending: column.order == ast::SortOrder::Descending,
1998                });
1999                continue;
2000            };
2001            let folded = self.ast.folded(name).to_vec();
2002            let Some(position) = target.column_position(&folded) else {
2003                return Err(crate::bind::no_such_column(
2004                    self.ast.text(name),
2005                    Span::default(),
2006                ));
2007            };
2008            let collation = match written_collation {
2009                Some(collation) => self.ast.folded(collation).to_vec(),
2010                None => target
2011                    .column(position)
2012                    .map(|column| column.collation.clone())
2013                    .unwrap_or_else(|| b"binary".to_vec()),
2014            };
2015            keys.push(IndexKeyColumn {
2016                column: Some(position),
2017                expr_sql: None,
2018                collation,
2019                descending: column.order == ast::SortOrder::Descending,
2020            });
2021        }
2022        self.record_write_dependency(index);
2023        Ok(Directive::CreateIndex {
2024            unique,
2025            if_not_exists,
2026            database: index,
2027            name: written,
2028            name_offset: self.name_offset(name),
2029            table: target.name.clone(),
2030            table_root: target.root,
2031            using,
2032            columns: keys,
2033            settings: parsed_settings,
2034            exists,
2035        })
2036    }
2037
2038    /// Binds a `DROP TABLE` or `DROP INDEX`.
2039    fn bind_drop(
2040        &mut self,
2041        kind: ObjectKind,
2042        if_exists: bool,
2043        database: Option<ast::NameId>,
2044        name: ast::NameId,
2045    ) -> Result<Directive, ParseError> {
2046        let written = self.ast.text(name).to_vec();
2047        let folded = self.ast.folded(name).to_vec();
2048        // **An unqualified name is looked for in every database, `temp`
2049        // first,** which is SQLite's `sqlite3LocateTable` order. Taking it to
2050        // mean `main` made `DROP TABLE s` "no such table" for a temporary `s`,
2051        // and dropped `main.s` where SQLite drops the temporary `s` that
2052        // shadows it.
2053        let index = match database {
2054            Some(_) => self.resolve_database(database)?,
2055            None => self.unqualified_home(kind, &folded).unwrap_or(0),
2056        };
2057        let database_name = self.catalog.database_name(index).to_vec();
2058        self.record_write_dependency(index);
2059        if kind == ObjectKind::Trigger {
2060            // A trigger owns no B-tree either, so dropping one is its schema row
2061            // and nothing else.
2062            let exists = self
2063                .catalog
2064                .find_trigger(Some(database_name.as_slice()), &folded)
2065                .is_some();
2066            if !exists && !if_exists {
2067                return Err(refused(
2068                    format!("no such trigger: {}", String::from_utf8_lossy(&written)),
2069                    Span::default(),
2070                ));
2071            }
2072            return Ok(Directive::Drop {
2073                kind,
2074                if_exists,
2075                database: index,
2076                name: written,
2077                root: 0,
2078                index_roots: Vec::new(),
2079                exists,
2080            });
2081        }
2082        if kind == ObjectKind::View {
2083            // A view owns no B-tree, so dropping one is the schema row and
2084            // nothing else - and it must refuse a table, because `DROP VIEW t`
2085            // on a table is an error rather than a drop.
2086            let found = self
2087                .catalog
2088                .find_table(Some(database_name.as_slice()), &folded)
2089                .cloned();
2090            let exists = found
2091                .as_ref()
2092                .is_some_and(|table| table.kind == crate::catalog_view::TableKind::View);
2093            if !exists && !if_exists {
2094                return Err(refused(
2095                    format!("no such view: {}", String::from_utf8_lossy(&written)),
2096                    Span::default(),
2097                ));
2098            }
2099            return Ok(Directive::Drop {
2100                kind,
2101                if_exists,
2102                database: index,
2103                name: written,
2104                root: 0,
2105                index_roots: Vec::new(),
2106                exists,
2107            });
2108        }
2109        if kind == ObjectKind::Table {
2110            let found = self
2111                .catalog
2112                .find_table(Some(database_name.as_slice()), &folded)
2113                .cloned();
2114            let Some(table) = found else {
2115                if if_exists {
2116                    return Ok(Directive::Drop {
2117                        kind,
2118                        if_exists,
2119                        database: index,
2120                        name: written,
2121                        root: 0,
2122                        index_roots: Vec::new(),
2123                        exists: false,
2124                    });
2125                }
2126                return Err(no_such_table(&written, Span::default()));
2127            };
2128            if table.kind == crate::catalog_view::TableKind::View {
2129                return Err(refused(
2130                    format!(
2131                        "use DROP VIEW to delete view {}",
2132                        String::from_utf8_lossy(&written)
2133                    ),
2134                    Span::default(),
2135                ));
2136            }
2137            // A WITHOUT ROWID table's primary key *is* the table's own b-tree,
2138            // so its entry names the same root. Freeing it twice frees a page
2139            // that is already on the free list, which reads back as a malformed
2140            // database.
2141            let index_roots = table
2142                .indexes
2143                .iter()
2144                .map(|index| index.root)
2145                .filter(|root| *root != 0 && *root != table.root)
2146                .collect();
2147            return Ok(Directive::Drop {
2148                kind,
2149                if_exists,
2150                database: index,
2151                name: written,
2152                root: table.root,
2153                index_roots,
2154                exists: true,
2155            });
2156        }
2157        let found = self.find_index_root(index, &folded);
2158        let Some(root) = found else {
2159            if if_exists {
2160                return Ok(Directive::Drop {
2161                    kind,
2162                    if_exists,
2163                    database: index,
2164                    name: written,
2165                    root: 0,
2166                    index_roots: Vec::new(),
2167                    exists: false,
2168                });
2169            }
2170            return Err(refused(
2171                format!("no such index: {}", String::from_utf8_lossy(&written)),
2172                Span::default(),
2173            ));
2174        };
2175        Ok(Directive::Drop {
2176            kind,
2177            if_exists,
2178            database: index,
2179            name: written,
2180            root,
2181            index_roots: Vec::new(),
2182            exists: true,
2183        })
2184    }
2185
2186    /// Returns the database an unqualified object name resolves to.
2187    ///
2188    /// `None` when no database holds an object of that kind by that name, so
2189    /// the caller reports it against `main` as before.
2190    ///
2191    /// @param kind - what sort of object the statement names
2192    /// @param folded - the object's folded name
2193    fn unqualified_home(&self, kind: ObjectKind, folded: &[u8]) -> Option<usize> {
2194        match kind {
2195            ObjectKind::Trigger => self
2196                .catalog
2197                .find_trigger(None, folded)
2198                .map(|(table, _)| table.database),
2199            ObjectKind::Index => self
2200                .catalog
2201                .find_index(None, folded)
2202                .map(|(table, _)| table.database),
2203            _ => self
2204                .catalog
2205                .find_table(None, folded)
2206                .map(|table| table.database),
2207        }
2208    }
2209
2210    /// Binds a `PRAGMA`.
2211    fn bind_pragma(
2212        &mut self,
2213        database: Option<ast::NameId>,
2214        name: ast::NameId,
2215        value: &ast::PragmaValue,
2216    ) -> Result<Directive, ParseError> {
2217        let argument = match value {
2218            ast::PragmaValue::None => None,
2219            ast::PragmaValue::Name(name) => {
2220                Some(PragmaArgument::Name(self.ast.text(*name).to_vec()))
2221            }
2222            ast::PragmaValue::Value(expr) => Some(PragmaArgument::Value(self.bind_expr(*expr)?)),
2223        };
2224        let database = match database {
2225            Some(id) => Some(self.resolve_database(Some(id))?),
2226            None => None,
2227        };
2228        Ok(Directive::Pragma {
2229            database,
2230            name: self.ast.folded(name).to_vec(),
2231            argument,
2232        })
2233    }
2234
2235    /// Returns the temporary database's number when `TEMP` was written.
2236    ///
2237    /// A temporary table's or view's name may be qualified only by `temp`:
2238    /// `CREATE TEMP TABLE main.t` says two different things about where the
2239    /// table goes, and SQLite refuses it rather than picking one, while
2240    /// `CREATE TEMP TABLE temp.t` says the same thing twice and SQLite accepts
2241    /// it. A temporary trigger takes no qualifier at all, which is SQLite's
2242    /// rule in `sqlite3BeginTrigger`.
2243    ///
2244    /// @param temporary - whether `TEMP` was written
2245    /// @param database - the qualifier, when one was written
2246    /// @param trigger - whether the object is a trigger
2247    fn temporary_database(
2248        &self,
2249        temporary: bool,
2250        database: Option<ast::NameId>,
2251        trigger: bool,
2252    ) -> Result<Option<usize>, ParseError> {
2253        if !temporary {
2254            return Ok(None);
2255        }
2256        if let Some(id) = database {
2257            if trigger {
2258                return Err(refused(
2259                    "temporary trigger may not have qualified name",
2260                    Span::default(),
2261                ));
2262            }
2263            if self.ast.folded(id) != b"temp" {
2264                return Err(refused(
2265                    "temporary table name must be unqualified",
2266                    Span::default(),
2267                ));
2268            }
2269        }
2270        self.catalog
2271            .database_index(b"temp")
2272            .map(Some)
2273            .ok_or_else(|| refused("no temporary database", Span::default()))
2274    }
2275
2276    /// Resolves a schema qualifier to an attached database index.
2277    fn resolve_database(&self, database: Option<ast::NameId>) -> Result<usize, ParseError> {
2278        let Some(id) = database else {
2279            return Ok(0);
2280        };
2281        let folded = self.ast.folded(id);
2282        self.catalog.database_index(folded).ok_or_else(|| {
2283            refused(
2284                format!(
2285                    "unknown database {}",
2286                    String::from_utf8_lossy(self.ast.text(id))
2287                ),
2288                Span::default(),
2289            )
2290        })
2291    }
2292
2293    /// Returns the byte an identifier starts at in the statement's source.
2294    ///
2295    /// The canonical `sqlite_schema` text is the statement from its object
2296    /// name onward, which is how `IF NOT EXISTS` and the schema qualifier come
2297    /// to be missing from what SQLite stores. Slicing the source is the only
2298    /// way to reproduce that exactly; rendering the tree back would normalise
2299    /// whitespace and quoting the user chose.
2300    fn name_offset(&self, name: ast::NameId) -> u32 {
2301        self.ast.name(name).map_or(0, |name| name.span.start)
2302    }
2303
2304    /// Returns an index's root page, searching every table of a database.
2305    fn find_index_root(&self, database: usize, folded: &[u8]) -> Option<u32> {
2306        let name = self.catalog.database_name(database).to_vec();
2307        self.catalog
2308            .find_index(Some(name.as_slice()), folded)
2309            .map(|(_, index)| index.root)
2310    }
2311}
2312
2313/// Returns a column name as it can be written back into a `CREATE` statement.
2314///
2315/// A name a query invented - `SELECT 1` reports the column as `1` - is not an
2316/// identifier, so it is quoted the way SQLite quotes it: `CREATE TABLE w("1")`.
2317///
2318/// @param name - the column's name as the query reports it
2319fn quoted_name(name: &[u8]) -> Vec<u8> {
2320    let plain = !name.is_empty()
2321        && !name.first().is_some_and(u8::is_ascii_digit)
2322        && name
2323            .iter()
2324            .all(|byte| byte.is_ascii_alphanumeric() || *byte == b'_');
2325    if plain {
2326        return name.to_vec();
2327    }
2328    let mut out = Vec::with_capacity(name.len().saturating_add(2));
2329    out.push(b'"');
2330    for byte in name {
2331        if *byte == b'"' {
2332            out.push(b'"');
2333        }
2334        out.push(*byte);
2335    }
2336    out.push(b'"');
2337    out
2338}
2339
2340/// Returns the type name a `CREATE TABLE ... AS SELECT` writes for a column.
2341///
2342/// The affinity's own name, with the leading space, exactly as SQLite writes
2343/// it: BLOB affinity - which is what a column with no declared type has -
2344/// writes nothing at all, so the copy of an untyped column is untyped.
2345///
2346/// @param declared - the source column's declared type, as written
2347fn affinity_type(declared: &[u8]) -> &'static [u8] {
2348    match inillucent_value::affinity::for_column(declared) {
2349        inillucent_value::affinity::Affinity::Blob => b"",
2350        inillucent_value::affinity::Affinity::Text => b" TEXT",
2351        inillucent_value::affinity::Affinity::Integer => b" INT",
2352        inillucent_value::affinity::Affinity::Real => b" REAL",
2353        inillucent_value::affinity::Affinity::Numeric
2354        | inillucent_value::affinity::Affinity::FlexNum => b" NUM",
2355    }
2356}
2357
2358/// Returns the width SQLite counts an identifier as when it decides whether to
2359/// write a `CREATE TABLE ... AS SELECT`'s columns one per line.
2360///
2361/// Its own `identLength`: the name plus the two quotes it might need, plus one
2362/// for each quote inside it that would have to be doubled. The rule that reads
2363/// it is "under fifty, one line", and reproducing both is what makes the stored
2364/// declaration byte-identical rather than merely equivalent.
2365///
2366/// @param name - the identifier
2367fn identifier_width(name: &[u8]) -> usize {
2368    name.len()
2369        .saturating_add(2)
2370        .saturating_add(name.iter().filter(|byte| **byte == b'"').count())
2371}
2372
2373/// The storage parameters `CREATE INDEX ... WITH ( ... )` accepts.
2374///
2375/// One entry per name the vector index understands, with the store option it
2376/// becomes. **A name that is not here is refused rather than ignored**, which is
2377/// the same rule `USING` follows a few lines above and for the same reason: an
2378/// index that quietly was not built the way it was asked to be is a wrong answer
2379/// nobody can see.
2380const INDEX_SETTINGS: [(&str, &str); 10] = [
2381    // The graph's own three, spelled as pgvector spells them.
2382    ("m", "m"),
2383    ("ef_construction", "ef_construction"),
2384    ("ef_search", "ef_search"),
2385    // Whether a query walks the graph (`approximate`, the default for an
2386    // `inillucent_hnsw` index) or compares every vector (`exact`). The store
2387    // validates the value, so `mode = 'fast'` is refused by name.
2388    ("mode", "mode"),
2389    // The distance the index is built for. pgvector puts this in an operator
2390    // class - `USING hnsw (v vector_l2_ops)` - and names it here as well.
2391    ("metric", "metric"),
2392    ("distance", "metric"),
2393    // How many threads the build uses, and how far behind the table the index
2394    // may fall before it is rebuilt.
2395    ("threads", "threads"),
2396    ("compact", "compact"),
2397    // The two an `ivfflat` has: how many centroids it clusters into, and how
2398    // many of those lists a query reads.
2399    ("lists", "lists"),
2400    ("probes", "probes"),
2401];
2402
2403/// Checks `WITH ( ... )` against the structure that will read it.
2404///
2405/// Returns the settings as folded `(name, value)` pairs, in the order written.
2406/// A plain `CREATE INDEX` may not carry any: a b-tree has no parameters, and
2407/// accepting them would mean accepting a setting nothing reads.
2408///
2409/// @param using - the module the index named, when it named one
2410/// @param settings - the raw `name = value` slices
2411fn index_settings(
2412    using: &Option<Vec<u8>>,
2413    settings: &[Vec<u8>],
2414) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ParseError> {
2415    if settings.is_empty() {
2416        return Ok(Vec::new());
2417    }
2418    if using.is_none() {
2419        return Err(unsupported(
2420            "WITH ( ... ) on an index that is not USING a module",
2421            Span::default(),
2422        ));
2423    }
2424    let mut held = Vec::with_capacity(settings.len());
2425    for setting in settings {
2426        let text = String::from_utf8_lossy(setting).to_string();
2427        let Some((name, value)) = text.split_once('=') else {
2428            return Err(refused(
2429                format!("index setting {} is not name = value", text.trim()),
2430                Span::default(),
2431            ));
2432        };
2433        let folded = name.trim().to_ascii_lowercase();
2434        let Some((_, option)) = INDEX_SETTINGS
2435            .iter()
2436            .find(|(known, _)| *known == folded.as_str())
2437        else {
2438            return Err(refused(
2439                format!("no such index setting: {folded}"),
2440                Span::default(),
2441            ));
2442        };
2443        let value = value
2444            .trim()
2445            .trim_matches(|held| held == '\'' || held == '"');
2446        held.push((option.as_bytes().to_vec(), value.as_bytes().to_vec()));
2447    }
2448    Ok(held)
2449}