1use 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
25fn 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
97fn 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
119fn 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
135pub enum BeginKind {
136 Deferred,
138 Immediate,
140 Exclusive,
142}
143
144impl BeginKind {
145 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
166pub struct AddedColumnRisk {
167 pub null_without_default: bool,
169 pub non_constant_default: bool,
171 pub generated_stored: bool,
173}
174
175impl AddedColumnRisk {
176 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#[derive(Clone, Debug, PartialEq, Eq)]
197pub enum AlterKind {
198 RenameTable {
200 to: Vec<u8>,
202 },
203 RenameColumn {
205 from: Vec<u8>,
207 to: Vec<u8>,
209 },
210 AddColumn {
212 start: u32,
220 end: u32,
222 risk: AddedColumnRisk,
224 },
225 DropColumn {
227 name: Vec<u8>,
229 position: u16,
231 },
232}
233
234#[derive(Clone, Debug, PartialEq, Eq)]
236pub struct IndexKeyColumn {
237 pub column: Option<u16>,
245 pub expr_sql: Option<Vec<u8>>,
247 pub collation: Vec<u8>,
249 pub descending: bool,
251}
252
253#[derive(Clone, Debug, PartialEq)]
255pub enum Directive {
256 Begin(BeginKind),
258 Commit,
260 Rollback {
262 savepoint: Option<Vec<u8>>,
264 },
265 Savepoint(Vec<u8>),
267 Release(Vec<u8>),
269 CreateTable {
271 if_not_exists: bool,
273 database: usize,
275 name: Vec<u8>,
277 name_offset: u32,
279 exists: bool,
281 },
282 CreateTableAsSelect {
291 if_not_exists: bool,
293 database: usize,
295 name: Vec<u8>,
297 exists: bool,
299 create_sql: Vec<u8>,
301 select_sql: Vec<u8>,
308 },
309 CreateVirtualTable {
311 if_not_exists: bool,
313 database: usize,
315 name: Vec<u8>,
317 module: Vec<u8>,
319 arguments: Vec<Vec<u8>>,
321 name_offset: u32,
323 exists: bool,
325 },
326 Alter {
328 database: usize,
330 table: Vec<u8>,
332 action: AlterKind,
334 },
335 Reindex {
337 database: usize,
339 indexes: Vec<Vec<u8>>,
341 },
342 Vacuum {
344 database: usize,
346 into: Option<Vec<u8>>,
353 },
354 Attach {
356 file: Vec<u8>,
358 schema: Vec<u8>,
360 },
361 Detach {
363 schema: Vec<u8>,
365 },
366 Analyze {
368 database: usize,
370 table: Option<Vec<u8>>,
372 },
373 CreateView {
375 if_not_exists: bool,
377 database: usize,
379 name: Vec<u8>,
381 name_offset: u32,
383 exists: bool,
385 },
386 CreateTrigger {
388 database: usize,
390 name: Vec<u8>,
392 name_offset: u32,
394 table: Vec<u8>,
396 exists: bool,
398 },
399 CreateIndex {
401 unique: bool,
403 if_not_exists: bool,
405 database: usize,
407 name: Vec<u8>,
409 name_offset: u32,
411 table: Vec<u8>,
413 table_root: u32,
415 using: Option<Vec<u8>>,
417 columns: Vec<IndexKeyColumn>,
419 settings: Vec<(Vec<u8>, Vec<u8>)>,
422 exists: bool,
424 },
425 Drop {
427 kind: ObjectKind,
429 if_exists: bool,
431 database: usize,
433 name: Vec<u8>,
435 root: u32,
437 index_roots: Vec<u32>,
439 exists: bool,
441 },
442 Pragma {
444 database: Option<usize>,
450 name: Vec<u8>,
452 argument: Option<PragmaArgument>,
454 },
455}
456
457#[derive(Clone, Debug, PartialEq)]
459pub enum PragmaArgument {
460 Name(Vec<u8>),
462 Value(BoundExpr),
464}
465
466#[derive(Clone, Copy, Debug, Eq, PartialEq)]
473pub enum Uniqueness {
474 Unique,
476 Duplicates,
478}
479
480#[derive(Clone, Copy, Debug, Eq, PartialEq)]
482pub enum IfNotExists {
483 Skip,
485 Refuse,
487}
488
489pub struct CreateIndexSpec<'a> {
494 pub unique: Uniqueness,
496 pub if_not_exists: IfNotExists,
498 pub database: Option<ast::NameId>,
500 pub name: ast::NameId,
502 pub table: ast::NameId,
504 pub using: Option<ast::NameId>,
506 pub columns: &'a [ast::IndexedColumn],
508 pub settings: &'a [Vec<u8>],
510 pub filter: Option<ast::ExprId>,
512}
513
514pub(crate) struct CreateTriggerParts<'p> {
520 pub temporary: bool,
522 pub if_not_exists: bool,
524 pub database: Option<ast::NameId>,
526 pub name: ast::NameId,
528 pub time: Option<ast::TriggerTime>,
530 pub table: ast::NameId,
532 pub for_each_row: bool,
534 pub when: Option<ast::ExprId>,
536 pub body: &'p [ast::Statement],
538}
539
540impl<'a> Binder<'a> {
541 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 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 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 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 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 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 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("ed_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 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 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 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 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 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 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 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 fn bind_alter(
1146 &mut self,
1147 database: Option<ast::NameId>,
1148 table: ast::NameId,
1149 action: &ast::AlterAction,
1150 ) -> Result<Directive, ParseError> {
1151 let written = match database {
1163 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let using = match using {
1824 None => None,
1825 Some(named) => {
1826 let folded = self.ast.folded(named).to_vec();
1827 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 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 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 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 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 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 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 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 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 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 fn name_offset(&self, name: ast::NameId) -> u32 {
2156 self.ast.name(name).map_or(0, |name| name.span.start)
2157 }
2158
2159 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
2168fn 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
2195fn 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
2213fn identifier_width(name: &[u8]) -> usize {
2223 name.len()
2224 .saturating_add(2)
2225 .saturating_add(name.iter().filter(|byte| **byte == b'"').count())
2226}
2227
2228const INDEX_SETTINGS: [(&str, &str); 9] = [
2236 ("m", "m"),
2238 ("ef_construction", "ef_construction"),
2239 ("ef_search", "ef_search"),
2240 ("metric", "metric"),
2243 ("distance", "metric"),
2244 ("threads", "threads"),
2247 ("compact", "compact"),
2248 ("lists", "lists"),
2251 ("probes", "probes"),
2252];
2253
2254fn index_settings(
2263 using: &Option<Vec<u8>>,
2264 settings: &[Vec<u8>],
2265) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ParseError> {
2266 if settings.is_empty() {
2267 return Ok(Vec::new());
2268 }
2269 if using.is_none() {
2270 return Err(unsupported(
2271 "WITH ( ... ) on an index that is not USING a module",
2272 Span::default(),
2273 ));
2274 }
2275 let mut held = Vec::with_capacity(settings.len());
2276 for setting in settings {
2277 let text = String::from_utf8_lossy(setting).to_string();
2278 let Some((name, value)) = text.split_once('=') else {
2279 return Err(refused(
2280 format!("index setting {} is not name = value", text.trim()),
2281 Span::default(),
2282 ));
2283 };
2284 let folded = name.trim().to_ascii_lowercase();
2285 let Some((_, option)) = INDEX_SETTINGS
2286 .iter()
2287 .find(|(known, _)| *known == folded.as_str())
2288 else {
2289 return Err(refused(
2290 format!("no such index setting: {folded}"),
2291 Span::default(),
2292 ));
2293 };
2294 let value = value
2295 .trim()
2296 .trim_matches(|held| held == '\'' || held == '"');
2297 held.push((option.as_bytes().to_vec(), value.as_bytes().to_vec()));
2298 }
2299 Ok(held)
2300}