diff --git a/core/incremental/compiler.rs b/core/incremental/compiler.rs index 28bad0d0d..3e25787a6 100644 --- a/core/incremental/compiler.rs +++ b/core/incremental/compiler.rs @@ -2272,42 +2272,28 @@ mod tests { root_page: 2, primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("age".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_text( + Some("name".to_string()), + "TEXT".to_string(), + None, + ), + SchemaColumn::new_default_integer( + Some("age".to_string()), + "INTEGER".to_string(), + None, + ), ], has_rowid: true, is_strict: false, @@ -2328,42 +2314,28 @@ mod tests { turso_parser::ast::SortOrder::Asc, )], columns: vec![ - SchemaColumn { - name: Some("product_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("product_name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("price".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("product_id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_text( + Some("product_name".to_string()), + "TEXT".to_string(), + None, + ), + SchemaColumn::new_default_integer( + Some("price".to_string()), + "INTEGER".to_string(), + None, + ), ], has_rowid: true, is_strict: false, @@ -2384,54 +2356,33 @@ mod tests { turso_parser::ast::SortOrder::Asc, )], columns: vec![ - SchemaColumn { - name: Some("order_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("user_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("product_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("quantity".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("order_id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_integer( + Some("user_id".to_string()), + "INTEGER".to_string(), + None, + ), + SchemaColumn::new_default_integer( + Some("product_id".to_string()), + "INTEGER".to_string(), + None, + ), + SchemaColumn::new_default_integer( + Some("quantity".to_string()), + "INTEGER".to_string(), + None, + ), ], has_rowid: true, has_autoincrement: false, @@ -2449,30 +2400,23 @@ mod tests { root_page: 6, primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_text( + Some("name".to_string()), + "TEXT".to_string(), + None, + ), ], has_rowid: true, is_strict: false, @@ -2490,54 +2434,33 @@ mod tests { root_page: 7, primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("customer_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("vendor_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("quantity".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_integer( + Some("customer_id".to_string()), + "INTEGER".to_string(), + None, + ), + SchemaColumn::new_default_integer( + Some("vendor_id".to_string()), + "INTEGER".to_string(), + None, + ), + SchemaColumn::new_default_integer( + Some("quantity".to_string()), + "INTEGER".to_string(), + None, + ), ], has_rowid: true, is_strict: false, @@ -2555,42 +2478,28 @@ mod tests { root_page: 8, primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("price".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_text( + Some("name".to_string()), + "TEXT".to_string(), + None, + ), + SchemaColumn::new_default_integer( + Some("price".to_string()), + "INTEGER".to_string(), + None, + ), ], has_rowid: true, is_strict: false, @@ -2607,30 +2516,16 @@ mod tests { root_page: 2, primary_key_columns: vec![], columns: vec![ - SchemaColumn { - name: Some("product_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("amount".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new_default_integer( + Some("product_id".to_string()), + "INTEGER".to_string(), + None, + ), + SchemaColumn::new_default_integer( + Some("amount".to_string()), + "INTEGER".to_string(), + None, + ), ], has_rowid: true, is_strict: false, diff --git a/core/incremental/view.rs b/core/incremental/view.rs index a82a1188b..39cfa3cff 100644 --- a/core/incremental/view.rs +++ b/core/incremental/view.rs @@ -685,7 +685,7 @@ impl IncrementalView { for table in referenced_tables { // Check if the table has a rowid alias (INTEGER PRIMARY KEY column) - let has_rowid_alias = table.columns.iter().any(|col| col.is_rowid_alias); + let has_rowid_alias = table.columns.iter().any(|col| col.is_rowid_alias()); // Select all columns. The circuit will handle filtering and projection // If there's a rowid alias, we don't need to select rowid separately @@ -1398,30 +1398,19 @@ mod tests { root_page: 2, primary_key_columns: vec![("id".to_string(), ast::SortOrder::Asc)], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_text(Some("name".to_string()), "TEXT".to_string(), None), ], has_rowid: true, is_strict: false, @@ -1436,42 +1425,35 @@ mod tests { root_page: 3, primary_key_columns: vec![("id".to_string(), ast::SortOrder::Asc)], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("customer_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("total".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new( + Some("customer_id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + false, + false, + false, + false, + false, + ), + SchemaColumn::new_default_integer( + Some("total".to_string()), + "INTEGER".to_string(), + None, + ), ], has_rowid: true, is_strict: false, @@ -1486,42 +1468,31 @@ mod tests { root_page: 4, primary_key_columns: vec![("id".to_string(), ast::SortOrder::Asc)], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("price".to_string()), - ty: Type::Real, - ty_str: "REAL".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_text(Some("name".to_string()), "TEXT".to_string(), None), + SchemaColumn::new( + Some("price".to_string()), + "REAL".to_string(), + None, + Type::Real, + None, + false, + false, + false, + false, + false, + ), ], has_rowid: true, is_strict: false, @@ -1536,42 +1507,28 @@ mod tests { root_page: 5, primary_key_columns: vec![], // No primary key, so no rowid alias columns: vec![ - SchemaColumn { - name: Some("message".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("level".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("timestamp".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("message".to_string()), + "TEXT".to_string(), + None, + Type::Text, + None, + false, + false, + false, + false, + false, + ), + SchemaColumn::new_default_integer( + Some("level".to_string()), + "INTEGER".to_string(), + None, + ), + SchemaColumn::new_default_integer( + Some("timestamp".to_string()), + "INTEGER".to_string(), + None, + ), ], has_rowid: true, // Has implicit rowid but no alias is_strict: false, diff --git a/core/lib.rs b/core/lib.rs index 399020ff1..85088ab46 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -2838,7 +2838,7 @@ impl Statement { .table_references .find_table_by_internal_id(*table)?; let table_column = table_ref.get_column_at(*column_idx)?; - match &table_column.ty { + match &table_column.ty() { crate::schema::Type::Integer => Some("INTEGER".to_string()), crate::schema::Type::Real => Some("REAL".to_string()), crate::schema::Type::Text => Some("TEXT".to_string()), diff --git a/core/schema.rs b/core/schema.rs index b8a08546c..73d06ea3c 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -7,8 +7,9 @@ use crate::translate::expr::{ use crate::translate::index::{resolve_index_method_parameters, resolve_sorted_columns}; use crate::translate::planner::ROWID_STRS; use parking_lot::RwLock; +use turso_macros::AtomicEnum; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, AtomicEnum)] pub enum ViewState { Ready, InProgress, @@ -21,7 +22,7 @@ pub struct View { pub sql: String, pub select_stmt: ast::Select, pub columns: Vec, - pub state: Mutex, + pub state: AtomicViewState, } impl View { @@ -31,28 +32,28 @@ impl View { sql, select_stmt, columns, - state: Mutex::new(ViewState::Ready), + state: AtomicViewState::new(ViewState::Ready), } } pub fn process(&self) -> Result<()> { - let mut state = self.state.lock().unwrap(); - match *state { + let state = self.state.get(); + match state { ViewState::InProgress => { bail_parse_error!("view {} is circularly defined", self.name) } ViewState::Ready => { - *state = ViewState::InProgress; + self.state.set(ViewState::InProgress); Ok(()) } } } pub fn done(&self) { - let mut state = self.state.lock().unwrap(); - match *state { + let state = self.state.get(); + match state { ViewState::InProgress => { - *state = ViewState::Ready; + self.state.set(ViewState::Ready); } ViewState::Ready => {} } @@ -66,7 +67,7 @@ impl Clone for View { sql: self.sql.clone(), select_stmt: self.select_stmt.clone(), columns: self.columns.clone(), - state: Mutex::new(ViewState::Ready), + state: AtomicViewState::new(ViewState::Ready), } } } @@ -545,8 +546,8 @@ impl Schema { table.name ))); }; - if column.primary_key && unique_set.is_primary_key { - if column.is_rowid_alias { + if column.primary_key() && unique_set.is_primary_key { + if column.is_rowid_alias() { // rowid alias, no index needed continue; } @@ -950,7 +951,7 @@ impl Schema { let pk_name = &parent_tbl.primary_key_columns[0].0; // rowid or alias INTEGER PRIMARY KEY; either is ok implicitly parent_tbl.columns.iter().any(|c| { - c.is_rowid_alias + c.is_rowid_alias() && c.name .as_deref() .is_some_and(|n| n.eq_ignore_ascii_case(pk_name)) @@ -1056,7 +1057,7 @@ impl Schema { let c = parent_cols[0].as_str(); ROWID_STRS.iter().any(|&r| r.eq_ignore_ascii_case(c)) || parent_tbl.columns.iter().any(|col| { - col.is_rowid_alias + col.is_rowid_alias() && col .name .as_deref() @@ -1325,7 +1326,7 @@ impl BTreeTable { pub fn get_rowid_alias_column(&self) -> Option<(usize, &Column)> { if self.primary_key_columns.len() == 1 { let (idx, col) = self.get_column(&self.primary_key_columns[0].0)?; - if col.is_rowid_alias { + if col.is_rowid_alias() { return Some((idx, col)); } } @@ -1384,14 +1385,14 @@ impl BTreeTable { sql.push(' '); sql.push_str(&column.ty_str); } - if column.notnull { + if column.notnull() { sql.push_str(" NOT NULL"); } - if column.unique { + if column.unique() { sql.push_str(" UNIQUE"); } - if needs_pk_inline && column.primary_key { + if needs_pk_inline && column.primary_key() { sql.push_str(" PRIMARY KEY"); } @@ -1462,8 +1463,11 @@ impl BTreeTable { sql } - pub fn column_collations(&self) -> Vec> { - self.columns.iter().map(|column| column.collation).collect() + pub fn column_collations(&self) -> Vec { + self.columns + .iter() + .map(|column| column.collation()) + .collect() } } @@ -1826,20 +1830,18 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R primary_key = true; } - cols.push(Column { - name: Some(normalize_ident(&name)), - ty, + cols.push(Column::new( + Some(normalize_ident(&name)), ty_str, - primary_key, - is_rowid_alias: typename_exactly_integer - && primary_key - && !primary_key_desc_columns_constraint, - notnull, default, - unique, + ty, collation, - hidden: false, - }); + primary_key, + typename_exactly_integer && primary_key && !primary_key_desc_columns_constraint, + notnull, + unique, + false, + )); } if options.contains(TableOptions::WITHOUT_ROWID) { has_rowid = false; @@ -1852,7 +1854,7 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R // or if the table has no rowid if !has_rowid || primary_key_columns.len() > 1 { for col in cols.iter_mut() { - col.is_rowid_alias = false; + col.set_rowid_alias(false); } } @@ -1865,14 +1867,14 @@ pub fn create_table(tbl_name: &str, body: &CreateTableBody, root_page: i64) -> R let pk_col = cols.iter().find(|c| c.name.as_deref() == Some(pk_col_name)); if let Some(col) = pk_col { - if col.ty != Type::Integer { + if col.ty() != Type::Integer { crate::bail_parse_error!("AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY"); } } } for col in cols.iter() { - if col.is_rowid_alias { + if col.is_rowid_alias() { // Unique sets are used for creating automatic indexes. An index is not created for a rowid alias PRIMARY KEY. // However, an index IS created for a rowid alias UNIQUE, e.g. CREATE TABLE t(x INTEGER PRIMARY KEY, UNIQUE(x)) let unique_set_w_only_rowid_alias = unique_sets.iter().position(|us| { @@ -2025,7 +2027,7 @@ impl ResolvedFkRef { .columns .iter() .enumerate() - .find(|(_, c)| c.is_rowid_alias) + .find(|(_, c)| c.is_rowid_alias()) { return updated_parent_positions.contains(&idx); } @@ -2053,7 +2055,7 @@ impl ResolvedFkRef { // special case: if FK uses a rowid alias on child, and rowid changed if self.child_cols.len() == 1 { let (i, col) = child_tbl.get_column(&self.child_cols[0]).unwrap(); - if col.is_rowid_alias && updated_child_positions.contains(&i) { + if col.is_rowid_alias() && updated_child_positions.contains(&i) { return true; } } @@ -2064,22 +2066,194 @@ impl ResolvedFkRef { #[derive(Debug, Clone)] pub struct Column { pub name: Option, - pub ty: Type, - // many sqlite operations like table_info retain the original string pub ty_str: String, - pub primary_key: bool, - pub is_rowid_alias: bool, - pub notnull: bool, pub default: Option>, - pub unique: bool, - pub collation: Option, - pub hidden: bool, + raw: u16, } +// flags +const F_PRIMARY_KEY: u16 = 1; +const F_ROWID_ALIAS: u16 = 2; +const F_NOTNULL: u16 = 4; +const F_UNIQUE: u16 = 8; +const F_HIDDEN: u16 = 16; + +// pack Type and Collation in the remaining bits +const TYPE_SHIFT: u16 = 5; +const TYPE_MASK: u16 = 0b111 << TYPE_SHIFT; +const COLL_SHIFT: u16 = TYPE_SHIFT + 3; +const COLL_MASK: u16 = 0b11 << COLL_SHIFT; + impl Column { pub fn affinity(&self) -> Affinity { affinity(&self.ty_str) } + pub const fn new_default_text( + name: Option, + ty_str: String, + default: Option>, + ) -> Self { + Self::new( + name, + ty_str, + default, + Type::Text, + None, + false, + false, + false, + false, + false, + ) + } + pub const fn new_default_integer( + name: Option, + ty_str: String, + default: Option>, + ) -> Self { + Self::new( + name, + ty_str, + default, + Type::Integer, + None, + false, + false, + false, + false, + false, + ) + } + #[inline] + #[allow(clippy::too_many_arguments)] + pub const fn new( + name: Option, + ty_str: String, + default: Option>, + ty: Type, + col: Option, + primary_key: bool, + rowid_alias: bool, + notnull: bool, + unique: bool, + hidden: bool, + ) -> Self { + let mut raw = 0u16; + raw |= (ty as u16) << TYPE_SHIFT; + if let Some(c) = col { + raw |= (c as u16) << COLL_SHIFT; + } + if primary_key { + raw |= F_PRIMARY_KEY + } + if rowid_alias { + raw |= F_ROWID_ALIAS + } + if notnull { + raw |= F_NOTNULL + } + if unique { + raw |= F_UNIQUE + } + if hidden { + raw |= F_HIDDEN + } + Self { + name, + ty_str, + default, + raw, + } + } + #[inline] + pub const fn ty(&self) -> Type { + let v = ((self.raw & TYPE_MASK) >> TYPE_SHIFT) as u8; + Type::from_bits(v) + } + + #[inline] + pub const fn set_ty(&mut self, ty: Type) { + self.raw = (self.raw & !TYPE_MASK) | (((ty as u16) << TYPE_SHIFT) & TYPE_MASK); + } + + #[inline] + pub const fn collation_opt(&self) -> Option { + if self.has_explicit_collation() { + Some(self.collation()) + } else { + None + } + } + + #[inline] + pub const fn collation(&self) -> CollationSeq { + let v = ((self.raw & COLL_MASK) >> COLL_SHIFT) as u8; + CollationSeq::from_bits(v) + } + + #[inline] + pub const fn has_explicit_collation(&self) -> bool { + let v = ((self.raw & COLL_MASK) >> COLL_SHIFT) as u8; + v != CollationSeq::Unset as u8 + } + + #[inline] + pub const fn set_collation(&mut self, c: Option) { + if let Some(c) = c { + self.raw = (self.raw & !COLL_MASK) | (((c as u16) << COLL_SHIFT) & COLL_MASK); + } + } + + #[inline] + pub fn primary_key(&self) -> bool { + self.raw & F_PRIMARY_KEY != 0 + } + #[inline] + pub const fn is_rowid_alias(&self) -> bool { + self.raw & F_ROWID_ALIAS != 0 + } + #[inline] + pub const fn notnull(&self) -> bool { + self.raw & F_NOTNULL != 0 + } + #[inline] + pub const fn unique(&self) -> bool { + self.raw & F_UNIQUE != 0 + } + #[inline] + pub const fn hidden(&self) -> bool { + self.raw & F_HIDDEN != 0 + } + + #[inline] + pub const fn set_primary_key(&mut self, v: bool) { + self.set_flag(F_PRIMARY_KEY, v); + } + #[inline] + pub const fn set_rowid_alias(&mut self, v: bool) { + self.set_flag(F_ROWID_ALIAS, v); + } + #[inline] + pub const fn set_notnull(&mut self, v: bool) { + self.set_flag(F_NOTNULL, v); + } + #[inline] + pub const fn set_unique(&mut self, v: bool) { + self.set_flag(F_UNIQUE, v); + } + #[inline] + pub const fn set_hidden(&mut self, v: bool) { + self.set_flag(F_HIDDEN, v); + } + + #[inline] + const fn set_flag(&mut self, mask: u16, val: bool) { + if val { + self.raw |= mask + } else { + self.raw &= !mask + } + } } // TODO: This might replace some of util::columns_from_create_table_body @@ -2125,18 +2299,18 @@ impl From<&ColumnDefinition> for Column { let hidden = ty_str.contains("HIDDEN"); - Column { - name: Some(normalize_ident(name)), - ty, - default, - notnull, + Column::new( + Some(normalize_ident(name)), ty_str, - primary_key, - is_rowid_alias: primary_key && matches!(ty, Type::Integer), - unique, + default, + ty, collation, + primary_key, + primary_key && matches!(ty, Type::Integer), + notnull, + unique, hidden, - } + ) } } @@ -2181,14 +2355,30 @@ pub fn affinity(datatype: &str) -> Affinity { Affinity::Numeric } +#[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq)] pub enum Type { - Null, - Text, - Numeric, - Integer, - Real, - Blob, + Null = 0, + Text = 1, + Numeric = 2, + Integer = 3, + Real = 4, + Blob = 5, +} + +impl Type { + #[inline] + const fn from_bits(bits: u8) -> Self { + match bits { + 0 => Type::Null, + 1 => Type::Text, + 2 => Type::Numeric, + 3 => Type::Integer, + 4 => Type::Real, + 5 => Type::Blob, + _ => Type::Null, + } + } } /// # SQLite Column Type Affinities @@ -2341,66 +2531,11 @@ pub fn sqlite_schema_table() -> BTreeTable { has_autoincrement: false, primary_key_columns: vec![], columns: vec![ - Column { - name: Some("type".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - Column { - name: Some("name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - Column { - name: Some("tbl_name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - Column { - name: Some("rootpage".to_string()), - ty: Type::Integer, - ty_str: "INT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - Column { - name: Some("sql".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + Column::new_default_text(Some("type".to_string()), "TEXT".to_string(), None), + Column::new_default_text(Some("name".to_string()), "TEXT".to_string(), None), + Column::new_default_text(Some("tbl_name".to_string()), "TEXT".to_string(), None), + Column::new_default_integer(Some("rootpage".to_string()), "INT".to_string(), None), + Column::new_default_text(Some("sql".to_string()), "TEXT".to_string(), None), ], foreign_keys: vec![], unique_sets: vec![], @@ -2540,7 +2675,7 @@ impl Index { name: normalize_ident(col_name), order: *order, pos_in_table, - collation: column.collation, + collation: column.collation_opt(), default: column.default.clone(), }); } @@ -2579,7 +2714,7 @@ impl Index { name: normalize_ident(col.name.as_ref().unwrap()), order: *sort_order, pos_in_table: *pos_in_table, - collation: col.collation, + collation: col.collation_opt(), default: col.default.clone(), }) }) @@ -2741,7 +2876,7 @@ mod tests { let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; assert!( - !column.is_rowid_alias, + !column.is_rowid_alias(), "column 'a´ has type different than INTEGER so can't be a rowid alias" ); Ok(()) @@ -2752,7 +2887,10 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!(column.is_rowid_alias, "column 'a´ should be a rowid alias"); + assert!( + column.is_rowid_alias(), + "column 'a´ should be a rowid alias" + ); Ok(()) } @@ -2762,7 +2900,10 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a));"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!(column.is_rowid_alias, "column 'a´ should be a rowid alias"); + assert!( + column.is_rowid_alias(), + "column 'a´ should be a rowid alias" + ); Ok(()) } @@ -2773,7 +2914,7 @@ mod tests { let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; assert!( - !column.is_rowid_alias, + !column.is_rowid_alias(), "column 'a´ shouldn't be a rowid alias because table has no rowid" ); Ok(()) @@ -2785,7 +2926,7 @@ mod tests { let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; assert!( - !column.is_rowid_alias, + !column.is_rowid_alias(), "column 'a´ shouldn't be a rowid alias because table has no rowid" ); Ok(()) @@ -2808,7 +2949,7 @@ mod tests { let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; assert!( - !column.is_rowid_alias, + !column.is_rowid_alias(), "column 'a´ shouldn't be a rowid alias because table has composite primary key" ); Ok(()) @@ -2819,11 +2960,17 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT, c REAL);"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!(column.primary_key, "column 'a' should be a primary key"); + assert!(column.primary_key(), "column 'a' should be a primary key"); let column = table.get_column("b").unwrap().1; - assert!(!column.primary_key, "column 'b' shouldn't be a primary key"); + assert!( + !column.primary_key(), + "column 'b' shouldn't be a primary key" + ); let column = table.get_column("c").unwrap().1; - assert!(!column.primary_key, "column 'c' shouldn't be a primary key"); + assert!( + !column.primary_key(), + "column 'c' shouldn't be a primary key" + ); assert_eq!( vec![("a".to_string(), SortOrder::Asc)], table.primary_key_columns, @@ -2848,11 +2995,17 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY(a desc));"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!(column.primary_key, "column 'a' should be a primary key"); + assert!(column.primary_key(), "column 'a' should be a primary key"); let column = table.get_column("b").unwrap().1; - assert!(!column.primary_key, "column 'b' shouldn't be a primary key"); + assert!( + !column.primary_key(), + "column 'b' shouldn't be a primary key" + ); let column = table.get_column("c").unwrap().1; - assert!(!column.primary_key, "column 'c' shouldn't be a primary key"); + assert!( + !column.primary_key(), + "column 'c' shouldn't be a primary key" + ); assert_eq!( vec![("a".to_string(), SortOrder::Desc)], table.primary_key_columns, @@ -2866,11 +3019,14 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY(a, b desc));"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!(column.primary_key, "column 'a' should be a primary key"); + assert!(column.primary_key(), "column 'a' should be a primary key"); let column = table.get_column("b").unwrap().1; - assert!(column.primary_key, "column 'b' shouldn be a primary key"); + assert!(column.primary_key(), "column 'b' shouldn be a primary key"); let column = table.get_column("c").unwrap().1; - assert!(!column.primary_key, "column 'c' shouldn't be a primary key"); + assert!( + !column.primary_key(), + "column 'c' shouldn't be a primary key" + ); assert_eq!( vec![ ("a".to_string(), SortOrder::Asc), @@ -2887,11 +3043,17 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY('a'));"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!(column.primary_key, "column 'a' should be a primary key"); + assert!(column.primary_key(), "column 'a' should be a primary key"); let column = table.get_column("b").unwrap().1; - assert!(!column.primary_key, "column 'b' shouldn't be a primary key"); + assert!( + !column.primary_key(), + "column 'b' shouldn't be a primary key" + ); let column = table.get_column("c").unwrap().1; - assert!(!column.primary_key, "column 'c' shouldn't be a primary key"); + assert!( + !column.primary_key(), + "column 'c' shouldn't be a primary key" + ); assert_eq!( vec![("a".to_string(), SortOrder::Asc)], table.primary_key_columns, @@ -2904,11 +3066,17 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY("a"));"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!(column.primary_key, "column 'a' should be a primary key"); + assert!(column.primary_key(), "column 'a' should be a primary key"); let column = table.get_column("b").unwrap().1; - assert!(!column.primary_key, "column 'b' shouldn't be a primary key"); + assert!( + !column.primary_key(), + "column 'b' shouldn't be a primary key" + ); let column = table.get_column("c").unwrap().1; - assert!(!column.primary_key, "column 'c' shouldn't be a primary key"); + assert!( + !column.primary_key(), + "column 'c' shouldn't be a primary key" + ); assert_eq!( vec![("a".to_string(), SortOrder::Asc)], table.primary_key_columns, @@ -2932,7 +3100,7 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER NOT NULL);"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!(column.notnull); + assert!(column.notnull()); Ok(()) } @@ -2941,7 +3109,7 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER);"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!(!column.notnull); + assert!(!column.notnull()); Ok(()) } @@ -3029,18 +3197,11 @@ mod tests { is_strict: false, has_autoincrement: false, primary_key_columns: vec![("nonexistent".to_string(), SortOrder::Asc)], - columns: vec![Column { - name: Some("a".to_string()), - ty: Type::Integer, - ty_str: "INT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }], + columns: vec![Column::new_default_integer( + Some("a".to_string()), + "INT".to_string(), + None, + )], unique_sets: vec![], foreign_keys: vec![], }; diff --git a/core/translate/aggregation.rs b/core/translate/aggregation.rs index 366332508..52a19962b 100644 --- a/core/translate/aggregation.rs +++ b/core/translate/aggregation.rs @@ -109,10 +109,10 @@ fn emit_collseq_if_needed( if let ast::Expr::Column { table, column, .. } = expr { if let Some((_, table_ref)) = referenced_tables.find_table_by_internal_id(*table) { if let Some(table_column) = table_ref.get_column_at(*column) { - if let Some(collation) = &table_column.collation { + if let Some(c) = table_column.collation_opt() { program.emit_insn(Insn::CollSeq { reg: None, - collation: *collation, + collation: c, }); } } diff --git a/core/translate/alter.rs b/core/translate/alter.rs index 4f8de0d99..4a4d6664e 100644 --- a/core/translate/alter.rs +++ b/core/translate/alter.rs @@ -110,13 +110,13 @@ pub fn translate_alter_table( // The column is used in the expression of a generated column. // The column appears in a trigger or view. - if column.primary_key { + if column.primary_key() { return Err(LimboError::ParseError(format!( "cannot drop column \"{column_name}\": PRIMARY KEY" ))); } - if column.unique + if column.unique() || btree.unique_sets.iter().any(|set| { set.columns .iter() diff --git a/core/translate/analyze.rs b/core/translate/analyze.rs index 665e43e0f..e26d599a0 100644 --- a/core/translate/analyze.rs +++ b/core/translate/analyze.rs @@ -161,7 +161,7 @@ pub fn translate_analyze( }); }; - if target_schema.columns().iter().any(|c| c.primary_key) { + if target_schema.columns().iter().any(|c| c.primary_key()) { bail_parse_error!("ANALYZE on tables with primary key is not supported"); } if !target_btree.has_rowid { diff --git a/core/translate/collate.rs b/core/translate/collate.rs index 425de8a8b..6c5048d50 100644 --- a/core/translate/collate.rs +++ b/core/translate/collate.rs @@ -19,14 +19,13 @@ use crate::{ /// **Pre defined collation sequences**\ /// Collating functions only matter when comparing string values. /// Numeric values are always compared numerically, and BLOBs are always compared byte-by-byte using memcmp(). +#[repr(u8)] pub enum CollationSeq { - /// Standard String compare + Unset = 0, #[default] - Binary, - /// Ascii case insensitive - NoCase, - /// Same as Binary but with trimmed whitespace - Rtrim, + Binary = 1, + NoCase = 2, + Rtrim = 3, } impl CollationSeq { @@ -35,11 +34,20 @@ impl CollationSeq { crate::LimboError::ParseError(format!("no such collation sequence: {collation}")) }) } + #[inline] + /// Returns the collation, defaulting to BINARY if unset + pub const fn from_bits(bits: u8) -> Self { + match bits { + 2 => CollationSeq::NoCase, + 3 => CollationSeq::Rtrim, + _ => CollationSeq::Binary, + } + } #[inline(always)] pub fn compare_strings(&self, lhs: &str, rhs: &str) -> Ordering { match self { - CollationSeq::Binary => Self::binary_cmp(lhs, rhs), + CollationSeq::Unset | CollationSeq::Binary => Self::binary_cmp(lhs, rhs), CollationSeq::NoCase => Self::nocase_cmp(lhs, rhs), CollationSeq::Rtrim => Self::rtrim_cmp(lhs, rhs), } @@ -112,7 +120,7 @@ pub fn get_collseq_from_expr( .get_column_at(*column) .ok_or_else(|| crate::LimboError::ParseError("column not found".to_string()))?; if maybe_column_collseq.is_none() { - maybe_column_collseq = column.collation; + maybe_column_collseq = column.collation_opt(); } return Ok(WalkControl::Continue); } @@ -123,7 +131,7 @@ pub fn get_collseq_from_expr( if let Some(btree) = table_ref.btree() { if let Some((_, rowid_alias_col)) = btree.get_rowid_alias_column() { if maybe_column_collseq.is_none() { - maybe_column_collseq = rowid_alias_col.collation; + maybe_column_collseq = rowid_alias_col.collation_opt(); } } } @@ -360,18 +368,18 @@ mod tests { is_strict: false, name: "foo".to_string(), primary_key_columns: vec![], - columns: vec![Column { - name: Some("foo".to_string()), - ty: Type::Text, - ty_str: "text".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, + columns: vec![Column::new( + Some("foo".to_string()), + "text".to_string(), + None, + Type::Text, collation, - hidden: false, - }], + false, + false, + false, + false, + false, + )], unique_sets: vec![], foreign_keys: vec![], })), @@ -403,18 +411,18 @@ mod tests { is_strict: false, name: "t1".to_string(), primary_key_columns: vec![], - columns: vec![Column { - name: Some("a".to_string()), - ty: Type::Text, - ty_str: "text".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: left, - hidden: false, - }], + columns: vec![Column::new( + Some("a".to_string()), + "text".to_string(), + None, + Type::Text, + left, + false, + false, + false, + false, + false, + )], unique_sets: vec![], foreign_keys: vec![], })), @@ -437,18 +445,18 @@ mod tests { is_strict: false, name: "t2".to_string(), primary_key_columns: vec![], - columns: vec![Column { - name: Some("b".to_string()), - ty: Type::Text, - ty_str: "text".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: right, - hidden: false, - }], + columns: vec![Column::new( + Some("b".to_string()), + "text".to_string(), + None, + Type::Text, + right, + false, + false, + false, + false, + false, + )], unique_sets: vec![], foreign_keys: vec![], })), @@ -478,18 +486,18 @@ mod tests { is_strict: false, name: "bar".to_string(), primary_key_columns: vec![("id".to_string(), SortOrder::Asc)], - columns: vec![Column { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: false, - default: None, - unique: true, + columns: vec![Column::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, collation, - hidden: false, - }], + true, + true, + false, + true, + false, + )], unique_sets: vec![], foreign_keys: vec![], })), diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index 1f61da880..49e2f485e 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -544,7 +544,7 @@ pub fn emit_fk_child_decrement_on_delete( let null_skip = program.allocate_label(); for cname in &fk_ref.child_cols { let (pos, col) = child_tbl.get_column(cname).unwrap(); - let src = if col.is_rowid_alias { + let src = if col.is_rowid_alias() { child_rowid_reg } else { let tmp = program.alloc_register(); @@ -571,7 +571,7 @@ pub fn emit_fk_child_decrement_on_delete( let pcur = open_read_table(program, &parent_tbl); let (pos, col) = child_tbl.get_column(&fk_ref.child_cols[0]).unwrap(); - let val = if col.is_rowid_alias { + let val = if col.is_rowid_alias() { child_rowid_reg } else { let tmp = program.alloc_register(); @@ -624,7 +624,7 @@ pub fn emit_fk_child_decrement_on_delete( let probe = program.alloc_registers(n); for (i, cname) in fk_ref.child_cols.iter().enumerate() { let (pos, col) = child_tbl.get_column(cname).unwrap(); - let src = if col.is_rowid_alias { + let src = if col.is_rowid_alias() { child_rowid_reg } else { let r = program.alloc_register(); @@ -1136,7 +1136,7 @@ fn emit_update_insns( .table .columns() .iter() - .position(|c| c.is_rowid_alias); + .position(|c| c.is_rowid_alias()); let has_direct_rowid_update = plan .set_clauses @@ -1232,7 +1232,7 @@ fn emit_update_insns( continue; } if has_user_provided_rowid - && (table_column.primary_key || table_column.is_rowid_alias) + && (table_column.primary_key() || table_column.is_rowid_alias()) && !is_virtual { let rowid_set_clause_reg = rowid_set_clause_reg.unwrap(); @@ -1257,7 +1257,7 @@ fn emit_update_insns( target_reg, &t_ctx.resolver, )?; - if table_column.notnull { + if table_column.notnull() { use crate::error::SQLITE_CONSTRAINT_NOTNULL; program.emit_insn(Insn::HaltIfNull { target_reg, @@ -1303,7 +1303,7 @@ fn emit_update_insns( // don't emit null for pkey of virtual tables. they require first two args // before the 'record' to be explicitly non-null - if table_column.is_rowid_alias && !is_virtual { + if table_column.is_rowid_alias() && !is_virtual { program.emit_null(target_reg, None); } else if is_virtual { program.emit_insn(Insn::VColumn { @@ -1511,7 +1511,7 @@ fn emit_update_insns( .get(col.pos_in_table) .expect("column index out of bounds"); program.emit_insn(Insn::Copy { - src_reg: if col_in_table.is_rowid_alias { + src_reg: if col_in_table.is_rowid_alias() { rowid_reg } else { start + col.pos_in_table @@ -1892,7 +1892,7 @@ pub fn emit_cdc_patch_record( rowid_reg: usize, ) -> usize { let columns = table.columns(); - let rowid_alias_position = columns.iter().position(|x| x.is_rowid_alias); + let rowid_alias_position = columns.iter().position(|x| x.is_rowid_alias()); if let Some(rowid_alias_position) = rowid_alias_position { let record_reg = program.alloc_register(); program.emit_insn(Insn::Copy { @@ -1927,7 +1927,7 @@ pub fn emit_cdc_full_record( ) -> usize { let columns_reg = program.alloc_registers(columns.len() + 1); for (i, column) in columns.iter().enumerate() { - if column.is_rowid_alias { + if column.is_rowid_alias() { program.emit_insn(Insn::Copy { src_reg: rowid_reg, dst_reg: columns_reg + 1 + i, @@ -2181,7 +2181,7 @@ fn rewrite_where_for_update_registers( .as_ref() .is_some_and(|n| n.eq_ignore_ascii_case(&normalized)) }) { - if c.is_rowid_alias { + if c.is_rowid_alias() { *e = Expr::Register(rowid_reg); } else { *e = Expr::Register(columns_start_reg + idx); @@ -2200,7 +2200,7 @@ fn rewrite_where_for_update_registers( .as_ref() .is_some_and(|n| n.eq_ignore_ascii_case(&normalized)) }) { - if c.is_rowid_alias { + if c.is_rowid_alias() { *e = Expr::Register(rowid_reg); } else { *e = Expr::Register(columns_start_reg + idx); diff --git a/core/translate/expr.rs b/core/translate/expr.rs index c0065dd3b..462b60de7 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -2133,7 +2133,7 @@ pub fn translate_expr( crate::bail_parse_error!("column index out of bounds"); }; // Counter intuitive but a column always needs to have a collation - program.set_collation(Some((table_column.collation.unwrap_or_default(), false))); + program.set_collation(Some((table_column.collation(), false))); } // If we are reading a column from a table, we find the cursor that corresponds to @@ -2222,7 +2222,7 @@ pub fn translate_expr( let Some(column) = table.get_column_at(*column) else { crate::bail_parse_error!("column index out of bounds"); }; - maybe_apply_affinity(column.ty, target_register, program); + maybe_apply_affinity(column.ty(), target_register, program); } Ok(target_register) } @@ -3701,7 +3701,7 @@ pub fn bind_and_rewrite_expr<'a>( match_result = Some(( joined_table.internal_id, col_idx.unwrap(), - col.is_rowid_alias, + col.is_rowid_alias(), )); } // only if we haven't found a match, check for explicit rowid reference @@ -3743,7 +3743,7 @@ pub fn bind_and_rewrite_expr<'a>( match_result = Some(( outer_ref.internal_id, col_idx.unwrap(), - col.is_rowid_alias, + col.is_rowid_alias(), )); } } @@ -3822,7 +3822,7 @@ pub fn bind_and_rewrite_expr<'a>( database: None, // TODO: support different databases table: tbl_id, column: col_idx, - is_rowid_alias: col.is_rowid_alias, + is_rowid_alias: col.is_rowid_alias(), }; tracing::debug!("rewritten to column"); referenced_tables.mark_column_used(tbl_id, col_idx); @@ -3882,7 +3882,7 @@ pub fn bind_and_rewrite_expr<'a>( let col = table.columns().get(col_idx).unwrap(); // Check if this is a rowid alias - let is_rowid_alias = col.is_rowid_alias; + let is_rowid_alias = col.is_rowid_alias(); // Convert to Column expression - since this is a cross-database reference, // we need to create a synthetic table reference for it diff --git a/core/translate/fkeys.rs b/core/translate/fkeys.rs index 32a7099ad..ec006b26f 100644 --- a/core/translate/fkeys.rs +++ b/core/translate/fkeys.rs @@ -245,7 +245,7 @@ pub fn stabilize_new_row_for_fk( .get_column(pk_name) .ok_or_else(|| crate::LimboError::InternalError(format!("pk col {pk_name} missing")))?; if !set_cols.contains(&pos) { - if col.is_rowid_alias { + if col.is_rowid_alias() { program.emit_insn(Insn::Copy { src_reg: rowid_new_reg, dst_reg: start + pos, @@ -388,7 +388,7 @@ pub fn emit_parent_index_key_change_checks( for (i, index_col) in index.columns.iter().enumerate() { let pos_in_table = index_col.pos_in_table; let column = &table_btree.columns[pos_in_table]; - if column.is_rowid_alias { + if column.is_rowid_alias() { program.emit_insn(Insn::Copy { src_reg: old_rowid_reg, dst_reg: old_key + i, @@ -407,7 +407,7 @@ pub fn emit_parent_index_key_change_checks( for (i, index_col) in index.columns.iter().enumerate() { let pos_in_table = index_col.pos_in_table; let column = &table_btree.columns[pos_in_table]; - let src = if column.is_rowid_alias { + let src = if column.is_rowid_alias() { new_rowid_reg } else { new_values_start + pos_in_table @@ -565,7 +565,7 @@ fn build_parent_key( let (pos, col) = parent_bt .get_column(pcol) .ok_or_else(|| crate::LimboError::InternalError(format!("col {pcol} missing")))?; - if col.is_rowid_alias { + if col.is_rowid_alias() { parent_rowid_reg } else { program.emit_insn(Insn::Column { @@ -716,7 +716,7 @@ pub fn emit_fk_child_update_counters( let fk_ok = program.allocate_label(); for cname in &fk_ref.fk.child_columns { let (i, col) = child_tbl.get_column(cname).unwrap(); - let src = if col.is_rowid_alias { + let src = if col.is_rowid_alias() { new_rowid_reg } else { new_start_reg + i @@ -736,7 +736,7 @@ pub fn emit_fk_child_update_counters( // Take the first child column value from NEW image let (i_child, col_child) = child_tbl.get_column(&fk_ref.child_cols[0]).unwrap(); - let val_reg = if col_child.is_rowid_alias { + let val_reg = if col_child.is_rowid_alias() { new_rowid_reg } else { new_start_reg + i_child @@ -781,7 +781,7 @@ pub fn emit_fk_child_update_counters( for (k, cname) in fk_ref.child_cols.iter().enumerate() { let (i, col) = child_tbl.get_column(cname).unwrap(); program.emit_insn(Insn::Copy { - src_reg: if col.is_rowid_alias { + src_reg: if col.is_rowid_alias() { new_rowid_reg } else { new_start_reg + i diff --git a/core/translate/index.rs b/core/translate/index.rs index f4e40d7ae..44e8efbb8 100644 --- a/core/translate/index.rs +++ b/core/translate/index.rs @@ -522,7 +522,7 @@ pub fn resolve_sorted_columns( name: col.1.name.as_ref().unwrap().clone(), order: sc.order.unwrap_or(SortOrder::Asc), pos_in_table: col.0, - collation: col.1.collation, + collation: col.1.collation_opt(), default: col.1.default.clone(), }); } diff --git a/core/translate/insert.rs b/core/translate/insert.rs index b5b7b7519..aaf590699 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -858,10 +858,10 @@ fn emit_notnulls(program: &mut ProgramBuilder, ctx: &InsertEmitCtx, insertion: & for column_mapping in insertion .col_mappings .iter() - .filter(|column_mapping| column_mapping.column.notnull) + .filter(|column_mapping| column_mapping.column.notnull()) { // if this is rowid alias - turso-db will emit NULL as a column value and always use rowid for the row as a column value - if column_mapping.column.is_rowid_alias { + if column_mapping.column.is_rowid_alias() { continue; } program.emit_insn(Insn::HaltIfNull { @@ -918,7 +918,7 @@ fn bind_insert( values = table .columns() .iter() - .filter(|c| !c.hidden) + .filter(|c| !c.hidden()) .map(|c| { c.default .clone() @@ -1133,7 +1133,7 @@ fn init_source_emission<'a>( ctx.table .columns .iter() - .filter(|col| !col.hidden) + .filter(|col| !col.hidden()) .map(|col| col.affinity().aff_mask()) .collect::() } else { @@ -1237,18 +1237,18 @@ pub struct AutoincMeta { table_name_reg: usize, } -pub const ROWID_COLUMN: &Column = &Column { - name: None, - ty: schema::Type::Integer, - ty_str: String::new(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, -}; +pub const ROWID_COLUMN: Column = Column::new( + None, // name + String::new(), // type string + None, // default + schema::Type::Integer, + None, + true, // primary key + true, // rowid alias + true, // notnull + false, // unique + false, // hidden +); /// Represents how a table should be populated during an INSERT. #[derive(Debug)] @@ -1389,7 +1389,7 @@ fn build_insertion<'a>( if columns.is_empty() { // Case 1: No columns specified - map values to columns in order - if num_values != table_columns.iter().filter(|c| !c.hidden).count() { + if num_values != table_columns.iter().filter(|c| !c.hidden()).count() { crate::bail_parse_error!( "table {} has {} columns but {} values were supplied", &table.get_name(), @@ -1399,11 +1399,11 @@ fn build_insertion<'a>( } let mut value_idx = 0; for (i, col) in table_columns.iter().enumerate() { - if col.hidden { + if col.hidden() { // Hidden columns are not taken into account. continue; } - if col.is_rowid_alias { + if col.is_rowid_alias() { insertion_key = InsertionKey::RowidAlias(ColMapping { column: col, value_index: Some(value_idx), @@ -1421,7 +1421,7 @@ fn build_insertion<'a>( let column_name = normalize_ident(column_name.as_str()); if let Some((idx_in_table, col_in_table)) = table.get_column_by_name(&column_name) { // Named column - if col_in_table.is_rowid_alias { + if col_in_table.is_rowid_alias() { insertion_key = InsertionKey::RowidAlias(ColMapping { column: col_in_table, value_index: Some(value_index), @@ -1435,7 +1435,7 @@ fn build_insertion<'a>( .any(|s| s.eq_ignore_ascii_case(&column_name)) { // Explicit use of the 'rowid' keyword - if let Some(col_in_table) = table.columns().iter().find(|c| c.is_rowid_alias) { + if let Some(col_in_table) = table.columns().iter().find(|c| c.is_rowid_alias()) { insertion_key = InsertionKey::RowidAlias(ColMapping { column: col_in_table, value_index: Some(value_index), @@ -1574,7 +1574,7 @@ fn translate_key( register, } => translate_column( program, - ROWID_COLUMN, + &ROWID_COLUMN, *register, *value_index, &mut translate_value_fn, @@ -1594,13 +1594,13 @@ fn translate_column( ) -> Result<()> { if let Some(value_index) = value_index { translate_value_fn(program, value_index, column_register)?; - } else if column.is_rowid_alias { + } else if column.is_rowid_alias() { // Although a non-NULL integer key is used for the insertion key, // the rowid alias column is emitted as NULL. program.emit_insn(Insn::SoftNull { reg: column_register, }); - } else if column.hidden { + } else if column.hidden() { // Emit NULL for not-explicitly-mentioned hidden columns, even ignoring DEFAULT. program.emit_insn(Insn::Null { dest: column_register, @@ -1609,7 +1609,7 @@ fn translate_column( } else if let Some(default_expr) = column.default.as_ref() { translate_expr(program, None, default_expr, column_register, resolver)?; } else { - let nullable = !column.notnull && !column.primary_key && !column.unique; + let nullable = !column.notnull() && !column.primary_key() && !column.unique(); if !nullable { crate::bail_parse_error!( "column {} is not nullable", @@ -2063,7 +2063,7 @@ pub fn rewrite_partial_index_where( if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(name)) { Some(insertion.key_register()) } else if let Some(c) = insertion.get_col_mapping_by_name(name) { - if c.column.is_rowid_alias { + if c.column.is_rowid_alias() { Some(insertion.key_register()) } else { Some(c.register) @@ -2231,7 +2231,7 @@ pub fn emit_fk_child_insert_checks( let fk_ok = program.allocate_label(); for cname in &fk_ref.child_cols { let (i, col) = child_tbl.get_column(cname).unwrap(); - let src = if col.is_rowid_alias { + let src = if col.is_rowid_alias() { new_rowid_reg } else { new_start_reg + i @@ -2250,7 +2250,7 @@ pub fn emit_fk_child_insert_checks( // first child col carries rowid let (i_child, col_child) = child_tbl.get_column(&fk_ref.child_cols[0]).unwrap(); - let val_reg = if col_child.is_rowid_alias { + let val_reg = if col_child.is_rowid_alias() { new_rowid_reg } else { new_start_reg + i_child @@ -2305,7 +2305,7 @@ pub fn emit_fk_child_insert_checks( for (k, cname) in fk_ref.child_cols.iter().enumerate() { let (i, col) = child_tbl.get_column(cname).unwrap(); program.emit_insn(Insn::Copy { - src_reg: if col.is_rowid_alias { + src_reg: if col.is_rowid_alias() { new_rowid_reg } else { new_start_reg + i @@ -2333,7 +2333,7 @@ pub fn emit_fk_child_insert_checks( for (i, pname) in parent_cols.iter().enumerate() { let (pos, col) = child_tbl.get_column(pname).unwrap(); program.emit_insn(Insn::Copy { - src_reg: if col.is_rowid_alias { + src_reg: if col.is_rowid_alias() { new_rowid_reg } else { new_start_reg + pos diff --git a/core/translate/logical.rs b/core/translate/logical.rs index bdd29972e..0f5cfd618 100644 --- a/core/translate/logical.rs +++ b/core/translate/logical.rs @@ -2273,7 +2273,7 @@ impl<'a> LogicalPlanBuilder<'a> { if let Some(ref name) = col.name { columns.push(ColumnInfo { name: name.clone(), - ty: col.ty, + ty: col.ty(), database: database.clone(), table: Some(actual_table.clone()), table_alias: alias.map(|s| s.to_string()), @@ -2391,54 +2391,25 @@ mod tests { primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], foreign_keys: vec![], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("age".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("email".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_text(Some("name".to_string()), "TEXT".to_string(), None), + SchemaColumn::new_default_integer( + Some("age".to_string()), + "INTEGER".to_string(), + None, + ), + SchemaColumn::new_default_text(Some("email".to_string()), "TEXT".to_string(), None), ], has_rowid: true, is_strict: false, @@ -2455,54 +2426,40 @@ mod tests { root_page: 3, primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("user_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("product".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("amount".to_string()), - ty: Type::Real, - ty_str: "REAL".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_integer( + Some("user_id".to_string()), + "INTEGER".to_string(), + None, + ), + SchemaColumn::new_default_text( + Some("product".to_string()), + "TEXT".to_string(), + None, + ), + SchemaColumn::new( + Some("amount".to_string()), + "REAL".to_string(), + None, + Type::Real, + None, + false, + false, + false, + false, + false, + ), ], has_rowid: true, is_strict: false, @@ -2520,54 +2477,36 @@ mod tests { root_page: 4, primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], columns: vec![ - SchemaColumn { - name: Some("id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: true, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("name".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("price".to_string()), - ty: Type::Real, - ty_str: "REAL".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, - SchemaColumn { - name: Some("product_id".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + SchemaColumn::new( + Some("id".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + true, + true, + true, + false, + false, + ), + SchemaColumn::new_default_text(Some("name".to_string()), "TEXT".to_string(), None), + SchemaColumn::new( + Some("price".to_string()), + "REAL".to_string(), + None, + Type::Real, + None, + false, + false, + false, + false, + false, + ), + SchemaColumn::new_default_integer( + Some("product_id".to_string()), + "INTEGER".to_string(), + None, + ), ], has_rowid: true, is_strict: false, diff --git a/core/translate/main_loop.rs b/core/translate/main_loop.rs index 611fc040c..394ce778e 100644 --- a/core/translate/main_loop.rs +++ b/core/translate/main_loop.rs @@ -1488,9 +1488,10 @@ fn emit_seek_termination( affinity = if let Some(table_ref) = tables .joined_tables() .iter() - .find(|t| t.columns().iter().any(|c| c.is_rowid_alias)) + .find(|t| t.columns().iter().any(|c| c.is_rowid_alias())) { - if let Some(rowid_col_idx) = table_ref.columns().iter().position(|c| c.is_rowid_alias) { + if let Some(rowid_col_idx) = table_ref.columns().iter().position(|c| c.is_rowid_alias()) + { Some(table_ref.columns()[rowid_col_idx].affinity()) } else { Some(Affinity::Numeric) diff --git a/core/translate/optimizer/access_method.rs b/core/translate/optimizer/access_method.rs index 883ae789c..455761574 100644 --- a/core/translate/optimizer/access_method.rs +++ b/core/translate/optimizer/access_method.rs @@ -101,7 +101,7 @@ fn find_best_access_method_for_btree( index: None, constraint_refs: vec![], }; - let rowid_column_idx = rhs_table.columns().iter().position(|c| c.is_rowid_alias); + let rowid_column_idx = rhs_table.columns().iter().position(|c| c.is_rowid_alias()); // Estimate cost for each candidate index (including the rowid index) and replace best_access_method if the cost is lower. for candidate in rhs_constraints.candidates.iter() { diff --git a/core/translate/optimizer/constraints.rs b/core/translate/optimizer/constraints.rs index a1740f2f9..21fe6407a 100644 --- a/core/translate/optimizer/constraints.rs +++ b/core/translate/optimizer/constraints.rs @@ -165,7 +165,7 @@ const SELECTIVITY_UNIQUE_EQUALITY: f64 = 1.0 / ESTIMATED_HARDCODED_ROWS_PER_TABL fn estimate_selectivity(column: &Column, op: ast::Operator) -> f64 { match op { ast::Operator::Equals => { - if column.is_rowid_alias || column.primary_key { + if column.is_rowid_alias() || column.primary_key() { SELECTIVITY_UNIQUE_EQUALITY } else { SELECTIVITY_EQ @@ -197,7 +197,7 @@ pub fn constraints_from_where_clause( let rowid_alias_column = table_reference .columns() .iter() - .position(|c| c.is_rowid_alias); + .position(|c| c.is_rowid_alias()); let mut cs = TableConstraints { table_id: table_reference.internal_id, @@ -313,7 +313,7 @@ pub fn constraints_from_where_clause( // For each constraint we found, add a reference to it for each index that may be able to use it. for (i, constraint) in cs.constraints.iter_mut().enumerate() { let constrained_column = &table_reference.table.columns()[constraint.table_col_pos]; - let column_collation = constrained_column.collation.unwrap_or_default(); + let column_collation = constrained_column.collation(); let constraining_expr = constraint.get_constraining_expr_ref(where_clause); // Index seek keys must use the same collation as the constrained column. match get_collseq_from_expr(constraining_expr, table_references)? { diff --git a/core/translate/optimizer/join.rs b/core/translate/optimizer/join.rs index 9c0f0425b..70a7b8ac3 100644 --- a/core/translate/optimizer/join.rs +++ b/core/translate/optimizer/join.rs @@ -1670,18 +1670,18 @@ mod tests { } fn _create_column(c: &TestColumn) -> Column { - Column { - name: Some(c.name.clone()), - ty: c.ty, - ty_str: c.ty.to_string(), - is_rowid_alias: c.is_rowid_alias, - primary_key: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - } + Column::new( + Some(c.name.clone()), + c.ty.to_string(), + None, + c.ty, + None, + c.is_rowid_alias, + false, + false, + false, + false, + ) } fn _create_column_of_type(name: &str, ty: Type) -> Column { _create_column(&TestColumn { diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index 862aa3790..6c36cb2ac 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -16,8 +16,9 @@ use turso_ext::{ConstraintInfo, ConstraintUsage}; use turso_parser::ast::{self, Expr, SortOrder}; use crate::{ - schema::{BTreeTable, Column, Index, IndexColumn, Schema, Table, Type, ROWID_SENTINEL}, + schema::{BTreeTable, Index, IndexColumn, Schema, Table, ROWID_SENTINEL}, translate::{ + insert::ROWID_COLUMN, optimizer::{ access_method::AccessMethodParams, constraints::{RangeConstraintRef, SeekRangeConstraint, TableConstraints}, @@ -167,7 +168,7 @@ fn optimize_update_plan( }; let Some(index) = table_ref.op.index() else { let rowid_alias_used = plan.set_clauses.iter().fold(false, |accum, (idx, _)| { - accum || (*idx != ROWID_SENTINEL && btree_table.columns[*idx].is_rowid_alias) + accum || (*idx != ROWID_SENTINEL && btree_table.columns[*idx].is_rowid_alias()) }); if rowid_alias_used { break 'requires true; @@ -217,18 +218,7 @@ fn add_ephemeral_table_to_update_plan( has_rowid: true, has_autoincrement: false, primary_key_columns: vec![], - columns: vec![Column { - name: Some("rowid".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: true, - is_rowid_alias: false, - notnull: true, - default: None, - unique: false, - collation: None, - hidden: false, - }], + columns: vec![ROWID_COLUMN], is_strict: false, unique_sets: vec![], foreign_keys: vec![], @@ -1034,7 +1024,7 @@ impl Optimizable for ast::Expr { .expect("table not found"); let columns = table_ref.columns(); let column = &columns[*column]; - column.primary_key || column.notnull + column.primary_key() || column.notnull() } Expr::RowId { .. } => true, Expr::InList { lhs, rhs, .. } => { @@ -1265,7 +1255,7 @@ fn ephemeral_index_build( name: c.name.clone().unwrap(), order: SortOrder::Asc, pos_in_table: i, - collation: c.collation, + collation: c.collation_opt(), default: c.default.clone(), }) // only include columns that are used in the query diff --git a/core/translate/optimizer/order.rs b/core/translate/optimizer/order.rs index b7b3c4edc..f32983b9e 100644 --- a/core/translate/optimizer/order.rs +++ b/core/translate/optimizer/order.rs @@ -184,7 +184,7 @@ pub fn plan_satisfies_order_target( .table .columns() .iter() - .position(|c| c.is_rowid_alias); + .position(|c| c.is_rowid_alias()); let Some(rowid_alias_col) = rowid_alias_col else { return false; }; diff --git a/core/translate/order_by.rs b/core/translate/order_by.rs index e2e8ccf15..c450c80df 100644 --- a/core/translate/order_by.rs +++ b/core/translate/order_by.rs @@ -87,7 +87,7 @@ pub fn init_order_by( name: pos_in_table.to_string(), order: SortOrder::Asc, pos_in_table, - collation: Some(CollationSeq::Binary), + collation: None, default: None, }); for _ in remappings.iter().filter(|r| !r.deduplicated) { diff --git a/core/translate/plan.rs b/core/translate/plan.rs index d69000526..7a18b63a6 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -512,7 +512,7 @@ pub fn select_star(tables: &[JoinedTable], out_columns: &mut Vec>(); for (i, column) in columns.iter_mut().enumerate() { - column.collation = - get_collseq_from_expr(&plan.result_columns[i].expr, &plan.table_references)?; + column.set_collation(get_collseq_from_expr( + &plan.result_columns[i].expr, + &plan.table_references, + )?); } let table = Table::FromClauseSubquery(FromClauseSubquery { diff --git a/core/translate/planner.rs b/core/translate/planner.rs index ecafb2880..ed1a27d8d 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -593,7 +593,7 @@ fn transform_args_into_where_terms( let mut args_iter = args.iter(); let mut hidden_count = 0; for (i, col) in table.columns().iter().enumerate() { - if !col.hidden { + if !col.hidden() { continue; } hidden_count += 1; @@ -603,7 +603,7 @@ fn transform_args_into_where_terms( database: None, table: internal_id, column: i, - is_rowid_alias: col.is_rowid_alias, + is_rowid_alias: col.is_rowid_alias(), }; let expr = match arg_expr.as_ref() { Expr::Literal(Null) => Expr::IsNull(Box::new(column_expr)), @@ -1087,14 +1087,14 @@ fn parse_join( let mut distinct_names: Vec = vec![]; // TODO: O(n^2) maybe not great for large tables or big multiway joins // SQLite doesn't use HIDDEN columns for NATURAL joins: https://www3.sqlite.org/src/info/ab09ef427181130b - for right_col in rightmost_table.columns().iter().filter(|col| !col.hidden) { + for right_col in rightmost_table.columns().iter().filter(|col| !col.hidden()) { let mut found_match = false; for left_table in table_references .joined_tables() .iter() .take(table_references.joined_tables().len() - 1) { - for left_col in left_table.columns().iter().filter(|col| !col.hidden) { + for left_col in left_table.columns().iter().filter(|col| !col.hidden()) { if left_col.name == right_col.name { distinct_names.push(ast::Name::exact( left_col.name.clone().expect("column name is None"), @@ -1154,7 +1154,7 @@ fn parse_join( .columns() .iter() .enumerate() - .filter(|(_, col)| !natural || !col.hidden) + .filter(|(_, col)| !natural || !col.hidden()) .find(|(_, col)| { col.name .as_ref() @@ -1189,14 +1189,14 @@ fn parse_join( database: None, table: left_table_id, column: left_col_idx, - is_rowid_alias: left_col.is_rowid_alias, + is_rowid_alias: left_col.is_rowid_alias(), }), ast::Operator::Equals, Box::new(Expr::Column { database: None, table: right_table.internal_id, column: right_col_idx, - is_rowid_alias: right_col.is_rowid_alias, + is_rowid_alias: right_col.is_rowid_alias(), }), ); diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index a9c5aa465..8cc0c110d 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -743,7 +743,7 @@ fn emit_columns_for_table_info( // According to the SQLite documentation: "The 'cid' column should not be taken to // mean more than 'rank within the current result set'." // Therefore, we enumerate only after filtering out hidden columns. - for (i, column) in columns.iter().filter(|col| !col.hidden).enumerate() { + for (i, column) in columns.iter().filter(|col| !col.hidden()).enumerate() { // cid program.emit_int(i as i64, base_reg); // name @@ -753,7 +753,7 @@ fn emit_columns_for_table_info( program.emit_string8(column.ty_str.clone(), base_reg + 2); // notnull - program.emit_bool(column.notnull, base_reg + 3); + program.emit_bool(column.notnull(), base_reg + 3); // dflt_value match &column.default { @@ -766,7 +766,7 @@ fn emit_columns_for_table_info( } // pk - program.emit_bool(column.primary_key, base_reg + 5); + program.emit_bool(column.primary_key(), base_reg + 5); program.emit_result_row(base_reg, 6); } diff --git a/core/translate/schema.rs b/core/translate/schema.rs index 0442b5542..d44d8838c 100644 --- a/core/translate/schema.rs +++ b/core/translate/schema.rs @@ -2,25 +2,16 @@ use std::sync::Arc; use crate::ast; use crate::ext::VTabImpl; -use crate::schema::create_table; -use crate::schema::BTreeTable; -use crate::schema::Column; -use crate::schema::Table; -use crate::schema::Type; -use crate::schema::RESERVED_TABLE_PREFIXES; +use crate::schema::{create_table, BTreeTable, Column, Table, Type, RESERVED_TABLE_PREFIXES}; use crate::storage::pager::CreateBTreeFlags; -use crate::translate::emitter::emit_cdc_full_record; -use crate::translate::emitter::emit_cdc_insns; -use crate::translate::emitter::prepare_cdc_if_necessary; -use crate::translate::emitter::OperationMode; -use crate::translate::emitter::Resolver; -use crate::translate::ProgramBuilder; -use crate::translate::ProgramBuilderOpts; +use crate::translate::emitter::{ + emit_cdc_full_record, emit_cdc_insns, prepare_cdc_if_necessary, OperationMode, Resolver, +}; +use crate::translate::{ProgramBuilder, ProgramBuilderOpts}; use crate::util::normalize_ident; use crate::util::PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX; use crate::vdbe::builder::CursorType; -use crate::vdbe::insn::Cookie; -use crate::vdbe::insn::{CmpInsFlags, InsertFlags, Insn}; +use crate::vdbe::insn::{CmpInsFlags, Cookie, InsertFlags, Insn}; use crate::Connection; use crate::{bail_parse_error, Result}; @@ -435,7 +426,7 @@ fn collect_autoindexes( }; let needs_index = if us.is_primary_key { - !(col.primary_key && col.is_rowid_alias) + !(col.primary_key() && col.is_rowid_alias()) } else { // UNIQUE single needs an index true @@ -841,18 +832,18 @@ pub fn translate_drop_table( has_rowid: true, has_autoincrement: false, primary_key_columns: vec![], - columns: vec![Column { - name: Some("rowid".to_string()), - ty: Type::Integer, - ty_str: "INTEGER".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }], + columns: vec![Column::new( + Some("rowid".to_string()), + "INTEGER".to_string(), + None, + Type::Integer, + None, + false, + false, + false, + false, + false, + )], is_strict: false, unique_sets: vec![], foreign_keys: vec![], diff --git a/core/translate/select.rs b/core/translate/select.rs index 3ec65af30..6ed92a875 100644 --- a/core/translate/select.rs +++ b/core/translate/select.rs @@ -236,14 +236,14 @@ fn prepare_one_select_plan( ResultColumn::Star => table_references .joined_tables() .iter() - .map(|t| t.columns().iter().filter(|col| !col.hidden).count()) + .map(|t| t.columns().iter().filter(|col| !col.hidden()).count()) .sum(), // Guess 5 columns if we can't find the table using the identifier (maybe it's in [brackets] or `tick_quotes`, or miXeDcAse) ResultColumn::TableStar(n) => table_references .joined_tables() .iter() .find(|t| t.identifier == n.as_str()) - .map(|t| t.columns().iter().filter(|col| !col.hidden).count()) + .map(|t| t.columns().iter().filter(|col| !col.hidden()).count()) .unwrap_or(5), // Otherwise allocate space for 1 column ResultColumn::Expr(_, _) => 1, @@ -317,7 +317,7 @@ fn prepare_one_select_plan( for table in plan.table_references.joined_tables_mut() { for idx in 0..table.columns().len() { let column = &table.columns()[idx]; - if column.hidden { + if column.hidden() { continue; } table.mark_column_used(idx); @@ -339,7 +339,7 @@ fn prepare_one_select_plan( let num_columns = table.columns().len(); for idx in 0..num_columns { let column = &table.columns()[idx]; - if column.hidden { + if column.hidden() { continue; } plan.result_columns.push(ResultSetColumn { @@ -347,7 +347,7 @@ fn prepare_one_select_plan( database: None, // TODO: support different databases table: table.internal_id, column: idx, - is_rowid_alias: column.is_rowid_alias, + is_rowid_alias: column.is_rowid_alias(), }, alias: None, contains_aggregates: false, diff --git a/core/translate/update.rs b/core/translate/update.rs index 1aac4c745..f542594ab 100644 --- a/core/translate/update.rs +++ b/core/translate/update.rs @@ -241,7 +241,7 @@ pub fn prepare_update_plan( .columns() .iter() .enumerate() - .find(|(_i, c)| c.is_rowid_alias) + .find(|(_i, c)| c.is_rowid_alias()) { // Use the rowid alias column index match set_clauses.iter_mut().find(|(i, _)| i == &idx) { @@ -320,7 +320,7 @@ pub fn prepare_update_plan( let updated_cols: HashSet = set_clauses.iter().map(|(i, _)| *i).collect(); let rowid_alias_used = set_clauses .iter() - .any(|(idx, _)| *idx == ROWID_SENTINEL || columns[*idx].is_rowid_alias); + .any(|(idx, _)| *idx == ROWID_SENTINEL || columns[*idx].is_rowid_alias()); let indexes_to_update = if rowid_alias_used { // If the rowid alias is used in the SET clause, we need to update all indexes indexes.cloned().collect() diff --git a/core/translate/upsert.rs b/core/translate/upsert.rs index 85f304283..e37a03f0d 100644 --- a/core/translate/upsert.rs +++ b/core/translate/upsert.rs @@ -5,7 +5,7 @@ use std::{collections::HashMap, sync::Arc}; use turso_parser::ast::{self, Upsert}; use crate::error::SQLITE_CONSTRAINT_PRIMARYKEY; -use crate::schema::ROWID_SENTINEL; +use crate::schema::{IndexColumn, ROWID_SENTINEL}; use crate::translate::emitter::UpdateRowSource; use crate::translate::expr::{walk_expr, WalkControl}; use crate::translate::fkeys::{emit_fk_child_update_counters, emit_parent_key_change_checks}; @@ -16,7 +16,7 @@ use crate::Connection; use crate::{ bail_parse_error, error::SQLITE_CONSTRAINT_NOTNULL, - schema::{Index, IndexColumn, Schema, Table}, + schema::{Index, Schema, Table}, translate::{ emitter::{ emit_cdc_full_record, emit_cdc_insns, emit_cdc_patch_record, OperationMode, Resolver, @@ -114,11 +114,7 @@ fn effective_collation_for_index_col(idx_col: &IndexColumn, table: &Table) -> St // Otherwise use the table default, or default to BINARY table .get_column_by_name(&idx_col.name) - .map(|s| { - s.1.collation - .map(|c| c.to_string().to_ascii_lowercase()) - .unwrap_or_else(|| "binary".to_string()) - }) + .map(|s| s.1.collation().to_string()) .unwrap_or_else(|| "binary".to_string()) } @@ -132,7 +128,7 @@ pub fn upsert_matches_rowid_alias(upsert: &Upsert, table: &Table) -> bool { return false; } // Only treat as PK if the PK is the rowid alias (INTEGER PRIMARY KEY) - let pk = table.columns().iter().find(|c| c.is_rowid_alias); + let pk = table.columns().iter().find(|c| c.is_rowid_alias()); if let Some(pkcol) = pk { extract_target_key(&t.targets[0].expr).is_some_and(|tk| { tk.col_name @@ -152,7 +148,7 @@ fn collect_changed_cols( let mut rowid_changed = false; for (col_idx, _) in set_pairs { if let Some(c) = table.columns().get(*col_idx) { - if c.is_rowid_alias { + if c.is_rowid_alias() { rowid_changed = true; } else { cols_changed.insert(*col_idx); @@ -356,7 +352,7 @@ pub fn emit_upsert( let num_cols = ctx.table.columns.len(); let current_start = program.alloc_registers(num_cols); for (i, col) in ctx.table.columns.iter().enumerate() { - if col.is_rowid_alias { + if col.is_rowid_alias() { program.emit_insn(Insn::RowId { cursor_id: ctx.cursor_id, dest: current_start + i, @@ -433,14 +429,14 @@ pub fn emit_upsert( NoConstantOptReason::RegisterReuse, )?; let col = &table.columns()[*col_idx]; - if col.notnull && !col.is_rowid_alias { + if col.notnull() && !col.is_rowid_alias() { program.emit_insn(Insn::HaltIfNull { target_reg: new_start + *col_idx, err_code: SQLITE_CONSTRAINT_NOTNULL, description: String::from(table.get_name()) + col.name.as_ref().unwrap(), }); } - if col.is_rowid_alias { + if col.is_rowid_alias() { // Must be integer; remember the NEW rowid value let r = program.alloc_register(); program.emit_insn(Insn::Copy { @@ -465,7 +461,7 @@ pub fn emit_upsert( } let (changed_cols, rowid_changed) = collect_changed_cols(table, set_pairs); - let rowid_alias_idx = table.columns().iter().position(|c| c.is_rowid_alias); + let rowid_alias_idx = table.columns().iter().position(|c| c.is_rowid_alias()); let has_direct_rowid_update = set_pairs .iter() .any(|(idx, _)| *idx == rowid_alias_idx.unwrap_or(ROWID_SENTINEL)); @@ -713,7 +709,7 @@ pub fn emit_upsert( table .columns() .iter() - .find(|c| c.is_rowid_alias) + .find(|c| c.is_rowid_alias()) .and_then(|c| c.name.as_ref()) .unwrap_or(&"rowid".to_string()) ), @@ -943,7 +939,7 @@ fn rewrite_expr_to_registers( return Some(rowid_reg); } let (idx, c) = table.get_column_by_name(name)?; - if c.is_rowid_alias { + if c.is_rowid_alias() { Some(rowid_reg) } else { Some(base_start + idx) diff --git a/core/util.rs b/core/util.rs index 745eb7a0b..9a5f43d33 100644 --- a/core/util.rs +++ b/core/util.rs @@ -354,7 +354,7 @@ pub fn simple_bind_expr( database: None, table: internal_id, column: col_idx, - is_rowid_alias: col.is_rowid_alias, + is_rowid_alias: col.is_rowid_alias(), }; } else { // only if we haven't found a match, check for explicit rowid reference @@ -1358,18 +1358,7 @@ pub fn extract_view_columns( columns.push(ViewColumn { table_index: table_index.unwrap_or(usize::MAX), - column: Column { - name: Some(col_name), - ty: Type::Text, // Default to TEXT, could be refined with type analysis - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + column: Column::new_default_text(Some(col_name), "TEXT".to_string(), None), }); } ast::ResultColumn::Star => { @@ -1392,18 +1381,18 @@ pub fn extract_view_columns( columns.push(ViewColumn { table_index: table_idx, - column: Column { - name: Some(final_name), - ty: table_column.ty, - ty_str: table_column.ty_str.clone(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: table_column.collation, - hidden: false, - }, + column: Column::new( + Some(final_name), + table_column.ty_str.clone(), + None, + table_column.ty(), + table_column.collation_opt(), + false, + false, + false, + false, + false, + ), }); } } @@ -1413,18 +1402,11 @@ pub fn extract_view_columns( if tables.is_empty() { columns.push(ViewColumn { table_index: usize::MAX, - column: Column { - name: Some("*".to_string()), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + column: Column::new_default_text( + Some("*".to_string()), + "TEXT".to_string(), + None, + ), }); } } @@ -1449,36 +1431,29 @@ pub fn extract_view_columns( columns.push(ViewColumn { table_index: table_idx, - column: Column { - name: Some(final_name), - ty: table_column.ty, - ty_str: table_column.ty_str.clone(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: table_column.collation, - hidden: false, - }, + column: Column::new( + Some(final_name), + table_column.ty_str.clone(), + None, + table_column.ty(), + table_column.collation_opt(), + false, + false, + false, + false, + false, + ), }); } } else { // Table not found, create placeholder columns.push(ViewColumn { table_index: usize::MAX, - column: Column { - name: Some(format!("{table_name_str}.*")), - ty: Type::Text, - ty_str: "TEXT".to_string(), - primary_key: false, - is_rowid_alias: false, - notnull: false, - default: None, - unique: false, - collation: None, - hidden: false, - }, + column: Column::new_default_text( + Some(format!("{table_name_str}.*")), + "TEXT".to_string(), + None, + ), }); } } diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index 72cfe9b78..05a89cfff 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -1028,7 +1028,7 @@ impl ProgramBuilder { .columns .get(column) .expect("column index out of bounds"); - if column_def.is_rowid_alias { + if column_def.is_rowid_alias() { self.emit_insn(Insn::RowId { cursor_id, dest: out, diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 00410e9e3..1ddb21553 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -1920,14 +1920,15 @@ pub fn op_type_check( .zip(table_reference.columns.iter()) .try_for_each(|(reg, col)| { // INT PRIMARY KEY is not row_id_alias so we throw error if this col is NULL - if !col.is_rowid_alias && col.primary_key && matches!(reg.get_value(), Value::Null) { + if !col.is_rowid_alias() && col.primary_key() && matches!(reg.get_value(), Value::Null) + { bail_constraint_error!( "NOT NULL constraint failed: {}.{} ({})", &table_reference.name, col.name.as_deref().unwrap_or(""), SQLITE_CONSTRAINT ) - } else if col.is_rowid_alias && matches!(reg.get_value(), Value::Null) { + } else if col.is_rowid_alias() && matches!(reg.get_value(), Value::Null) { // Handle INTEGER PRIMARY KEY for null as usual (Rowid will be auto-assigned) return Ok(()); } @@ -6011,7 +6012,7 @@ pub fn op_insert( // Fix rowid alias columns: replace Null with actual rowid value if let Some(table) = schema.get_table(table_name) { for (i, col) in table.columns().iter().enumerate() { - if col.is_rowid_alias && i < values.len() { + if col.is_rowid_alias() && i < values.len() { values[i] = Value::Integer(key); } } @@ -6162,7 +6163,7 @@ pub fn op_insert( let schema = program.connection.schema.read(); if let Some(table) = schema.get_table(table_name) { for (i, col) in table.columns().iter().enumerate() { - if col.is_rowid_alias && i < new_values.len() { + if col.is_rowid_alias() && i < new_values.len() { new_values[i] = Value::Integer(key); } } @@ -6280,7 +6281,7 @@ pub fn op_delete( // Fix rowid alias columns: replace Null with actual rowid value if let Some(table) = schema.get_table(table_name) { for (i, col) in table.columns().iter().enumerate() { - if col.is_rowid_alias && i < values.len() { + if col.is_rowid_alias() && i < values.len() { values[i] = Value::Integer(key); } } @@ -8666,7 +8667,7 @@ pub fn op_alter_column( // Maintain rowid-alias bit after change/rename (INTEGER PRIMARY KEY) if !*rename { // recompute alias from `new_column` - btree.columns[*column_index].is_rowid_alias = new_column.is_rowid_alias; + btree.columns[*column_index].set_rowid_alias(new_column.is_rowid_alias()); } // Update this table’s OWN foreign keys