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