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