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