1use crate::alloc::vec;
2use crate::alloc::TursoFromIterator;
3use crate::alloc::*;
4use crate::function::{Deterministic, Func, ScalarFunc};
5use crate::incremental::view::IncrementalView;
6use crate::incremental::{compiler::DBSP_CIRCUIT_VERSION, operator::create_dbsp_state_index};
7use crate::index_method::{IndexMethodAttachment, IndexMethodConfiguration};
8use crate::return_if_io;
9use crate::stats::AnalyzeStats;
10use crate::sync::RwLock;
11use crate::translate::emitter::Resolver;
12use crate::translate::expr::{
13 bind_and_rewrite_expr, walk_expr, walk_expr_mut, BindingBehavior, WalkControl,
14};
15use crate::translate::index::{resolve_index_method_parameters, resolve_sorted_columns};
16use crate::translate::planner::ROWID_STRS;
17use crate::types::{IOResult, ImmutableRecord};
18use crate::util::{exprs_are_equivalent, normalize_ident};
19use crate::vdbe::affinity::Affinity;
20use crate::vdbe::CursorID;
21use crate::{turso_assert, turso_debug_assert};
22use smallvec::SmallVec;
23use turso_macros::AtomicEnum;
24
25#[derive(Debug, Clone, AtomicEnum)]
26pub enum ViewState {
27 Ready,
28 InProgress,
29}
30
31#[derive(Debug)]
33pub struct View {
34 pub name: String,
35 pub sql: String,
36 pub select_stmt: ast::Select,
37 pub columns: Vec<Column>,
38 pub state: AtomicViewState,
39}
40
41impl View {
42 fn new(name: String, sql: String, select_stmt: ast::Select, columns: Vec<Column>) -> Self {
43 Self {
44 name,
45 sql,
46 select_stmt,
47 columns,
48 state: AtomicViewState::new(ViewState::Ready),
49 }
50 }
51
52 pub fn process(&self) -> Result<()> {
53 let state = self.state.get();
54 match state {
55 ViewState::InProgress => {
56 bail_parse_error!("view {} is circularly defined", self.name)
57 }
58 ViewState::Ready => {
59 self.state.set(ViewState::InProgress);
60 Ok(())
61 }
62 }
63 }
64
65 pub fn done(&self) {
66 let state = self.state.get();
67 match state {
68 ViewState::InProgress => {
69 self.state.set(ViewState::Ready);
70 }
71 ViewState::Ready => {}
72 }
73 }
74}
75
76impl Clone for View {
77 fn clone(&self) -> Self {
78 Self {
79 name: self.name.clone(),
80 sql: self.sql.clone(),
81 select_stmt: self.select_stmt.clone(),
82 columns: self.columns.clone(),
83 state: AtomicViewState::new(ViewState::Ready),
84 }
85 }
86}
87
88pub type ViewsMap = HashMap<String, Arc<View>>;
90
91#[derive(Debug, Clone)]
93pub struct Trigger {
94 pub name: String,
95 pub sql: String,
96 pub table_name: String,
97 pub time: turso_parser::ast::TriggerTime,
98 pub event: turso_parser::ast::TriggerEvent,
99 pub for_each_row: bool,
100 pub when_clause: Option<turso_parser::ast::Expr>,
101 pub commands: std::vec::Vec<turso_parser::ast::TriggerCmd>,
102 pub temporary: bool,
103 pub target_database_id: Option<usize>,
115}
116
117impl Trigger {
118 #[allow(clippy::too_many_arguments)]
119 pub fn new(
120 name: String,
121 sql: String,
122 table_name: String,
123 time: Option<turso_parser::ast::TriggerTime>,
124 event: turso_parser::ast::TriggerEvent,
125 for_each_row: bool,
126 when_clause: Option<turso_parser::ast::Expr>,
127 commands: std::vec::Vec<turso_parser::ast::TriggerCmd>,
128 temporary: bool,
129 target_database_id: Option<usize>,
130 ) -> Self {
131 Self {
132 name,
133 sql,
134 table_name,
135 time: time.unwrap_or(turso_parser::ast::TriggerTime::Before),
136 event,
137 for_each_row,
138 when_clause,
139 commands,
140 temporary,
141 target_database_id,
142 }
143 }
144}
145
146use crate::storage::btree::{BTreeCursor, CursorTrait};
147use crate::sync::Arc;
148use crate::sync::Mutex;
149use crate::translate::collate::CollationSeq;
150use crate::translate::plan::{BitSet, ColumnMask, Plan, TableReferences};
151use crate::util::{
152 module_args_from_sql, module_name_from_sql, type_from_name, UnparsedFromSqlIndex,
153};
154use crate::Result;
155use crate::{bail_parse_error, LimboError, MvCursor, Pager, SymbolTable, ValueRef, VirtualTable};
156use bitflags::bitflags;
157use core::fmt;
158use rustc_hash::{FxBuildHasher, FxHashMap as HashMap, FxHashSet as HashSet};
159use std::collections::VecDeque;
160use std::sync::OnceLock;
161use tracing::trace;
162use turso_parser::ast::{
163 self, ColumnDefinition, Expr, InitDeferredPred, Literal, Name, RefAct, ResolveType, SortOrder,
164 TableInternalId, TypeOperator,
165};
166use turso_parser::{
167 ast::{Cmd, CreateTableBody, ResultColumn, Stmt},
168 parser::Parser,
169};
170
171pub const SCHEMA_TABLE_NAME: &str = "sqlite_schema";
172pub const SCHEMA_TABLE_NAME_ALT: &str = "sqlite_master";
173pub const TEMP_SCHEMA_TABLE_NAME: &str = "sqlite_temp_schema";
174pub const TEMP_SCHEMA_TABLE_NAME_ALT: &str = "sqlite_temp_master";
175pub const SQLITE_SEQUENCE_TABLE_NAME: &str = "sqlite_sequence";
176pub const TURSO_TYPES_TABLE_NAME: &str = "__turso_internal_types";
177pub const DBSP_TABLE_PREFIX: &str = "__turso_internal_dbsp_state_v";
178pub const TURSO_INTERNAL_PREFIX: &str = "__turso_internal_";
179pub const SEQ_BACKING_TABLE_PREFIX: &str = "__turso_internal_seq_";
180pub const AUTOINCREMENT_SEQ_PREFIX: &str = "__turso_internal_autoincrement_";
184
185pub fn autoincrement_sequence_name(table_name: &str) -> String {
187 String::from(AUTOINCREMENT_SEQ_PREFIX) + table_name
188}
189
190struct SequenceBackingTableSource {
191 sequence_name: String,
192 root_page: i64,
193 num_columns: usize,
194}
195
196struct SequenceMetadata {
197 start: i64,
201 increment: i64,
202 min: i64,
203 max: i64,
204 cycle: bool,
205}
206
207use crate::util::quote_identifier as quote_ident;
208
209pub fn rewrite_value_to_column(expr: &ast::Expr, col_name: &str) -> Box<ast::Expr> {
211 let mut cloned = expr.clone();
212 let _ = walk_expr_mut(&mut cloned, &mut |e| {
213 if let ast::Expr::Id(name) = e {
214 if name.as_str().eq_ignore_ascii_case("value") {
215 *e = ast::Expr::Id(ast::Name::exact(col_name.to_string()));
216 }
217 }
218 Ok(WalkControl::Continue)
219 });
220 Box::new(cloned)
221}
222
223#[derive(Debug, Clone)]
225pub struct StructFieldDef {
226 pub name: String,
227 pub base_affinity: Affinity,
228 pub type_name: String,
229}
230
231#[derive(Debug, Clone)]
233pub struct StructDef {
234 pub fields: Vec<StructFieldDef>,
235}
236
237#[derive(Debug, Clone)]
239pub struct UnionVariantDef {
240 pub tag_name: String,
241 pub tag_index: u8,
242 pub base_affinity: Affinity,
243 pub type_name: String,
244}
245
246#[derive(Debug, Clone)]
248pub struct UnionDef {
249 pub variants: Vec<UnionVariantDef>,
250 pub tag_names: Arc<[String]>,
253}
254
255#[derive(Debug, Clone)]
257pub enum TypeDefKind {
258 Custom {
259 params: std::vec::Vec<ast::TypeParam>,
260 base: String,
261 encode: Option<Box<ast::Expr>>,
262 decode: Option<Box<ast::Expr>>,
263 operators: std::vec::Vec<TypeOperator>,
264 default: Option<Box<ast::Expr>>,
265 },
266 Struct(StructDef),
267 Union(UnionDef),
268}
269
270#[derive(Debug, Clone)]
272pub struct ResolvedType {
275 pub primitive: String,
277 pub chain: Vec<Arc<TypeDef>>,
279}
280
281impl ResolvedType {
282 pub fn leaf(&self) -> &TypeDef {
284 &self.chain[0]
285 }
286
287 pub fn is_domain(&self) -> bool {
289 self.chain[0].is_domain
290 }
291
292 pub fn default_expr(&self) -> Option<&ast::Expr> {
296 self.chain.iter().find_map(|td| td.default_expr())
297 }
298}
299
300#[derive(Debug, Clone)]
301pub struct TypeDef {
302 pub name: String,
303 pub is_builtin: bool,
304 pub not_null: bool,
305 pub is_domain: bool,
307 pub sql: String,
309 pub domain_checks: std::vec::Vec<ast::DomainConstraint>,
312 pub kind: TypeDefKind,
313}
314
315impl TypeDef {
316 pub fn is_struct(&self) -> bool {
318 matches!(self.kind, TypeDefKind::Struct(_))
319 }
320
321 pub fn is_union(&self) -> bool {
323 matches!(self.kind, TypeDefKind::Union(_))
324 }
325
326 pub fn struct_def(&self) -> Option<&StructDef> {
328 match &self.kind {
329 TypeDefKind::Struct(sd) => Some(sd),
330 _ => None,
331 }
332 }
333
334 pub fn union_def(&self) -> Option<&UnionDef> {
336 match &self.kind {
337 TypeDefKind::Union(ud) => Some(ud),
338 _ => None,
339 }
340 }
341
342 pub fn encode(&self) -> Option<&ast::Expr> {
344 match &self.kind {
345 TypeDefKind::Custom { encode, .. } => encode.as_deref(),
346 _ => None,
347 }
348 }
349
350 pub fn decode(&self) -> Option<&ast::Expr> {
352 match &self.kind {
353 TypeDefKind::Custom { decode, .. } => decode.as_deref(),
354 _ => None,
355 }
356 }
357
358 pub fn base(&self) -> &str {
360 match &self.kind {
361 TypeDefKind::Custom { base, .. } => base,
362 TypeDefKind::Struct(_) | TypeDefKind::Union(_) => "blob",
363 }
364 }
365
366 pub fn params(&self) -> &[ast::TypeParam] {
368 match &self.kind {
369 TypeDefKind::Custom { params, .. } => params,
370 _ => &[],
371 }
372 }
373
374 pub fn operators(&self) -> &[TypeOperator] {
376 match &self.kind {
377 TypeDefKind::Custom { operators, .. } => operators,
378 _ => &[],
379 }
380 }
381
382 pub fn default_expr(&self) -> Option<&ast::Expr> {
384 match &self.kind {
385 TypeDefKind::Custom { default, .. } => default.as_deref(),
386 _ => None,
387 }
388 }
389
390 pub fn find_struct_field(&self, name: &str) -> Option<(usize, &StructFieldDef)> {
392 self.struct_def().and_then(|sd| {
393 sd.fields
394 .iter()
395 .enumerate()
396 .find(|(_, f)| f.name.eq_ignore_ascii_case(name))
397 })
398 }
399
400 pub fn resolve_union_tag_index(&self, tag_name: &str) -> Option<u8> {
403 self.find_union_variant(tag_name).map(|(idx, _)| idx)
404 }
405
406 pub fn find_union_variant(&self, name: &str) -> Option<(u8, &UnionVariantDef)> {
408 self.union_def().and_then(|ud| {
409 ud.variants
410 .iter()
411 .find(|v| v.tag_name.eq_ignore_ascii_case(name))
412 .map(|v| (v.tag_index, v))
413 })
414 }
415
416 pub fn from_create_type(
418 type_name: &str,
419 body: &ast::CreateTypeBody,
420 is_builtin: bool,
421 sql: String,
422 ) -> crate::Result<Self> {
423 Ok(match body {
424 ast::CreateTypeBody::CustomType {
425 params,
426 base,
427 encode,
428 decode,
429 operators,
430 default,
431 } => Self {
432 name: type_name.to_string(),
433 is_builtin,
434 not_null: false,
435 is_domain: false,
436 sql,
437 domain_checks: std::vec::Vec::new(),
438 kind: TypeDefKind::Custom {
439 params: params.clone(),
440 base: base.clone(),
441 encode: encode.clone(),
442 decode: decode.clone(),
443 operators: operators.clone(),
444 default: default.clone(),
445 },
446 },
447 ast::CreateTypeBody::Struct(fields) => {
448 let struct_fields: Vec<StructFieldDef> = fields
449 .iter()
450 .map(|f| StructFieldDef {
451 name: f.name.to_string(),
452 base_affinity: Affinity::affinity(&f.field_type.name),
453 type_name: f.field_type.name.clone(),
454 })
455 .try_collect()?;
456 Self {
457 name: type_name.to_string(),
458 is_builtin,
459 not_null: false,
460 is_domain: false,
461 sql,
462 domain_checks: std::vec::Vec::new(),
463 kind: TypeDefKind::Struct(StructDef {
464 fields: struct_fields,
465 }),
466 }
467 }
468 ast::CreateTypeBody::Union(fields) => {
469 if fields.len() > 256 {
470 return Err(crate::LimboError::ParseError(format!(
471 "UNION type cannot have more than 256 variants (got {})",
472 fields.len()
473 )));
474 }
475 let variants: Vec<UnionVariantDef> = fields
476 .iter()
477 .enumerate()
478 .map(|(i, f)| UnionVariantDef {
479 tag_name: f.name.to_string(),
480 tag_index: i as u8,
481 base_affinity: Affinity::affinity(&f.field_type.name),
482 type_name: f.field_type.name.clone(),
483 })
484 .try_collect()?;
485 Self {
486 name: type_name.to_string(),
487 is_builtin,
488 not_null: false,
489 is_domain: false,
490 sql,
491 domain_checks: std::vec::Vec::new(),
492 kind: TypeDefKind::Union(UnionDef {
493 tag_names: variants.iter().map(|v| v.tag_name.clone()).collect(),
496 variants,
497 }),
498 }
499 }
500 })
501 }
502
503 pub fn from_domain(
506 domain_name: &str,
507 base_type: &str,
508 not_null: bool,
509 constraints: &[ast::DomainConstraint],
510 default: Option<Box<ast::Expr>>,
511 sql: String,
512 ) -> Self {
513 Self {
514 name: domain_name.to_string(),
515 is_builtin: false,
516 not_null,
517 is_domain: true,
518 sql,
519 domain_checks: constraints.to_vec(),
520 kind: TypeDefKind::Custom {
521 params: std::vec::Vec::new(),
522 base: base_type.to_string(),
523 encode: None,
524 decode: None,
525 operators: std::vec::Vec::new(),
526 default,
527 },
528 }
529 }
530
531 pub fn value_input_type(&self) -> &str {
535 for p in self.params() {
536 if p.name.eq_ignore_ascii_case("value") {
537 return p.ty.as_deref().unwrap_or_else(|| self.base());
538 }
539 }
540 self.base()
541 }
542
543 pub fn user_params(&self) -> impl Iterator<Item = &turso_parser::ast::TypeParam> {
545 self.params()
546 .iter()
547 .filter(|p| !p.name.eq_ignore_ascii_case("value"))
548 }
549
550 pub fn to_sql(&self) -> &str {
552 &self.sql
553 }
554}
555
556struct MakeFromBtreeAccumulators {
558 from_sql_indexes: Vec<UnparsedFromSqlIndex>,
559 automatic_indices: HashMap<String, Vec<(String, i64)>>,
560 dbsp_state_roots: HashMap<String, i64>,
562 dbsp_state_index_roots: HashMap<String, i64>,
564 materialized_view_info: HashMap<String, (String, i64)>,
566}
567
568#[derive(Default, Debug)]
570pub enum MakeFromBtreePhase {
571 #[default]
572 Init,
573 Rewinding,
574 FetchingRecord,
575 Advancing,
576 PopulatingSequencesRewind,
586 PopulatingSequencesFetch,
587 Done,
588}
589
590pub struct MakeFromBtreeState {
592 phase: MakeFromBtreePhase,
593 cursor: Option<BTreeCursor>,
594 accumulators: Option<MakeFromBtreeAccumulators>,
595 read_tx_active: bool,
596 sequence_sources: Vec<SequenceBackingTableSource>,
599 sequence_cursor: Option<BTreeCursor>,
602}
603
604impl Default for MakeFromBtreeState {
605 fn default() -> Self {
606 Self::new()
607 }
608}
609
610impl MakeFromBtreeState {
611 pub fn new() -> Self {
612 Self {
613 phase: MakeFromBtreePhase::Init,
614 cursor: None,
615 accumulators: None,
616 read_tx_active: false,
617 sequence_sources: vec![],
618 sequence_cursor: None,
619 }
620 }
621
622 pub fn cleanup(&mut self, pager: &Pager) {
624 if self.read_tx_active {
625 pager.end_read_tx();
626 self.read_tx_active = false;
627 }
628 self.cursor = None;
629 self.accumulators = None;
630 }
631}
632
633pub const ROWID_SENTINEL: usize = usize::MAX;
635
636pub const EXPR_INDEX_SENTINEL: usize = usize::MAX;
638
639pub const RESERVED_TABLE_PREFIXES: [&str; 2] = ["sqlite_", "__turso_internal_"];
641
642pub fn is_system_table(table_name: &str) -> bool {
644 RESERVED_TABLE_PREFIXES
645 .iter()
646 .any(|prefix| table_name.to_lowercase().starts_with(prefix))
647}
648
649pub fn allow_user_dml(table_name: &str) -> bool {
650 const NAMES: [&str; 2] = [SCHEMA_TABLE_NAME, SCHEMA_TABLE_NAME_ALT];
651 !(NAMES.iter().any(|n| n.eq_ignore_ascii_case(table_name))
652 || table_name.starts_with(TURSO_INTERNAL_PREFIX)) }
654
655#[derive(Debug, Clone)]
697pub struct Sequence {
698 pub name: String,
699 pub start_value: i64,
700 pub increment_by: i64,
701 pub min_value: i64,
702 pub max_value: i64,
703 pub cycle: bool,
704}
705
706impl Sequence {
707 pub fn new(
708 name: String,
709 start: Option<i64>,
710 increment: Option<i64>,
711 min_value: Option<i64>,
712 max_value: Option<i64>,
713 cycle: bool,
714 ) -> crate::Result<Self> {
715 let increment_by = increment.unwrap_or(1);
716 if increment_by == 0 {
717 return Err(crate::LimboError::ParseError(
718 "INCREMENT must not be zero".to_string(),
719 ));
720 }
721 let min_val = min_value.unwrap_or(if increment_by > 0 { 1 } else { i64::MIN });
722 let max_val = max_value.unwrap_or(if increment_by > 0 { i64::MAX } else { -1 });
723 if min_val >= max_val {
724 return Err(crate::LimboError::ParseError(format!(
725 "MINVALUE ({min_val}) must be less than MAXVALUE ({max_val})"
726 )));
727 }
728 let start_val = start.unwrap_or(if increment_by > 0 { min_val } else { max_val });
729 if start_val < min_val {
730 return Err(crate::LimboError::ParseError(format!(
731 "START value ({start_val}) cannot be less than MINVALUE ({min_val})"
732 )));
733 }
734 if start_val > max_val {
735 return Err(crate::LimboError::ParseError(format!(
736 "START value ({start_val}) cannot be greater than MAXVALUE ({max_val})"
737 )));
738 }
739 Ok(Self {
740 name,
741 start_value: start_val,
742 increment_by,
743 min_value: min_val,
744 max_value: max_val,
745 cycle,
746 })
747 }
748}
749
750#[derive(Debug, Clone, Copy, PartialEq, Eq)]
752pub enum SchemaObjectType {
753 Table,
754 View,
755 Index,
756}
757
758#[derive(Debug)]
759pub struct Schema {
760 pub tables: HashMap<String, Arc<Table>>,
761 #[cfg(feature = "conn_raw_api")]
762 pub(crate) table_names_by_root_page: HashMap<i64, String>,
763
764 pub materialized_view_names: HashSet<String>,
766 pub materialized_view_sql: HashMap<String, String>,
768 pub incremental_views: HashMap<String, Arc<Mutex<IncrementalView>>>,
770
771 pub views: ViewsMap,
772
773 pub triggers: HashMap<String, VecDeque<Arc<Trigger>>>,
775
776 pub indexes: HashMap<String, VecDeque<Arc<Index>>>,
778 pub has_indexes: HashSet<String>,
779 pub schema_version: u32,
780 pub analyze_stats: AnalyzeStats,
782
783 pub table_to_materialized_views: HashMap<String, Vec<String>>,
785
786 pub incompatible_views: HashSet<String>,
788
789 pub broken_views: HashSet<String>,
794
795 pub dropped_root_pages: HashSet<i64>,
799
800 pub type_registry: HashMap<String, Arc<TypeDef>>,
802
803 pub generated_columns_enabled: bool,
804 pub sequences: HashMap<String, Arc<Sequence>>,
806}
807
808impl Default for Schema {
809 fn default() -> Self {
810 Self::new()
811 }
812}
813
814fn bootstrap_builtin_types(registry: &mut HashMap<String, Arc<TypeDef>>) -> crate::Result<()> {
815 use turso_parser::ast::{Cmd, Stmt};
816 use turso_parser::parser::Parser;
817
818 let type_sqls: &[&str] = &[
819 #[cfg(feature = "uuid")]
820 "CREATE TYPE uuid(value text) BASE blob ENCODE uuid_blob(value) DECODE uuid_str(value) DEFAULT uuid4_str() OPERATOR '<'",
821 "CREATE TYPE boolean(value any) BASE integer ENCODE boolean_to_int(value) DECODE CASE WHEN value THEN 1 ELSE 0 END OPERATOR '<'",
822 #[cfg(feature = "json")]
823 "CREATE TYPE json(value text) BASE text ENCODE json(value) DECODE value",
824 #[cfg(feature = "json")]
825 "CREATE TYPE jsonb(value text) BASE blob ENCODE jsonb(value) DECODE json(value)",
826 "CREATE TYPE varchar(value text, maxlen integer) BASE text ENCODE CASE WHEN length(value) <= maxlen THEN value ELSE RAISE(ABORT, 'value too long for varchar') END DECODE value OPERATOR '<'",
827 "CREATE TYPE date(value text) BASE text ENCODE CASE WHEN value IS NULL THEN NULL WHEN date(value) IS NULL THEN RAISE(ABORT, 'invalid date value') ELSE date(value) END DECODE value OPERATOR '<'",
828 "CREATE TYPE time(value text) BASE text ENCODE CASE WHEN value IS NULL THEN NULL WHEN time(value) IS NULL THEN RAISE(ABORT, 'invalid time value') ELSE rtrim(rtrim(strftime('%H:%M:%f', value), '0'), '.') END DECODE value OPERATOR '<'",
837 "CREATE TYPE timestamp(value text) BASE text ENCODE CASE WHEN value IS NULL THEN NULL WHEN datetime(value) IS NULL THEN RAISE(ABORT, 'invalid timestamp value') ELSE rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', value), '0'), '.') END DECODE value OPERATOR '<'",
838 "CREATE TYPE smallint(value integer) BASE integer ENCODE CASE WHEN value BETWEEN -32768 AND 32767 THEN value ELSE RAISE(ABORT, 'integer out of range for smallint') END DECODE value OPERATOR '<'",
839 "CREATE TYPE bigint(value integer) BASE integer",
840 "CREATE TYPE inet(value text) BASE text ENCODE validate_ipaddr(value) DECODE value",
841 "CREATE TYPE bytea(value blob) BASE blob OPERATOR '<'",
842 "CREATE TYPE numeric(value any, precision integer, scale integer) BASE blob ENCODE numeric_encode(value, precision, scale) DECODE numeric_decode(value) OPERATOR '+' numeric_add OPERATOR '-' numeric_sub OPERATOR '*' numeric_mul OPERATOR '/' numeric_div OPERATOR '<' numeric_lt OPERATOR '=' numeric_eq",
843 ];
844
845 for sql in type_sqls {
846 let mut parser = Parser::new(sql.as_bytes());
847 let Ok(Some(Cmd::Stmt(Stmt::CreateType {
848 type_name, body, ..
849 }))) = parser.next_cmd()
850 else {
851 return Err(crate::LimboError::InternalError(format!(
852 "failed to parse built-in type SQL: {sql}"
853 )));
854 };
855
856 let type_def = TypeDef::from_create_type(&type_name, &body, true, sql.to_string())?;
857 registry.insert(type_name.to_lowercase(), Arc::new(type_def));
858 }
859
860 let aliases: &[(&str, &str)] = &[
862 ("bool", "boolean"),
863 ("int2", "smallint"),
864 ("int8", "bigint"),
865 ];
866 for (alias, target) in aliases {
867 if let Some(type_def) = registry.get(*target).cloned() {
868 registry.insert(alias.to_string(), type_def);
869 }
870 }
871 Ok(())
872}
873
874impl Schema {
875 fn normalize_table_lookup_name(&self, name: &str) -> String {
876 let name = normalize_ident(name);
877 if name.eq(SCHEMA_TABLE_NAME_ALT)
878 || name.eq(TEMP_SCHEMA_TABLE_NAME)
879 || name.eq(TEMP_SCHEMA_TABLE_NAME_ALT)
880 {
881 SCHEMA_TABLE_NAME.to_string()
882 } else {
883 name
884 }
885 }
886
887 pub fn new() -> Self {
893 Self::with_options(true).expect("built-in type definitions are malformed")
894 }
895
896 pub fn with_options(enable_custom_types: bool) -> crate::Result<Self> {
897 let mut tables: HashMap<String, Arc<Table>> = HashMap::default();
898 #[cfg(feature = "conn_raw_api")]
899 let mut table_names_by_root_page = HashMap::default();
900 let has_indexes = HashSet::default();
901 let indexes: HashMap<String, VecDeque<Arc<Index>>> = HashMap::default();
902 #[allow(clippy::arc_with_non_send_sync)]
903 tables.insert(
904 SCHEMA_TABLE_NAME.to_string(),
905 Arc::new(Table::BTree(sqlite_schema_table()?.into())),
906 );
907 #[cfg(feature = "conn_raw_api")]
908 table_names_by_root_page.insert(1, SCHEMA_TABLE_NAME.to_string());
909 let materialized_view_names = HashSet::default();
910 let materialized_view_sql = HashMap::default();
911 let incremental_views = HashMap::default();
912 let views: ViewsMap = HashMap::default();
913 let triggers = HashMap::default();
914 let table_to_materialized_views: HashMap<String, Vec<String>> = HashMap::default();
915 let incompatible_views = HashSet::default();
916 let mut type_registry = HashMap::default();
917 if enable_custom_types {
918 bootstrap_builtin_types(&mut type_registry)?;
919 }
920 let mut schema = Self {
921 tables,
922 #[cfg(feature = "conn_raw_api")]
923 table_names_by_root_page,
924 materialized_view_names,
925 materialized_view_sql,
926 incremental_views,
927 views,
928 triggers,
929 indexes,
930 has_indexes,
931 schema_version: 0,
932 analyze_stats: AnalyzeStats::default(),
933 table_to_materialized_views,
934 incompatible_views,
935 broken_views: HashSet::default(),
936 dropped_root_pages: HashSet::default(),
937 type_registry,
938 generated_columns_enabled: false,
939 sequences: HashMap::default(),
940 };
941 crate::dialect::sqlite::register_builtin_catalog(&mut schema, enable_custom_types)?;
942 Ok(schema)
943 }
944
945 pub fn register_internal_vtab<T>(&mut self, table: T) -> crate::Result<String>
953 where
954 T: crate::vtab::InternalVirtualTable + 'static,
955 {
956 let vtab = crate::vtab::VirtualTable::wrap_internal_table(table)?;
957 let name = vtab.name.clone();
958 let lookup_name = normalize_ident(&name);
959 self.tables.insert(
960 lookup_name,
961 Arc::new(Table::Virtual(Arc::new((*vtab).clone()))),
962 );
963 Ok(name)
964 }
965
966 pub fn get_type_def(&self, type_name: &str, is_strict: bool) -> Option<&Arc<TypeDef>> {
970 if !is_strict {
971 return None;
972 }
973 self.type_registry.get(&type_name.to_lowercase())
974 }
975
976 pub fn get_type_def_unchecked(&self, type_name: &str) -> Option<&Arc<TypeDef>> {
980 self.type_registry.get(&type_name.to_lowercase())
981 }
982
983 pub fn resolve_type(
987 &self,
988 type_name: &str,
989 is_strict: bool,
990 ) -> crate::Result<Option<ResolvedType>> {
991 if !is_strict {
992 return Ok(None);
993 }
994 self.resolve_type_unchecked(type_name)
995 }
996
997 pub fn resolve_type_unchecked(&self, type_name: &str) -> crate::Result<Option<ResolvedType>> {
1000 let key = type_name.to_lowercase();
1001 if !self.type_registry.contains_key(&key) {
1002 return Ok(None);
1003 }
1004 let (primitive, chain) = self.resolve_base_type_chain(type_name)?;
1005 Ok(Some(ResolvedType { primitive, chain }))
1006 }
1007
1008 pub fn remove_type(&mut self, type_name: &str) {
1009 self.type_registry.remove(&type_name.to_lowercase());
1010 }
1011
1012 pub fn resolve_base_type_chain(
1017 &self,
1018 type_name: &str,
1019 ) -> crate::Result<(String, Vec<Arc<TypeDef>>)> {
1020 let mut chain = vec![];
1021 let mut visited = std::collections::HashSet::new();
1022 let mut current = type_name.to_lowercase();
1023
1024 loop {
1025 if !visited.insert(current.clone()) {
1026 return Err(crate::LimboError::ParseError(format!(
1027 "circular type dependency detected: {current}"
1028 )));
1029 }
1030 match self.type_registry.get(¤t) {
1031 Some(td) => {
1032 chain.try_push(Arc::clone(td))?;
1033 current = td.base().to_lowercase();
1034 }
1035 None => {
1036 return Ok((current, chain));
1038 }
1039 }
1040 }
1041 }
1042
1043 pub fn add_type_from_sql(&mut self, sql: &str) -> crate::Result<()> {
1045 use turso_parser::ast::{Cmd, Stmt};
1046 use turso_parser::parser::Parser;
1047
1048 let mut parser = Parser::new(sql.as_bytes());
1049 let cmd = parser.next_cmd();
1050 match cmd {
1051 Ok(Some(Cmd::Stmt(Stmt::CreateType {
1052 type_name, body, ..
1053 }))) => {
1054 let type_def =
1055 TypeDef::from_create_type(&type_name, &body, false, sql.to_string())?;
1056 self.type_registry
1057 .insert(type_name.to_lowercase(), Arc::new(type_def));
1058 }
1059 Ok(Some(Cmd::Stmt(Stmt::CreateDomain {
1060 domain_name,
1061 base_type,
1062 default,
1063 not_null,
1064 constraints,
1065 ..
1066 }))) => {
1067 let type_def = TypeDef::from_domain(
1068 &domain_name,
1069 &base_type,
1070 not_null,
1071 &constraints,
1072 default,
1073 sql.to_string(),
1074 );
1075 self.type_registry
1076 .insert(domain_name.to_lowercase(), Arc::new(type_def));
1077 }
1078 _ => {
1079 return Err(crate::LimboError::ParseError(format!(
1080 "invalid type sql: {sql}"
1081 )));
1082 }
1083 }
1084 Ok(())
1085 }
1086
1087 pub fn load_type_definitions(&mut self, type_sqls: &[String]) -> crate::Result<()> {
1091 for sql in type_sqls {
1092 self.add_type_from_sql(sql)?;
1093 }
1094 self.resolve_all_custom_type_affinities()?;
1095 Ok(())
1096 }
1097
1098 pub fn resolve_all_custom_type_affinities(&mut self) -> Result<()> {
1102 let mut tables: SmallVec<[(String, Arc<Table>); 8]> = SmallVec::with_capacity(8);
1103 for (name, table) in self.tables.iter().filter(|(_, t)| {
1104 t.is_strict()
1105 && t.btree().is_some_and(|bt| {
1106 bt.columns
1107 .iter()
1108 .any(|c| self.get_type_def_unchecked(&c.ty_str).is_some())
1109 })
1110 }) {
1111 let bt = table.btree().expect("checked btree table");
1112 let mut modified = (*bt).clone();
1113 modified.resolve_custom_type_affinities(self);
1114 modified.propagate_domain_constraints(self)?;
1115 tables.push((name.clone(), Arc::new(Table::BTree(Arc::new(modified)))));
1116 }
1117 for (name, table) in tables {
1118 self.tables.insert(name, table);
1119 }
1120 Ok(())
1121 }
1122
1123 pub fn is_unique_idx_name(&self, name: &str) -> bool {
1124 !self
1125 .indexes
1126 .iter()
1127 .any(|idx| idx.1.iter().any(|i| i.name == name))
1128 }
1129
1130 pub fn add_materialized_view(&mut self, view: IncrementalView, table: Arc<Table>, sql: String) {
1131 let name = normalize_ident(view.name());
1132
1133 #[cfg(feature = "conn_raw_api")]
1135 self.register_table_root_page(&name, table.as_ref());
1136 self.tables.insert(name.clone(), table);
1137
1138 self.materialized_view_names.insert(name.clone());
1140 self.materialized_view_sql.insert(name.clone(), sql);
1141
1142 self.incremental_views
1144 .insert(name, Arc::new(Mutex::new(view)));
1145 }
1146
1147 pub fn get_materialized_view(&self, name: &str) -> Option<Arc<Mutex<IncrementalView>>> {
1148 let name = normalize_ident(name);
1149 self.incremental_views.get(&name).cloned()
1150 }
1151
1152 pub fn has_compatible_dbsp_state_table(&self, view_name: &str) -> bool {
1154 let view_name = normalize_ident(view_name);
1155 let expected_table_name = format!("{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{view_name}");
1156
1157 self.tables.contains_key(&expected_table_name)
1159 }
1160
1161 pub fn is_materialized_view(&self, name: &str) -> bool {
1162 let name = normalize_ident(name);
1163 self.materialized_view_names.contains(&name)
1164 }
1165
1166 pub fn with_incompatible_dependent_views<F, T>(&self, table_name: &str, f: F) -> T
1168 where
1169 F: FnOnce(&[&String]) -> T,
1170 {
1171 let table_name = normalize_ident(table_name);
1172 let mut views: SmallVec<[&String; 8]> = SmallVec::with_capacity(8);
1173
1174 if let Some(v) = self.table_to_materialized_views.get(&table_name) {
1176 v.iter()
1177 .filter(|name| self.incompatible_views.contains(&**name))
1178 .for_each(|n| views.push(n));
1179 }
1180 f(&views)
1181 }
1182
1183 pub fn remove_view(&mut self, name: &str) -> Result<()> {
1184 let name = normalize_ident(name);
1185
1186 if self.views.contains_key(&name) {
1187 self.views.remove(&name);
1188 Ok(())
1189 } else if self.materialized_view_names.contains(&name) {
1190 self.remove_table(&name);
1192
1193 let dbsp_table_name = format!("{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{name}");
1195 self.remove_table(&dbsp_table_name);
1196 self.remove_indices_for_table(&dbsp_table_name);
1197
1198 self.materialized_view_names.remove(&name);
1200 self.materialized_view_sql.remove(&name);
1201 self.incremental_views.remove(&name);
1202
1203 for views in self.table_to_materialized_views.values_mut() {
1205 views.retain(|v| v != &name);
1206 }
1207
1208 Ok(())
1209 } else {
1210 Err(crate::LimboError::ParseError(format!(
1211 "no such view: {name}"
1212 )))
1213 }
1214 }
1215
1216 pub fn add_materialized_view_dependency(&mut self, table_name: &str, view_name: &str) {
1218 let table_name = normalize_ident(table_name);
1219 let view_name = normalize_ident(view_name);
1220
1221 self.table_to_materialized_views
1222 .entry(table_name)
1223 .or_insert_with(|| vec![])
1224 .push(view_name);
1225 }
1226
1227 pub fn get_dependent_materialized_views(&self, table_name: &str) -> Vec<String> {
1229 if self.table_to_materialized_views.is_empty() {
1230 return vec![];
1231 }
1232 let table_name = normalize_ident(table_name);
1233 self.table_to_materialized_views
1234 .get(&table_name)
1235 .cloned()
1236 .unwrap_or_else(|| vec![])
1237 }
1238
1239 pub fn add_view(&mut self, view: View) -> Result<()> {
1241 self.check_object_name_conflict(&view.name)?;
1242 let name = normalize_ident(&view.name);
1243 self.views.insert(name, Arc::new(view));
1244 Ok(())
1245 }
1246
1247 pub fn get_view(&self, name: &str) -> Option<Arc<View>> {
1249 let name = normalize_ident(name);
1250 self.views.get(&name).cloned()
1251 }
1252
1253 pub fn add_trigger(&mut self, trigger: Trigger, table_name: &str) -> Result<()> {
1254 let table_name = normalize_ident(table_name);
1257
1258 self.triggers
1260 .entry(table_name)
1261 .or_default()
1262 .push_front(Arc::new(trigger));
1263
1264 Ok(())
1265 }
1266
1267 pub fn remove_trigger(&mut self, name: &str) -> Result<()> {
1268 let name = normalize_ident(name);
1269
1270 let mut removed = false;
1271 for triggers_list in self.triggers.values_mut() {
1272 for i in 0..triggers_list.len() {
1273 let trigger = &triggers_list[i];
1274 if normalize_ident(&trigger.name) == name {
1275 removed = true;
1276 triggers_list.remove(i);
1277 break;
1278 }
1279 }
1280 if removed {
1281 break;
1282 }
1283 }
1284 if !removed {
1285 return Err(crate::LimboError::ParseError(format!(
1286 "no such trigger: {name}"
1287 )));
1288 }
1289 Ok(())
1290 }
1291 pub fn remove_triggers_for_table(&mut self, table_name: &str) {
1292 let table_name = normalize_ident(table_name);
1293 self.triggers.remove(&table_name);
1294 }
1295
1296 pub fn remove_triggers_for_table_with_db(&mut self, table_name: &str, target_db: usize) {
1304 let table_name = normalize_ident(table_name);
1305 let Some(bucket) = self.triggers.get_mut(&table_name) else {
1306 return;
1307 };
1308 let has_shadow_table = self.tables.contains_key(&table_name);
1312 bucket.retain(|trigger| {
1313 match trigger.target_database_id {
1314 Some(db) => db != target_db,
1315 None => has_shadow_table,
1318 }
1319 });
1320 if bucket.is_empty() {
1321 self.triggers.remove(&table_name);
1322 }
1323 }
1324
1325 pub fn get_trigger_for_table(&self, table_name: &str, name: &str) -> Option<Arc<Trigger>> {
1326 let table_name = normalize_ident(table_name);
1327 let name = normalize_ident(name);
1328 self.triggers
1329 .get(&table_name)
1330 .and_then(|triggers| triggers.iter().find(|t| t.name == name).cloned())
1331 }
1332
1333 pub fn get_triggers_for_table(
1334 &self,
1335 table_name: &str,
1336 ) -> impl Iterator<Item = &Arc<Trigger>> + Clone {
1337 let table_name = normalize_ident(table_name);
1338 self.triggers
1339 .get(&table_name)
1340 .map(|triggers| triggers.iter())
1341 .unwrap_or_default()
1342 }
1343
1344 pub fn get_trigger(&self, name: &str) -> Option<Arc<Trigger>> {
1345 let name = normalize_ident(name);
1346 self.triggers
1347 .values()
1348 .flatten()
1349 .find(|t| t.name == name)
1350 .cloned()
1351 }
1352
1353 pub fn add_btree_table(&mut self, table: Arc<BTreeTable>) -> Result<()> {
1354 self.check_object_name_conflict(&table.name)?;
1355 let name = normalize_ident(&table.name);
1356 #[cfg(feature = "conn_raw_api")]
1357 self.table_names_by_root_page
1358 .insert(table.root_page, name.clone());
1359 self.tables.insert(name, Table::BTree(table).into());
1360 Ok(())
1361 }
1362
1363 pub fn add_virtual_table(&mut self, table: Arc<VirtualTable>) -> Result<()> {
1364 self.check_object_name_conflict(&table.name)?;
1365 let name = normalize_ident(&table.name);
1366 self.tables.insert(name, Table::Virtual(table).into());
1367 Ok(())
1368 }
1369
1370 pub fn get_table(&self, name: &str) -> Option<Arc<Table>> {
1371 let name = self.normalize_table_lookup_name(name);
1372 self.tables.get(&name).cloned()
1373 }
1374
1375 #[cfg(feature = "conn_raw_api")]
1376 pub fn table_name_for_root_page(&self, root_page: i64) -> Option<&str> {
1377 self.table_names_by_root_page
1378 .get(&root_page)
1379 .map(String::as_str)
1380 }
1381
1382 pub fn remove_table(&mut self, table_name: &str) {
1383 let name = normalize_ident(table_name);
1384 #[cfg(feature = "conn_raw_api")]
1385 {
1386 if let Some(table) = self.tables.remove(&name) {
1387 self.unregister_table_root_page(&table);
1388 }
1389 }
1390 #[cfg(not(feature = "conn_raw_api"))]
1391 {
1392 self.tables.remove(&name);
1393 }
1394 self.analyze_stats.remove_table(&name);
1395
1396 if self.materialized_view_names.remove(&name) {
1398 self.incremental_views.remove(&name);
1399 self.materialized_view_sql.remove(&name);
1400 }
1401 }
1402
1403 #[cfg(feature = "conn_raw_api")]
1404 pub fn register_table_root_page(&mut self, name: &str, table: &Table) {
1405 if let Table::BTree(table) = table {
1406 self.table_names_by_root_page
1407 .insert(table.root_page, normalize_ident(name));
1408 }
1409 }
1410
1411 #[cfg(feature = "conn_raw_api")]
1412 pub fn unregister_table_root_page(&mut self, table: &Table) {
1413 if let Table::BTree(table) = table {
1414 self.table_names_by_root_page.remove(&table.root_page);
1415 }
1416 }
1417
1418 pub fn get_btree_table(&self, name: &str) -> Option<Arc<BTreeTable>> {
1419 let name = self.normalize_table_lookup_name(name);
1420 if let Some(table) = self.tables.get(&name) {
1421 table.btree()
1422 } else {
1423 None
1424 }
1425 }
1426
1427 pub fn add_index(&mut self, index: Arc<Index>) -> Result<()> {
1428 self.check_object_name_conflict(&index.name)?;
1429 let table_name = normalize_ident(&index.table_name);
1430 let is_replace = index.on_conflict == Some(ResolveType::Replace);
1437 let indexes_for_table = self.indexes.entry(table_name).or_default();
1438 if is_replace {
1439 let first_replace = indexes_for_table
1441 .iter()
1442 .position(|idx| idx.on_conflict == Some(ResolveType::Replace));
1443 let pos = first_replace.unwrap_or(indexes_for_table.len());
1444 indexes_for_table.insert(pos, index);
1445 } else {
1446 indexes_for_table.push_front(index);
1448 }
1449 turso_debug_assert!(
1450 indexes_for_table
1451 .iter()
1452 .position(|idx| idx.on_conflict == Some(ResolveType::Replace))
1453 .is_none_or(|first_replace| {
1454 indexes_for_table
1455 .iter()
1456 .skip(first_replace)
1457 .all(|idx| idx.on_conflict == Some(ResolveType::Replace))
1458 }),
1459 "REPLACE indexes must form a contiguous suffix"
1460 );
1461 Ok(())
1462 }
1463
1464 pub fn get_indices(&self, table_name: &str) -> impl Iterator<Item = &Arc<Index>> {
1465 let name = normalize_ident(table_name);
1466 self.indexes
1467 .get(&name)
1468 .map(|v| v.iter())
1469 .unwrap_or_default()
1470 .filter(|i| !i.is_backing_btree_index())
1471 }
1472
1473 #[cfg(all(feature = "fts", not(target_family = "wasm")))]
1474 pub fn has_fts_index(&self, table_name: &str) -> bool {
1475 self.get_indices(table_name).any(|idx| {
1476 idx.index_method.as_ref().is_some_and(|m| {
1477 m.definition().method_name == crate::index_method::fts::FTS_INDEX_METHOD_NAME
1478 })
1479 })
1480 }
1481
1482 pub fn get_index(&self, table_name: &str, index_name: &str) -> Option<&Arc<Index>> {
1483 let name = normalize_ident(table_name);
1484 self.indexes
1485 .get(&name)?
1486 .iter()
1487 .find(|index| index.name == index_name)
1488 }
1489
1490 pub fn remove_indices_for_table(&mut self, table_name: &str) {
1491 let name = normalize_ident(table_name);
1492 self.indexes.remove(&name);
1493 self.analyze_stats.remove_table(&name);
1494 }
1495
1496 pub fn remove_index(&mut self, idx: &Index) {
1497 let name = normalize_ident(&idx.table_name);
1498 self.indexes
1499 .get_mut(&name)
1500 .expect("Must have the index")
1501 .retain_mut(|other_idx| other_idx.name != idx.name);
1502 self.analyze_stats.remove_index(&name, &idx.name);
1503 }
1504
1505 pub fn table_has_indexes(&self, table_name: &str) -> bool {
1506 let name = normalize_ident(table_name);
1507 self.has_indexes.contains(&name)
1508 }
1509
1510 pub fn table_set_has_index(&mut self, table_name: &str) {
1511 self.has_indexes.insert(table_name.to_string());
1512 }
1513
1514 pub fn make_from_btree(
1517 &mut self,
1518 state: &mut MakeFromBtreeState,
1519 mv_cursor: Option<Arc<RwLock<MvCursor>>>,
1520 pager: &Arc<Pager>,
1521 syms: &SymbolTable,
1522 ) -> Result<IOResult<()>> {
1523 let result = self.make_from_btree_internal(state, mv_cursor, pager, syms);
1524 if result.is_err() {
1525 state.cleanup(pager);
1526 } else if let Ok(IOResult::Done(..)) = result {
1527 turso_assert!(
1528 !state.read_tx_active,
1529 "make_from_btree must properly cleanup internal state in case of success"
1530 );
1531 }
1532 result
1533 }
1534
1535 fn make_from_btree_internal(
1536 &mut self,
1537 state: &mut MakeFromBtreeState,
1538 mv_cursor: Option<Arc<RwLock<MvCursor>>>,
1539 pager: &Arc<Pager>,
1540 syms: &SymbolTable,
1541 ) -> Result<IOResult<()>> {
1542 loop {
1543 tracing::debug!("make_from_btree: state.phase={:?}", state.phase);
1544 match &state.phase {
1545 MakeFromBtreePhase::Init => {
1546 if mv_cursor.is_some() {
1547 return Err(crate::LimboError::ParseError(
1548 "MVCC is not supported for make_from_btree schema recovery".to_string(),
1549 ));
1550 }
1551
1552 state.cursor = Some(BTreeCursor::new_table(Arc::clone(pager), 1, 10));
1553 pager.begin_read_tx()?;
1554 state.read_tx_active = true;
1555
1556 state.accumulators = Some(MakeFromBtreeAccumulators {
1557 from_sql_indexes: Vec::try_with_capacity_ext(10)?,
1558 automatic_indices: HashMap::with_capacity_and_hasher(10, FxBuildHasher),
1559 dbsp_state_roots: HashMap::default(),
1560 dbsp_state_index_roots: HashMap::default(),
1561 materialized_view_info: HashMap::default(),
1562 });
1563
1564 state.phase = MakeFromBtreePhase::Rewinding;
1565 }
1566
1567 MakeFromBtreePhase::Rewinding => {
1568 let cursor = state
1569 .cursor
1570 .as_mut()
1571 .expect("cursor must be initialized in Init phase");
1572 return_if_io!(cursor.rewind());
1573 state.phase = MakeFromBtreePhase::FetchingRecord;
1574 }
1575
1576 MakeFromBtreePhase::FetchingRecord => {
1577 let cursor = state
1578 .cursor
1579 .as_mut()
1580 .expect("cursor must be initialized in Init phase");
1581 let row = return_if_io!(cursor.record());
1582
1583 let Some(row) = row else {
1584 state.sequence_sources = self.sequence_backing_tables();
1594 state.cursor = None;
1595 state.phase = MakeFromBtreePhase::PopulatingSequencesRewind;
1596 continue;
1597 };
1598
1599 let ty_value = row.get_value(0)?;
1602 let ValueRef::Text(ty) = ty_value else {
1603 return Err(LimboError::ConversionError("Expected text value".into()));
1604 };
1605 let ValueRef::Text(name) = row.get_value(1)? else {
1606 return Err(LimboError::ConversionError("Expected text value".into()));
1607 };
1608 let table_name_value = row.get_value(2)?;
1609 let ValueRef::Text(table_name) = table_name_value else {
1610 return Err(LimboError::ConversionError("Expected text value".into()));
1611 };
1612 let root_page_value = row.get_value(3)?;
1613 let ValueRef::Numeric(crate::numeric::Numeric::Integer(root_page)) =
1614 root_page_value
1615 else {
1616 return Err(LimboError::ConversionError("Expected integer value".into()));
1617 };
1618 let sql_value = row.get_value(4)?;
1619 let sql_textref = match sql_value {
1620 ValueRef::Text(sql) => Some(sql),
1621 _ => None,
1622 };
1623 let sql = sql_textref.map(|s| s.as_str());
1624
1625 let acc = state
1626 .accumulators
1627 .as_mut()
1628 .expect("accumulators must be initialized in Init phase");
1629 self.handle_schema_row(
1635 &ty,
1636 &name,
1637 &table_name,
1638 root_page,
1639 sql,
1640 syms,
1641 &mut acc.from_sql_indexes,
1642 &mut acc.automatic_indices,
1643 &mut acc.dbsp_state_roots,
1644 &mut acc.dbsp_state_index_roots,
1645 &mut acc.materialized_view_info,
1646 &|_| None,
1647 )?;
1648
1649 state.phase = MakeFromBtreePhase::Advancing;
1650 }
1651
1652 MakeFromBtreePhase::Advancing => {
1653 let cursor = state
1654 .cursor
1655 .as_mut()
1656 .expect("cursor must be initialized in Init phase");
1657 return_if_io!(cursor.next());
1658 state.phase = MakeFromBtreePhase::FetchingRecord;
1659 }
1660
1661 MakeFromBtreePhase::PopulatingSequencesRewind => {
1662 if state.sequence_sources.is_empty() {
1665 pager.end_read_tx();
1666 state.read_tx_active = false;
1667
1668 let acc = state
1669 .accumulators
1670 .take()
1671 .expect("accumulators must be initialized in Init phase");
1672 self.populate_indices(
1673 syms,
1674 acc.from_sql_indexes,
1675 acc.automatic_indices,
1676 mv_cursor.is_some(),
1677 )?;
1678 self.populate_materialized_views(
1679 acc.materialized_view_info,
1680 acc.dbsp_state_roots,
1681 acc.dbsp_state_index_roots,
1682 )?;
1683
1684 state.phase = MakeFromBtreePhase::Done;
1685 return Ok(IOResult::Done(()));
1686 }
1687 state.sequence_cursor = None;
1691 let source = state
1692 .sequence_sources
1693 .last()
1694 .expect("non-empty checked above");
1695 if source.root_page <= 0 {
1701 state.sequence_sources.pop();
1702 continue;
1703 }
1704 let cursor =
1705 BTreeCursor::new_table(pager.clone(), source.root_page, source.num_columns);
1706 state.sequence_cursor = Some(cursor);
1707 let cursor = state.sequence_cursor.as_mut().expect("just set");
1708 return_if_io!(cursor.rewind());
1709 state.phase = MakeFromBtreePhase::PopulatingSequencesFetch;
1710 }
1711
1712 MakeFromBtreePhase::PopulatingSequencesFetch => {
1713 let cursor = state
1714 .sequence_cursor
1715 .as_mut()
1716 .expect("cursor must be initialized in PopulatingSequencesRewind");
1717 let record = return_if_io!(cursor.record());
1718 let source = state.sequence_sources.pop().expect("at least one source");
1719 let record = record.ok_or_else(|| {
1720 LimboError::Corrupt(format!(
1721 "internal sequence backing table for \"{}\" is empty; \
1722 the descriptor metadata row must always be present",
1723 source.sequence_name
1724 ))
1725 })?;
1726 let metadata = Self::read_sequence_metadata(record).ok_or_else(|| {
1727 LimboError::Corrupt(format!(
1728 "internal sequence backing table for \"{}\" descriptor \
1729 row is malformed (expected integers for \
1730 start/inc/min/max/cycle)",
1731 source.sequence_name
1732 ))
1733 })?;
1734 self.install_sequence_descriptor(&source.sequence_name, metadata)?;
1735 state.sequence_cursor = None;
1739 state.phase = MakeFromBtreePhase::PopulatingSequencesRewind;
1740 }
1741
1742 MakeFromBtreePhase::Done => {
1743 return Ok(IOResult::Done(()));
1744 }
1745 }
1746 }
1747 }
1748
1749 pub fn populate_indices(
1753 &mut self,
1754 syms: &SymbolTable,
1755 from_sql_indexes: Vec<UnparsedFromSqlIndex>,
1756 automatic_indices: HashMap<String, Vec<(String, i64)>>,
1757 mvcc_enabled: bool,
1758 ) -> Result<()> {
1759 for unparsed_sql_from_index in from_sql_indexes {
1760 let table = self
1761 .get_btree_table(&unparsed_sql_from_index.table_name)
1762 .ok_or_else(|| {
1763 LimboError::Corrupt(format!(
1764 "sqlite_schema contains index for missing table '{}': rootpage={} sql={}",
1765 unparsed_sql_from_index.table_name,
1766 unparsed_sql_from_index.root_page,
1767 unparsed_sql_from_index.sql
1768 ))
1769 })?;
1770 let index = Index::from_sql(
1771 syms,
1772 &unparsed_sql_from_index.sql,
1773 unparsed_sql_from_index.root_page,
1774 table.as_ref(),
1775 )?;
1776 if mvcc_enabled && index.index_method.is_some() {
1777 crate::bail_parse_error!("Custom index modules are not supported with MVCC");
1778 }
1779 self.add_index(Arc::new(index))?;
1780 }
1781
1782 for automatic_index in automatic_indices {
1783 let table = self.get_btree_table(&automatic_index.0).ok_or_else(|| {
1788 LimboError::Corrupt(format!(
1789 "sqlite_schema contains automatic index for missing table '{}': indexes={:?}",
1790 automatic_index.0, automatic_index.1
1791 ))
1792 })?;
1793 let mut automatic_indexes = automatic_index.1;
1794 automatic_indexes.reverse(); let mut pk_index_added = false;
1798 for unique_set in &table.unique_sets {
1799 if unique_set.is_primary_key {
1800 assert!(
1801 table.primary_key_columns.len() == unique_set.columns.len(),
1802 "trying to add a {}-column primary key index for table {}, but the table has {} primary key columns",
1803 unique_set.columns.len(),
1804 table.name,
1805 table.primary_key_columns.len()
1806 );
1807 assert!(
1809 !pk_index_added,
1810 "trying to add a second primary key index for table {}",
1811 table.name
1812 );
1813 pk_index_added = true;
1814
1815 if unique_set.columns.len() == 1 {
1816 let col_name = &unique_set.columns.first().unwrap().0;
1817 let Some((_, column)) = table.get_column(col_name) else {
1818 return Err(LimboError::ParseError(format!(
1819 "Column {col_name} not found in table {}",
1820 table.name
1821 )));
1822 };
1823 if column.is_rowid_alias() {
1824 continue;
1826 }
1827 }
1828
1829 if let Some(index_entry) = automatic_indexes.pop() {
1830 self.add_index(Arc::new(Index::automatic_from_primary_key(
1831 table.as_ref(),
1832 index_entry,
1833 unique_set.columns.len(),
1834 unique_set.conflict_clause,
1835 &unique_set.collations,
1836 )?))?;
1837 } else if mvcc_enabled {
1838 continue;
1841 } else {
1842 return Err(LimboError::InternalError(format!(
1843 "Missing automatic index entry for primary key on table {}",
1844 table.name
1845 )));
1846 }
1847 } else {
1848 let mut column_indices_and_sort_orders =
1850 Vec::try_with_capacity_ext(unique_set.columns.len())?;
1851 for (col_name, sort_order) in unique_set.columns.iter() {
1852 let Some((pos_in_table, _)) = table.get_column(col_name) else {
1853 return Err(crate::LimboError::ParseError(format!(
1854 "Column {} not found in table {}",
1855 col_name, table.name
1856 )));
1857 };
1858 column_indices_and_sort_orders
1859 .push_within_capacity((pos_in_table, *sort_order))
1860 .expect("unique columns vector was preallocated to its input length");
1861 }
1862 if let Some(index_entry) = automatic_indexes.pop() {
1863 self.add_index(Arc::new(Index::automatic_from_unique(
1864 table.as_ref(),
1865 index_entry,
1866 column_indices_and_sort_orders,
1867 unique_set.conflict_clause,
1868 &unique_set.collations,
1869 )?))?;
1870 } else if mvcc_enabled {
1871 continue;
1874 } else {
1875 return Err(LimboError::InternalError(format!(
1876 "Missing automatic index entry for UNIQUE constraint on table {}",
1877 table.name
1878 )));
1879 }
1880 }
1881 }
1882
1883 if !mvcc_enabled {
1886 assert!(
1887 automatic_indexes.is_empty(),
1888 "all automatic indexes parsed from sqlite_schema should have been consumed, but {} remain",
1889 automatic_indexes.len()
1890 );
1891 }
1892 }
1893 Ok(())
1894 }
1895
1896 pub fn populate_materialized_views(
1898 &mut self,
1899 materialized_view_info: HashMap<String, (String, i64)>,
1900 dbsp_state_roots: HashMap<String, i64>,
1901 dbsp_state_index_roots: HashMap<String, i64>,
1902 ) -> Result<()> {
1903 for (view_name, (sql, main_root)) in materialized_view_info {
1904 let dbsp_state_root = if let Some(&root) = dbsp_state_roots.get(&view_name) {
1908 root
1909 } else {
1910 tracing::warn!(
1911 "Materialized view '{}' has incompatible version or missing DBSP state table",
1912 view_name
1913 );
1914 self.incompatible_views.insert(view_name.clone());
1916 0
1918 };
1919
1920 let dbsp_state_index_root =
1922 dbsp_state_index_roots.get(&view_name).copied().unwrap_or(0);
1923
1924 if dbsp_state_index_root > 0 && dbsp_state_root > 0 {
1926 let mut index = create_dbsp_state_index(dbsp_state_index_root);
1927 let dbsp_table_name =
1928 format!("{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{view_name}");
1929 index.name = format!("sqlite_autoindex_{dbsp_table_name}_1");
1930 index.table_name = dbsp_table_name;
1931 if let Err(e) = self.add_index(std::sync::Arc::new(index)) {
1932 if !e.to_string().contains("already exists") {
1933 return Err(e);
1934 }
1935 }
1936 }
1937
1938 let incremental_view = IncrementalView::from_sql(
1940 &sql,
1941 self,
1942 main_root,
1943 dbsp_state_root,
1944 dbsp_state_index_root,
1945 )?;
1946 let referenced_tables = incremental_view.get_referenced_table_names();
1947
1948 let cols = incremental_view.column_schema.flat_columns();
1950 let logical_to_physical_map =
1951 BTreeTable::build_logical_to_physical_map(&cols, &[], true);
1952 let table = Arc::new(Table::BTree(Arc::new(BTreeTable {
1953 name: view_name.clone(),
1954 root_page: main_root,
1955 columns: cols,
1956 primary_key_columns: vec![],
1957 has_rowid: true,
1958 is_strict: false,
1959 has_autoincrement: false,
1960 foreign_keys: vec![],
1961 check_constraints: vec![],
1962 rowid_alias_conflict_clause: None,
1963 unique_sets: vec![],
1964 has_virtual_columns: false,
1965 logical_to_physical_map,
1966 column_dependencies: Default::default(),
1967 })));
1968
1969 if !self.incompatible_views.contains(&view_name) {
1971 self.add_materialized_view(incremental_view, table, sql);
1972 }
1973
1974 for table_name in referenced_tables {
1976 self.add_materialized_view_dependency(&table_name, &view_name);
1977 }
1978 }
1979 Ok(())
1980 }
1981
1982 pub fn sequence_backing_table_names(&self) -> Vec<(String, String)> {
1986 self.tables
1987 .keys()
1988 .filter_map(|name| {
1989 let seq_name = name.strip_prefix(SEQ_BACKING_TABLE_PREFIX)?;
1990 Some((name.clone(), seq_name.to_string()))
1991 })
1992 .try_collect()
1993 .expect(crate::alloc::ALLOC_ERR_MSG)
1994 }
1995
1996 fn sequence_backing_tables(&self) -> Vec<SequenceBackingTableSource> {
1997 self.tables
1998 .iter()
1999 .filter_map(|(name, table)| {
2000 let bt = table.btree()?;
2001 let sequence_name = name.strip_prefix(SEQ_BACKING_TABLE_PREFIX)?.to_string();
2002 Some(SequenceBackingTableSource {
2003 sequence_name,
2004 root_page: bt.root_page,
2005 num_columns: bt.columns().len(),
2006 })
2007 })
2008 .try_collect()
2009 .expect(crate::alloc::ALLOC_ERR_MSG)
2010 }
2011
2012 fn read_sequence_metadata(record: &ImmutableRecord) -> Option<SequenceMetadata> {
2013 let mut values = [0i64; 6];
2014 for (i, value) in values.iter_mut().enumerate() {
2015 match record.get_value(i + 1) {
2016 Ok(ValueRef::Numeric(crate::numeric::Numeric::Integer(v))) => {
2017 *value = v;
2018 }
2019 _ => return None,
2020 }
2021 }
2022 let [_is_called, start, increment, min, max, cycle] = values;
2023 Some(SequenceMetadata {
2024 start,
2025 increment,
2026 min,
2027 max,
2028 cycle: cycle != 0,
2029 })
2030 }
2031
2032 fn install_sequence_descriptor(
2033 &mut self,
2034 sequence_name: &str,
2035 metadata: SequenceMetadata,
2036 ) -> crate::Result<()> {
2037 let seq = Sequence::new(
2038 sequence_name.to_string(),
2039 Some(metadata.start),
2040 Some(metadata.increment),
2041 Some(metadata.min),
2042 Some(metadata.max),
2043 metadata.cycle,
2044 )
2045 .map_err(|err| {
2046 LimboError::Corrupt(format!(
2047 "internal sequence backing table for \"{sequence_name}\" \
2048 has invalid persisted metadata \
2049 (start={}, increment={}, min={}, max={}, cycle={}): {err}",
2050 metadata.start, metadata.increment, metadata.min, metadata.max, metadata.cycle,
2051 ))
2052 })?;
2053 self.sequences
2054 .insert(normalize_ident(sequence_name), std::sync::Arc::new(seq));
2055 Ok(())
2056 }
2057
2058 #[allow(clippy::too_many_arguments)]
2059 pub fn handle_schema_row(
2060 &mut self,
2061 ty: &str,
2062 name: &str,
2063 table_name: &str,
2064 root_page: i64,
2065 maybe_sql: Option<&str>,
2066 syms: &SymbolTable,
2067 from_sql_indexes: &mut Vec<UnparsedFromSqlIndex>,
2068 automatic_indices: &mut HashMap<String, Vec<(String, i64)>>,
2069 dbsp_state_roots: &mut HashMap<String, i64>,
2070 dbsp_state_index_roots: &mut HashMap<String, i64>,
2071 materialized_view_info: &mut HashMap<String, (String, i64)>,
2072 resolve_attached_db: &dyn Fn(&str) -> Option<usize>,
2080 ) -> Result<()> {
2081 match ty {
2082 "table" => {
2083 let sql = maybe_sql.expect("sql should be present for table");
2084 match Parser::new(sql.as_bytes()).next_cmd()? {
2089 Some(Cmd::Stmt(Stmt::CreateVirtualTable(_))) => {
2090 if root_page != 0 {
2091 return Err(LimboError::Corrupt(format!(
2092 "sqlite_schema root_page must be 0 for virtual table {name}, got {root_page}"
2093 )));
2094 }
2095 let vtab = if let Some(vtab) = syms.vtabs.get(name) {
2099 vtab.clone()
2100 } else {
2101 let mod_name = module_name_from_sql(sql)?;
2102 crate::VirtualTable::table(
2103 Some(name),
2104 mod_name,
2105 module_args_from_sql(sql)?,
2106 syms,
2107 )?
2108 };
2109 self.add_virtual_table(vtab)?;
2110 }
2111 Some(Cmd::Stmt(Stmt::CreateTable { tbl_name, body, .. })) => {
2112 let table = create_table(tbl_name.name.as_str(), &body, root_page)?;
2113
2114 if table.has_virtual_columns && !self.generated_columns_enabled {
2115 return Err(LimboError::ParseError(format!(
2116 "table '{}' uses generated columns but the generated_columns feature is not enabled",
2117 table.name
2118 )));
2119 }
2120
2121 if table.name.starts_with(SEQ_BACKING_TABLE_PREFIX) {
2125 self.add_btree_table(Arc::new(table))?;
2126 return Ok(());
2127 }
2128
2129 if table.name.starts_with(DBSP_TABLE_PREFIX) {
2131 let suffix = table.name.strip_prefix(DBSP_TABLE_PREFIX).unwrap();
2133
2134 if let Some(underscore_pos) = suffix.find('_') {
2136 let version_str = &suffix[..underscore_pos];
2137 let view_name = &suffix[underscore_pos + 1..];
2138
2139 if let Ok(stored_version) = version_str.parse::<u32>() {
2141 if stored_version == DBSP_CIRCUIT_VERSION {
2142 dbsp_state_roots.insert(view_name.to_string(), root_page);
2144 } else {
2145 tracing::warn!(
2148 "Skipping materialized view '{}' - has version {} but current version is {}. DROP and recreate the view to use it.",
2149 view_name,
2150 stored_version,
2151 DBSP_CIRCUIT_VERSION
2152 );
2153 }
2156 }
2157 }
2158 }
2159
2160 let mut table = table;
2161 table.resolve_custom_type_affinities(self);
2162 table.propagate_domain_constraints(self)?;
2163 let has_autoinc = table.has_autoincrement;
2164 let tbl_name = table.name.clone();
2165 self.add_btree_table(Arc::new(table))?;
2166
2167 if has_autoinc {
2173 let seq_name = autoincrement_sequence_name(&tbl_name);
2174 if let std::collections::hash_map::Entry::Vacant(e) =
2175 self.sequences.entry(normalize_ident(&seq_name))
2176 {
2177 let seq = Sequence::new(
2178 seq_name.clone(),
2179 Some(1),
2180 Some(1),
2181 None,
2182 None,
2183 false,
2184 )?;
2185 e.insert(Arc::new(seq));
2186 }
2187 }
2188 }
2189 other => {
2190 return Err(LimboError::Corrupt(format!(
2191 "sqlite_schema table row {name} has unexpected SQL {sql:?}: parsed as {other:?}"
2192 )));
2193 }
2194 }
2195 }
2196 "index" => {
2197 match maybe_sql {
2198 Some(sql) => {
2199 from_sql_indexes.push(UnparsedFromSqlIndex {
2200 table_name: table_name.to_string(),
2201 root_page,
2202 sql: sql.to_string(),
2203 });
2204 }
2205 None => {
2206 let index_name = name.to_string();
2210 let table_name = table_name.to_string();
2211
2212 if table_name.starts_with(DBSP_TABLE_PREFIX) {
2214 let suffix = table_name.strip_prefix(DBSP_TABLE_PREFIX).unwrap();
2216
2217 if let Some(underscore_pos) = suffix.find('_') {
2219 let version_str = &suffix[..underscore_pos];
2220 let view_name = &suffix[underscore_pos + 1..];
2221
2222 if let Ok(stored_version) = version_str.parse::<u32>() {
2224 if stored_version == DBSP_CIRCUIT_VERSION {
2225 dbsp_state_index_roots
2226 .insert(view_name.to_string(), root_page);
2227 }
2228 }
2229 }
2230 } else {
2231 match automatic_indices.entry(table_name) {
2232 std::collections::hash_map::Entry::Vacant(e) => {
2233 e.insert(vec![(index_name, root_page)]);
2234 }
2235 std::collections::hash_map::Entry::Occupied(mut e) => {
2236 e.get_mut().push((index_name, root_page));
2237 }
2238 }
2239 }
2240 }
2241 }
2242 }
2243 "view" => {
2244 use crate::schema::View;
2245 use turso_parser::ast::{Cmd, Stmt};
2246 use turso_parser::parser::Parser;
2247
2248 let sql = maybe_sql.expect("sql should be present for view");
2249 let view_name = name.to_string();
2250
2251 let mut parser = Parser::new(sql.as_bytes());
2253 let parsed = parser.next_cmd();
2254 if !matches!(&parsed, Ok(Some(Cmd::Stmt(_)))) {
2255 tracing::warn!(
2260 "view '{view_name}' has unparseable SQL in sqlite_schema; \
2261 it is unavailable but can be removed with DROP VIEW: {sql}"
2262 );
2263 self.broken_views.insert(view_name);
2264 } else if let Ok(Some(Cmd::Stmt(stmt))) = parsed {
2265 match stmt {
2266 Stmt::CreateMaterializedView { .. } => {
2267 materialized_view_info
2271 .insert(view_name.clone(), (sql.to_string(), root_page));
2272
2273 if self.incremental_views.contains_key(&view_name) {
2275 }
2277 }
2278 Stmt::CreateView {
2279 view_name: _,
2280 columns: column_names,
2281 select,
2282 ..
2283 } => {
2284 crate::util::validate_select_for_unsupported_features(&select)?;
2285
2286 let view_column_schema =
2288 crate::util::extract_view_columns(&select, self)?;
2289
2290 let mut final_columns = view_column_schema.flat_columns();
2293 for (i, indexed_col) in column_names.iter().enumerate() {
2294 if let Some(col) = final_columns.get_mut(i) {
2295 col.name = Some(indexed_col.col_name.as_str().to_string());
2298 }
2299 }
2300
2301 let view =
2303 View::new(name.to_string(), sql.to_string(), select, final_columns);
2304 self.add_view(view)?;
2305 }
2306 _ => {}
2307 }
2308 }
2309 }
2310 "trigger" => {
2311 use turso_parser::ast::{Cmd, Stmt};
2312 use turso_parser::parser::Parser;
2313
2314 let sql = maybe_sql.expect("sql should be present for trigger");
2315 let trigger_name = name.to_string();
2316
2317 let mut parser = Parser::new(sql.as_bytes());
2318 let Ok(Some(Cmd::Stmt(Stmt::CreateTrigger {
2319 temporary,
2320 if_not_exists: _,
2321 trigger_name: _,
2322 time,
2323 event,
2324 tbl_name,
2325 for_each_row,
2326 when_clause,
2327 commands,
2328 }))) = parser.next_cmd()
2329 else {
2330 return Err(crate::LimboError::ParseError(format!(
2331 "invalid trigger sql: {sql}"
2332 )));
2333 };
2334 let target_database_id = tbl_name.db_name.as_ref().map(|db_name| {
2345 let db = db_name.as_str();
2346 if db.eq_ignore_ascii_case("main") {
2347 crate::MAIN_DB_ID
2348 } else if db.eq_ignore_ascii_case("temp") {
2349 crate::TEMP_DB_ID
2350 } else {
2351 resolve_attached_db(db).unwrap_or(crate::INVALID_DB_ID)
2352 }
2353 });
2354 self.add_trigger(
2355 Trigger::new(
2356 trigger_name,
2357 sql.to_string(),
2358 tbl_name.name.as_str().to_string(),
2363 time,
2364 event,
2365 for_each_row,
2366 when_clause.map(|e| *e),
2367 commands,
2368 temporary,
2369 target_database_id,
2370 ),
2371 tbl_name.name.as_str(),
2372 )?;
2373 }
2374 _ => {}
2376 };
2377
2378 Ok(())
2379 }
2380
2381 pub fn resolved_fks_referencing(&self, table_name: &str) -> Result<Vec<ResolvedFkRef>> {
2385 let target = normalize_ident(table_name);
2386 let parent_tbl = self
2387 .get_btree_table(&target)
2388 .ok_or_else(|| fk_mismatch_err("<unknown>", &target))?;
2389
2390 let mut out = Vec::try_with_capacity_ext(4)?; for t in self.tables.values() {
2392 let Some(child) = t.btree() else {
2393 continue;
2394 };
2395 for fk in &child.foreign_keys {
2396 if !fk.parent_table.eq_ignore_ascii_case(&target) {
2397 continue;
2398 }
2399 out.try_push(self.resolve_fk(
2400 fk,
2401 &child,
2402 &parent_tbl,
2403 false,
2404 )?)?;
2405 }
2406 }
2407 Ok(out)
2408 }
2409
2410 pub fn resolved_fks_for_child(&self, child_table: &str) -> crate::Result<Vec<ResolvedFkRef>> {
2414 let child_name = normalize_ident(child_table);
2415 let child = self
2416 .get_btree_table(&child_name)
2417 .ok_or_else(|| fk_mismatch_err(&child_name, "<unknown>"))?;
2418
2419 let mut out = Vec::try_with_capacity_ext(child.foreign_keys.len())?;
2420 for fk in &child.foreign_keys {
2421 let parent_name = normalize_ident(&fk.parent_table);
2422 let parent_tbl = self
2423 .get_btree_table(&parent_name)
2424 .ok_or_else(|| fk_mismatch_err(&child.name, &parent_name))?;
2425 out.push_within_capacity(self.resolve_fk(
2426 fk,
2427 &child,
2428 &parent_tbl,
2429 true,
2430 )?)
2431 .expect("resolved FK vector was preallocated to child.foreign_keys.len()");
2432 }
2433 Ok(out)
2434 }
2435
2436 fn resolve_fk(
2440 &self,
2441 fk: &Arc<ForeignKey>,
2442 child: &Arc<BTreeTable>,
2443 parent_tbl: &Arc<BTreeTable>,
2444 require_unique: bool,
2445 ) -> Result<ResolvedFkRef> {
2446 if fk.child_columns.is_empty() {
2449 return Err(fk_mismatch_err(&child.name, &parent_tbl.name));
2450 }
2451
2452 let mut child_pos: Vec<usize> = Vec::try_with_capacity_ext(fk.child_columns.len())?;
2453 for cname in fk.child_columns.iter() {
2454 let (i, _) = child
2455 .get_column(cname)
2456 .ok_or_else(|| fk_mismatch_err(&child.name, &parent_tbl.name))?;
2457 child_pos
2458 .push_within_capacity(i)
2459 .expect("child FK position vector was preallocated to fk.child_columns.len()");
2460 }
2461
2462 let parent_cols: Box<[String]> = if fk.parent_columns.is_empty() {
2464 if parent_tbl.primary_key_columns.is_empty() {
2465 return Err(fk_mismatch_err(&child.name, &parent_tbl.name));
2466 }
2467 parent_tbl
2468 .primary_key_columns
2469 .iter()
2470 .map(|(col, _)| col.clone())
2471 .try_collect()?
2472 } else {
2473 fk.parent_columns.clone()
2474 };
2475
2476 if parent_cols.len() != fk.child_columns.len() {
2477 return Err(fk_mismatch_err(&child.name, &parent_tbl.name));
2478 }
2479
2480 let mut parent_pos: Vec<usize> = Vec::try_with_capacity_ext(parent_cols.len())?;
2481 for pc in parent_cols.iter() {
2482 let pos = parent_tbl.get_column(pc).map(|(i, _)| i).or_else(|| {
2483 ROWID_STRS
2484 .iter()
2485 .any(|r| pc.eq_ignore_ascii_case(r))
2486 .then_some(0)
2487 });
2488 let Some(p) = pos else {
2489 return Err(fk_mismatch_err(&child.name, &parent_tbl.name));
2490 };
2491 parent_pos
2492 .push_within_capacity(p)
2493 .expect("parent FK position vector was preallocated to parent_cols.len()");
2494 }
2495
2496 let parent_uses_rowid = parent_cols.len() == 1 && {
2499 let pc = parent_cols[0].as_str();
2500 ROWID_STRS.iter().any(|r| pc.eq_ignore_ascii_case(r))
2501 || parent_tbl.columns.iter().any(|col| {
2502 col.is_rowid_alias()
2503 && col
2504 .name
2505 .as_deref()
2506 .is_some_and(|n| n.eq_ignore_ascii_case(pc))
2507 })
2508 };
2509
2510 let parent_unique_index = if parent_uses_rowid {
2511 None
2512 } else {
2513 let found = self
2514 .get_indices(&parent_tbl.name)
2515 .find(|idx| {
2516 idx.unique
2517 && idx.where_clause.is_none()
2518 && idx.columns.len() == parent_cols.len()
2519 && idx
2520 .columns
2521 .iter()
2522 .zip(parent_cols.iter())
2523 .all(|(ic, pc)| ic.name.eq_ignore_ascii_case(pc))
2524 })
2525 .cloned();
2526 if require_unique && found.is_none() {
2527 return Err(fk_mismatch_err(&child.name, &parent_tbl.name));
2528 }
2529 found
2530 };
2531
2532 fk.validate()?;
2533 Ok(ResolvedFkRef {
2534 child_table: Arc::clone(child),
2535 fk: Arc::clone(fk),
2536 parent_cols,
2537 child_pos: child_pos.into_boxed_slice(),
2538 parent_pos: parent_pos.into_boxed_slice(),
2539 parent_uses_rowid,
2540 parent_unique_index,
2541 })
2542 }
2543
2544 pub fn any_resolved_fks_referencing(&self, table_name: &str) -> bool {
2546 self.tables.values().any(|t| {
2547 let Some(bt) = t.btree() else {
2548 return false;
2549 };
2550 bt.foreign_keys
2551 .iter()
2552 .any(|fk| fk.parent_table == table_name)
2553 })
2554 }
2555
2556 pub fn has_child_fks(&self, table_name: &str) -> bool {
2558 self.get_table(table_name)
2559 .and_then(|t| t.btree())
2560 .is_some_and(|t| !t.foreign_keys.is_empty())
2561 }
2562
2563 fn check_object_name_conflict(&self, name: &str) -> Result<()> {
2564 if let Some(object_type) = self.get_object_type(name) {
2565 let type_str = match object_type {
2566 SchemaObjectType::Table => "table",
2567 SchemaObjectType::View => "view",
2568 SchemaObjectType::Index => "index",
2569 };
2570 return Err(crate::LimboError::ParseError(format!(
2571 "{type_str} \"{name}\" already exists"
2572 )));
2573 }
2574 Ok(())
2575 }
2576
2577 pub fn get_sequence(&self, name: &str) -> Option<&Arc<Sequence>> {
2578 self.sequences.get(&normalize_ident(name))
2579 }
2580
2581 pub fn remove_sequence(&mut self, name: &str) {
2583 let normalized = normalize_ident(name);
2584 self.sequences.remove(&normalized);
2585 let backing_table = crate::translate::sequence::sequence_backing_table_name(&normalized);
2586 self.tables.remove(&backing_table);
2587 }
2588
2589 pub fn get_object_type(&self, name: &str) -> Option<SchemaObjectType> {
2592 let normalized_name = self.normalize_table_lookup_name(name);
2593
2594 if self.tables.contains_key(&normalized_name) {
2595 return Some(SchemaObjectType::Table);
2596 }
2597
2598 if self.views.contains_key(&normalized_name) {
2599 return Some(SchemaObjectType::View);
2600 }
2601
2602 for index_list in self.indexes.values() {
2603 if index_list.iter().any(|i| i.name.eq_ignore_ascii_case(name)) {
2604 return Some(SchemaObjectType::Index);
2605 }
2606 }
2607
2608 None
2609 }
2610}
2611
2612impl TryClone for UniqueSet {
2613 type Error = TryReserveError;
2614
2615 fn try_clone(&self) -> Result<Self, Self::Error> {
2616 Ok(Self {
2617 columns: self.columns.try_clone()?,
2618 collations: self.collations.try_clone()?,
2619 is_primary_key: self.is_primary_key,
2620 conflict_clause: self.conflict_clause,
2621 })
2622 }
2623}
2624
2625crate::alloc::impl_try_clone_via_clone!(
2627 turso_parser::ast::SortOrder,
2628 crate::translate::collate::CollationSeq,
2629);
2630
2631crate::alloc::impl_try_clone_via_clone!(Column, IndexColumn, CheckConstraint);
2636
2637impl Schema {
2638 #[turso_macros::allocation_site(crate::alloc::SchemaAllocationSite::MakeMut)]
2639 pub(crate) fn try_make_mut(schema: &mut Arc<Self>) -> Result<&mut Self, TryReserveError> {
2640 if Arc::get_mut(schema).is_none() {
2641 *schema = Arc::new(schema.as_ref().try_clone()?);
2642 }
2643 Ok(Arc::get_mut(schema).expect("schema was made unique above"))
2644 }
2645}
2646
2647impl TryClone for View {
2648 type Error = TryReserveError;
2649
2650 fn try_clone(&self) -> Result<Self, Self::Error> {
2651 Ok(Self {
2652 name: self.name.clone(),
2653 sql: self.sql.clone(),
2654 select_stmt: self.select_stmt.clone(),
2655 columns: self.columns.try_clone()?,
2656 state: AtomicViewState::new(ViewState::Ready),
2657 })
2658 }
2659}
2660
2661impl TryClone for VirtualTable {
2662 type Error = TryReserveError;
2663
2664 fn try_clone(&self) -> Result<Self, Self::Error> {
2665 Ok(Self {
2666 name: self.name.clone(),
2667 columns: self.columns.try_clone()?,
2668 kind: self.kind,
2669 vtab_type: self.vtab_type.clone(),
2670 vtab_id: self.vtab_id,
2671 innocuous: self.innocuous,
2672 })
2673 }
2674}
2675
2676impl TryClone for BTreeTable {
2677 type Error = TryReserveError;
2678
2679 fn try_clone(&self) -> Result<Self, Self::Error> {
2680 Ok(Self {
2681 root_page: self.root_page,
2682 name: self.name.clone(),
2683 primary_key_columns: self.primary_key_columns.try_clone()?,
2684 columns: self.columns.try_clone()?,
2685 has_rowid: self.has_rowid,
2686 is_strict: self.is_strict,
2687 has_autoincrement: self.has_autoincrement,
2688 unique_sets: self.unique_sets.try_clone()?,
2689 foreign_keys: self.foreign_keys.try_clone()?,
2690 check_constraints: self.check_constraints.try_clone()?,
2691 rowid_alias_conflict_clause: self.rowid_alias_conflict_clause,
2692 has_virtual_columns: self.has_virtual_columns,
2693 logical_to_physical_map: self.logical_to_physical_map.try_clone()?,
2694 column_dependencies: Default::default(),
2695 })
2696 }
2697}
2698
2699impl TryClone for FromClauseSubquery {
2700 type Error = TryReserveError;
2701
2702 fn try_clone(&self) -> Result<Self, Self::Error> {
2703 Ok(Self {
2704 name: self.name.clone(),
2705 plan: self.plan.clone(),
2706 columns: self.columns.try_clone()?,
2707 result_columns_start_reg: self.result_columns_start_reg,
2708 materialized_cursor_id: self.materialized_cursor_id,
2709 cte: self.cte,
2710 })
2711 }
2712}
2713
2714impl TryClone for Table {
2715 type Error = TryReserveError;
2716
2717 fn try_clone(&self) -> Result<Self, Self::Error> {
2718 Ok(match self {
2719 Table::BTree(table) => Table::BTree(Arc::new(table.as_ref().try_clone()?)),
2720 Table::Virtual(table) => Table::Virtual(Arc::new(table.as_ref().try_clone()?)),
2721 Table::FromClauseSubquery(from_clause_subquery) => {
2722 Table::FromClauseSubquery(Arc::new(from_clause_subquery.as_ref().try_clone()?))
2723 }
2724 })
2725 }
2726}
2727
2728impl TryClone for Index {
2729 type Error = TryReserveError;
2730
2731 fn try_clone(&self) -> Result<Self, Self::Error> {
2732 Ok(Self {
2733 name: self.name.clone(),
2734 table_name: self.table_name.clone(),
2735 root_page: self.root_page,
2736 columns: self.columns.try_clone()?,
2737 unique: self.unique,
2738 ephemeral: self.ephemeral,
2739 has_rowid: self.has_rowid,
2740 where_clause: self.where_clause.clone(),
2741 index_method: self.index_method.clone(),
2742 on_conflict: self.on_conflict,
2743 })
2744 }
2745}
2746
2747impl TryClone for Schema {
2748 type Error = TryReserveError;
2753
2754 fn try_clone(&self) -> Result<Self, Self::Error> {
2755 let tables = self
2756 .tables
2757 .iter()
2758 .map(|(name, table)| {
2759 Ok::<_, TryReserveError>((name.clone(), Arc::new(table.as_ref().try_clone()?)))
2760 })
2761 .try_collect::<Result<_, TryReserveError>>()??;
2762 let indexes = self
2763 .indexes
2764 .iter()
2765 .map(|(name, indexes)| {
2766 let indexes = indexes
2767 .iter()
2768 .map(|index| index.as_ref().try_clone().map(Arc::new))
2769 .try_collect::<Result<VecDeque<_>, TryReserveError>>()??;
2770 Ok::<_, TryReserveError>((name.clone(), indexes))
2771 })
2772 .try_collect::<Result<_, TryReserveError>>()??;
2773 let materialized_view_names = self.materialized_view_names.try_clone()?;
2774 let materialized_view_sql = self.materialized_view_sql.try_clone()?;
2775 let incremental_views = self
2776 .incremental_views
2777 .iter()
2778 .map(|(name, view)| (name.clone(), view.clone()))
2779 .try_collect()?;
2780 let views = self
2781 .views
2782 .iter()
2783 .map(|(name, view)| {
2784 Ok::<_, TryReserveError>((name.clone(), Arc::new(view.as_ref().try_clone()?)))
2785 })
2786 .try_collect::<Result<_, TryReserveError>>()??;
2787 let triggers = self
2788 .triggers
2789 .iter()
2790 .map(|(table_name, triggers)| {
2791 Ok::<_, TryReserveError>((
2792 table_name.clone(),
2793 triggers
2794 .iter()
2795 .map(|t| Arc::new((**t).clone()))
2796 .try_collect()?,
2797 ))
2798 })
2799 .try_collect::<Result<_, TryReserveError>>()??;
2800 let incompatible_views = self.incompatible_views.try_clone()?;
2801 Ok(Self {
2802 tables,
2803 #[cfg(feature = "conn_raw_api")]
2804 table_names_by_root_page: self.table_names_by_root_page.try_clone()?,
2805 materialized_view_names,
2806 materialized_view_sql,
2807 incremental_views,
2808 views,
2809 triggers,
2810 indexes,
2811 has_indexes: self.has_indexes.try_clone()?,
2812 schema_version: self.schema_version,
2813 analyze_stats: self.analyze_stats.clone(),
2814 table_to_materialized_views: self.table_to_materialized_views.try_clone()?,
2815 incompatible_views,
2816 broken_views: self.broken_views.try_clone()?,
2817 dropped_root_pages: self.dropped_root_pages.try_clone()?,
2818 type_registry: self.type_registry.try_clone()?,
2819 generated_columns_enabled: self.generated_columns_enabled,
2820 sequences: self.sequences.try_clone()?,
2821 })
2822 }
2823}
2824
2825#[derive(Debug, Clone)]
2829pub enum ColumnLayout {
2830 Identity {
2831 column_count: usize,
2832 },
2833 Mapped {
2834 offsets: Vec<usize>,
2836 non_virtual_col_count: usize,
2837 },
2838}
2839
2840impl ColumnLayout {
2841 pub fn from_table(table: &Table) -> Result<Self, TryReserveError> {
2842 match table {
2843 Table::BTree(btree) => Self::from_btree(btree),
2844 Table::Virtual(vtable) => Ok(Self::Identity {
2845 column_count: vtable.as_ref().columns.len(),
2846 }),
2847 Table::FromClauseSubquery(subquery) => Ok(Self::Identity {
2848 column_count: subquery.columns.len(),
2849 }),
2850 }
2851 }
2852
2853 pub fn from_btree(btree: &BTreeTable) -> Result<Self, TryReserveError> {
2854 let total = btree.columns.len();
2855 let non_virtual_col_count = btree
2856 .columns
2857 .iter()
2858 .filter(|c| !c.is_virtual_generated())
2859 .count();
2860 let offsets = btree.logical_to_physical_map.try_clone()?;
2861 let is_identity = non_virtual_col_count == total && offsets.iter().copied().eq(0..total);
2862 if is_identity {
2863 Ok(Self::Identity {
2864 column_count: total,
2865 })
2866 } else {
2867 Ok(Self::Mapped {
2868 offsets,
2869 non_virtual_col_count,
2870 })
2871 }
2872 }
2873
2874 pub fn from_columns(columns: &[Column]) -> Result<Self, TryReserveError> {
2875 let total = columns.len();
2876 let non_virtual_col_count = columns.iter().filter(|c| !c.is_virtual_generated()).count();
2877 if non_virtual_col_count == total {
2878 return Ok(Self::Identity {
2879 column_count: total,
2880 });
2881 }
2882 let mut offsets = try_vec![0usize; total]?;
2883 let mut nv_idx = 0;
2884 let mut v_idx = non_virtual_col_count;
2885 for (i, col) in columns.iter().enumerate() {
2886 if col.is_virtual_generated() {
2887 offsets[i] = v_idx;
2888 v_idx += 1;
2889 } else {
2890 offsets[i] = nv_idx;
2891 nv_idx += 1;
2892 }
2893 }
2894 Ok(Self::Mapped {
2895 offsets,
2896 non_virtual_col_count,
2897 })
2898 }
2899
2900 #[inline(always)]
2902 pub fn to_reg_offset(&self, col_idx: usize) -> usize {
2903 match self {
2904 Self::Identity { .. } => col_idx,
2905 Self::Mapped { offsets, .. } => offsets[col_idx],
2906 }
2907 }
2908
2909 #[inline(always)]
2911 pub fn to_register(&self, base: usize, schema_idx: usize) -> usize {
2912 base + self.to_reg_offset(schema_idx)
2913 }
2914
2915 #[inline(always)]
2916 pub fn num_non_virtual_cols(&self) -> usize {
2917 match self {
2918 Self::Identity {
2919 column_count: total,
2920 } => *total,
2921 Self::Mapped {
2922 non_virtual_col_count,
2923 ..
2924 } => *non_virtual_col_count,
2925 }
2926 }
2927
2928 #[inline(always)]
2929 pub fn column_count(&self) -> usize {
2930 match self {
2931 Self::Identity {
2932 column_count: total,
2933 } => *total,
2934 Self::Mapped { offsets, .. } => offsets.len(),
2935 }
2936 }
2937
2938 pub fn column_idx_for_offset(&self, offset: usize) -> Option<usize> {
2939 match self {
2940 Self::Identity { column_count } => {
2941 if offset < *column_count {
2942 Some(offset)
2943 } else {
2944 None
2945 }
2946 }
2947 Self::Mapped { offsets, .. } => offsets.iter().position(|&s| s == offset),
2948 }
2949 }
2950}
2951
2952#[derive(Clone, Debug)]
2953pub enum Table {
2954 BTree(Arc<BTreeTable>),
2955 Virtual(Arc<VirtualTable>),
2956 FromClauseSubquery(Arc<FromClauseSubquery>),
2957}
2958
2959impl Table {
2960 pub fn get_root_page(&self) -> crate::Result<i64> {
2961 match self {
2962 Table::BTree(table) => Ok(table.root_page),
2963 Table::Virtual(_) => Err(crate::LimboError::InternalError(
2964 "Virtual tables do not have a root page".to_string(),
2965 )),
2966 Table::FromClauseSubquery(_) => Err(crate::LimboError::InternalError(
2967 "FROM clause subqueries do not have a root page".to_string(),
2968 )),
2969 }
2970 }
2971
2972 pub fn get_name(&self) -> &str {
2973 match self {
2974 Self::BTree(table) => &table.name,
2975 Self::Virtual(table) => &table.name,
2976 Self::FromClauseSubquery(from_clause_subquery) => &from_clause_subquery.name,
2977 }
2978 }
2979
2980 pub fn get_column_at(&self, index: usize) -> Option<&Column> {
2981 match self {
2982 Self::BTree(table) => table.columns.get(index),
2983 Self::Virtual(table) => table.columns.get(index),
2984 Self::FromClauseSubquery(from_clause_subquery) => {
2985 from_clause_subquery.columns.get(index)
2986 }
2987 }
2988 }
2989
2990 pub fn get_column_by_name(&self, name: &str) -> Option<(usize, &Column)> {
2992 match self {
2993 Self::BTree(table) => table.get_column(name),
2994 Self::Virtual(table) => table.columns.iter().enumerate().find(|(_, col)| {
2995 col.name
2996 .as_ref()
2997 .is_some_and(|n| n.eq_ignore_ascii_case(name))
2998 }),
2999 Self::FromClauseSubquery(from_clause_subquery) => from_clause_subquery
3000 .columns
3001 .iter()
3002 .enumerate()
3003 .find(|(_, col)| {
3004 col.name
3005 .as_ref()
3006 .is_some_and(|n| n.eq_ignore_ascii_case(name))
3007 }),
3008 }
3009 }
3010
3011 pub fn columns(&self) -> &[Column] {
3012 match self {
3013 Self::BTree(table) => &table.columns,
3014 Self::Virtual(table) => &table.columns,
3015 Self::FromClauseSubquery(from_clause_subquery) => &from_clause_subquery.columns,
3016 }
3017 }
3018
3019 pub fn is_strict(&self) -> bool {
3020 match self {
3021 Self::BTree(table) => table.is_strict,
3022 Self::Virtual(_) => false,
3023 Self::FromClauseSubquery(_) => false,
3024 }
3025 }
3026
3027 pub fn btree(&self) -> Option<Arc<BTreeTable>> {
3028 match self {
3029 Self::BTree(table) => Some(table.clone()),
3030 Self::Virtual(_) => None,
3031 Self::FromClauseSubquery(_) => None,
3032 }
3033 }
3034
3035 pub fn require_btree(&self) -> crate::Result<Arc<BTreeTable>> {
3037 self.btree().ok_or_else(|| {
3038 crate::LimboError::InternalError(
3039 "operation requires a btree table, not a virtual table".into(),
3040 )
3041 })
3042 }
3043
3044 pub fn btree_mut(&mut self) -> Option<&mut Arc<BTreeTable>> {
3045 match self {
3046 Self::BTree(table) => Some(table),
3047 Self::Virtual(_) => None,
3048 Self::FromClauseSubquery(_) => None,
3049 }
3050 }
3051
3052 pub fn virtual_table(&self) -> Option<Arc<VirtualTable>> {
3053 match self {
3054 Self::Virtual(table) => Some(table.clone()),
3055 _ => None,
3056 }
3057 }
3058}
3059
3060impl PartialEq for Table {
3061 fn eq(&self, other: &Self) -> bool {
3062 match (self, other) {
3063 (Self::BTree(a), Self::BTree(b)) => Arc::ptr_eq(a, b),
3064 (Self::Virtual(a), Self::Virtual(b)) => Arc::ptr_eq(a, b),
3065 _ => false,
3066 }
3067 }
3068}
3069
3070#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
3071pub struct UniqueSet {
3072 pub columns: Vec<(String, SortOrder)>,
3073 pub collations: Vec<Option<CollationSeq>>,
3077 pub is_primary_key: bool,
3078 pub conflict_clause: Option<ResolveType>,
3079}
3080
3081#[derive(Clone, Debug)]
3082pub struct CheckConstraint {
3083 pub name: Option<String>,
3085 pub expr: ast::Expr,
3087 pub column: Option<String>,
3090}
3091
3092impl CheckConstraint {
3093 pub fn new(name: Option<&ast::Name>, expr: &ast::Expr, column: Option<&str>) -> Self {
3094 Self {
3095 name: name.map(|n| n.as_str().to_string()),
3096 expr: expr.clone(),
3097 column: column.map(|s| s.to_string()),
3098 }
3099 }
3100
3101 pub fn sql(&self) -> String {
3103 format!("CHECK({})", self.expr)
3104 }
3105}
3106
3107#[derive(Debug, Default)]
3109pub struct ResetOnClone<T: Default>(T);
3110
3111impl<T: Default> Clone for ResetOnClone<T> {
3112 fn clone(&self) -> Self {
3113 Self(T::default())
3114 }
3115}
3116
3117bitflags! {
3118 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
3119 pub struct BTreeCharacteristics: u8 {
3120 const HAS_ROWID = 0b0000_0001;
3122 const STRICT = 0b0000_0010;
3124 const HAS_AUTOINCREMENT = 0b0000_0100;
3126 }
3127}
3128
3129#[derive(Debug)]
3130pub(crate) struct GeneratedColGraph {
3131 dependencies: Vec<ColumnMask>,
3133 dependents: Vec<ColumnMask>,
3135 topological_sort: Vec<usize>,
3137}
3138
3139impl GeneratedColGraph {
3140 fn build(columns: &[Column]) -> Result<Self> {
3141 let n = columns.len();
3142
3143 let mut direct_deps = try_vec![ColumnMask::default(); n]?;
3144 let mut direct_dependents = try_vec![ColumnMask::default(); n]?;
3145 let mut in_degree: Vec<u32> = try_vec![0; n]?;
3146
3147 for (j, col) in columns.iter().enumerate() {
3149 let GeneratedType::Virtual { ref expr, .. } = col.generated_type() else {
3150 continue;
3151 };
3152 let mut direct = BitSet::default();
3153 collect_column_dependencies_of_gencol(expr, columns, &mut direct);
3154 if direct.get(j) {
3155 bail_parse_error!(
3156 "generated column \"{}\" cannot reference itself",
3157 col.name.as_deref().unwrap_or("?")
3158 );
3159 }
3160 let direct_mask: ColumnMask = ColumnMask::try_from_iter(direct.iter())?;
3161 direct_deps[j].union_with(&direct_mask)?;
3162 for i in direct.iter() {
3163 direct_dependents[i].set(j)?;
3164 in_degree[j] += 1;
3165 }
3166 }
3167
3168 let mut topological_sort: Vec<usize> = Vec::try_with_capacity_ext(n)?;
3170 let mut ready: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).try_collect()?;
3171 while let Some(i) = ready.pop() {
3172 topological_sort.try_push(i)?;
3173 for j in direct_dependents[i].iter() {
3174 in_degree[j] -= 1;
3175 if in_degree[j] == 0 {
3176 ready.try_push(j)?;
3177 }
3178 }
3179 }
3180
3181 if topological_sort.len() != n {
3183 let cycle_names: Vec<&str> = (0..n)
3184 .filter(|i| in_degree[*i] > 0)
3185 .filter_map(|i| columns[i].name.as_deref())
3186 .try_collect()?;
3187 bail_parse_error!(
3188 "circular dependency in generated columns: {}",
3189 cycle_names.join(", ")
3190 );
3191 }
3192
3193 let mut dependencies = try_vec![ColumnMask::default(); n]?;
3195 for &j in &topological_sort {
3196 dependencies[j] = direct_deps[j].try_clone()?;
3197 for i in direct_deps[j].iter() {
3198 let snapshot = dependencies[i].try_clone()?;
3199 dependencies[j].union_with(&snapshot)?;
3200 }
3201 }
3202
3203 let mut dependents = try_vec![ColumnMask::default(); n]?;
3205 for &i in topological_sort.iter().rev() {
3206 dependents[i] = direct_dependents[i].try_clone()?;
3207 for j in direct_dependents[i].iter() {
3208 let snapshot = dependents[j].try_clone()?;
3209 dependents[i].union_with(&snapshot)?;
3210 }
3211 }
3212
3213 Ok(Self {
3214 dependencies,
3215 dependents,
3216 topological_sort,
3217 })
3218 }
3219}
3220
3221#[derive(Clone, Debug)]
3222pub struct BTreeTable {
3223 pub root_page: i64,
3224 pub name: String,
3225 pub primary_key_columns: Vec<(String, SortOrder)>,
3226 columns: Vec<Column>,
3227 pub has_rowid: bool,
3228 pub is_strict: bool,
3229 pub has_autoincrement: bool,
3230 pub unique_sets: Vec<UniqueSet>,
3231 pub foreign_keys: Vec<Arc<ForeignKey>>,
3232 pub check_constraints: Vec<CheckConstraint>,
3233 pub rowid_alias_conflict_clause: Option<ResolveType>,
3236 pub has_virtual_columns: bool,
3237 pub logical_to_physical_map: Vec<usize>,
3238 column_dependencies: ResetOnClone<OnceLock<GeneratedColGraph>>,
3239}
3240
3241pub struct ColumnsMut<'a> {
3242 table: &'a mut BTreeTable,
3243}
3244
3245impl std::ops::Deref for ColumnsMut<'_> {
3246 type Target = Vec<Column>;
3247 fn deref(&self) -> &Vec<Column> {
3248 &self.table.columns
3249 }
3250}
3251
3252impl std::ops::DerefMut for ColumnsMut<'_> {
3253 fn deref_mut(&mut self) -> &mut Vec<Column> {
3254 &mut self.table.columns
3255 }
3256}
3257
3258impl Drop for ColumnsMut<'_> {
3259 fn drop(&mut self) {
3260 self.table.column_dependencies.0 = OnceLock::new();
3261 self.table.has_virtual_columns =
3262 self.table.columns.iter().any(|c| c.is_virtual_generated());
3263 self.table.logical_to_physical_map = BTreeTable::build_logical_to_physical_map(
3264 &self.table.columns,
3265 &self.table.primary_key_columns,
3266 self.table.has_rowid,
3267 );
3268 }
3269}
3270
3271impl BTreeTable {
3272 #[allow(clippy::too_many_arguments)]
3273 pub fn new(
3274 root_page: i64,
3275 name: String,
3276 primary_key_columns: Vec<(String, SortOrder)>,
3277 columns: Vec<Column>,
3278 characteristics: BTreeCharacteristics,
3279 unique_sets: Vec<UniqueSet>,
3280 foreign_keys: Vec<Arc<ForeignKey>>,
3281 check_constraints: Vec<CheckConstraint>,
3282 rowid_alias_conflict_clause: Option<ResolveType>,
3283 ) -> Self {
3284 let has_virtual_columns = columns.iter().any(|c| c.is_virtual_generated());
3285 let has_rowid = characteristics.contains(BTreeCharacteristics::HAS_ROWID);
3286 let logical_to_physical_map =
3287 Self::build_logical_to_physical_map(&columns, &primary_key_columns, has_rowid);
3288 Self {
3289 root_page,
3290 name,
3291 primary_key_columns,
3292 columns,
3293 has_rowid,
3294 is_strict: characteristics.contains(BTreeCharacteristics::STRICT),
3295 has_autoincrement: characteristics.contains(BTreeCharacteristics::HAS_AUTOINCREMENT),
3296 unique_sets,
3297 foreign_keys,
3298 check_constraints,
3299 rowid_alias_conflict_clause,
3300 has_virtual_columns,
3301 logical_to_physical_map,
3302 column_dependencies: Default::default(),
3303 }
3304 }
3305
3306 pub fn columns(&self) -> &[Column] {
3307 &self.columns
3308 }
3309
3310 pub fn columns_mut(&mut self) -> ColumnsMut<'_> {
3311 ColumnsMut { table: self }
3312 }
3313
3314 pub fn type_check_table_ref(table: &Arc<BTreeTable>, schema: &Schema) -> Arc<BTreeTable> {
3319 let has_virtual = table.has_virtual_columns();
3320 let has_custom = table
3321 .columns
3322 .iter()
3323 .any(|c| c.is_array() || schema.get_type_def(&c.ty_str, table.is_strict).is_some());
3324 if !has_custom && !has_virtual {
3325 return Arc::clone(table);
3326 }
3327 let mut modified = (**table).clone();
3328 if has_virtual {
3329 modified.columns.retain(|c| !c.is_virtual_generated());
3330 modified.has_virtual_columns = false;
3331 }
3332 for col in &mut modified.columns {
3333 if col.is_array() {
3334 col.ty_str = "BLOB".to_string();
3336 } else if let Ok(Some(resolved)) = schema.resolve_type(&col.ty_str, table.is_strict) {
3337 col.ty_str = resolved.primitive.to_uppercase();
3338 }
3339 }
3340 Arc::new(modified)
3341 }
3342
3343 pub fn input_type_check_table_ref(
3348 table: &Arc<BTreeTable>,
3349 schema: &Schema,
3350 only_columns: Option<&ColumnMask>,
3351 ) -> Result<Arc<BTreeTable>> {
3352 let has_virtual = table.has_virtual_columns();
3353 let has_custom = table
3354 .columns
3355 .iter()
3356 .any(|c| c.is_array() || schema.get_type_def(&c.ty_str, table.is_strict).is_some());
3357 if !has_custom && !has_virtual {
3358 return Ok(Arc::clone(table));
3359 }
3360 let mut modified = (**table).clone();
3361 let remapped_only_columns = if has_virtual {
3362 let remapped = only_columns
3363 .map(|only| {
3364 let mut new_set = ColumnMask::default();
3365 let mut physical = 0usize;
3366 for (orig, col) in modified.columns.iter().enumerate() {
3367 if col.is_virtual_generated() {
3368 continue;
3369 }
3370 if only.get(orig) {
3371 new_set.set(physical)?;
3372 }
3373 physical += 1;
3374 }
3375 Ok::<_, LimboError>(new_set)
3376 })
3377 .transpose()?;
3378 modified.columns.retain(|c| !c.is_virtual_generated());
3379 modified.has_virtual_columns = false;
3380 remapped
3381 } else {
3382 None
3383 };
3384 let effective_only = remapped_only_columns.as_ref().or(only_columns);
3385 for (i, col) in modified.columns.iter_mut().enumerate() {
3386 if let Some(only) = effective_only {
3387 if !only.get(i) {
3388 col.ty_str = "ANY".to_string();
3389 continue;
3390 }
3391 }
3392 if col.is_array() {
3393 col.ty_str = "ANY".to_string();
3396 } else if let Some(type_def) = schema.get_type_def(&col.ty_str, table.is_strict) {
3397 col.ty_str = type_def.value_input_type().to_uppercase();
3398 }
3399 }
3400 Ok(Arc::new(modified))
3401 }
3402
3403 pub fn resolve_custom_type_affinities(&mut self, schema: &Schema) {
3408 if !self.is_strict {
3409 return;
3410 }
3411 for col in &mut self.columns {
3412 if col.is_array() {
3413 col.set_ty(Type::Blob);
3415 col.set_base_affinity(Affinity::Blob);
3416 continue;
3417 }
3418 if let Ok(Some(resolved)) = schema.resolve_type_unchecked(&col.ty_str) {
3419 let (base_ty, _) = type_from_name(&resolved.primitive);
3420 col.set_ty(base_ty);
3421 col.set_base_affinity(Affinity::affinity(&resolved.primitive));
3422 }
3423 }
3424 }
3425
3426 pub fn propagate_domain_constraints(&mut self, schema: &Schema) -> Result<()> {
3432 if !self.is_strict {
3433 return Ok(());
3434 }
3435 let mut new_checks = vec![];
3437 let mut notnull_cols = vec![];
3438
3439 for (col_idx, col) in self.columns.iter().enumerate() {
3440 let Ok(Some(resolved)) = schema.resolve_type_unchecked(&col.ty_str) else {
3441 continue;
3442 };
3443 if !resolved.is_domain() {
3444 continue;
3445 }
3446 let col_name = col.name.as_deref().unwrap_or("").to_string();
3447 for td in &resolved.chain {
3448 if td.not_null {
3449 notnull_cols.try_push(col_idx)?;
3450 }
3451 for (i, dc) in td.domain_checks.iter().enumerate() {
3452 let rewritten = rewrite_value_to_column(&dc.check, &col_name);
3453 let name = dc
3454 .name
3455 .clone()
3456 .unwrap_or_else(|| format!("{}_{}", td.name, i));
3457 new_checks.try_push(CheckConstraint {
3458 name: Some(name),
3459 expr: *rewritten,
3460 column: Some(col_name.clone()),
3461 })?;
3462 }
3463 }
3464 }
3465
3466 for col_idx in notnull_cols {
3467 self.columns[col_idx].set_notnull(true);
3468 }
3469 self.check_constraints.try_extend(new_checks)?;
3470 Ok(())
3471 }
3472
3473 pub fn get_rowid_alias_column(&self) -> Option<(usize, &Column)> {
3474 self.columns
3475 .iter()
3476 .enumerate()
3477 .find(|(_, column)| column.is_rowid_alias())
3478 }
3479
3480 pub fn has_virtual_columns(&self) -> bool {
3481 self.has_virtual_columns
3482 }
3483
3484 pub fn column_layout(&self) -> Result<ColumnLayout, TryReserveError> {
3486 ColumnLayout::from_btree(self)
3487 }
3488
3489 pub fn get_column(&self, name: &str) -> Option<(usize, &Column)> {
3494 self.columns.iter().enumerate().find(|(_, column)| {
3495 column
3496 .name
3497 .as_ref()
3498 .is_some_and(|n| n.eq_ignore_ascii_case(name))
3499 })
3500 }
3501
3502 pub fn from_sql(sql: &str, root_page: i64) -> Result<BTreeTable> {
3503 let mut parser = Parser::new(sql.as_bytes());
3504 let cmd = parser.next_cmd()?;
3505 match cmd {
3506 Some(Cmd::Stmt(Stmt::CreateTable { tbl_name, body, .. })) => {
3507 create_table(tbl_name.name.as_str(), &body, root_page)
3508 }
3509 _ => unreachable!("Expected CREATE TABLE statement"),
3510 }
3511 }
3512
3513 pub fn to_sql(&self) -> String {
3519 let mut sql = format!("CREATE TABLE {} (", quote_ident(&self.name));
3520 let needs_pk_inline = self.primary_key_columns.len() == 1;
3521 for (i, column) in self.columns.iter().enumerate() {
3523 if i > 0 {
3524 sql.push_str(", ");
3525 }
3526
3527 let column_name = column.name.as_ref().expect("column name is None");
3528 sql.push_str("e_ident(column_name));
3529
3530 if !column.ty_str.is_empty() {
3531 sql.push(' ');
3532 sql.push_str(&column.ty_str);
3533 if column.is_array() {
3534 sql.push_str("[]");
3535 }
3536 }
3537 if column.notnull()
3538 && (column.explicit_notnull() || !self.is_without_rowid_inline_pk(column))
3539 {
3540 sql.push_str(" NOT NULL");
3541 }
3542
3543 if column.unique() {
3544 sql.push_str(" UNIQUE");
3545 }
3546 if needs_pk_inline && column.primary_key() {
3547 sql.push_str(" PRIMARY KEY");
3548 if self.has_autoincrement && column.is_rowid_alias() {
3549 sql.push_str(" AUTOINCREMENT");
3550 }
3551 }
3552
3553 if let Some(default) = &column.default {
3554 sql.push_str(" DEFAULT ");
3555 sql.push_str(&default.to_string());
3556 }
3557
3558 if let GeneratedType::Virtual { original_sql, .. } = &column.generated_type() {
3559 sql.push_str(" AS (");
3560 sql.push_str(original_sql);
3561 sql.push(')');
3562 }
3563
3564 for check_constraint in &self.check_constraints {
3566 if check_constraint.column.as_deref() == Some(column_name) {
3567 sql.push(' ');
3568 if let Some(name) = &check_constraint.name {
3569 sql.push_str("CONSTRAINT ");
3570 sql.push_str(&Name::exact(name.clone()).as_ident());
3571 sql.push(' ');
3572 }
3573 sql.push_str(&check_constraint.sql());
3574 }
3575 }
3576 }
3577
3578 let has_table_pk = !self.primary_key_columns.is_empty();
3579 if !needs_pk_inline && has_table_pk {
3581 sql.push_str(", PRIMARY KEY (");
3582 for (i, col) in self.primary_key_columns.iter().enumerate() {
3583 if i > 0 {
3584 sql.push_str(", ");
3585 }
3586 sql.push_str(&col.0);
3587 }
3588 sql.push(')');
3589 }
3590
3591 for fk in &self.foreign_keys {
3592 sql.push_str(", FOREIGN KEY (");
3593 for (i, col) in fk.child_columns.iter().enumerate() {
3594 if i > 0 {
3595 sql.push_str(", ");
3596 }
3597 sql.push_str(col);
3598 }
3599 sql.push_str(") REFERENCES ");
3600 sql.push_str(&fk.parent_table);
3601 sql.push('(');
3602 for (i, col) in fk.parent_columns.iter().enumerate() {
3603 if i > 0 {
3604 sql.push_str(", ");
3605 }
3606 sql.push_str(col);
3607 }
3608 sql.push(')');
3609
3610 if fk.on_delete != RefAct::NoAction {
3612 sql.push_str(" ON DELETE ");
3613 sql.push_str(match fk.on_delete {
3614 RefAct::SetNull => "SET NULL",
3615 RefAct::SetDefault => "SET DEFAULT",
3616 RefAct::Cascade => "CASCADE",
3617 RefAct::Restrict => "RESTRICT",
3618 _ => "",
3619 });
3620 }
3621 if fk.on_update != RefAct::NoAction {
3622 sql.push_str(" ON UPDATE ");
3623 sql.push_str(match fk.on_update {
3624 RefAct::SetNull => "SET NULL",
3625 RefAct::SetDefault => "SET DEFAULT",
3626 RefAct::Cascade => "CASCADE",
3627 RefAct::Restrict => "RESTRICT",
3628 _ => "",
3629 });
3630 }
3631 if fk.deferred {
3632 sql.push_str(" DEFERRABLE INITIALLY DEFERRED");
3633 }
3634 }
3635
3636 for check_constraint in &self.check_constraints {
3638 if check_constraint.column.is_some() {
3639 continue;
3640 }
3641 sql.push_str(", ");
3642 if let Some(name) = &check_constraint.name {
3643 sql.push_str("CONSTRAINT ");
3644 sql.push_str(&Name::exact(name.clone()).as_ident());
3645 sql.push(' ');
3646 }
3647 sql.push_str(&check_constraint.sql());
3648 }
3649
3650 for unique_set in &self.unique_sets {
3652 if unique_set.is_primary_key {
3654 continue;
3655 }
3656 if unique_set.columns.len() == 1 {
3658 let col_name = &unique_set.columns[0].0;
3659 if let Some((_, col)) = self.get_column(col_name) {
3660 if col.unique() {
3661 continue;
3662 }
3663 }
3664 }
3665 sql.push_str(", UNIQUE (");
3666 for (i, (col_name, _)) in unique_set.columns.iter().enumerate() {
3667 if i > 0 {
3668 sql.push_str(", ");
3669 }
3670 sql.push_str("e_ident(col_name));
3671 }
3672 sql.push(')');
3673 }
3674
3675 sql.push(')');
3676
3677 if self.is_strict {
3679 sql.push_str(" STRICT");
3680 }
3681 if !self.has_rowid {
3682 if self.is_strict {
3683 sql.push_str(", WITHOUT ROWID");
3684 } else {
3685 sql.push_str(" WITHOUT ROWID");
3686 }
3687 }
3688
3689 sql
3690 }
3691
3692 fn is_without_rowid_inline_pk(&self, column: &Column) -> bool {
3693 !self.has_rowid && self.primary_key_columns.len() == 1 && column.primary_key()
3694 }
3695
3696 pub fn column_collations(&self) -> Result<Vec<CollationSeq>> {
3697 Ok(self
3698 .columns
3699 .iter()
3700 .map(|column| column.collation())
3701 .try_collect()?)
3702 }
3703
3704 #[inline]
3705 pub fn logical_to_physical_column(&self, logical: usize) -> usize {
3706 self.logical_to_physical_map[logical]
3707 }
3708
3709 pub fn build_logical_to_physical_map(
3710 columns: &[Column],
3711 primary_key_columns: &[(String, SortOrder)],
3712 has_rowid: bool,
3713 ) -> Vec<usize> {
3714 Self::try_build_logical_to_physical_map(columns, primary_key_columns, has_rowid)
3715 .expect(crate::alloc::ALLOC_ERR_MSG)
3716 }
3717
3718 pub fn try_build_logical_to_physical_map(
3719 columns: &[Column],
3720 primary_key_columns: &[(String, SortOrder)],
3721 has_rowid: bool,
3722 ) -> Result<Vec<usize>, crate::alloc::TryReserveError> {
3723 let mut map = try_vec![usize::MAX; columns.len()]?;
3724 let mut physical = 0;
3725
3726 if !has_rowid {
3727 for (pk_name, _) in primary_key_columns {
3728 let Some((pk_idx, col)) = columns.iter().enumerate().find(|(_, col)| {
3729 col.name
3730 .as_ref()
3731 .is_some_and(|name| name.eq_ignore_ascii_case(pk_name))
3732 }) else {
3733 continue;
3734 };
3735 if col.is_virtual_generated() || map[pk_idx] != usize::MAX {
3736 continue;
3737 }
3738 map[pk_idx] = physical;
3739 physical += 1;
3740 }
3741 }
3742
3743 for (idx, col) in columns.iter().enumerate() {
3744 if col.is_virtual_generated() || map[idx] != usize::MAX {
3745 continue;
3746 }
3747 map[idx] = physical;
3748 physical += 1;
3749 }
3750
3751 for offset in &mut map {
3752 if *offset == usize::MAX {
3753 *offset = physical;
3754 physical += 1;
3755 }
3756 }
3757 Ok(map)
3758 }
3759
3760 pub fn prepare_generated_columns(&mut self) -> Result<()> {
3761 {
3762 let mut guard = self.columns_mut();
3763 for i in 0..guard.len() {
3764 if guard[i].is_virtual_generated() {
3765 let mut expr = guard[i].generated_expr().cloned().unwrap();
3766 resolve_gencol_expr_columns(&mut expr, &guard)?;
3767 *guard[i].generated_expr_mut().unwrap() = expr;
3768 }
3769 }
3770 }
3771 self.column_graph()?;
3772 Ok(())
3773 }
3774
3775 pub fn shift_generated_column_indices_after_drop(
3776 &mut self,
3777 dropped_index: usize,
3778 ) -> Result<()> {
3779 if !self.has_virtual_columns {
3780 return Ok(());
3781 }
3782
3783 for column in &mut self.columns {
3784 let Some(expr) = column.generated_expr_mut() else {
3785 continue;
3786 };
3787
3788 walk_expr_mut(expr, &mut |e| match e {
3789 Expr::Column {
3790 table,
3791 column,
3792 is_rowid_alias: _,
3793 ..
3794 } if table.is_self_table() => {
3795 if *column == dropped_index {
3796 return Err(LimboError::InternalError(
3797 "dropped column remained referenced by generated column".to_string(),
3798 ));
3799 }
3800 if *column > dropped_index {
3801 *column -= 1;
3802 }
3803 Ok(WalkControl::Continue)
3804 }
3805 _ => Ok(WalkControl::Continue),
3806 })?;
3807 }
3808
3809 Ok(())
3810 }
3811
3812 fn column_graph(&self) -> Result<&GeneratedColGraph> {
3813 if let Some(graph) = self.column_dependencies.0.get() {
3814 return Ok(graph);
3815 }
3816 let graph = GeneratedColGraph::build(&self.columns)?;
3817 let _ = self.column_dependencies.0.set(graph);
3819 Ok(self
3820 .column_dependencies
3821 .0
3822 .get()
3823 .expect("column_dependencies was just initialized"))
3824 }
3825
3826 pub(crate) fn columns_topo_sort(&self) -> Result<ColumnsTopologicalSort<'_>> {
3830 let topo = self.column_graph()?.topological_sort.try_to_vec()?;
3831 Ok(ColumnsTopologicalSort {
3832 columns: &self.columns,
3833 topological_sort: topo,
3834 })
3835 }
3836
3837 #[cfg(test)]
3838 pub(crate) fn peek_column_dependencies(&self) -> Option<&GeneratedColGraph> {
3839 self.column_dependencies.0.get()
3840 }
3841
3842 pub(crate) fn columns_affected_by_update(
3843 &self,
3844 updated_cols: impl IntoIterator<Item = usize>,
3845 ) -> Result<ColumnMask> {
3846 let graph = self.column_graph()?;
3847 let mut affected = ColumnMask::default();
3848 for i in updated_cols {
3849 affected.set(i)?;
3850 if i < graph.dependents.len() {
3851 let snapshot = graph.dependents[i].try_clone()?;
3852 affected.union_with(&snapshot)?;
3853 }
3854 }
3855 Ok(affected)
3856 }
3857
3858 pub(crate) fn dependencies_of_columns(
3859 &self,
3860 targets: impl IntoIterator<Item = usize>,
3861 ) -> Result<ColumnMask> {
3862 let graph = self.column_graph()?;
3863 let mut deps = ColumnMask::default();
3864 for j in targets {
3865 if !self.columns[j].is_virtual_generated() {
3866 deps.set(j)?;
3867 continue;
3868 }
3869 for i in graph.dependencies[j].iter() {
3870 if !self.columns[i].is_virtual_generated() {
3871 deps.set(i)?;
3872 }
3873 }
3874 }
3875 Ok(deps)
3876 }
3877}
3878
3879pub(crate) struct ColumnsTopologicalSort<'a> {
3881 columns: &'a [Column],
3882 topological_sort: Vec<usize>,
3884}
3885
3886impl<'a> ColumnsTopologicalSort<'a> {
3887 pub fn iter(&self) -> impl Iterator<Item = (usize, &'a Column)> + '_ {
3888 self.topological_sort
3889 .iter()
3890 .map(|&idx| (idx, &self.columns[idx]))
3891 }
3892}
3893
3894#[derive(Debug, Default, Clone, Copy)]
3895pub struct PseudoCursorType {
3896 pub column_count: usize,
3897}
3898
3899impl PseudoCursorType {
3900 pub fn new() -> Self {
3901 Self { column_count: 0 }
3902 }
3903
3904 pub fn new_with_columns(columns: impl AsRef<[Column]>) -> Self {
3905 Self {
3906 column_count: columns.as_ref().len(),
3907 }
3908 }
3909}
3910
3911#[derive(Debug, Clone)]
3913pub struct FromClauseSubquery {
3914 pub name: String,
3916 pub plan: Box<Plan>,
3919 pub columns: Vec<Column>,
3921 pub result_columns_start_reg: Option<usize>,
3924 pub materialized_cursor_id: Option<CursorID>,
3927 pub cte: Option<FromClauseSubqueryCteMetadata>,
3930}
3931
3932#[derive(Debug, Clone, Copy)]
3933pub struct FromClauseSubqueryCteMetadata {
3934 pub id: usize,
3936 pub shared_materialization: bool,
3939 pub materialize_hint: bool,
3941}
3942
3943impl FromClauseSubquery {
3944 pub fn cte_id(&self) -> Option<usize> {
3945 self.cte.map(|cte| cte.id)
3946 }
3947
3948 pub fn materialize_hint(&self) -> bool {
3949 self.cte.is_some_and(|cte| cte.materialize_hint)
3950 }
3951
3952 pub fn shared_materialization(&self) -> bool {
3953 self.cte.is_some_and(|cte| cte.shared_materialization)
3954 }
3955
3956 pub fn set_shared_materialization(&mut self, shared: bool) {
3957 if let Some(cte) = &mut self.cte {
3958 cte.shared_materialization = shared;
3959 }
3960 }
3961
3962 pub fn requires_table_materialization(&self) -> bool {
3965 self.shared_materialization() || self.materialize_hint()
3966 }
3967
3968 pub fn supports_direct_index_materialization(&self) -> bool {
3973 matches!(self.plan.as_ref(), Plan::Select(_)) && !self.requires_table_materialization()
3974 }
3975}
3976
3977fn collect_column_refs(expr: &Expr) -> HashSet<String> {
3978 collect_column_dependencies_of_expr(expr, &[])
3979}
3980
3981pub fn collect_column_dependencies_of_expr(expr: &Expr, columns: &[Column]) -> HashSet<String> {
3985 let mut refs = HashSet::default();
3986
3987 let _ = walk_expr(expr, &mut |e| match e {
3988 Expr::Id(name) | Expr::Name(name) => {
3989 refs.insert(normalize_ident(name.as_str()));
3990 Ok(WalkControl::Continue)
3991 }
3992 Expr::Qualified(_, col) | Expr::DoublyQualified(_, _, col) => {
3993 refs.insert(normalize_ident(col.as_str()));
3994 Ok(WalkControl::Continue)
3995 }
3996 Expr::Column { table, column, .. } if table.is_self_table() => {
3997 if let Some(col) = columns.get(*column) {
3998 if let Some(name) = &col.name {
3999 refs.insert(normalize_ident(name));
4000 }
4001 }
4002 Ok(WalkControl::Continue)
4003 }
4004 Expr::Subquery(_)
4005 | Expr::Exists(_)
4006 | Expr::InTable { .. }
4007 | Expr::SubqueryResult { .. } => Ok(WalkControl::SkipChildren),
4008 _ => Ok(WalkControl::Continue),
4009 });
4010
4011 refs
4012}
4013
4014fn collect_column_dependencies_of_gencol(expr: &Expr, columns: &[Column], out: &mut BitSet) {
4015 let _ = walk_expr(expr, &mut |e| {
4016 match e {
4017 Expr::Column { table, column, .. } if table.is_self_table() => {
4018 out.set(*column)?;
4019 }
4020 Expr::Id(name) | Expr::Name(name) => {
4021 if let Some(idx) = find_column_index_by_name(columns, name.as_str()) {
4022 out.set(idx)?;
4023 }
4024 }
4025 Expr::Qualified(_, col) | Expr::DoublyQualified(_, _, col) => {
4026 if let Some(idx) = find_column_index_by_name(columns, col.as_str()) {
4027 out.set(idx)?;
4028 }
4029 }
4030 Expr::Subquery(_)
4031 | Expr::Exists(_)
4032 | Expr::InTable { .. }
4033 | Expr::SubqueryResult { .. } => {
4034 unreachable!("generated columns cannot contain subqueries")
4035 }
4036 _ => {}
4037 }
4038 Ok(WalkControl::Continue)
4039 });
4040}
4041
4042fn find_column_index_by_name(columns: &[Column], col_name: &str) -> Option<usize> {
4043 columns.iter().enumerate().find_map(|(i, col)| {
4044 col.name
4045 .as_ref()
4046 .filter(|name| name.eq_ignore_ascii_case(col_name))
4047 .map(|_| i)
4048 })
4049}
4050
4051pub fn resolve_gencol_expr_columns(gencol_expr: &mut Expr, columns: &[Column]) -> Result<()> {
4054 walk_expr_mut(gencol_expr, &mut |e| match e {
4055 Expr::Id(name) | Expr::Qualified(_, name) | Expr::DoublyQualified(_, _, name) => {
4056 let col_name = normalize_ident(name.as_str());
4057 let (idx, col) = columns
4058 .iter()
4059 .enumerate()
4060 .find(|(_, c)| {
4061 c.name
4062 .as_ref()
4063 .is_some_and(|n| n.eq_ignore_ascii_case(&col_name))
4064 })
4065 .ok_or_else(|| LimboError::ParseError(format!("no such column: {col_name}")))?;
4066 *e = Expr::Column {
4067 database: None,
4068 table: TableInternalId::SELF_TABLE,
4069 column: idx,
4070 is_rowid_alias: col.is_rowid_alias(),
4071 };
4072 Ok(WalkControl::Continue)
4073 }
4074 _ => Ok(WalkControl::Continue),
4075 })?;
4076 Ok(())
4077}
4078
4079pub fn render_gencol_expr_sql_with_new_names(expr: &Expr, columns: &[Column]) -> Result<String> {
4085 let mut clone = expr.clone();
4086 walk_expr_mut(&mut clone, &mut |e| -> Result<WalkControl> {
4087 if let Expr::Column { table, column, .. } = e {
4088 if table.is_self_table() {
4089 if let Some(col) = columns.get(*column) {
4090 if let Some(name) = col.name.as_ref() {
4091 *e = Expr::Id(Name::exact(name.clone()));
4092 }
4093 }
4094 }
4095 }
4096 Ok(WalkControl::Continue)
4097 })?;
4098 Ok(clone.to_string())
4099}
4100
4101pub(crate) fn is_deterministic_schema_function_call(func: &Func, args: &[Box<Expr>]) -> bool {
4102 match func {
4103 Func::Scalar(
4104 ScalarFunc::Date
4105 | ScalarFunc::Time
4106 | ScalarFunc::DateTime
4107 | ScalarFunc::UnixEpoch
4108 | ScalarFunc::JulianDay
4109 | ScalarFunc::StrfTime
4110 | ScalarFunc::TimeDiff,
4111 ) => is_deterministic_datetime_call(func, args),
4112 _ => func.is_deterministic(),
4113 }
4114}
4115
4116fn is_deterministic_datetime_call(func: &Func, args: &[Box<Expr>]) -> bool {
4120 match func {
4121 Func::Scalar(ScalarFunc::Date)
4122 | Func::Scalar(ScalarFunc::Time)
4123 | Func::Scalar(ScalarFunc::DateTime)
4124 | Func::Scalar(ScalarFunc::UnixEpoch)
4125 | Func::Scalar(ScalarFunc::JulianDay) => {
4126 !args.is_empty()
4127 && !is_current_time_expr(args[0].as_ref())
4128 && !args[1..]
4129 .iter()
4130 .any(|arg| is_unsafe_datetime_modifier(arg.as_ref()))
4131 }
4132 Func::Scalar(ScalarFunc::StrfTime) => {
4133 args.len() >= 2
4134 && !is_current_time_expr(args[1].as_ref())
4135 && !args[2..]
4136 .iter()
4137 .any(|arg| is_unsafe_datetime_modifier(arg.as_ref()))
4138 }
4139 Func::Scalar(ScalarFunc::TimeDiff) => {
4140 !args.iter().any(|arg| is_current_time_expr(arg.as_ref()))
4141 }
4142 _ => unreachable!("non-datetime function passed to datetime index validator"),
4143 }
4144}
4145
4146fn is_current_time_expr(expr: &Expr) -> bool {
4147 matches!(
4148 expr,
4149 Expr::Literal(ast::Literal::String(value)) if string_literal_eq(value, "now")
4150 ) || matches!(
4151 expr,
4152 Expr::Literal(
4153 ast::Literal::CurrentDate | ast::Literal::CurrentTime | ast::Literal::CurrentTimestamp
4154 )
4155 )
4156}
4157
4158fn is_unsafe_datetime_modifier(expr: &Expr) -> bool {
4159 matches!(
4160 expr,
4161 Expr::Literal(ast::Literal::String(value))
4162 if string_literal_eq(value, "localtime") || string_literal_eq(value, "utc")
4163 ) || is_current_time_expr(expr)
4164}
4165
4166fn string_literal_eq(value: &str, expected: &str) -> bool {
4167 value.trim_matches('\'').eq_ignore_ascii_case(expected)
4168}
4169
4170pub(crate) fn validate_generated_expr(expr: &Expr) -> Result<()> {
4171 use ast::Expr;
4172 match expr {
4173 Expr::Qualified(_, _) => {
4174 bail_parse_error!("the \".\" operator prohibited in generated columns");
4175 }
4176 Expr::DoublyQualified(_, _, _) => {
4177 bail_parse_error!("the \".\" operator prohibited in generated columns");
4178 }
4179
4180 Expr::Variable(_) => {
4181 bail_parse_error!("bind parameters prohibited in generated columns");
4182 }
4183
4184 Expr::Subquery(_) | Expr::InSelect { .. } | Expr::Exists(_) | Expr::InTable { .. } => {
4185 bail_parse_error!("subqueries prohibited in generated columns");
4186 }
4187
4188 Expr::FunctionCall {
4189 name,
4190 args,
4191 filter_over,
4192 ..
4193 } => {
4194 if filter_over.over_clause.is_some() {
4195 bail_parse_error!("window functions prohibited in generated columns");
4196 }
4197 let arg_count = args.len();
4198 let Some(func) = Func::resolve_function(name.as_str(), arg_count)? else {
4199 return Err(LimboError::ParseError(format!(
4200 "could not resolve function {}",
4201 name.as_str()
4202 )));
4203 };
4204 if matches!(func, Func::Agg(_)) {
4205 bail_parse_error!("aggregate functions prohibited in generated columns");
4206 }
4207 if !is_deterministic_schema_function_call(&func, args) {
4208 bail_parse_error!("non-deterministic functions prohibited in generated columns");
4209 }
4210 for arg in args {
4211 validate_generated_expr(arg)?;
4212 }
4213 }
4214
4215 Expr::FunctionCallStar { name, filter_over } => {
4216 if filter_over.over_clause.is_some() {
4217 bail_parse_error!("window functions prohibited in generated columns");
4218 }
4219 let Some(func) = Func::resolve_function(name.as_str(), 0)? else {
4220 return Err(LimboError::ParseError(format!(
4221 "could not resolve function {}",
4222 name.as_str()
4223 )));
4224 };
4225
4226 if matches!(func, Func::Agg(_)) {
4227 bail_parse_error!("aggregate functions prohibited in generated columns");
4228 }
4229 if !func.is_deterministic() {
4230 bail_parse_error!("non-deterministic functions prohibited in generated columns");
4231 }
4232 }
4233
4234 Expr::Binary(lhs, _, rhs) => {
4235 validate_generated_expr(lhs)?;
4236 validate_generated_expr(rhs)?;
4237 }
4238 Expr::Unary(_, inner) => {
4239 validate_generated_expr(inner)?;
4240 }
4241 Expr::Parenthesized(exprs) => {
4242 for e in exprs {
4243 validate_generated_expr(e)?;
4244 }
4245 }
4246 Expr::Case {
4247 base,
4248 when_then_pairs,
4249 else_expr,
4250 ..
4251 } => {
4252 if let Some(b) = base {
4253 validate_generated_expr(b)?;
4254 }
4255 for (w, t) in when_then_pairs {
4256 validate_generated_expr(w)?;
4257 validate_generated_expr(t)?;
4258 }
4259 if let Some(e) = else_expr {
4260 validate_generated_expr(e)?;
4261 }
4262 }
4263 Expr::Cast { expr, .. } => {
4264 validate_generated_expr(expr)?;
4265 }
4266 Expr::InList { lhs, rhs, .. } => {
4267 validate_generated_expr(lhs)?;
4268 for e in rhs {
4269 validate_generated_expr(e)?;
4270 }
4271 }
4272 Expr::Between {
4273 lhs, start, end, ..
4274 } => {
4275 validate_generated_expr(lhs)?;
4276 validate_generated_expr(start)?;
4277 validate_generated_expr(end)?;
4278 }
4279 Expr::Like {
4280 lhs, rhs, escape, ..
4281 } => {
4282 validate_generated_expr(lhs)?;
4283 validate_generated_expr(rhs)?;
4284 if let Some(e) = escape {
4285 validate_generated_expr(e)?;
4286 }
4287 }
4288 Expr::Collate(inner, _) => {
4289 validate_generated_expr(inner)?;
4290 }
4291 Expr::IsNull(inner) | Expr::NotNull(inner) => {
4292 validate_generated_expr(inner)?;
4293 }
4294 Expr::Literal(
4299 ast::Literal::CurrentDate | ast::Literal::CurrentTime | ast::Literal::CurrentTimestamp,
4300 ) => {
4301 bail_parse_error!("non-deterministic functions prohibited in generated columns");
4302 }
4303 _ => {}
4304 }
4305 Ok(())
4306}
4307
4308fn constraint_column_collation(expr: &Expr) -> Result<(&Expr, Option<CollationSeq>)> {
4312 match expr {
4313 Expr::Collate(inner, collation_name) => {
4314 let collation_seq = CollationSeq::new(collation_name.as_str())?;
4315 if collation_seq.is_custom() {
4316 crate::bail_parse_error!(
4317 "custom collations are not supported in schema definitions"
4318 );
4319 }
4320 Ok((inner.as_ref(), Some(collation_seq)))
4321 }
4322 _ => Ok((expr, None)),
4323 }
4324}
4325
4326pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> Result<BTreeTable> {
4327 let table_name = normalize_ident(tbl_name);
4328 trace!("Creating table {}", table_name);
4329 let has_rowid;
4330 let mut has_autoincrement = false;
4331 let mut primary_key_columns = vec![];
4332 let mut foreign_keys = vec![];
4333 let mut check_constraints = vec![];
4334 let mut cols: Vec<Column> = vec![];
4335 let is_strict: bool;
4336 let mut unique_sets_columns: Vec<UniqueSet> = vec![];
4337 let mut unique_sets_constraints: Vec<UniqueSet> = vec![];
4338 match body {
4339 CreateTableBody::ColumnsAndConstraints {
4340 columns,
4341 constraints,
4342 options,
4343 } => {
4344 has_rowid = !options.contains_without_rowid();
4345 is_strict = options.contains_strict();
4346 let column_fk_count = columns
4347 .iter()
4348 .flat_map(|col| col.constraints.iter())
4349 .filter(|constraint| {
4350 matches!(
4351 &constraint.constraint,
4352 ast::ColumnConstraint::ForeignKey { .. }
4353 )
4354 })
4355 .count();
4356
4357 let mut table_fk_order = column_fk_count;
4362 for c in constraints {
4363 if let ast::TableConstraint::PrimaryKey {
4364 columns,
4365 auto_increment,
4366 conflict_clause,
4367 } = &c.constraint
4368 {
4369 if !primary_key_columns.is_empty() {
4370 crate::bail_parse_error!(
4371 "table \"{}\" has more than one primary key",
4372 tbl_name
4373 );
4374 }
4375 if *auto_increment {
4376 has_autoincrement = true;
4377 }
4378
4379 let mut pk_collations = Vec::try_with_capacity_ext(columns.len())?;
4380 for column in columns {
4381 let (expr, collation) = constraint_column_collation(column.expr.as_ref())?;
4382 let col_name = match expr {
4383 Expr::Id(id) => normalize_ident(id.as_str()),
4384 Expr::Literal(Literal::String(value)) => {
4385 value.trim_matches('\'').to_owned()
4386 }
4387 expr => {
4388 bail_parse_error!("unsupported primary key expression: {}", expr)
4389 }
4390 };
4391 primary_key_columns
4392 .try_push((col_name, column.order.unwrap_or(SortOrder::Asc)))?;
4393 pk_collations.try_push(collation)?;
4394 }
4395 unique_sets_constraints.try_push(UniqueSet {
4396 columns: primary_key_columns.try_clone()?,
4397 collations: pk_collations,
4398 is_primary_key: true,
4399 conflict_clause: *conflict_clause,
4400 })?;
4401 } else if let ast::TableConstraint::Unique {
4402 columns,
4403 conflict_clause,
4404 } = &c.constraint
4405 {
4406 let mut unique_columns = Vec::try_with_capacity_ext(columns.len())?;
4407 let mut unique_collations = Vec::try_with_capacity_ext(columns.len())?;
4408 for column in columns {
4409 let (expr, collation) = constraint_column_collation(column.expr.as_ref())?;
4410 match expr {
4411 Expr::Id(id) => unique_columns.try_push((
4412 id.as_str().to_string(),
4413 column.order.unwrap_or(SortOrder::Asc),
4414 ))?,
4415 Expr::Literal(Literal::String(value)) => unique_columns.try_push((
4416 value.trim_matches('\'').to_owned(),
4417 column.order.unwrap_or(SortOrder::Asc),
4418 ))?,
4419 expr => {
4420 bail_parse_error!("unsupported unique key expression: {}", expr)
4421 }
4422 }
4423 unique_collations.try_push(collation)?;
4424 }
4425 let unique_set = UniqueSet {
4426 columns: unique_columns,
4427 collations: unique_collations,
4428 is_primary_key: false,
4429 conflict_clause: *conflict_clause,
4430 };
4431 unique_sets_constraints.try_push(unique_set)?;
4432 } else if let ast::TableConstraint::ForeignKey {
4433 columns,
4434 clause,
4435 defer_clause,
4436 } = &c.constraint
4437 {
4438 let child_columns: Box<[String]> = columns
4439 .iter()
4440 .map(|ic| normalize_ident(ic.col_name.as_str()))
4441 .try_collect()?;
4442 let parent_table = normalize_ident(clause.tbl_name.as_str());
4444 let parent_columns: Box<[String]> = clause
4445 .columns
4446 .iter()
4447 .map(|ic| normalize_ident(ic.col_name.as_str()))
4448 .try_collect()?;
4449
4450 if !parent_columns.is_empty() && child_columns.len() != parent_columns.len() {
4452 crate::bail_parse_error!(
4453 "foreign key on \"{}\" has {} child column(s) but {} parent column(s)",
4454 tbl_name,
4455 child_columns.len(),
4456 parent_columns.len()
4457 );
4458 }
4459 let deferred = match defer_clause {
4461 Some(d) => {
4462 d.deferrable
4463 && matches!(
4464 d.init_deferred,
4465 Some(InitDeferredPred::InitiallyDeferred)
4466 )
4467 }
4468 None => false, };
4470 let fk = ForeignKey {
4471 parent_table,
4472 parent_columns,
4473 child_columns,
4474 on_delete: clause
4475 .args
4476 .iter()
4477 .find_map(|a| {
4478 if let ast::RefArg::OnDelete(x) = a {
4479 Some(*x)
4480 } else {
4481 None
4482 }
4483 })
4484 .unwrap_or(RefAct::NoAction),
4485 on_update: clause
4486 .args
4487 .iter()
4488 .find_map(|a| {
4489 if let ast::RefArg::OnUpdate(x) = a {
4490 Some(*x)
4491 } else {
4492 None
4493 }
4494 })
4495 .unwrap_or(RefAct::NoAction),
4496 deferred,
4497 decl_order: table_fk_order,
4498 };
4499 foreign_keys.try_push(Arc::new(fk))?;
4500 table_fk_order += 1;
4501 } else if let ast::TableConstraint::Check(expr) = &c.constraint {
4502 check_constraints.try_push(CheckConstraint::new(
4503 c.name.as_ref(),
4504 expr,
4505 None,
4506 ))?;
4507 }
4508 }
4509
4510 let mut primary_key_desc_columns_constraint = false;
4514
4515 let mut column_fk_order = 0;
4516 for ast::ColumnDefinition {
4517 col_name,
4518 col_type,
4519 constraints,
4520 } in columns
4521 {
4522 let name = col_name.as_str().to_string();
4523 let ty_str = col_type
4532 .as_ref()
4533 .cloned()
4534 .map(|ast::Type { name, .. }| name)
4535 .unwrap_or_default();
4536
4537 let ty_params: std::vec::Vec<Box<Expr>> = match col_type {
4538 Some(ast::Type {
4539 size: Some(ast::TypeSize::MaxSize(ref expr)),
4540 ..
4541 }) => std::vec![expr.clone()],
4542 Some(ast::Type {
4543 size: Some(ast::TypeSize::TypeSize(ref e1, ref e2)),
4544 ..
4545 }) => std::vec![e1.clone(), e2.clone()],
4546 _ => std::vec::Vec::new(),
4547 };
4548
4549 let mut typename_exactly_integer = false;
4550 let ty = match col_type {
4551 Some(data_type) => {
4552 let (ty, ei) = type_from_name(&data_type.name);
4553 typename_exactly_integer = ei;
4554 ty
4555 }
4556 None => Type::Null,
4557 };
4558
4559 let mut default = None;
4560 let mut generated: Option<Box<Expr>> = None;
4561 let mut primary_key = false;
4562 let mut notnull = false;
4563 let mut explicit_notnull = false;
4564 let mut notnull_conflict_clause = None;
4565 let mut order = SortOrder::Asc;
4566 let mut unique = false;
4567 let mut collation = None;
4568 for c_def in constraints {
4569 match &c_def.constraint {
4570 ast::ColumnConstraint::Check(expr) => {
4571 check_constraints.try_push(CheckConstraint::new(
4572 c_def.name.as_ref(),
4573 expr,
4574 Some(&name),
4575 ))?;
4576 }
4577 ast::ColumnConstraint::Generated { expr, typ } => {
4578 if typ
4579 .as_ref()
4580 .is_some_and(|t| matches!(t, ast::GeneratedColumnType::Stored))
4581 {
4582 bail_parse_error!("Stored generated columns are not supported");
4583 }
4584 validate_generated_expr(expr)?;
4585 generated = Some(expr.clone());
4586 }
4587 ast::ColumnConstraint::PrimaryKey {
4588 order: o,
4589 auto_increment,
4590 conflict_clause,
4591 ..
4592 } => {
4593 if !primary_key_columns.is_empty() {
4594 crate::bail_parse_error!(
4595 "table \"{}\" has more than one primary key",
4596 tbl_name
4597 );
4598 }
4599 primary_key = true;
4600 if *auto_increment {
4601 has_autoincrement = true;
4602 }
4603 if let Some(o) = o {
4604 order = *o;
4605 }
4606 unique_sets_columns.try_push(UniqueSet {
4607 columns: try_vec![(name.clone(), order)]?,
4608 collations: try_vec![None]?,
4609 is_primary_key: true,
4610 conflict_clause: *conflict_clause,
4611 })?;
4612 }
4613 ast::ColumnConstraint::NotNull {
4614 nullable,
4615 conflict_clause,
4616 ..
4617 } => {
4618 notnull = !nullable;
4619 explicit_notnull = !nullable;
4620 notnull_conflict_clause = *conflict_clause;
4621 }
4622 ast::ColumnConstraint::Default(ref expr) => {
4623 default = Some(
4624 translate_ident_to_string_literal(expr)
4625 .unwrap_or_else(|| expr.clone()),
4626 );
4627 }
4628 ast::ColumnConstraint::Unique(conflict) => {
4629 unique = true;
4630 unique_sets_columns.try_push(UniqueSet {
4631 columns: try_vec![(name.clone(), order)]?,
4632 collations: try_vec![None]?,
4633 is_primary_key: false,
4634 conflict_clause: *conflict,
4635 })?;
4636 }
4637 ast::ColumnConstraint::Collate { ref collation_name } => {
4638 let collation_seq = CollationSeq::new(collation_name.as_str())?;
4639 if collation_seq.is_custom() {
4640 crate::bail_parse_error!(
4641 "custom collations are not supported in schema definitions"
4642 );
4643 }
4644 collation = Some(collation_seq);
4645 }
4646 ast::ColumnConstraint::ForeignKey {
4647 clause,
4648 defer_clause,
4649 } => {
4650 if clause.columns.len() > 1 {
4651 crate::bail_parse_error!(
4652 "foreign key on {} should reference only one column of table {}",
4653 name,
4654 clause.tbl_name.as_str()
4655 );
4656 }
4657 let fk = ForeignKey {
4658 parent_table: normalize_ident(clause.tbl_name.as_str()),
4659 parent_columns: clause
4660 .columns
4661 .iter()
4662 .map(|c| normalize_ident(c.col_name.as_str()))
4663 .try_collect()?,
4664 on_delete: clause
4665 .args
4666 .iter()
4667 .find_map(|arg| {
4668 if let ast::RefArg::OnDelete(act) = arg {
4669 Some(*act)
4670 } else {
4671 None
4672 }
4673 })
4674 .unwrap_or(RefAct::NoAction),
4675 on_update: clause
4676 .args
4677 .iter()
4678 .find_map(|arg| {
4679 if let ast::RefArg::OnUpdate(act) = arg {
4680 Some(*act)
4681 } else {
4682 None
4683 }
4684 })
4685 .unwrap_or(RefAct::NoAction),
4686 child_columns: Box::from([name.clone()]),
4687 deferred: match defer_clause {
4688 Some(d) => {
4689 d.deferrable
4690 && matches!(
4691 d.init_deferred,
4692 Some(InitDeferredPred::InitiallyDeferred)
4693 )
4694 }
4695 None => false,
4696 },
4697 decl_order: column_fk_order,
4698 };
4699 foreign_keys.try_push(Arc::new(fk))?;
4700 column_fk_order += 1;
4701 }
4702 }
4703 }
4704
4705 if let Some(ref gen_expr) = generated {
4706 if primary_key {
4707 bail_parse_error!(
4708 "generated column \"{}\" cannot be part of the PRIMARY KEY",
4709 name
4710 );
4711 }
4712 if default.is_some() {
4713 bail_parse_error!(
4714 "generated column \"{}\" cannot have a DEFAULT value",
4715 name
4716 );
4717 }
4718
4719 let referenced_cols = collect_column_refs(gen_expr);
4720 let current_col_name = normalize_ident(&name);
4721
4722 if referenced_cols.iter().any(|c| c == ¤t_col_name) {
4723 bail_parse_error!("generated column \"{}\" cannot reference itself", name);
4724 }
4725 }
4726
4727 if primary_key {
4728 primary_key_columns.try_push((name.clone(), order))?;
4729 if order == SortOrder::Desc {
4730 primary_key_desc_columns_constraint = true;
4731 }
4732 } else if primary_key_columns
4733 .iter()
4734 .any(|(col_name, _)| col_name.eq_ignore_ascii_case(&name))
4735 {
4736 if generated.is_some() {
4737 crate::bail_parse_error!(
4738 "generated column \"{}\" cannot be part of the PRIMARY KEY",
4739 name
4740 );
4741 }
4742 primary_key = true;
4743 }
4744
4745 let mut col = Column::new(
4746 Some(name),
4747 ty_str,
4748 default,
4749 generated,
4750 ty,
4751 collation,
4752 ColDef {
4753 primary_key,
4754 rowid_alias: typename_exactly_integer
4755 && primary_key
4756 && !primary_key_desc_columns_constraint,
4757 notnull,
4758 explicit_notnull,
4759 unique,
4760 hidden: false,
4761 notnull_conflict_clause,
4762 },
4763 );
4764 col.ty_params = ty_params;
4765 if let Some(t) = col_type.as_ref() {
4766 if t.is_array() {
4767 col.set_array_dimensions(t.array_dimensions);
4768 }
4769 }
4770 cols.try_push(col)?;
4771 }
4772 }
4773 CreateTableBody::AsSelect(_) => {
4774 crate::bail_parse_error!("CREATE TABLE AS SELECT is not supported")
4775 }
4776 };
4777
4778 if !has_rowid || primary_key_columns.len() > 1 {
4781 for col in cols.iter_mut() {
4782 col.set_rowid_alias(false);
4783 }
4784 }
4785
4786 if has_autoincrement {
4787 if primary_key_columns.len() != 1 {
4789 crate::bail_parse_error!("AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY");
4790 }
4791
4792 let pk_col_name = &primary_key_columns[0].0;
4793 let pk_col = cols.iter().find(|c| {
4794 c.name
4795 .as_deref()
4796 .is_some_and(|n| n.eq_ignore_ascii_case(pk_col_name))
4797 });
4798
4799 if let Some(col) = pk_col {
4800 if col.ty() != Type::Integer {
4801 crate::bail_parse_error!("AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY");
4802 }
4803 }
4804 }
4805
4806 let mut unique_sets = unique_sets_columns
4808 .into_iter()
4809 .chain(unique_sets_constraints)
4810 .try_collect::<Vec<_>>()?;
4811 let rowid_alias_conflict_clause = unique_sets
4813 .iter()
4814 .find(|us| us.is_primary_key)
4815 .and_then(|us| us.conflict_clause);
4816 for col in cols.iter() {
4817 if col.is_rowid_alias() {
4818 let unique_set_w_only_rowid_alias = unique_sets.iter().position(|us| {
4821 us.is_primary_key
4822 && us.columns.len() == 1
4823 && us
4824 .columns
4825 .first()
4826 .unwrap()
4827 .0
4828 .eq_ignore_ascii_case(col.name.as_ref().unwrap())
4829 });
4830 if let Some(u) = unique_set_w_only_rowid_alias {
4831 unique_sets.remove(u);
4832 }
4833 }
4834 }
4835
4836 let mut table = BTreeTable {
4837 root_page,
4838 name: table_name,
4839 has_rowid,
4840 primary_key_columns,
4841 has_autoincrement,
4842 columns: cols,
4843 is_strict,
4844 foreign_keys,
4845 unique_sets: {
4846 let mut i = 0;
4853 while i < unique_sets.len() {
4854 let mut j = i + 1;
4855 while j < unique_sets.len() {
4856 let lengths_equal =
4857 unique_sets[i].columns.len() == unique_sets[j].columns.len();
4858 if lengths_equal
4859 && unique_sets[i]
4860 .columns
4861 .iter()
4862 .zip(unique_sets[j].columns.iter())
4863 .all(|((a_name, _), (b_name, _))| a_name.eq_ignore_ascii_case(b_name))
4864 {
4865 if let (Some(a), Some(b)) = (
4868 unique_sets[i].conflict_clause,
4869 unique_sets[j].conflict_clause,
4870 ) {
4871 if a != b {
4872 crate::bail_parse_error!(
4873 "conflicting ON CONFLICT clauses specified"
4874 );
4875 }
4876 }
4877 unique_sets.remove(j);
4878 } else {
4879 j += 1;
4880 }
4881 }
4882 i += 1;
4883 }
4884 unique_sets
4885 },
4886 check_constraints,
4887 rowid_alias_conflict_clause,
4888 has_virtual_columns: false,
4889 logical_to_physical_map: vec![],
4890 column_dependencies: Default::default(),
4891 };
4892 table.prepare_generated_columns()?;
4893 if !table.has_rowid {
4894 if table.primary_key_columns.is_empty() {
4895 crate::bail_parse_error!("PRIMARY KEY missing on table {}", table.name);
4896 }
4897 for (pk_name, _) in &table.primary_key_columns {
4898 let Some((_, col)) = table.get_column(pk_name) else {
4899 crate::bail_parse_error!(
4900 "PRIMARY KEY column {pk_name} not found in table {}",
4901 table.name
4902 );
4903 };
4904 if !col.notnull() {
4905 let Some(idx) = table.get_column(pk_name).map(|(idx, _)| idx) else {
4906 unreachable!("PRIMARY KEY column should exist");
4907 };
4908 table.columns[idx].set_notnull(true);
4909 }
4910 }
4911 }
4912 table.logical_to_physical_map = BTreeTable::build_logical_to_physical_map(
4913 &table.columns,
4914 &table.primary_key_columns,
4915 table.has_rowid,
4916 );
4917 Ok(table)
4918}
4919
4920pub fn translate_ident_to_string_literal(expr: &Expr) -> Option<Box<Expr>> {
4923 match expr {
4924 Expr::Name(name) | Expr::Id(name) => {
4925 Some(Box::new(Expr::Literal(Literal::String(name.as_literal()))))
4926 }
4927 _ => None,
4928 }
4929}
4930
4931pub fn _build_pseudo_table(columns: &[ResultColumn]) -> PseudoCursorType {
4932 let table = PseudoCursorType::new();
4933 for column in columns {
4934 match column {
4935 ResultColumn::Expr(expr, _as_name) => {
4936 todo!("unsupported expression {:?}", expr);
4937 }
4938 ResultColumn::Star => {
4939 todo!();
4940 }
4941 ResultColumn::TableStar(_) => {
4942 todo!();
4943 }
4944 }
4945 }
4946 table
4947}
4948
4949#[derive(Debug, Clone)]
4950pub struct ForeignKey {
4951 pub child_columns: Box<[String]>,
4953 pub parent_table: String,
4955 pub parent_columns: Box<[String]>,
4957 pub on_delete: RefAct,
4958 pub on_update: RefAct,
4959 pub deferred: bool,
4961 pub decl_order: usize,
4965}
4966#[inline]
4967fn fk_mismatch_err(child: &str, parent: &str) -> crate::LimboError {
4968 crate::LimboError::ForeignKeyConstraint(format!(
4969 "foreign key mismatch - \"{child}\" referencing \"{parent}\""
4970 ))
4971}
4972
4973impl ForeignKey {
4974 fn validate(&self) -> Result<()> {
4975 if self
4976 .parent_columns
4977 .iter()
4978 .any(|c| ROWID_STRS.iter().any(|&r| r.eq_ignore_ascii_case(c)))
4979 {
4980 return Err(crate::LimboError::ForeignKeyConstraint(format!(
4981 "foreign key mismatch referencing \"{}\"",
4982 self.parent_table
4983 )));
4984 }
4985 Ok(())
4986 }
4987}
4988
4989#[derive(Clone, Debug)]
4993pub struct ResolvedFkRef {
4994 pub child_table: Arc<BTreeTable>,
4996 pub fk: Arc<ForeignKey>,
4998
4999 pub parent_cols: Box<[String]>,
5002 pub child_pos: BoxedSlice<usize>,
5004 pub parent_pos: BoxedSlice<usize>,
5005
5006 pub parent_uses_rowid: bool,
5008 pub parent_unique_index: Option<Arc<Index>>,
5011}
5012
5013impl ResolvedFkRef {
5014 pub fn parent_key_may_change(
5016 &self,
5017 updated_parent_positions: &ColumnMask,
5018 parent_tbl: &BTreeTable,
5019 ) -> Result<bool> {
5020 if self.parent_uses_rowid {
5021 if let Some((idx, _)) = parent_tbl
5023 .columns
5024 .iter()
5025 .enumerate()
5026 .find(|(_, c)| c.is_rowid_alias())
5027 {
5028 return Ok(updated_parent_positions.get(idx));
5029 }
5030 return Ok(true);
5032 }
5033 let affected = parent_tbl.columns_affected_by_update(updated_parent_positions)?;
5034 Ok(self.parent_pos.iter().any(|p| affected.get(*p)))
5035 }
5036
5037 pub fn child_key_changed(
5039 &self,
5040 updated_child_positions: &ColumnMask,
5041 child_tbl: &BTreeTable,
5042 ) -> bool {
5043 if self
5044 .child_pos
5045 .iter()
5046 .any(|p| updated_child_positions.get(*p))
5047 {
5048 return true;
5049 }
5050 if self.fk.child_columns.len() == 1 {
5052 let (i, col) = child_tbl.get_column(&self.fk.child_columns[0]).unwrap();
5053 if col.is_rowid_alias() && updated_child_positions.get(i) {
5054 return true;
5055 }
5056 }
5057 false
5058 }
5059}
5060
5061#[derive(Debug, Clone)]
5062pub struct Column {
5063 pub name: Option<String>,
5064 pub ty_str: String,
5065 pub ty_params: std::vec::Vec<Box<Expr>>,
5066 pub default: Option<Box<Expr>>,
5067 generated_type: GeneratedType,
5068 raw: u32,
5069 explicit_notnull: bool,
5070 pub notnull_conflict_clause: Option<ResolveType>,
5072}
5073
5074#[derive(Default)]
5075pub struct ColDef {
5076 pub primary_key: bool,
5077 pub rowid_alias: bool,
5078 pub notnull: bool,
5079 pub explicit_notnull: bool,
5080 pub unique: bool,
5081 pub hidden: bool,
5082 pub notnull_conflict_clause: Option<ResolveType>,
5083}
5084
5085#[derive(Debug, Clone)]
5086pub enum GeneratedType {
5087 Virtual {
5091 expr: Box<Expr>,
5092 original_sql: String,
5093 },
5094 NotGenerated,
5096}
5097
5098const F_PRIMARY_KEY: u32 = 1;
5100const F_ROWID_ALIAS: u32 = 2;
5101const F_NOTNULL: u32 = 4;
5102const F_UNIQUE: u32 = 8;
5103const F_HIDDEN: u32 = 16;
5104
5105const TYPE_SHIFT: u32 = 5;
5107const TYPE_MASK: u32 = 0b111 << TYPE_SHIFT;
5108const COLL_SHIFT: u32 = TYPE_SHIFT + 3;
5109const COLL_MASK: u32 = 0b1111_1111_1111 << COLL_SHIFT;
5110
5111const BASE_AFF_SHIFT: u32 = COLL_SHIFT + 12;
5114const BASE_AFF_MASK: u32 = 0b111 << BASE_AFF_SHIFT;
5115
5116const ARRAY_DIM_SHIFT: u32 = BASE_AFF_SHIFT + 3;
5118const ARRAY_DIM_MASK: u32 = 0b111 << ARRAY_DIM_SHIFT;
5119
5120impl Column {
5121 pub fn affinity(&self) -> Affinity {
5122 let v = ((self.raw & BASE_AFF_MASK) >> BASE_AFF_SHIFT) as u8;
5123 if v > 0 {
5124 match v {
5126 1 => Affinity::Integer,
5127 2 => Affinity::Text,
5128 3 => Affinity::Blob,
5129 4 => Affinity::Real,
5130 _ => Affinity::Numeric,
5131 }
5132 } else {
5133 Affinity::affinity(&self.ty_str)
5134 }
5135 }
5136
5137 pub fn set_base_affinity(&mut self, affinity: Affinity) {
5141 let v: u32 = match affinity {
5142 Affinity::Integer => 1,
5143 Affinity::Text => 2,
5144 Affinity::Blob => 3,
5145 Affinity::Real => 4,
5146 Affinity::Numeric => 5,
5147 };
5148 self.raw = (self.raw & !BASE_AFF_MASK) | ((v << BASE_AFF_SHIFT) & BASE_AFF_MASK);
5149 }
5150 pub fn affinity_with_strict(&self, is_strict: bool) -> Affinity {
5151 if is_strict && self.ty_str.eq_ignore_ascii_case("ANY") {
5152 Affinity::Blob
5153 } else {
5154 self.affinity()
5155 }
5156 }
5157 pub fn new_default_text(
5158 name: Option<String>,
5159 ty_str: String,
5160 default: Option<Box<Expr>>,
5161 ) -> Self {
5162 Self::new(
5163 name,
5164 ty_str,
5165 default,
5166 None,
5167 Type::Text,
5168 None,
5169 ColDef::default(),
5170 )
5171 }
5172 pub fn new_default_integer(
5173 name: Option<String>,
5174 ty_str: String,
5175 default: Option<Box<Expr>>,
5176 ) -> Self {
5177 Self::new(
5178 name,
5179 ty_str,
5180 default,
5181 None,
5182 Type::Integer,
5183 None,
5184 ColDef::default(),
5185 )
5186 }
5187 #[inline]
5188 pub fn new(
5189 name: Option<String>,
5190 ty_str: String,
5191 default: Option<Box<Expr>>,
5192 generated: Option<Box<Expr>>,
5193 ty: Type,
5194 col: Option<CollationSeq>,
5195 coldef: ColDef,
5196 ) -> Self {
5197 let generated_type = match generated {
5198 Some(expr) => {
5199 let original_sql = expr.to_string();
5200 GeneratedType::Virtual { expr, original_sql }
5201 }
5202 None => GeneratedType::NotGenerated,
5203 };
5204 let mut raw = 0u32;
5205 raw |= (ty as u32) << TYPE_SHIFT;
5206 if let Some(c) = col {
5207 raw |= (u32::from(c.to_bits()) << COLL_SHIFT) & COLL_MASK;
5208 }
5209 if coldef.primary_key {
5210 raw |= F_PRIMARY_KEY
5211 }
5212 if coldef.rowid_alias {
5213 raw |= F_ROWID_ALIAS
5214 }
5215 if coldef.notnull {
5216 raw |= F_NOTNULL
5217 }
5218 if coldef.unique {
5219 raw |= F_UNIQUE
5220 }
5221 if coldef.hidden {
5222 raw |= F_HIDDEN
5223 }
5224 Self {
5225 name,
5226 ty_str,
5227 ty_params: std::vec::Vec::new(),
5228 default,
5229 generated_type,
5230 raw,
5231 explicit_notnull: coldef.explicit_notnull,
5232 notnull_conflict_clause: coldef.notnull_conflict_clause,
5233 }
5234 }
5235 #[inline]
5236 pub const fn ty(&self) -> Type {
5237 let v = ((self.raw & TYPE_MASK) >> TYPE_SHIFT) as u8;
5238 Type::from_bits(v)
5239 }
5240
5241 #[inline]
5242 pub const fn set_ty(&mut self, ty: Type) {
5243 self.raw = (self.raw & !TYPE_MASK) | (((ty as u32) << TYPE_SHIFT) & TYPE_MASK);
5244 }
5245
5246 #[inline]
5247 pub const fn collation_opt(&self) -> Option<CollationSeq> {
5248 if self.has_explicit_collation() {
5249 Some(self.collation())
5250 } else {
5251 None
5252 }
5253 }
5254
5255 #[inline]
5256 pub const fn collation(&self) -> CollationSeq {
5257 let v = ((self.raw & COLL_MASK) >> COLL_SHIFT) as u16;
5258 if v == CollationSeq::Unset.to_bits() {
5259 CollationSeq::Binary
5260 } else {
5261 CollationSeq::from_storage_bits(v)
5262 }
5263 }
5264
5265 #[inline]
5266 pub const fn has_explicit_collation(&self) -> bool {
5267 let v = ((self.raw & COLL_MASK) >> COLL_SHIFT) as u16;
5268 v != CollationSeq::Unset.to_bits()
5269 }
5270
5271 #[inline]
5272 pub const fn set_collation(&mut self, c: Option<CollationSeq>) {
5273 if let Some(c) = c {
5274 self.raw = (self.raw & !COLL_MASK) | (((c.to_bits() as u32) << COLL_SHIFT) & COLL_MASK);
5275 }
5276 }
5277
5278 #[inline]
5279 pub fn primary_key(&self) -> bool {
5280 self.raw & F_PRIMARY_KEY != 0
5281 }
5282 #[inline]
5283 pub const fn is_rowid_alias(&self) -> bool {
5284 self.raw & F_ROWID_ALIAS != 0
5285 }
5286 #[inline]
5287 pub const fn notnull(&self) -> bool {
5288 self.raw & F_NOTNULL != 0
5289 }
5290 #[inline]
5291 pub const fn explicit_notnull(&self) -> bool {
5292 self.explicit_notnull
5293 }
5294 #[inline]
5295 pub const fn unique(&self) -> bool {
5296 self.raw & F_UNIQUE != 0
5297 }
5298 #[inline]
5299 pub const fn hidden(&self) -> bool {
5300 self.raw & F_HIDDEN != 0
5301 }
5302
5303 pub fn ensure_not_generated(&self, verb_phrase: &str, col_name: &str) -> Result<()> {
5306 if !matches!(self.generated_type, GeneratedType::NotGenerated) {
5307 bail_parse_error!("cannot {} generated column \"{}\"", verb_phrase, col_name);
5308 }
5309 Ok(())
5310 }
5311
5312 #[inline]
5313 pub fn generated_type(&self) -> &GeneratedType {
5314 &self.generated_type
5315 }
5316
5317 #[inline]
5318 pub const fn is_generated(&self) -> bool {
5319 !matches!(self.generated_type, GeneratedType::NotGenerated)
5320 }
5321
5322 #[inline]
5323 pub const fn is_virtual_generated(&self) -> bool {
5324 matches!(self.generated_type, GeneratedType::Virtual { .. })
5325 }
5326
5327 #[inline]
5328 pub fn generated_expr(&self) -> Option<&Expr> {
5329 match &self.generated_type {
5330 GeneratedType::Virtual { expr, .. } => Some(expr.as_ref()),
5331 GeneratedType::NotGenerated => None,
5332 }
5333 }
5334
5335 #[inline]
5336 pub fn generated_expr_mut(&mut self) -> Option<&mut Expr> {
5337 match &mut self.generated_type {
5338 GeneratedType::Virtual { expr, .. } => Some(expr.as_mut()),
5339 GeneratedType::NotGenerated => None,
5340 }
5341 }
5342
5343 #[inline]
5344 pub fn set_generated_original_sql(&mut self, new_sql: String) {
5345 if let GeneratedType::Virtual {
5346 ref mut original_sql,
5347 ..
5348 } = self.generated_type
5349 {
5350 *original_sql = new_sql;
5351 }
5352 }
5353
5354 #[inline]
5355 pub const fn set_primary_key(&mut self, v: bool) {
5356 self.set_flag(F_PRIMARY_KEY, v);
5357 }
5358 #[inline]
5359 pub const fn set_rowid_alias(&mut self, v: bool) {
5360 self.set_flag(F_ROWID_ALIAS, v);
5361 }
5362 #[inline]
5363 pub const fn set_notnull(&mut self, v: bool) {
5364 self.set_flag(F_NOTNULL, v);
5365 }
5366 #[inline]
5367 pub const fn set_unique(&mut self, v: bool) {
5368 self.set_flag(F_UNIQUE, v);
5369 }
5370 #[inline]
5371 pub const fn set_hidden(&mut self, v: bool) {
5372 self.set_flag(F_HIDDEN, v);
5373 }
5374
5375 #[inline]
5376 pub const fn is_array(&self) -> bool {
5377 (self.raw & ARRAY_DIM_MASK) != 0
5378 }
5379
5380 #[inline]
5382 pub const fn array_dimensions(&self) -> u32 {
5383 (self.raw & ARRAY_DIM_MASK) >> ARRAY_DIM_SHIFT
5384 }
5385
5386 #[inline]
5387 pub fn set_array_dimensions(&mut self, dims: u32) {
5388 assert!(dims <= 7, "array dimensions must be <= 7");
5389 self.raw = (self.raw & !ARRAY_DIM_MASK) | (dims << ARRAY_DIM_SHIFT);
5390 }
5391
5392 #[inline]
5393 const fn set_flag(&mut self, mask: u32, val: bool) {
5394 if val {
5395 self.raw |= mask
5396 } else {
5397 self.raw &= !mask
5398 }
5399 }
5400}
5401
5402impl TryFrom<&ColumnDefinition> for Column {
5404 type Error = crate::LimboError;
5405
5406 fn try_from(value: &ColumnDefinition) -> crate::Result<Self> {
5407 let name = value.col_name.as_str();
5408
5409 let mut default = None;
5410 let mut generated = None;
5411 let mut notnull = false;
5412 let mut notnull_conflict_clause = None;
5413 let mut primary_key = false;
5414 let mut unique = false;
5415 let mut collation = None;
5416
5417 for ast::NamedColumnConstraint { constraint, .. } in &value.constraints {
5418 match constraint {
5419 ast::ColumnConstraint::PrimaryKey { .. } => primary_key = true,
5420 ast::ColumnConstraint::NotNull {
5421 conflict_clause, ..
5422 } => {
5423 notnull = true;
5424 notnull_conflict_clause = *conflict_clause;
5425 }
5426 ast::ColumnConstraint::Unique(..) => unique = true,
5427 ast::ColumnConstraint::Default(expr) => {
5428 default.replace(
5429 translate_ident_to_string_literal(expr).unwrap_or_else(|| expr.clone()),
5430 );
5431 }
5432 ast::ColumnConstraint::Collate { collation_name } => {
5433 let collation_seq = CollationSeq::new(collation_name.as_str())?;
5434 if collation_seq.is_custom() {
5435 crate::bail_parse_error!(
5436 "custom collations are not supported in schema definitions"
5437 );
5438 }
5439 collation.replace(collation_seq);
5440 }
5441 ast::ColumnConstraint::Generated { expr, .. } => {
5442 generated = Some(expr.clone());
5443 }
5444 _ => {}
5445 };
5446 }
5447
5448 let ty = match value.col_type {
5449 Some(ref data_type) => type_from_name(&data_type.name).0,
5450 None => Type::Null,
5451 };
5452
5453 let ty_str = value
5454 .col_type
5455 .as_ref()
5456 .map(|t| t.name.to_string())
5457 .unwrap_or_default();
5458
5459 let ty_params: std::vec::Vec<Box<turso_parser::ast::Expr>> = match &value.col_type {
5460 Some(ast::Type {
5461 size: Some(ast::TypeSize::MaxSize(ref expr)),
5462 ..
5463 }) => std::vec![expr.clone()],
5464 Some(ast::Type {
5465 size: Some(ast::TypeSize::TypeSize(ref e1, ref e2)),
5466 ..
5467 }) => std::vec![e1.clone(), e2.clone()],
5468 _ => std::vec::Vec::new(),
5469 };
5470
5471 let hidden = ty_str.contains("HIDDEN");
5472
5473 let mut col = Column::new(
5474 Some(name.to_string()),
5475 ty_str,
5476 default,
5477 generated,
5478 ty,
5479 collation,
5480 ColDef {
5481 primary_key,
5482 rowid_alias: primary_key && matches!(ty, Type::Integer),
5483 notnull,
5484 explicit_notnull: notnull,
5485 unique,
5486 hidden,
5487 notnull_conflict_clause,
5488 },
5489 );
5490 col.ty_params = ty_params;
5491 if let Some(t) = value.col_type.as_ref() {
5492 if t.is_array() {
5493 col.set_array_dimensions(t.array_dimensions);
5494 }
5495 }
5496 Ok(col)
5497 }
5498}
5499
5500#[repr(u8)]
5501#[derive(Debug, Clone, Copy, PartialEq)]
5502pub enum Type {
5503 Null = 0,
5504 Text = 1,
5505 Numeric = 2,
5506 Integer = 3,
5507 Real = 4,
5508 Blob = 5,
5509}
5510
5511impl Type {
5512 #[inline]
5513 const fn from_bits(bits: u8) -> Self {
5514 match bits {
5515 0 => Type::Null,
5516 1 => Type::Text,
5517 2 => Type::Numeric,
5518 3 => Type::Integer,
5519 4 => Type::Real,
5520 5 => Type::Blob,
5521 _ => Type::Null,
5522 }
5523 }
5524}
5525
5526impl fmt::Display for Type {
5527 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5528 let s = match self {
5529 Self::Null => "",
5530 Self::Text => "TEXT",
5531 Self::Numeric => "NUMERIC",
5532 Self::Integer => "INTEGER",
5533 Self::Real => "REAL",
5534 Self::Blob => "BLOB",
5535 };
5536 write!(f, "{s}")
5537 }
5538}
5539
5540pub fn sqlite_schema_table() -> Result<BTreeTable> {
5541 let columns = try_vec![
5542 Column::new_default_text(Some("type".to_string()), "TEXT".to_string(), None),
5543 Column::new_default_text(Some("name".to_string()), "TEXT".to_string(), None),
5544 Column::new_default_text(Some("tbl_name".to_string()), "TEXT".to_string(), None),
5545 Column::new_default_integer(Some("rootpage".to_string()), "INT".to_string(), None),
5546 Column::new_default_text(Some("sql".to_string()), "TEXT".to_string(), None),
5547 ]?;
5548 let logical_to_physical_map =
5549 BTreeTable::try_build_logical_to_physical_map(&columns, &[], true)?;
5550 Ok(BTreeTable {
5551 root_page: 1,
5552 name: "sqlite_schema".to_string(),
5553 has_rowid: true,
5554 is_strict: false,
5555 has_autoincrement: false,
5556 primary_key_columns: try_vec![]?,
5557 columns,
5558 foreign_keys: try_vec![]?,
5559 check_constraints: try_vec![]?,
5560 rowid_alias_conflict_clause: None,
5561 unique_sets: try_vec![]?,
5562 has_virtual_columns: false,
5563 logical_to_physical_map,
5564 column_dependencies: Default::default(),
5565 })
5566}
5567
5568#[allow(dead_code)]
5569#[derive(Debug, Clone)]
5570pub struct Index {
5571 pub name: String,
5572 pub table_name: String,
5573 pub root_page: i64,
5574 pub columns: Vec<IndexColumn>,
5575 pub unique: bool,
5576 pub ephemeral: bool,
5577 pub has_rowid: bool,
5583 pub where_clause: Option<Box<Expr>>,
5584 pub index_method: Option<Arc<dyn IndexMethodAttachment>>,
5585 pub on_conflict: Option<ResolveType>,
5587}
5588
5589#[allow(dead_code)]
5590#[derive(Debug, Clone)]
5591pub struct IndexColumn {
5592 pub name: String,
5593 pub order: SortOrder,
5594 pub pos_in_table: usize,
5600 pub collation: Option<CollationSeq>,
5601 pub default: Option<Box<Expr>>,
5602 pub expr: Option<Box<Expr>>,
5604}
5605
5606impl Index {
5607 pub fn from_sql(
5608 syms: &SymbolTable,
5609 sql: &str,
5610 root_page: i64,
5611 table: &BTreeTable,
5612 ) -> Result<Index> {
5613 let mut parser = Parser::new(sql.as_bytes());
5614 let cmd = parser.next_cmd()?;
5615 match cmd {
5616 Some(Cmd::Stmt(Stmt::CreateIndex {
5617 idx_name,
5618 tbl_name,
5619 columns,
5620 unique,
5621 where_clause,
5622 using,
5623 with_clause,
5624 ..
5625 })) => {
5626 let index_name = normalize_ident(idx_name.name.as_str());
5627 let index_columns = resolve_sorted_columns(table, &columns)?;
5628 if let Some(using) = using {
5629 if where_clause.is_some() {
5630 bail_parse_error!("custom index module do not support partial indices");
5631 }
5632 if unique {
5633 bail_parse_error!("custom index module do not support UNIQUE indices");
5634 }
5635 let parameters = resolve_index_method_parameters(with_clause)?;
5636 let Some(module) = syms.index_methods.get(using.as_str()) else {
5637 bail_parse_error!("unknown module name: '{}'", using);
5638 };
5639 let configuration = IndexMethodConfiguration {
5640 table_name: table.name.clone(),
5641 index_name: index_name.clone(),
5642 columns: index_columns.try_clone()?,
5643 parameters,
5644 };
5645 let descriptor = module.attach(&configuration)?;
5646 Ok(Index {
5647 name: index_name,
5648 table_name: normalize_ident(tbl_name.as_str()),
5649 root_page,
5650 columns: index_columns,
5651 unique: false,
5652 ephemeral: false,
5653 has_rowid: table.has_rowid,
5654 where_clause: None,
5655 index_method: Some(descriptor),
5656 on_conflict: None,
5657 })
5658 } else {
5659 Ok(Index {
5660 name: index_name,
5661 table_name: normalize_ident(tbl_name.as_str()),
5662 root_page,
5663 columns: index_columns,
5664 unique,
5665 ephemeral: false,
5666 has_rowid: table.has_rowid,
5667 where_clause,
5668 index_method: None,
5669 on_conflict: None,
5670 })
5671 }
5672 }
5673 _ => todo!("Expected create index statement"),
5674 }
5675 }
5676
5677 pub fn is_expression_index(&self) -> bool {
5679 self.columns.iter().any(|c| c.expr.is_some())
5680 }
5681
5682 pub fn is_backing_btree_index(&self) -> bool {
5684 self.index_method
5685 .as_ref()
5686 .is_some_and(|x| x.definition().backing_btree)
5687 }
5688
5689 pub fn automatic_from_primary_key(
5690 table: &BTreeTable,
5691 auto_index: (String, i64), column_count: usize,
5693 conflict_clause: Option<ResolveType>,
5694 collation_overrides: &[Option<CollationSeq>],
5695 ) -> Result<Index> {
5696 let has_primary_key_index =
5697 table.get_rowid_alias_column().is_none() && !table.primary_key_columns.is_empty();
5698 assert!(has_primary_key_index);
5699 let (index_name, root_page) = auto_index;
5700
5701 let mut primary_keys = Vec::try_with_capacity_ext(column_count)?;
5702 for (i, (col_name, order)) in table.primary_key_columns.iter().enumerate() {
5703 let Some((pos_in_table, _)) = table.get_column(col_name) else {
5704 return Err(crate::LimboError::ParseError(format!(
5705 "Column {} not found in table {}",
5706 col_name, table.name
5707 )));
5708 };
5709 let (_, column) = table.get_column(col_name).unwrap();
5710 primary_keys
5711 .push_within_capacity(IndexColumn {
5712 name: normalize_ident(col_name),
5713 order: *order,
5714 pos_in_table,
5715 collation: collation_overrides
5716 .get(i)
5717 .copied()
5718 .flatten()
5719 .or_else(|| column.collation_opt()),
5720 default: column.default.clone(),
5721 expr: None,
5722 })
5723 .expect("primary key index columns vector was preallocated");
5724 }
5725
5726 assert!(primary_keys.len() == column_count);
5727
5728 Ok(Index {
5729 name: normalize_ident(index_name.as_str()),
5730 table_name: table.name.clone(),
5731 root_page,
5732 columns: primary_keys,
5733 unique: true,
5734 ephemeral: false,
5735 has_rowid: table.has_rowid,
5736 where_clause: None,
5737 index_method: None,
5738 on_conflict: conflict_clause,
5739 })
5740 }
5741
5742 pub fn automatic_from_unique(
5743 table: &BTreeTable,
5744 auto_index: (String, i64), column_indices_and_sort_orders: Vec<(usize, SortOrder)>,
5746 conflict_clause: Option<ResolveType>,
5747 collation_overrides: &[Option<CollationSeq>],
5748 ) -> Result<Index> {
5749 let (index_name, root_page) = auto_index;
5750
5751 let mut unique_cols = Vec::try_with_capacity_ext(column_indices_and_sort_orders.len())?;
5752 for (i, (pos, sort_order)) in column_indices_and_sort_orders.iter().enumerate() {
5753 let Some((pos_in_table, col)) = table
5754 .columns
5755 .iter()
5756 .enumerate()
5757 .find(|(pos_in_table, _)| pos == pos_in_table)
5758 else {
5759 return Err(crate::LimboError::ParseError(format!(
5760 "Unique constraint column not found in table {}",
5761 table.name
5762 )));
5763 };
5764 unique_cols
5765 .push_within_capacity(IndexColumn {
5766 name: normalize_ident(col.name.as_ref().unwrap()),
5767 order: *sort_order,
5768 pos_in_table,
5769 collation: collation_overrides
5770 .get(i)
5771 .copied()
5772 .flatten()
5773 .or_else(|| col.collation_opt()),
5774 default: col.default.clone(),
5775 expr: None,
5776 })
5777 .expect("unique index columns vector was preallocated");
5778 }
5779
5780 Ok(Index {
5781 name: normalize_ident(index_name.as_str()),
5782 table_name: table.name.clone(),
5783 root_page,
5784 columns: unique_cols,
5785 unique: true,
5786 ephemeral: false,
5787 has_rowid: table.has_rowid,
5788 where_clause: None,
5789 index_method: None,
5790 on_conflict: conflict_clause,
5791 })
5792 }
5793
5794 pub fn column_table_pos_to_index_pos(&self, table_pos: usize) -> Option<usize> {
5801 self.columns
5802 .iter()
5803 .position(|c| c.pos_in_table == table_pos)
5804 }
5805
5806 pub fn expression_to_index_pos(&self, expr: &Expr) -> Option<usize> {
5810 self.columns.iter().position(|c| {
5811 c.expr
5812 .as_ref()
5813 .is_some_and(|e| exprs_are_equivalent(e, expr))
5814 })
5815 }
5816
5817 pub fn validate_where_expr(&self, table: &Table, _resolver: &Resolver) -> bool {
5820 let Some(where_clause) = &self.where_clause else {
5821 return true;
5822 };
5823
5824 let tbl_norm = self.table_name.as_str();
5825 let has_col = |name: &str| {
5826 table.columns().iter().any(|c| {
5827 c.name
5828 .as_ref()
5829 .is_some_and(|cn| cn.eq_ignore_ascii_case(name))
5830 })
5831 };
5832 let is_tbl = |ns: &str| normalize_ident(ns) == tbl_norm;
5833 let is_deterministic_fn = |name: &str, argc: usize| {
5834 let n = normalize_ident(name);
5835 Func::resolve_function(&n, argc).is_ok_and(|f| f.is_some_and(|f| f.is_deterministic()))
5836 };
5837
5838 let mut ok = true;
5839 let _ = walk_expr(where_clause.as_ref(), &mut |e: &Expr| -> crate::Result<
5840 WalkControl,
5841 > {
5842 if !ok {
5843 return Ok(WalkControl::SkipChildren);
5844 }
5845 match e {
5846 Expr::Literal(_) | Expr::RowId { .. } => {}
5847 Expr::Id(n) => {
5849 let n = n.as_str();
5850 if !ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(n)) && !has_col(n) {
5851 ok = false;
5852 }
5853 }
5854 Expr::Qualified(ns, col) | Expr::DoublyQualified(_, ns, col) => {
5856 if !is_tbl(ns.as_str()) || !has_col(col.as_str()) {
5857 ok = false;
5858 }
5859 }
5860 Expr::FunctionCall {
5861 name, filter_over, ..
5862 }
5863 | Expr::FunctionCallStar {
5864 name, filter_over, ..
5865 } => {
5866 if filter_over.over_clause.is_some() {
5868 ok = false;
5869 } else {
5870 let argc = match e {
5871 Expr::FunctionCall { args, .. } => args.len(),
5872 Expr::FunctionCallStar { .. } => 0,
5873 _ => unreachable!(),
5874 };
5875 if !is_deterministic_fn(name.as_str(), argc) {
5879 ok = false;
5880 }
5881 }
5882 }
5883 Expr::Exists(_)
5885 | Expr::InSelect { .. }
5886 | Expr::Subquery(_)
5887 | Expr::Raise { .. }
5888 | Expr::Variable(_) => {
5889 ok = false;
5890 }
5891 _ => {}
5892 }
5893 Ok(if ok {
5894 WalkControl::Continue
5895 } else {
5896 WalkControl::SkipChildren
5897 })
5898 });
5899 ok
5900 }
5901
5902 pub fn bind_where_expr(
5903 &self,
5904 table_refs: Option<&mut TableReferences>,
5905 resolver: &Resolver,
5906 ) -> Option<ast::Expr> {
5907 let Some(where_clause) = &self.where_clause else {
5908 return None;
5909 };
5910 let mut expr = where_clause.clone();
5911 bind_and_rewrite_expr(
5912 &mut expr,
5913 table_refs,
5914 None,
5915 resolver,
5916 BindingBehavior::ResultColumnsNotAllowed,
5917 )
5918 .ok()?;
5919 Some(*expr)
5920 }
5921}
5922
5923#[cfg(test)]
5924mod tests {
5925 use super::*;
5926 use crate::alloc::vec;
5927
5928 #[test]
5929 pub fn test_has_rowid_true() -> Result<()> {
5930 let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);"#;
5931 let table = BTreeTable::from_sql(sql, 0)?;
5932 assert!(table.has_rowid, "has_rowid should be set to true");
5933 Ok(())
5934 }
5935
5936 #[test]
5937 pub fn test_has_rowid_false() -> Result<()> {
5938 let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT) WITHOUT ROWID;"#;
5939 let table = BTreeTable::from_sql(sql, 0)?;
5940 assert!(!table.has_rowid, "has_rowid should be set to false");
5941 Ok(())
5942 }
5943
5944 #[test]
5945 pub fn test_column_default_collation_is_effective_binary() -> Result<()> {
5946 let sql = r#"CREATE TABLE t1 (a TEXT);"#;
5947 let table = BTreeTable::from_sql(sql, 0)?;
5948 let column = table.get_column("a").unwrap().1;
5949 assert_eq!(column.collation(), CollationSeq::Binary);
5950 assert_eq!(column.collation_opt(), None);
5951 Ok(())
5952 }
5953
5954 #[test]
5955 pub fn test_column_is_rowid_alias_single_text() -> Result<()> {
5956 let sql = r#"CREATE TABLE t1 (a TEXT PRIMARY KEY, b TEXT);"#;
5957 let table = BTreeTable::from_sql(sql, 0)?;
5958 let column = table.get_column("a").unwrap().1;
5959 assert!(
5960 !column.is_rowid_alias(),
5961 "column 'a´ has type different than INTEGER so can't be a rowid alias"
5962 );
5963 Ok(())
5964 }
5965
5966 #[test]
5967 pub fn test_column_is_rowid_alias_single_integer() -> Result<()> {
5968 let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);"#;
5969 let table = BTreeTable::from_sql(sql, 0)?;
5970 let column = table.get_column("a").unwrap().1;
5971 assert!(
5972 column.is_rowid_alias(),
5973 "column 'a´ should be a rowid alias"
5974 );
5975 Ok(())
5976 }
5977
5978 #[test]
5979 pub fn test_column_is_rowid_alias_single_integer_separate_primary_key_definition() -> Result<()>
5980 {
5981 let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a));"#;
5982 let table = BTreeTable::from_sql(sql, 0)?;
5983 let column = table.get_column("a").unwrap().1;
5984 assert!(
5985 column.is_rowid_alias(),
5986 "column 'a´ should be a rowid alias"
5987 );
5988 Ok(())
5989 }
5990
5991 #[test]
5992 pub fn test_column_is_rowid_alias_single_integer_separate_primary_key_definition_without_rowid(
5993 ) -> Result<()> {
5994 let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a)) WITHOUT ROWID;"#;
5995 let table = BTreeTable::from_sql(sql, 0)?;
5996 let column = table.get_column("a").unwrap().1;
5997 assert!(
5998 !column.is_rowid_alias(),
5999 "column 'a´ shouldn't be a rowid alias because table has no rowid"
6000 );
6001 Ok(())
6002 }
6003
6004 #[test]
6005 pub fn test_column_is_rowid_alias_single_integer_without_rowid() -> Result<()> {
6006 let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT) WITHOUT ROWID;"#;
6007 let table = BTreeTable::from_sql(sql, 0)?;
6008 let column = table.get_column("a").unwrap().1;
6009 assert!(
6010 !column.is_rowid_alias(),
6011 "column 'a´ shouldn't be a rowid alias because table has no rowid"
6012 );
6013 Ok(())
6014 }
6015
6016 #[test]
6017 pub fn test_multiple_pk_forbidden() -> Result<()> {
6018 let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT PRIMARY KEY);"#;
6019 let table = BTreeTable::from_sql(sql, 0);
6020 let error = table.unwrap_err();
6021 assert!(
6022 matches!(error, LimboError::ParseError(e) if e.contains("table \"t1\" has more than one primary key"))
6023 );
6024 Ok(())
6025 }
6026
6027 #[test]
6028 pub fn test_column_is_rowid_alias_separate_composite_primary_key_definition() -> Result<()> {
6029 let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a, b));"#;
6030 let table = BTreeTable::from_sql(sql, 0)?;
6031 let column = table.get_column("a").unwrap().1;
6032 assert!(
6033 !column.is_rowid_alias(),
6034 "column 'a´ shouldn't be a rowid alias because table has composite primary key"
6035 );
6036 Ok(())
6037 }
6038
6039 #[test]
6040 pub fn test_primary_key_inline_single() -> Result<()> {
6041 let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT, c REAL);"#;
6042 let table = BTreeTable::from_sql(sql, 0)?;
6043 let column = table.get_column("a").unwrap().1;
6044 assert!(column.primary_key(), "column 'a' should be a primary key");
6045 let column = table.get_column("b").unwrap().1;
6046 assert!(
6047 !column.primary_key(),
6048 "column 'b' shouldn't be a primary key"
6049 );
6050 let column = table.get_column("c").unwrap().1;
6051 assert!(
6052 !column.primary_key(),
6053 "column 'c' shouldn't be a primary key"
6054 );
6055 assert_eq!(
6056 vec![("a".to_string(), SortOrder::Asc)],
6057 table.primary_key_columns,
6058 "primary key column names should be ['a']"
6059 );
6060 Ok(())
6061 }
6062
6063 #[test]
6064 pub fn test_primary_key_inline_multiple_forbidden() -> Result<()> {
6065 let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT PRIMARY KEY, c REAL);"#;
6066 let table = BTreeTable::from_sql(sql, 0);
6067 let error = table.unwrap_err();
6068 assert!(
6069 matches!(error, LimboError::ParseError(e) if e.contains("table \"t1\" has more than one primary key"))
6070 );
6071 Ok(())
6072 }
6073
6074 #[test]
6075 pub fn test_conflicting_on_conflict_unique_rejected() -> Result<()> {
6076 let sql =
6077 r#"CREATE TABLE t1 (a UNIQUE ON CONFLICT FAIL, b, UNIQUE(a) ON CONFLICT IGNORE);"#;
6078 let table = BTreeTable::from_sql(sql, 0);
6079 let error = table.unwrap_err();
6080 assert!(
6081 matches!(error, LimboError::ParseError(e) if e.contains("conflicting ON CONFLICT clauses"))
6082 );
6083 Ok(())
6084 }
6085
6086 #[test]
6087 pub fn test_conflicting_on_conflict_composite_unique_rejected() -> Result<()> {
6088 let sql = r#"CREATE TABLE t1 (a, b, UNIQUE(a, b) ON CONFLICT FAIL, UNIQUE(a, b) ON CONFLICT IGNORE);"#;
6089 let table = BTreeTable::from_sql(sql, 0);
6090 let error = table.unwrap_err();
6091 assert!(
6092 matches!(error, LimboError::ParseError(e) if e.contains("conflicting ON CONFLICT clauses"))
6093 );
6094 Ok(())
6095 }
6096
6097 #[test]
6098 pub fn test_same_on_conflict_unique_allowed() -> Result<()> {
6099 let sql = r#"CREATE TABLE t1 (a UNIQUE ON CONFLICT FAIL, b, UNIQUE(a) ON CONFLICT FAIL);"#;
6100 assert!(BTreeTable::from_sql(sql, 0).is_ok());
6101 Ok(())
6102 }
6103
6104 #[test]
6105 pub fn test_one_on_conflict_unique_allowed() -> Result<()> {
6106 let sql = r#"CREATE TABLE t1 (a UNIQUE ON CONFLICT FAIL, b, UNIQUE(a));"#;
6107 assert!(BTreeTable::from_sql(sql, 0).is_ok());
6108 Ok(())
6109 }
6110
6111 #[test]
6112 pub fn test_primary_key_separate_single() -> Result<()> {
6113 let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY(a desc));"#;
6114 let table = BTreeTable::from_sql(sql, 0)?;
6115 let column = table.get_column("a").unwrap().1;
6116 assert!(column.primary_key(), "column 'a' should be a primary key");
6117 let column = table.get_column("b").unwrap().1;
6118 assert!(
6119 !column.primary_key(),
6120 "column 'b' shouldn't be a primary key"
6121 );
6122 let column = table.get_column("c").unwrap().1;
6123 assert!(
6124 !column.primary_key(),
6125 "column 'c' shouldn't be a primary key"
6126 );
6127 assert_eq!(
6128 vec![("a".to_string(), SortOrder::Desc)],
6129 table.primary_key_columns,
6130 "primary key column names should be ['a']"
6131 );
6132 Ok(())
6133 }
6134
6135 #[test]
6136 pub fn test_primary_key_separate_multiple() -> Result<()> {
6137 let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY(a, b desc));"#;
6138 let table = BTreeTable::from_sql(sql, 0)?;
6139 let column = table.get_column("a").unwrap().1;
6140 assert!(column.primary_key(), "column 'a' should be a primary key");
6141 let column = table.get_column("b").unwrap().1;
6142 assert!(column.primary_key(), "column 'b' shouldn be a primary key");
6143 let column = table.get_column("c").unwrap().1;
6144 assert!(
6145 !column.primary_key(),
6146 "column 'c' shouldn't be a primary key"
6147 );
6148 assert_eq!(
6149 vec![
6150 ("a".to_string(), SortOrder::Asc),
6151 ("b".to_string(), SortOrder::Desc)
6152 ],
6153 table.primary_key_columns,
6154 "primary key column names should be ['a', 'b']"
6155 );
6156 Ok(())
6157 }
6158
6159 #[test]
6160 pub fn test_primary_key_separate_single_quoted() -> Result<()> {
6161 let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY('a'));"#;
6162 let table = BTreeTable::from_sql(sql, 0)?;
6163 let column = table.get_column("a").unwrap().1;
6164 assert!(column.primary_key(), "column 'a' should be a primary key");
6165 let column = table.get_column("b").unwrap().1;
6166 assert!(
6167 !column.primary_key(),
6168 "column 'b' shouldn't be a primary key"
6169 );
6170 let column = table.get_column("c").unwrap().1;
6171 assert!(
6172 !column.primary_key(),
6173 "column 'c' shouldn't be a primary key"
6174 );
6175 assert_eq!(
6176 vec![("a".to_string(), SortOrder::Asc)],
6177 table.primary_key_columns,
6178 "primary key column names should be ['a']"
6179 );
6180 Ok(())
6181 }
6182 #[test]
6183 pub fn test_primary_key_separate_single_doubly_quoted() -> Result<()> {
6184 let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY("a"));"#;
6185 let table = BTreeTable::from_sql(sql, 0)?;
6186 let column = table.get_column("a").unwrap().1;
6187 assert!(column.primary_key(), "column 'a' should be a primary key");
6188 let column = table.get_column("b").unwrap().1;
6189 assert!(
6190 !column.primary_key(),
6191 "column 'b' shouldn't be a primary key"
6192 );
6193 let column = table.get_column("c").unwrap().1;
6194 assert!(
6195 !column.primary_key(),
6196 "column 'c' shouldn't be a primary key"
6197 );
6198 assert_eq!(
6199 vec![("a".to_string(), SortOrder::Asc)],
6200 table.primary_key_columns,
6201 "primary key column names should be ['a']"
6202 );
6203 Ok(())
6204 }
6205
6206 #[test]
6207 pub fn test_default_value() -> Result<()> {
6208 let sql = r#"CREATE TABLE t1 (a INTEGER DEFAULT 23);"#;
6209 let table = BTreeTable::from_sql(sql, 0)?;
6210 let column = table.get_column("a").unwrap().1;
6211 let default = column.default.clone().unwrap();
6212 assert_eq!(default.to_string(), "23");
6213 Ok(())
6214 }
6215
6216 #[test]
6217 pub fn test_col_notnull() -> Result<()> {
6218 let sql = r#"CREATE TABLE t1 (a INTEGER NOT NULL);"#;
6219 let table = BTreeTable::from_sql(sql, 0)?;
6220 let column = table.get_column("a").unwrap().1;
6221 assert!(column.notnull());
6222 Ok(())
6223 }
6224
6225 #[test]
6226 pub fn test_col_notnull_negative() -> Result<()> {
6227 let sql = r#"CREATE TABLE t1 (a INTEGER);"#;
6228 let table = BTreeTable::from_sql(sql, 0)?;
6229 let column = table.get_column("a").unwrap().1;
6230 assert!(!column.notnull());
6231 Ok(())
6232 }
6233
6234 #[test]
6235 pub fn test_col_type_string_integer() -> Result<()> {
6236 let sql = r#"CREATE TABLE t1 (a InTeGeR);"#;
6237 let table = BTreeTable::from_sql(sql, 0)?;
6238 let column = table.get_column("a").unwrap().1;
6239 assert_eq!(column.ty_str, "InTeGeR");
6240 Ok(())
6241 }
6242
6243 #[test]
6244 pub fn test_sqlite_schema() -> Result<()> {
6245 let expected = r#"CREATE TABLE sqlite_schema (type TEXT, name TEXT, tbl_name TEXT, rootpage INT, sql TEXT)"#;
6246 let actual = sqlite_schema_table()?.to_sql();
6247 assert_eq!(expected, actual);
6248 Ok(())
6249 }
6250
6251 #[test]
6252 pub fn test_special_column_names() -> Result<()> {
6253 let tests = [
6254 ("foobar", "CREATE TABLE t (foobar TEXT)"),
6255 ("_table_name3", r#"CREATE TABLE t (_table_name3 TEXT)"#),
6256 ("special name", r#"CREATE TABLE t ("special name" TEXT)"#),
6257 ("foo&bar", r#"CREATE TABLE t ("foo&bar" TEXT)"#),
6258 (" name", r#"CREATE TABLE t (" name" TEXT)"#),
6259 ];
6260
6261 for (input_column_name, expected_sql) in tests {
6262 let sql = format!(r#"CREATE TABLE t ("{input_column_name}" TEXT)"#);
6263 let actual = BTreeTable::from_sql(&sql, 0)?.to_sql();
6264 assert_eq!(expected_sql, actual);
6265 }
6266
6267 Ok(())
6268 }
6269
6270 #[test]
6271 fn test_special_table_names_are_quoted_in_to_sql() -> Result<()> {
6272 let tests = [
6273 (
6274 r#"CREATE TABLE "t t" (x TEXT)"#,
6275 r#"CREATE TABLE "t t" (x TEXT)"#,
6276 ),
6277 (
6278 r#"CREATE TABLE "123table" (x TEXT)"#,
6279 r#"CREATE TABLE "123table" (x TEXT)"#,
6280 ),
6281 (
6282 r#"CREATE TABLE "t""t" (x TEXT)"#,
6283 r#"CREATE TABLE "t""t" (x TEXT)"#,
6284 ),
6285 ];
6286
6287 for (input_sql, expected_sql) in tests {
6288 let actual = BTreeTable::from_sql(input_sql, 0)?.to_sql();
6289 assert_eq!(actual, expected_sql);
6290 }
6291
6292 Ok(())
6293 }
6294
6295 #[test]
6296 #[should_panic]
6297 fn test_automatic_index_single_column() {
6298 let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);"#;
6300 let table = BTreeTable::from_sql(sql, 0).unwrap();
6301 let _index = Index::automatic_from_primary_key(
6302 &table,
6303 ("sqlite_autoindex_t1_1".to_string(), 2),
6304 1,
6305 None,
6306 &[],
6307 )
6308 .unwrap();
6309 }
6310
6311 #[test]
6312 fn test_automatic_index_composite_key() -> Result<()> {
6313 let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a, b));"#;
6314 let table = BTreeTable::from_sql(sql, 0)?;
6315 let index = Index::automatic_from_primary_key(
6316 &table,
6317 ("sqlite_autoindex_t1_1".to_string(), 2),
6318 2,
6319 None,
6320 &[],
6321 )?;
6322
6323 assert_eq!(index.name, "sqlite_autoindex_t1_1");
6324 assert_eq!(index.table_name, "t1");
6325 assert_eq!(index.root_page, 2);
6326 assert!(index.unique);
6327 assert_eq!(index.columns.len(), 2);
6328 assert_eq!(index.columns[0].name, "a");
6329 assert_eq!(index.columns[1].name, "b");
6330 assert!(matches!(index.columns[0].order, SortOrder::Asc));
6331 assert!(matches!(index.columns[1].order, SortOrder::Asc));
6332 Ok(())
6333 }
6334
6335 #[test]
6336 #[should_panic]
6337 fn test_automatic_index_no_primary_key() {
6338 let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT);"#;
6339 let table = BTreeTable::from_sql(sql, 0).unwrap();
6340 Index::automatic_from_primary_key(
6341 &table,
6342 ("sqlite_autoindex_t1_1".to_string(), 2),
6343 1,
6344 None,
6345 &[],
6346 )
6347 .unwrap();
6348 }
6349
6350 #[test]
6351 fn test_automatic_index_nonexistent_column() {
6352 let columns = vec![Column::new_default_integer(
6354 Some("a".to_string()),
6355 "INT".to_string(),
6356 None,
6357 )];
6358 let logical_to_physical_map =
6359 BTreeTable::build_logical_to_physical_map(&columns, &[], true);
6360 let table = BTreeTable {
6361 root_page: 0,
6362 name: "t1".to_string(),
6363 has_rowid: true,
6364 is_strict: false,
6365 has_autoincrement: false,
6366 primary_key_columns: vec![("nonexistent".to_string(), SortOrder::Asc)],
6367 columns,
6368 unique_sets: vec![],
6369 foreign_keys: vec![],
6370 check_constraints: vec![],
6371 rowid_alias_conflict_clause: None,
6372 has_virtual_columns: false,
6373 logical_to_physical_map,
6374 column_dependencies: Default::default(),
6375 };
6376
6377 let result = Index::automatic_from_primary_key(
6378 &table,
6379 ("sqlite_autoindex_t1_1".to_string(), 2),
6380 1,
6381 None,
6382 &[],
6383 );
6384 assert!(result.is_err());
6385 }
6386
6387 #[test]
6388 fn test_automatic_index_unique_column() -> Result<()> {
6389 let sql = r#"CREATE table t1 (x INTEGER, y INTEGER UNIQUE);"#;
6390 let table = BTreeTable::from_sql(sql, 0)?;
6391 let index = Index::automatic_from_unique(
6392 &table,
6393 ("sqlite_autoindex_t1_1".to_string(), 2),
6394 vec![(1, SortOrder::Asc)],
6395 None,
6396 &[],
6397 )?;
6398
6399 assert_eq!(index.name, "sqlite_autoindex_t1_1");
6400 assert_eq!(index.table_name, "t1");
6401 assert_eq!(index.root_page, 2);
6402 assert!(index.unique);
6403 assert_eq!(index.columns.len(), 1);
6404 assert_eq!(index.columns[0].name, "y");
6405 assert!(matches!(index.columns[0].order, SortOrder::Asc));
6406 Ok(())
6407 }
6408
6409 #[test]
6410 fn test_automatic_index_pkey_unique_column() -> Result<()> {
6411 let sql = r#"CREATE TABLE t1 (x PRIMARY KEY, y UNIQUE);"#;
6412 let table = BTreeTable::from_sql(sql, 0)?;
6413 let indices = [
6414 Index::automatic_from_primary_key(
6415 &table,
6416 ("sqlite_autoindex_t1_1".to_string(), 2),
6417 1,
6418 None,
6419 &[],
6420 )?,
6421 Index::automatic_from_unique(
6422 &table,
6423 ("sqlite_autoindex_t1_2".to_string(), 3),
6424 vec![(1, SortOrder::Asc)],
6425 None,
6426 &[],
6427 )?,
6428 ];
6429
6430 assert_eq!(indices[0].name, "sqlite_autoindex_t1_1");
6431 assert_eq!(indices[0].table_name, "t1");
6432 assert_eq!(indices[0].root_page, 2);
6433 assert!(indices[0].unique);
6434 assert_eq!(indices[0].columns.len(), 1);
6435 assert_eq!(indices[0].columns[0].name, "x");
6436 assert!(matches!(indices[0].columns[0].order, SortOrder::Asc));
6437
6438 assert_eq!(indices[1].name, "sqlite_autoindex_t1_2");
6439 assert_eq!(indices[1].table_name, "t1");
6440 assert_eq!(indices[1].root_page, 3);
6441 assert!(indices[1].unique);
6442 assert_eq!(indices[1].columns.len(), 1);
6443 assert_eq!(indices[1].columns[0].name, "y");
6444 assert!(matches!(indices[1].columns[0].order, SortOrder::Asc));
6445
6446 Ok(())
6447 }
6448
6449 #[test]
6450 fn test_automatic_index_pkey_many_unique_columns() -> Result<()> {
6451 let sql = r#"CREATE TABLE t1 (a PRIMARY KEY, b UNIQUE, c, d, UNIQUE(c, d));"#;
6452 let table = BTreeTable::from_sql(sql, 0)?;
6453 let auto_indices = [
6454 ("sqlite_autoindex_t1_1".to_string(), 2),
6455 ("sqlite_autoindex_t1_2".to_string(), 3),
6456 ("sqlite_autoindex_t1_3".to_string(), 4),
6457 ];
6458 let indices = vec![
6459 Index::automatic_from_primary_key(
6460 &table,
6461 ("sqlite_autoindex_t1_1".to_string(), 2),
6462 1,
6463 None,
6464 &[],
6465 )?,
6466 Index::automatic_from_unique(
6467 &table,
6468 ("sqlite_autoindex_t1_2".to_string(), 3),
6469 vec![(1, SortOrder::Asc)],
6470 None,
6471 &[],
6472 )?,
6473 Index::automatic_from_unique(
6474 &table,
6475 ("sqlite_autoindex_t1_3".to_string(), 4),
6476 vec![(2, SortOrder::Asc), (3, SortOrder::Asc)],
6477 None,
6478 &[],
6479 )?,
6480 ];
6481
6482 assert!(indices.len() == auto_indices.len());
6483
6484 for (pos, index) in indices.iter().enumerate() {
6485 let (index_name, root_page) = &auto_indices[pos];
6486 assert_eq!(index.name, *index_name);
6487 assert_eq!(index.table_name, "t1");
6488 assert_eq!(index.root_page, *root_page);
6489 assert!(index.unique);
6490
6491 if pos == 0 {
6492 assert_eq!(index.columns.len(), 1);
6493 assert_eq!(index.columns[0].name, "a");
6494 } else if pos == 1 {
6495 assert_eq!(index.columns.len(), 1);
6496 assert_eq!(index.columns[0].name, "b");
6497 } else if pos == 2 {
6498 assert_eq!(index.columns.len(), 2);
6499 assert_eq!(index.columns[0].name, "c");
6500 assert_eq!(index.columns[1].name, "d");
6501 }
6502
6503 assert!(matches!(index.columns[0].order, SortOrder::Asc));
6504 }
6505
6506 Ok(())
6507 }
6508
6509 #[test]
6510 fn test_automatic_index_unique_set_dedup() -> Result<()> {
6511 let sql = r#"CREATE TABLE t1 (a, b, UNIQUE(a, b), UNIQUE(a, b));"#;
6512 let table = BTreeTable::from_sql(sql, 0)?;
6513 let index = Index::automatic_from_unique(
6514 &table,
6515 ("sqlite_autoindex_t1_1".to_string(), 2),
6516 vec![(0, SortOrder::Asc), (1, SortOrder::Asc)],
6517 None,
6518 &[],
6519 )?;
6520
6521 assert_eq!(index.name, "sqlite_autoindex_t1_1");
6522 assert_eq!(index.table_name, "t1");
6523 assert_eq!(index.root_page, 2);
6524 assert!(index.unique);
6525 assert_eq!(index.columns.len(), 2);
6526 assert_eq!(index.columns[0].name, "a");
6527 assert!(matches!(index.columns[0].order, SortOrder::Asc));
6528 assert_eq!(index.columns[1].name, "b");
6529 assert!(matches!(index.columns[1].order, SortOrder::Asc));
6530
6531 Ok(())
6532 }
6533
6534 #[test]
6535 fn test_automatic_index_primary_key_is_unique() -> Result<()> {
6536 let sql = r#"CREATE TABLE t1 (a primary key unique);"#;
6537 let table = BTreeTable::from_sql(sql, 0)?;
6538 let index = Index::automatic_from_primary_key(
6539 &table,
6540 ("sqlite_autoindex_t1_1".to_string(), 2),
6541 1,
6542 None,
6543 &[],
6544 )?;
6545
6546 assert_eq!(index.name, "sqlite_autoindex_t1_1");
6547 assert_eq!(index.table_name, "t1");
6548 assert_eq!(index.root_page, 2);
6549 assert!(index.unique);
6550 assert_eq!(index.columns.len(), 1);
6551 assert_eq!(index.columns[0].name, "a");
6552 assert!(matches!(index.columns[0].order, SortOrder::Asc));
6553
6554 Ok(())
6555 }
6556
6557 #[test]
6558 fn test_automatic_index_primary_key_is_unique_and_composite() -> Result<()> {
6559 let sql = r#"CREATE TABLE t1 (a, b, PRIMARY KEY(a, b), UNIQUE(a, b));"#;
6560 let table = BTreeTable::from_sql(sql, 0)?;
6561 let index = Index::automatic_from_primary_key(
6562 &table,
6563 ("sqlite_autoindex_t1_1".to_string(), 2),
6564 2,
6565 None,
6566 &[],
6567 )?;
6568
6569 assert_eq!(index.name, "sqlite_autoindex_t1_1");
6570 assert_eq!(index.table_name, "t1");
6571 assert_eq!(index.root_page, 2);
6572 assert!(index.unique);
6573 assert_eq!(index.columns.len(), 2);
6574 assert_eq!(index.columns[0].name, "a");
6575 assert_eq!(index.columns[1].name, "b");
6576 assert!(matches!(index.columns[0].order, SortOrder::Asc));
6577
6578 Ok(())
6579 }
6580
6581 #[test]
6582 fn test_strict_table_to_sql() -> Result<()> {
6583 let sql = r#"CREATE TABLE test_strict (id INTEGER, name TEXT) STRICT"#;
6584 let table = BTreeTable::from_sql(sql, 0)?;
6585
6586 assert!(table.is_strict);
6588
6589 let reconstructed_sql = table.to_sql();
6591 assert!(
6592 reconstructed_sql.contains("STRICT"),
6593 "Reconstructed SQL should contain STRICT keyword: {reconstructed_sql}"
6594 );
6595 assert_eq!(
6596 reconstructed_sql,
6597 "CREATE TABLE test_strict (id INTEGER, name TEXT) STRICT"
6598 );
6599
6600 Ok(())
6601 }
6602
6603 #[test]
6604 fn test_non_strict_table_to_sql() -> Result<()> {
6605 let sql = r#"CREATE TABLE test_normal (id INTEGER, name TEXT)"#;
6606 let table = BTreeTable::from_sql(sql, 0)?;
6607
6608 assert!(!table.is_strict);
6610
6611 let reconstructed_sql = table.to_sql();
6613 assert!(
6614 !reconstructed_sql.contains("STRICT"),
6615 "Non-strict table SQL should not contain STRICT keyword: {reconstructed_sql}"
6616 );
6617 assert_eq!(
6618 reconstructed_sql,
6619 "CREATE TABLE test_normal (id INTEGER, name TEXT)"
6620 );
6621
6622 Ok(())
6623 }
6624
6625 #[test]
6626 fn test_autoincrement_preserved_in_to_sql() -> Result<()> {
6627 let sql = r#"CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, doomed INT, v TEXT)"#;
6628 let table = BTreeTable::from_sql(sql, 0)?;
6629
6630 assert!(table.has_autoincrement);
6631 assert_eq!(
6632 table.to_sql(),
6633 "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, doomed INT, v TEXT)"
6634 );
6635
6636 Ok(())
6637 }
6638
6639 #[test]
6640 fn test_without_rowid_preserved_in_sql() -> Result<()> {
6641 let sql = r#"CREATE TABLE t(code TEXT PRIMARY KEY, val TEXT) WITHOUT ROWID"#;
6642 let table = BTreeTable::from_sql(sql, 0)?;
6643 assert!(table.get_column("code").unwrap().1.notnull());
6644 assert_eq!(
6645 table.to_sql(),
6646 "CREATE TABLE t (code TEXT PRIMARY KEY, val TEXT) WITHOUT ROWID"
6647 );
6648 Ok(())
6649 }
6650
6651 #[test]
6652 fn test_strict_without_rowid_preserved_in_sql() -> Result<()> {
6653 let sql = r#"CREATE TABLE t(code TEXT PRIMARY KEY, val TEXT) STRICT, WITHOUT ROWID"#;
6654 let table = BTreeTable::from_sql(sql, 0)?;
6655 assert!(table.get_column("code").unwrap().1.notnull());
6656 assert_eq!(
6657 table.to_sql(),
6658 "CREATE TABLE t (code TEXT PRIMARY KEY, val TEXT) STRICT, WITHOUT ROWID"
6659 );
6660 Ok(())
6661 }
6662
6663 #[test]
6664 fn test_automatic_index_unique_and_a_pk() -> Result<()> {
6665 let sql = r#"CREATE TABLE t1 (a NUMERIC UNIQUE UNIQUE, b TEXT PRIMARY KEY)"#;
6666 let table = BTreeTable::from_sql(sql, 0)?;
6667 let mut indexes = vec![
6668 Index::automatic_from_unique(
6669 &table,
6670 ("sqlite_autoindex_t1_1".to_string(), 2),
6671 vec![(0, SortOrder::Asc)],
6672 None,
6673 &[],
6674 )?,
6675 Index::automatic_from_primary_key(
6676 &table,
6677 ("sqlite_autoindex_t1_2".to_string(), 3),
6678 1,
6679 None,
6680 &[],
6681 )?,
6682 ];
6683
6684 assert!(indexes.len() == 2);
6685 let index = indexes.pop().unwrap();
6686 assert_eq!(index.name, "sqlite_autoindex_t1_2");
6687 assert_eq!(index.table_name, "t1");
6688 assert_eq!(index.root_page, 3);
6689 assert!(index.unique);
6690 assert_eq!(index.columns.len(), 1);
6691 assert_eq!(index.columns[0].name, "b");
6692 assert!(matches!(index.columns[0].order, SortOrder::Asc));
6693
6694 let index = indexes.pop().unwrap();
6695 assert_eq!(index.name, "sqlite_autoindex_t1_1");
6696 assert_eq!(index.table_name, "t1");
6697 assert_eq!(index.root_page, 2);
6698 assert!(index.unique);
6699 assert_eq!(index.columns.len(), 1);
6700 assert_eq!(index.columns[0].name, "a");
6701 assert!(matches!(index.columns[0].order, SortOrder::Asc));
6702
6703 Ok(())
6704 }
6705
6706 #[test]
6707 fn test_schema_loading_rejects_gencol_without_flag() {
6708 let mut schema = Schema::new();
6709 schema.generated_columns_enabled = false;
6710
6711 let result = schema.handle_schema_row(
6712 "table",
6713 "t1",
6714 "t1",
6715 2,
6716 Some("CREATE TABLE t1(a INTEGER, b AS (a*2))"),
6717 &SymbolTable::default(),
6718 &mut vec![],
6719 &mut HashMap::default(),
6720 &mut HashMap::default(),
6721 &mut HashMap::default(),
6722 &mut HashMap::default(),
6723 &|_| None,
6724 );
6725 assert!(result
6726 .unwrap_err()
6727 .to_string()
6728 .contains("generated columns"));
6729 }
6730
6731 fn indices(mask: &ColumnMask) -> Vec<usize> {
6732 let mut v: Vec<usize> = mask.iter().try_collect().unwrap();
6733 v.sort_unstable();
6734 v
6735 }
6736
6737 fn stored(bits: &ColumnMask) -> Vec<usize> {
6738 let mut v: Vec<usize> = bits.iter().try_collect().unwrap();
6739 v.sort_unstable();
6740 v
6741 }
6742
6743 #[test]
6744 fn gencol_graph_no_virtual_columns() -> Result<()> {
6745 let t = BTreeTable::from_sql("CREATE TABLE t(a, b)", 0)?;
6746 assert_eq!(indices(&t.columns_affected_by_update([0])?), vec![0]);
6747 assert_eq!(indices(&t.columns_affected_by_update([0, 1])?), vec![0, 1]);
6748 assert_eq!(stored(&t.dependencies_of_columns([0])?), vec![0]);
6749 assert_eq!(stored(&t.dependencies_of_columns([])?), Vec::<usize>::new());
6750 Ok(())
6751 }
6752
6753 #[test]
6754 fn gencol_graph_linear_chain() -> Result<()> {
6755 let t = BTreeTable::from_sql("CREATE TABLE t(a, b AS (a) VIRTUAL, c AS (b) VIRTUAL)", 0)?;
6756 assert_eq!(indices(&t.columns_affected_by_update([0])?), vec![0, 1, 2]);
6758 assert_eq!(indices(&t.columns_affected_by_update([1])?), vec![1, 2]);
6760 assert_eq!(stored(&t.dependencies_of_columns([2])?), vec![0]);
6762 assert_eq!(stored(&t.dependencies_of_columns([1])?), vec![0]);
6764 assert_eq!(stored(&t.dependencies_of_columns([0])?), vec![0]);
6766 Ok(())
6767 }
6768
6769 #[test]
6770 fn gencol_graph_diamond() -> Result<()> {
6771 let t = BTreeTable::from_sql(
6772 "CREATE TABLE t(a, b AS (a) VIRTUAL, c AS (a) VIRTUAL, d AS (b + c) VIRTUAL)",
6773 0,
6774 )?;
6775 assert_eq!(
6776 indices(&t.columns_affected_by_update([0])?),
6777 vec![0, 1, 2, 3]
6778 );
6779 assert_eq!(stored(&t.dependencies_of_columns([3])?), vec![0]);
6780 assert_eq!(stored(&t.dependencies_of_columns([1])?), vec![0]);
6781 Ok(())
6782 }
6783
6784 #[test]
6785 fn gencol_graph_multiple_stored_roots() -> Result<()> {
6786 let t = BTreeTable::from_sql("CREATE TABLE t(a, b, c AS (a + b) VIRTUAL)", 0)?;
6787 assert_eq!(indices(&t.columns_affected_by_update([0])?), vec![0, 2]);
6788 assert_eq!(indices(&t.columns_affected_by_update([1])?), vec![1, 2]);
6789 assert_eq!(
6790 indices(&t.columns_affected_by_update([0, 1])?),
6791 vec![0, 1, 2]
6792 );
6793 assert_eq!(stored(&t.dependencies_of_columns([2])?), vec![0, 1]);
6794 Ok(())
6795 }
6796
6797 #[test]
6798 fn gencol_graph_empty_input() -> Result<()> {
6799 let t = BTreeTable::from_sql("CREATE TABLE t(a, b AS (a) VIRTUAL)", 0)?;
6800 assert!(t.columns_affected_by_update(std::iter::empty())?.is_empty());
6801 assert!(t.dependencies_of_columns(std::iter::empty())?.is_empty());
6802 Ok(())
6803 }
6804
6805 #[test]
6806 fn gencol_graph_disjoint_components() -> Result<()> {
6807 let t = BTreeTable::from_sql(
6808 "CREATE TABLE t(a, b AS (a) VIRTUAL, c, d AS (c) VIRTUAL)",
6809 0,
6810 )?;
6811 assert_eq!(indices(&t.columns_affected_by_update([0])?), vec![0, 1]);
6812 assert_eq!(indices(&t.columns_affected_by_update([2])?), vec![2, 3]);
6813 assert_eq!(stored(&t.dependencies_of_columns([1])?), vec![0]);
6814 assert_eq!(stored(&t.dependencies_of_columns([3])?), vec![2]);
6815 Ok(())
6816 }
6817
6818 #[test]
6819 fn gencol_graph_deep_chain() -> Result<()> {
6820 let mut sql = String::from("CREATE TABLE t(c0");
6822 for i in 1..50 {
6823 sql.push_str(&format!(", c{i} AS (c{prev}) VIRTUAL", prev = i - 1));
6824 }
6825 sql.push(')');
6826 let t = BTreeTable::from_sql(&sql, 0)?;
6827 let affected = t.columns_affected_by_update([0])?;
6829 assert_eq!(affected.count(), 50);
6830 assert_eq!(stored(&t.dependencies_of_columns([49])?), vec![0]);
6832 Ok(())
6833 }
6834
6835 #[test]
6836 fn gencol_graph_very_deep_chain_no_stack_overflow() -> Result<()> {
6837 let mut sql = String::from("CREATE TABLE t(c0");
6840 for i in 1..500 {
6841 sql.push_str(&format!(", c{i} AS (c{prev}) VIRTUAL", prev = i - 1));
6842 }
6843 sql.push(')');
6844 let t = BTreeTable::from_sql(&sql, 0)?;
6845 assert_eq!(t.columns_affected_by_update([0])?.count(), 500);
6846 assert_eq!(stored(&t.dependencies_of_columns([499])?), vec![0]);
6847 Ok(())
6848 }
6849
6850 #[test]
6851 fn gencol_graph_rowid_sentinel_passthrough() -> Result<()> {
6852 let t = BTreeTable::from_sql("CREATE TABLE t(a, b AS (a) VIRTUAL)", 0)?;
6853 let affected = t.columns_affected_by_update([ROWID_SENTINEL])?;
6854 assert!(affected.get(ROWID_SENTINEL));
6857 assert_eq!(affected.count(), 1);
6858 Ok(())
6859 }
6860
6861 #[test]
6862 fn gencol_graph_transpose_duality() -> Result<()> {
6863 let t = BTreeTable::from_sql(
6864 "CREATE TABLE t(a, b AS (a) VIRTUAL, c AS (b) VIRTUAL, d AS (a + c) VIRTUAL)",
6865 0,
6866 )?;
6867 let graph = t.column_graph()?;
6868 for i in 0..graph.dependencies.len() {
6870 for j in graph.dependencies[i].iter() {
6871 assert!(
6872 graph.dependents[j].get(i),
6873 "transpose violated: {j} is in dependencies[{i}] but {i} is not in dependents[{j}]"
6874 );
6875 }
6876 for j in graph.dependents[i].iter() {
6877 assert!(
6878 graph.dependencies[j].get(i),
6879 "transpose violated: {j} is in dependents[{i}] but {i} is not in dependencies[{j}]"
6880 );
6881 }
6882 }
6883 Ok(())
6884 }
6885
6886 #[test]
6887 fn gencol_graph_idempotence() -> Result<()> {
6888 let t = BTreeTable::from_sql(
6890 "CREATE TABLE t(a, b, c AS (a) VIRTUAL, d AS (b + c) VIRTUAL)",
6891 0,
6892 )?;
6893 let once = t.columns_affected_by_update([0, 1])?;
6894 let twice = t.columns_affected_by_update(once.iter())?;
6895 assert_eq!(indices(&twice), indices(&once));
6896 Ok(())
6897 }
6898
6899 #[test]
6900 fn gencol_graph_union_monotonicity() -> Result<()> {
6901 let t = BTreeTable::from_sql(
6903 "CREATE TABLE t(a, b, c AS (a) VIRTUAL, d AS (b) VIRTUAL, e AS (c + d) VIRTUAL)",
6904 0,
6905 )?;
6906 let mut expected = t.columns_affected_by_update([0])?;
6907 let b_mask = t.columns_affected_by_update([1])?;
6908 expected.union_with(&b_mask).unwrap();
6909 let union_mask = t.columns_affected_by_update([0, 1])?;
6910 assert_eq!(indices(&union_mask), indices(&expected));
6911 Ok(())
6912 }
6913
6914 #[test]
6915 fn gencol_graph_cycle_rejected() {
6916 let err = BTreeTable::from_sql(
6918 "CREATE TABLE t(stored, a AS (b) VIRTUAL, b AS (a) VIRTUAL)",
6919 0,
6920 )
6921 .expect_err("cycle must be rejected");
6922 assert!(
6923 err.to_string().contains("circular dependency")
6924 || err.to_string().contains("cannot reference itself"),
6925 "unexpected error: {err}"
6926 );
6927 }
6928
6929 #[test]
6930 fn gencol_graph_three_cycle_rejected() {
6931 let err = BTreeTable::from_sql(
6933 "CREATE TABLE t(stored, a AS (b) VIRTUAL, b AS (c) VIRTUAL, c AS (a) VIRTUAL)",
6934 0,
6935 )
6936 .expect_err("cycle must be rejected");
6937 assert!(err.to_string().contains("circular dependency"));
6938 }
6939
6940 #[test]
6941 fn gencol_graph_self_reference_rejected() {
6942 let err = BTreeTable::from_sql("CREATE TABLE t(a, b AS (b) VIRTUAL)", 0)
6943 .expect_err("self-reference must be rejected");
6944 assert!(err.to_string().contains("cannot reference itself"));
6945 }
6946
6947 #[test]
6948 #[allow(clippy::redundant_clone)]
6949 fn gencol_graph_clone_invalidates_cache() -> Result<()> {
6950 let original = BTreeTable::from_sql("CREATE TABLE t(a, b AS (a) VIRTUAL)", 0)?;
6954 let _ = original.columns_affected_by_update([0])?;
6956 assert!(original.peek_column_dependencies().is_some());
6957
6958 let cloned = original.clone();
6962 assert!(cloned.peek_column_dependencies().is_none());
6963 assert!(original.peek_column_dependencies().is_some());
6965
6966 assert_eq!(
6968 indices(&cloned.columns_affected_by_update([0])?),
6969 vec![0, 1]
6970 );
6971 assert!(cloned.peek_column_dependencies().is_some());
6972 Ok(())
6973 }
6974
6975 #[test]
6976 fn gencol_graph_columns_mut_invalidates_cache() -> Result<()> {
6977 let mut t = BTreeTable::from_sql("CREATE TABLE t(a, b AS (a) VIRTUAL)", 0)?;
6978 let _ = t.columns_affected_by_update([0])?;
6980 assert!(t.peek_column_dependencies().is_some());
6981
6982 let _ = t.columns_mut();
6984 assert!(t.peek_column_dependencies().is_none());
6985 Ok(())
6986 }
6987
6988 #[test]
6995 fn install_sequence_descriptor_rejects_invalid_metadata_with_corruption_error() {
6996 let mut schema = Schema::new();
6997 let bogus = SequenceMetadata {
6998 start: 0,
7001 increment: 0,
7002 min: 0,
7003 max: 100,
7004 cycle: false,
7005 };
7006 let result = schema.install_sequence_descriptor("broken_seq", bogus);
7007 let err = result.expect_err(
7008 "invalid persisted descriptor must surface as an error, not be silently dropped",
7009 );
7010 assert!(
7011 matches!(err, LimboError::Corrupt(_)),
7012 "expected Corrupt error for unreadable internal backing table, got: {err:?}",
7013 );
7014 assert!(
7015 !schema.sequences.contains_key("broken_seq"),
7016 "rejected descriptor must not land in the sequences map",
7017 );
7018 }
7019}