Optimize and refactor schema::Column type

This commit is contained in:
PThorpe92
2025-11-02 20:46:02 -05:00
parent 72edc6d758
commit 481d86f567
31 changed files with 900 additions and 982 deletions

View File

@@ -2272,42 +2272,28 @@ mod tests {
root_page: 2, root_page: 2,
primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_text(
name: Some("name".to_string()), Some("name".to_string()),
ty: Type::Text, "TEXT".to_string(),
ty_str: "TEXT".to_string(), None,
primary_key: false, ),
is_rowid_alias: false, SchemaColumn::new_default_integer(
notnull: false, Some("age".to_string()),
default: None, "INTEGER".to_string(),
unique: false, None,
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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -2328,42 +2314,28 @@ mod tests {
turso_parser::ast::SortOrder::Asc, turso_parser::ast::SortOrder::Asc,
)], )],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("product_id".to_string()), Some("product_id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_text(
name: Some("product_name".to_string()), Some("product_name".to_string()),
ty: Type::Text, "TEXT".to_string(),
ty_str: "TEXT".to_string(), None,
primary_key: false, ),
is_rowid_alias: false, SchemaColumn::new_default_integer(
notnull: false, Some("price".to_string()),
default: None, "INTEGER".to_string(),
unique: false, None,
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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -2384,54 +2356,33 @@ mod tests {
turso_parser::ast::SortOrder::Asc, turso_parser::ast::SortOrder::Asc,
)], )],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("order_id".to_string()), Some("order_id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_integer(
name: Some("user_id".to_string()), Some("user_id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: false, ),
is_rowid_alias: false, SchemaColumn::new_default_integer(
notnull: false, Some("product_id".to_string()),
default: None, "INTEGER".to_string(),
unique: false, None,
collation: None, ),
hidden: false, SchemaColumn::new_default_integer(
}, Some("quantity".to_string()),
SchemaColumn { "INTEGER".to_string(),
name: Some("product_id".to_string()), None,
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,
},
], ],
has_rowid: true, has_rowid: true,
has_autoincrement: false, has_autoincrement: false,
@@ -2449,30 +2400,23 @@ mod tests {
root_page: 6, root_page: 6,
primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_text(
name: Some("name".to_string()), Some("name".to_string()),
ty: Type::Text, "TEXT".to_string(),
ty_str: "TEXT".to_string(), None,
primary_key: false, ),
is_rowid_alias: false,
notnull: false,
default: None,
unique: false,
collation: None,
hidden: false,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -2490,54 +2434,33 @@ mod tests {
root_page: 7, root_page: 7,
primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_integer(
name: Some("customer_id".to_string()), Some("customer_id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: false, ),
is_rowid_alias: false, SchemaColumn::new_default_integer(
notnull: false, Some("vendor_id".to_string()),
default: None, "INTEGER".to_string(),
unique: false, None,
collation: None, ),
hidden: false, SchemaColumn::new_default_integer(
}, Some("quantity".to_string()),
SchemaColumn { "INTEGER".to_string(),
name: Some("vendor_id".to_string()), None,
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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -2555,42 +2478,28 @@ mod tests {
root_page: 8, root_page: 8,
primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_text(
name: Some("name".to_string()), Some("name".to_string()),
ty: Type::Text, "TEXT".to_string(),
ty_str: "TEXT".to_string(), None,
primary_key: false, ),
is_rowid_alias: false, SchemaColumn::new_default_integer(
notnull: false, Some("price".to_string()),
default: None, "INTEGER".to_string(),
unique: false, None,
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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -2607,30 +2516,16 @@ mod tests {
root_page: 2, root_page: 2,
primary_key_columns: vec![], primary_key_columns: vec![],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new_default_integer(
name: Some("product_id".to_string()), Some("product_id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: false, ),
is_rowid_alias: false, SchemaColumn::new_default_integer(
notnull: false, Some("amount".to_string()),
default: None, "INTEGER".to_string(),
unique: false, None,
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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,

View File

@@ -685,7 +685,7 @@ impl IncrementalView {
for table in referenced_tables { for table in referenced_tables {
// Check if the table has a rowid alias (INTEGER PRIMARY KEY column) // 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 // Select all columns. The circuit will handle filtering and projection
// If there's a rowid alias, we don't need to select rowid separately // If there's a rowid alias, we don't need to select rowid separately
@@ -1398,30 +1398,19 @@ mod tests {
root_page: 2, root_page: 2,
primary_key_columns: vec![("id".to_string(), ast::SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), ast::SortOrder::Asc)],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_text(Some("name".to_string()), "TEXT".to_string(), None),
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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -1436,42 +1425,35 @@ mod tests {
root_page: 3, root_page: 3,
primary_key_columns: vec![("id".to_string(), ast::SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), ast::SortOrder::Asc)],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new(
name: Some("customer_id".to_string()), Some("customer_id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: false, Type::Integer,
is_rowid_alias: false, None,
notnull: false, false,
default: None, false,
unique: false, false,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_integer(
name: Some("total".to_string()), Some("total".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: false, ),
is_rowid_alias: false,
notnull: false,
default: None,
unique: false,
collation: None,
hidden: false,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -1486,42 +1468,31 @@ mod tests {
root_page: 4, root_page: 4,
primary_key_columns: vec![("id".to_string(), ast::SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), ast::SortOrder::Asc)],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_text(Some("name".to_string()), "TEXT".to_string(), None),
name: Some("name".to_string()), SchemaColumn::new(
ty: Type::Text, Some("price".to_string()),
ty_str: "TEXT".to_string(), "REAL".to_string(),
primary_key: false, None,
is_rowid_alias: false, Type::Real,
notnull: false, None,
default: None, false,
unique: false, false,
collation: None, false,
hidden: false, false,
}, 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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -1536,42 +1507,28 @@ mod tests {
root_page: 5, root_page: 5,
primary_key_columns: vec![], // No primary key, so no rowid alias primary_key_columns: vec![], // No primary key, so no rowid alias
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("message".to_string()), Some("message".to_string()),
ty: Type::Text, "TEXT".to_string(),
ty_str: "TEXT".to_string(), None,
primary_key: false, Type::Text,
is_rowid_alias: false, None,
notnull: false, false,
default: None, false,
unique: false, false,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_integer(
name: Some("level".to_string()), Some("level".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: false, ),
is_rowid_alias: false, SchemaColumn::new_default_integer(
notnull: false, Some("timestamp".to_string()),
default: None, "INTEGER".to_string(),
unique: false, None,
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,
},
], ],
has_rowid: true, // Has implicit rowid but no alias has_rowid: true, // Has implicit rowid but no alias
is_strict: false, is_strict: false,

View File

@@ -2838,7 +2838,7 @@ impl Statement {
.table_references .table_references
.find_table_by_internal_id(*table)?; .find_table_by_internal_id(*table)?;
let table_column = table_ref.get_column_at(*column_idx)?; 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::Integer => Some("INTEGER".to_string()),
crate::schema::Type::Real => Some("REAL".to_string()), crate::schema::Type::Real => Some("REAL".to_string()),
crate::schema::Type::Text => Some("TEXT".to_string()), crate::schema::Type::Text => Some("TEXT".to_string()),

View File

@@ -7,8 +7,9 @@ use crate::translate::expr::{
use crate::translate::index::{resolve_index_method_parameters, resolve_sorted_columns}; use crate::translate::index::{resolve_index_method_parameters, resolve_sorted_columns};
use crate::translate::planner::ROWID_STRS; use crate::translate::planner::ROWID_STRS;
use parking_lot::RwLock; use parking_lot::RwLock;
use turso_macros::AtomicEnum;
#[derive(Debug, Clone)] #[derive(Debug, Clone, AtomicEnum)]
pub enum ViewState { pub enum ViewState {
Ready, Ready,
InProgress, InProgress,
@@ -21,7 +22,7 @@ pub struct View {
pub sql: String, pub sql: String,
pub select_stmt: ast::Select, pub select_stmt: ast::Select,
pub columns: Vec<Column>, pub columns: Vec<Column>,
pub state: Mutex<ViewState>, pub state: AtomicViewState,
} }
impl View { impl View {
@@ -31,28 +32,28 @@ impl View {
sql, sql,
select_stmt, select_stmt,
columns, columns,
state: Mutex::new(ViewState::Ready), state: AtomicViewState::new(ViewState::Ready),
} }
} }
pub fn process(&self) -> Result<()> { pub fn process(&self) -> Result<()> {
let mut state = self.state.lock().unwrap(); let state = self.state.get();
match *state { match state {
ViewState::InProgress => { ViewState::InProgress => {
bail_parse_error!("view {} is circularly defined", self.name) bail_parse_error!("view {} is circularly defined", self.name)
} }
ViewState::Ready => { ViewState::Ready => {
*state = ViewState::InProgress; self.state.set(ViewState::InProgress);
Ok(()) Ok(())
} }
} }
} }
pub fn done(&self) { pub fn done(&self) {
let mut state = self.state.lock().unwrap(); let state = self.state.get();
match *state { match state {
ViewState::InProgress => { ViewState::InProgress => {
*state = ViewState::Ready; self.state.set(ViewState::Ready);
} }
ViewState::Ready => {} ViewState::Ready => {}
} }
@@ -66,7 +67,7 @@ impl Clone for View {
sql: self.sql.clone(), sql: self.sql.clone(),
select_stmt: self.select_stmt.clone(), select_stmt: self.select_stmt.clone(),
columns: self.columns.clone(), columns: self.columns.clone(),
state: Mutex::new(ViewState::Ready), state: AtomicViewState::new(ViewState::Ready),
} }
} }
} }
@@ -545,8 +546,8 @@ impl Schema {
table.name table.name
))); )));
}; };
if column.primary_key && unique_set.is_primary_key { if column.primary_key() && unique_set.is_primary_key {
if column.is_rowid_alias { if column.is_rowid_alias() {
// rowid alias, no index needed // rowid alias, no index needed
continue; continue;
} }
@@ -950,7 +951,7 @@ impl Schema {
let pk_name = &parent_tbl.primary_key_columns[0].0; let pk_name = &parent_tbl.primary_key_columns[0].0;
// rowid or alias INTEGER PRIMARY KEY; either is ok implicitly // rowid or alias INTEGER PRIMARY KEY; either is ok implicitly
parent_tbl.columns.iter().any(|c| { parent_tbl.columns.iter().any(|c| {
c.is_rowid_alias c.is_rowid_alias()
&& c.name && c.name
.as_deref() .as_deref()
.is_some_and(|n| n.eq_ignore_ascii_case(pk_name)) .is_some_and(|n| n.eq_ignore_ascii_case(pk_name))
@@ -1056,7 +1057,7 @@ impl Schema {
let c = parent_cols[0].as_str(); let c = parent_cols[0].as_str();
ROWID_STRS.iter().any(|&r| r.eq_ignore_ascii_case(c)) ROWID_STRS.iter().any(|&r| r.eq_ignore_ascii_case(c))
|| parent_tbl.columns.iter().any(|col| { || parent_tbl.columns.iter().any(|col| {
col.is_rowid_alias col.is_rowid_alias()
&& col && col
.name .name
.as_deref() .as_deref()
@@ -1325,7 +1326,7 @@ impl BTreeTable {
pub fn get_rowid_alias_column(&self) -> Option<(usize, &Column)> { pub fn get_rowid_alias_column(&self) -> Option<(usize, &Column)> {
if self.primary_key_columns.len() == 1 { if self.primary_key_columns.len() == 1 {
let (idx, col) = self.get_column(&self.primary_key_columns[0].0)?; 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)); return Some((idx, col));
} }
} }
@@ -1384,14 +1385,14 @@ impl BTreeTable {
sql.push(' '); sql.push(' ');
sql.push_str(&column.ty_str); sql.push_str(&column.ty_str);
} }
if column.notnull { if column.notnull() {
sql.push_str(" NOT NULL"); sql.push_str(" NOT NULL");
} }
if column.unique { if column.unique() {
sql.push_str(" UNIQUE"); sql.push_str(" UNIQUE");
} }
if needs_pk_inline && column.primary_key { if needs_pk_inline && column.primary_key() {
sql.push_str(" PRIMARY KEY"); sql.push_str(" PRIMARY KEY");
} }
@@ -1462,8 +1463,11 @@ impl BTreeTable {
sql sql
} }
pub fn column_collations(&self) -> Vec<Option<CollationSeq>> { pub fn column_collations(&self) -> Vec<CollationSeq> {
self.columns.iter().map(|column| column.collation).collect() 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; primary_key = true;
} }
cols.push(Column { cols.push(Column::new(
name: Some(normalize_ident(&name)), Some(normalize_ident(&name)),
ty,
ty_str, ty_str,
primary_key,
is_rowid_alias: typename_exactly_integer
&& primary_key
&& !primary_key_desc_columns_constraint,
notnull,
default, default,
unique, ty,
collation, collation,
hidden: false, primary_key,
}); typename_exactly_integer && primary_key && !primary_key_desc_columns_constraint,
notnull,
unique,
false,
));
} }
if options.contains(TableOptions::WITHOUT_ROWID) { if options.contains(TableOptions::WITHOUT_ROWID) {
has_rowid = false; 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 // or if the table has no rowid
if !has_rowid || primary_key_columns.len() > 1 { if !has_rowid || primary_key_columns.len() > 1 {
for col in cols.iter_mut() { 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)); let pk_col = cols.iter().find(|c| c.name.as_deref() == Some(pk_col_name));
if let Some(col) = pk_col { 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"); crate::bail_parse_error!("AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY");
} }
} }
} }
for col in cols.iter() { 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. // 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)) // 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| { let unique_set_w_only_rowid_alias = unique_sets.iter().position(|us| {
@@ -2025,7 +2027,7 @@ impl ResolvedFkRef {
.columns .columns
.iter() .iter()
.enumerate() .enumerate()
.find(|(_, c)| c.is_rowid_alias) .find(|(_, c)| c.is_rowid_alias())
{ {
return updated_parent_positions.contains(&idx); 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 // special case: if FK uses a rowid alias on child, and rowid changed
if self.child_cols.len() == 1 { if self.child_cols.len() == 1 {
let (i, col) = child_tbl.get_column(&self.child_cols[0]).unwrap(); 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; return true;
} }
} }
@@ -2064,22 +2066,194 @@ impl ResolvedFkRef {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Column { pub struct Column {
pub name: Option<String>, pub name: Option<String>,
pub ty: Type,
// many sqlite operations like table_info retain the original string
pub ty_str: String, pub ty_str: String,
pub primary_key: bool,
pub is_rowid_alias: bool,
pub notnull: bool,
pub default: Option<Box<Expr>>, pub default: Option<Box<Expr>>,
pub unique: bool, raw: u16,
pub collation: Option<CollationSeq>,
pub hidden: bool,
} }
// 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 { impl Column {
pub fn affinity(&self) -> Affinity { pub fn affinity(&self) -> Affinity {
affinity(&self.ty_str) affinity(&self.ty_str)
} }
pub const fn new_default_text(
name: Option<String>,
ty_str: String,
default: Option<Box<Expr>>,
) -> Self {
Self::new(
name,
ty_str,
default,
Type::Text,
None,
false,
false,
false,
false,
false,
)
}
pub const fn new_default_integer(
name: Option<String>,
ty_str: String,
default: Option<Box<Expr>>,
) -> 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<String>,
ty_str: String,
default: Option<Box<Expr>>,
ty: Type,
col: Option<CollationSeq>,
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<CollationSeq> {
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<CollationSeq>) {
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 // 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"); let hidden = ty_str.contains("HIDDEN");
Column { Column::new(
name: Some(normalize_ident(name)), Some(normalize_ident(name)),
ty,
default,
notnull,
ty_str, ty_str,
primary_key, default,
is_rowid_alias: primary_key && matches!(ty, Type::Integer), ty,
unique,
collation, collation,
primary_key,
primary_key && matches!(ty, Type::Integer),
notnull,
unique,
hidden, hidden,
} )
} }
} }
@@ -2181,14 +2355,30 @@ pub fn affinity(datatype: &str) -> Affinity {
Affinity::Numeric Affinity::Numeric
} }
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum Type { pub enum Type {
Null, Null = 0,
Text, Text = 1,
Numeric, Numeric = 2,
Integer, Integer = 3,
Real, Real = 4,
Blob, 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 /// # SQLite Column Type Affinities
@@ -2341,66 +2531,11 @@ pub fn sqlite_schema_table() -> BTreeTable {
has_autoincrement: false, has_autoincrement: false,
primary_key_columns: vec![], primary_key_columns: vec![],
columns: vec![ columns: vec![
Column { Column::new_default_text(Some("type".to_string()), "TEXT".to_string(), None),
name: Some("type".to_string()), Column::new_default_text(Some("name".to_string()), "TEXT".to_string(), None),
ty: Type::Text, Column::new_default_text(Some("tbl_name".to_string()), "TEXT".to_string(), None),
ty_str: "TEXT".to_string(), Column::new_default_integer(Some("rootpage".to_string()), "INT".to_string(), None),
primary_key: false, Column::new_default_text(Some("sql".to_string()), "TEXT".to_string(), None),
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,
},
], ],
foreign_keys: vec![], foreign_keys: vec![],
unique_sets: vec![], unique_sets: vec![],
@@ -2540,7 +2675,7 @@ impl Index {
name: normalize_ident(col_name), name: normalize_ident(col_name),
order: *order, order: *order,
pos_in_table, pos_in_table,
collation: column.collation, collation: column.collation_opt(),
default: column.default.clone(), default: column.default.clone(),
}); });
} }
@@ -2579,7 +2714,7 @@ impl Index {
name: normalize_ident(col.name.as_ref().unwrap()), name: normalize_ident(col.name.as_ref().unwrap()),
order: *sort_order, order: *sort_order,
pos_in_table: *pos_in_table, pos_in_table: *pos_in_table,
collation: col.collation, collation: col.collation_opt(),
default: col.default.clone(), default: col.default.clone(),
}) })
}) })
@@ -2741,7 +2876,7 @@ mod tests {
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; let column = table.get_column("a").unwrap().1;
assert!( assert!(
!column.is_rowid_alias, !column.is_rowid_alias(),
"column 'a´ has type different than INTEGER so can't be a rowid alias" "column 'a´ has type different than INTEGER so can't be a rowid alias"
); );
Ok(()) Ok(())
@@ -2752,7 +2887,10 @@ mod tests {
let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);"#; let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);"#;
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; 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(()) Ok(())
} }
@@ -2762,7 +2900,10 @@ mod tests {
let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a));"#; let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a));"#;
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; 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(()) Ok(())
} }
@@ -2773,7 +2914,7 @@ mod tests {
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; let column = table.get_column("a").unwrap().1;
assert!( assert!(
!column.is_rowid_alias, !column.is_rowid_alias(),
"column 'a´ shouldn't be a rowid alias because table has no rowid" "column 'a´ shouldn't be a rowid alias because table has no rowid"
); );
Ok(()) Ok(())
@@ -2785,7 +2926,7 @@ mod tests {
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; let column = table.get_column("a").unwrap().1;
assert!( assert!(
!column.is_rowid_alias, !column.is_rowid_alias(),
"column 'a´ shouldn't be a rowid alias because table has no rowid" "column 'a´ shouldn't be a rowid alias because table has no rowid"
); );
Ok(()) Ok(())
@@ -2808,7 +2949,7 @@ mod tests {
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; let column = table.get_column("a").unwrap().1;
assert!( assert!(
!column.is_rowid_alias, !column.is_rowid_alias(),
"column 'a´ shouldn't be a rowid alias because table has composite primary key" "column 'a´ shouldn't be a rowid alias because table has composite primary key"
); );
Ok(()) Ok(())
@@ -2819,11 +2960,17 @@ mod tests {
let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT, c REAL);"#; let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT, c REAL);"#;
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; 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; 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; 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!( assert_eq!(
vec![("a".to_string(), SortOrder::Asc)], vec![("a".to_string(), SortOrder::Asc)],
table.primary_key_columns, 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 sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY(a desc));"#;
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; 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; 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; 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!( assert_eq!(
vec![("a".to_string(), SortOrder::Desc)], vec![("a".to_string(), SortOrder::Desc)],
table.primary_key_columns, 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 sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY(a, b desc));"#;
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; 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; 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; 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!( assert_eq!(
vec![ vec![
("a".to_string(), SortOrder::Asc), ("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 sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY('a'));"#;
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; 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; 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; 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!( assert_eq!(
vec![("a".to_string(), SortOrder::Asc)], vec![("a".to_string(), SortOrder::Asc)],
table.primary_key_columns, 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 sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, c REAL, PRIMARY KEY("a"));"#;
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; 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; 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; 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!( assert_eq!(
vec![("a".to_string(), SortOrder::Asc)], vec![("a".to_string(), SortOrder::Asc)],
table.primary_key_columns, table.primary_key_columns,
@@ -2932,7 +3100,7 @@ mod tests {
let sql = r#"CREATE TABLE t1 (a INTEGER NOT NULL);"#; let sql = r#"CREATE TABLE t1 (a INTEGER NOT NULL);"#;
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; let column = table.get_column("a").unwrap().1;
assert!(column.notnull); assert!(column.notnull());
Ok(()) Ok(())
} }
@@ -2941,7 +3109,7 @@ mod tests {
let sql = r#"CREATE TABLE t1 (a INTEGER);"#; let sql = r#"CREATE TABLE t1 (a INTEGER);"#;
let table = BTreeTable::from_sql(sql, 0)?; let table = BTreeTable::from_sql(sql, 0)?;
let column = table.get_column("a").unwrap().1; let column = table.get_column("a").unwrap().1;
assert!(!column.notnull); assert!(!column.notnull());
Ok(()) Ok(())
} }
@@ -3029,18 +3197,11 @@ mod tests {
is_strict: false, is_strict: false,
has_autoincrement: false, has_autoincrement: false,
primary_key_columns: vec![("nonexistent".to_string(), SortOrder::Asc)], primary_key_columns: vec![("nonexistent".to_string(), SortOrder::Asc)],
columns: vec![Column { columns: vec![Column::new_default_integer(
name: Some("a".to_string()), Some("a".to_string()),
ty: Type::Integer, "INT".to_string(),
ty_str: "INT".to_string(), None,
primary_key: false, )],
is_rowid_alias: false,
notnull: false,
default: None,
unique: false,
collation: None,
hidden: false,
}],
unique_sets: vec![], unique_sets: vec![],
foreign_keys: vec![], foreign_keys: vec![],
}; };

View File

@@ -109,10 +109,10 @@ fn emit_collseq_if_needed(
if let ast::Expr::Column { table, column, .. } = expr { 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_ref)) = referenced_tables.find_table_by_internal_id(*table) {
if let Some(table_column) = table_ref.get_column_at(*column) { 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 { program.emit_insn(Insn::CollSeq {
reg: None, reg: None,
collation: *collation, collation: c,
}); });
} }
} }

View File

@@ -110,13 +110,13 @@ pub fn translate_alter_table(
// The column is used in the expression of a generated column. // The column is used in the expression of a generated column.
// The column appears in a trigger or view. // The column appears in a trigger or view.
if column.primary_key { if column.primary_key() {
return Err(LimboError::ParseError(format!( return Err(LimboError::ParseError(format!(
"cannot drop column \"{column_name}\": PRIMARY KEY" "cannot drop column \"{column_name}\": PRIMARY KEY"
))); )));
} }
if column.unique if column.unique()
|| btree.unique_sets.iter().any(|set| { || btree.unique_sets.iter().any(|set| {
set.columns set.columns
.iter() .iter()

View File

@@ -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"); bail_parse_error!("ANALYZE on tables with primary key is not supported");
} }
if !target_btree.has_rowid { if !target_btree.has_rowid {

View File

@@ -19,14 +19,13 @@ use crate::{
/// **Pre defined collation sequences**\ /// **Pre defined collation sequences**\
/// Collating functions only matter when comparing string values. /// Collating functions only matter when comparing string values.
/// Numeric values are always compared numerically, and BLOBs are always compared byte-by-byte using memcmp(). /// Numeric values are always compared numerically, and BLOBs are always compared byte-by-byte using memcmp().
#[repr(u8)]
pub enum CollationSeq { pub enum CollationSeq {
/// Standard String compare Unset = 0,
#[default] #[default]
Binary, Binary = 1,
/// Ascii case insensitive NoCase = 2,
NoCase, Rtrim = 3,
/// Same as Binary but with trimmed whitespace
Rtrim,
} }
impl CollationSeq { impl CollationSeq {
@@ -35,11 +34,20 @@ impl CollationSeq {
crate::LimboError::ParseError(format!("no such collation sequence: {collation}")) 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)] #[inline(always)]
pub fn compare_strings(&self, lhs: &str, rhs: &str) -> Ordering { pub fn compare_strings(&self, lhs: &str, rhs: &str) -> Ordering {
match self { 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::NoCase => Self::nocase_cmp(lhs, rhs),
CollationSeq::Rtrim => Self::rtrim_cmp(lhs, rhs), CollationSeq::Rtrim => Self::rtrim_cmp(lhs, rhs),
} }
@@ -112,7 +120,7 @@ pub fn get_collseq_from_expr(
.get_column_at(*column) .get_column_at(*column)
.ok_or_else(|| crate::LimboError::ParseError("column not found".to_string()))?; .ok_or_else(|| crate::LimboError::ParseError("column not found".to_string()))?;
if maybe_column_collseq.is_none() { if maybe_column_collseq.is_none() {
maybe_column_collseq = column.collation; maybe_column_collseq = column.collation_opt();
} }
return Ok(WalkControl::Continue); return Ok(WalkControl::Continue);
} }
@@ -123,7 +131,7 @@ pub fn get_collseq_from_expr(
if let Some(btree) = table_ref.btree() { if let Some(btree) = table_ref.btree() {
if let Some((_, rowid_alias_col)) = btree.get_rowid_alias_column() { if let Some((_, rowid_alias_col)) = btree.get_rowid_alias_column() {
if maybe_column_collseq.is_none() { 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, is_strict: false,
name: "foo".to_string(), name: "foo".to_string(),
primary_key_columns: vec![], primary_key_columns: vec![],
columns: vec![Column { columns: vec![Column::new(
name: Some("foo".to_string()), Some("foo".to_string()),
ty: Type::Text, "text".to_string(),
ty_str: "text".to_string(), None,
primary_key: false, Type::Text,
is_rowid_alias: false,
notnull: false,
default: None,
unique: false,
collation, collation,
hidden: false, false,
}], false,
false,
false,
false,
)],
unique_sets: vec![], unique_sets: vec![],
foreign_keys: vec![], foreign_keys: vec![],
})), })),
@@ -403,18 +411,18 @@ mod tests {
is_strict: false, is_strict: false,
name: "t1".to_string(), name: "t1".to_string(),
primary_key_columns: vec![], primary_key_columns: vec![],
columns: vec![Column { columns: vec![Column::new(
name: Some("a".to_string()), Some("a".to_string()),
ty: Type::Text, "text".to_string(),
ty_str: "text".to_string(), None,
primary_key: false, Type::Text,
is_rowid_alias: false, left,
notnull: false, false,
default: None, false,
unique: false, false,
collation: left, false,
hidden: false, false,
}], )],
unique_sets: vec![], unique_sets: vec![],
foreign_keys: vec![], foreign_keys: vec![],
})), })),
@@ -437,18 +445,18 @@ mod tests {
is_strict: false, is_strict: false,
name: "t2".to_string(), name: "t2".to_string(),
primary_key_columns: vec![], primary_key_columns: vec![],
columns: vec![Column { columns: vec![Column::new(
name: Some("b".to_string()), Some("b".to_string()),
ty: Type::Text, "text".to_string(),
ty_str: "text".to_string(), None,
primary_key: false, Type::Text,
is_rowid_alias: false, right,
notnull: false, false,
default: None, false,
unique: false, false,
collation: right, false,
hidden: false, false,
}], )],
unique_sets: vec![], unique_sets: vec![],
foreign_keys: vec![], foreign_keys: vec![],
})), })),
@@ -478,18 +486,18 @@ mod tests {
is_strict: false, is_strict: false,
name: "bar".to_string(), name: "bar".to_string(),
primary_key_columns: vec![("id".to_string(), SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), SortOrder::Asc)],
columns: vec![Column { columns: vec![Column::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true,
notnull: false,
default: None,
unique: true,
collation, collation,
hidden: false, true,
}], true,
false,
true,
false,
)],
unique_sets: vec![], unique_sets: vec![],
foreign_keys: vec![], foreign_keys: vec![],
})), })),

View File

@@ -544,7 +544,7 @@ pub fn emit_fk_child_decrement_on_delete(
let null_skip = program.allocate_label(); let null_skip = program.allocate_label();
for cname in &fk_ref.child_cols { for cname in &fk_ref.child_cols {
let (pos, col) = child_tbl.get_column(cname).unwrap(); 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 child_rowid_reg
} else { } else {
let tmp = program.alloc_register(); 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 pcur = open_read_table(program, &parent_tbl);
let (pos, col) = child_tbl.get_column(&fk_ref.child_cols[0]).unwrap(); 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 child_rowid_reg
} else { } else {
let tmp = program.alloc_register(); let tmp = program.alloc_register();
@@ -624,7 +624,7 @@ pub fn emit_fk_child_decrement_on_delete(
let probe = program.alloc_registers(n); let probe = program.alloc_registers(n);
for (i, cname) in fk_ref.child_cols.iter().enumerate() { for (i, cname) in fk_ref.child_cols.iter().enumerate() {
let (pos, col) = child_tbl.get_column(cname).unwrap(); 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 child_rowid_reg
} else { } else {
let r = program.alloc_register(); let r = program.alloc_register();
@@ -1136,7 +1136,7 @@ fn emit_update_insns(
.table .table
.columns() .columns()
.iter() .iter()
.position(|c| c.is_rowid_alias); .position(|c| c.is_rowid_alias());
let has_direct_rowid_update = plan let has_direct_rowid_update = plan
.set_clauses .set_clauses
@@ -1232,7 +1232,7 @@ fn emit_update_insns(
continue; continue;
} }
if has_user_provided_rowid 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 && !is_virtual
{ {
let rowid_set_clause_reg = rowid_set_clause_reg.unwrap(); let rowid_set_clause_reg = rowid_set_clause_reg.unwrap();
@@ -1257,7 +1257,7 @@ fn emit_update_insns(
target_reg, target_reg,
&t_ctx.resolver, &t_ctx.resolver,
)?; )?;
if table_column.notnull { if table_column.notnull() {
use crate::error::SQLITE_CONSTRAINT_NOTNULL; use crate::error::SQLITE_CONSTRAINT_NOTNULL;
program.emit_insn(Insn::HaltIfNull { program.emit_insn(Insn::HaltIfNull {
target_reg, target_reg,
@@ -1303,7 +1303,7 @@ fn emit_update_insns(
// don't emit null for pkey of virtual tables. they require first two args // don't emit null for pkey of virtual tables. they require first two args
// before the 'record' to be explicitly non-null // 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); program.emit_null(target_reg, None);
} else if is_virtual { } else if is_virtual {
program.emit_insn(Insn::VColumn { program.emit_insn(Insn::VColumn {
@@ -1511,7 +1511,7 @@ fn emit_update_insns(
.get(col.pos_in_table) .get(col.pos_in_table)
.expect("column index out of bounds"); .expect("column index out of bounds");
program.emit_insn(Insn::Copy { 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 rowid_reg
} else { } else {
start + col.pos_in_table start + col.pos_in_table
@@ -1892,7 +1892,7 @@ pub fn emit_cdc_patch_record(
rowid_reg: usize, rowid_reg: usize,
) -> usize { ) -> usize {
let columns = table.columns(); 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 { if let Some(rowid_alias_position) = rowid_alias_position {
let record_reg = program.alloc_register(); let record_reg = program.alloc_register();
program.emit_insn(Insn::Copy { program.emit_insn(Insn::Copy {
@@ -1927,7 +1927,7 @@ pub fn emit_cdc_full_record(
) -> usize { ) -> usize {
let columns_reg = program.alloc_registers(columns.len() + 1); let columns_reg = program.alloc_registers(columns.len() + 1);
for (i, column) in columns.iter().enumerate() { for (i, column) in columns.iter().enumerate() {
if column.is_rowid_alias { if column.is_rowid_alias() {
program.emit_insn(Insn::Copy { program.emit_insn(Insn::Copy {
src_reg: rowid_reg, src_reg: rowid_reg,
dst_reg: columns_reg + 1 + i, dst_reg: columns_reg + 1 + i,
@@ -2181,7 +2181,7 @@ fn rewrite_where_for_update_registers(
.as_ref() .as_ref()
.is_some_and(|n| n.eq_ignore_ascii_case(&normalized)) .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); *e = Expr::Register(rowid_reg);
} else { } else {
*e = Expr::Register(columns_start_reg + idx); *e = Expr::Register(columns_start_reg + idx);
@@ -2200,7 +2200,7 @@ fn rewrite_where_for_update_registers(
.as_ref() .as_ref()
.is_some_and(|n| n.eq_ignore_ascii_case(&normalized)) .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); *e = Expr::Register(rowid_reg);
} else { } else {
*e = Expr::Register(columns_start_reg + idx); *e = Expr::Register(columns_start_reg + idx);

View File

@@ -2133,7 +2133,7 @@ pub fn translate_expr(
crate::bail_parse_error!("column index out of bounds"); crate::bail_parse_error!("column index out of bounds");
}; };
// Counter intuitive but a column always needs to have a collation // 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 // 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 { let Some(column) = table.get_column_at(*column) else {
crate::bail_parse_error!("column index out of bounds"); 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) Ok(target_register)
} }
@@ -3701,7 +3701,7 @@ pub fn bind_and_rewrite_expr<'a>(
match_result = Some(( match_result = Some((
joined_table.internal_id, joined_table.internal_id,
col_idx.unwrap(), col_idx.unwrap(),
col.is_rowid_alias, col.is_rowid_alias(),
)); ));
} }
// only if we haven't found a match, check for explicit rowid reference // 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(( match_result = Some((
outer_ref.internal_id, outer_ref.internal_id,
col_idx.unwrap(), 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 database: None, // TODO: support different databases
table: tbl_id, table: tbl_id,
column: col_idx, column: col_idx,
is_rowid_alias: col.is_rowid_alias, is_rowid_alias: col.is_rowid_alias(),
}; };
tracing::debug!("rewritten to column"); tracing::debug!("rewritten to column");
referenced_tables.mark_column_used(tbl_id, col_idx); 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(); let col = table.columns().get(col_idx).unwrap();
// Check if this is a rowid alias // 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, // Convert to Column expression - since this is a cross-database reference,
// we need to create a synthetic table reference for it // we need to create a synthetic table reference for it

View File

@@ -245,7 +245,7 @@ pub fn stabilize_new_row_for_fk(
.get_column(pk_name) .get_column(pk_name)
.ok_or_else(|| crate::LimboError::InternalError(format!("pk col {pk_name} missing")))?; .ok_or_else(|| crate::LimboError::InternalError(format!("pk col {pk_name} missing")))?;
if !set_cols.contains(&pos) { if !set_cols.contains(&pos) {
if col.is_rowid_alias { if col.is_rowid_alias() {
program.emit_insn(Insn::Copy { program.emit_insn(Insn::Copy {
src_reg: rowid_new_reg, src_reg: rowid_new_reg,
dst_reg: start + pos, 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() { for (i, index_col) in index.columns.iter().enumerate() {
let pos_in_table = index_col.pos_in_table; let pos_in_table = index_col.pos_in_table;
let column = &table_btree.columns[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 { program.emit_insn(Insn::Copy {
src_reg: old_rowid_reg, src_reg: old_rowid_reg,
dst_reg: old_key + i, 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() { for (i, index_col) in index.columns.iter().enumerate() {
let pos_in_table = index_col.pos_in_table; let pos_in_table = index_col.pos_in_table;
let column = &table_btree.columns[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 new_rowid_reg
} else { } else {
new_values_start + pos_in_table new_values_start + pos_in_table
@@ -565,7 +565,7 @@ fn build_parent_key(
let (pos, col) = parent_bt let (pos, col) = parent_bt
.get_column(pcol) .get_column(pcol)
.ok_or_else(|| crate::LimboError::InternalError(format!("col {pcol} missing")))?; .ok_or_else(|| crate::LimboError::InternalError(format!("col {pcol} missing")))?;
if col.is_rowid_alias { if col.is_rowid_alias() {
parent_rowid_reg parent_rowid_reg
} else { } else {
program.emit_insn(Insn::Column { program.emit_insn(Insn::Column {
@@ -716,7 +716,7 @@ pub fn emit_fk_child_update_counters(
let fk_ok = program.allocate_label(); let fk_ok = program.allocate_label();
for cname in &fk_ref.fk.child_columns { for cname in &fk_ref.fk.child_columns {
let (i, col) = child_tbl.get_column(cname).unwrap(); 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 new_rowid_reg
} else { } else {
new_start_reg + i new_start_reg + i
@@ -736,7 +736,7 @@ pub fn emit_fk_child_update_counters(
// Take the first child column value from NEW image // 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 (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 new_rowid_reg
} else { } else {
new_start_reg + i_child 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() { for (k, cname) in fk_ref.child_cols.iter().enumerate() {
let (i, col) = child_tbl.get_column(cname).unwrap(); let (i, col) = child_tbl.get_column(cname).unwrap();
program.emit_insn(Insn::Copy { program.emit_insn(Insn::Copy {
src_reg: if col.is_rowid_alias { src_reg: if col.is_rowid_alias() {
new_rowid_reg new_rowid_reg
} else { } else {
new_start_reg + i new_start_reg + i

View File

@@ -522,7 +522,7 @@ pub fn resolve_sorted_columns(
name: col.1.name.as_ref().unwrap().clone(), name: col.1.name.as_ref().unwrap().clone(),
order: sc.order.unwrap_or(SortOrder::Asc), order: sc.order.unwrap_or(SortOrder::Asc),
pos_in_table: col.0, pos_in_table: col.0,
collation: col.1.collation, collation: col.1.collation_opt(),
default: col.1.default.clone(), default: col.1.default.clone(),
}); });
} }

View File

@@ -858,10 +858,10 @@ fn emit_notnulls(program: &mut ProgramBuilder, ctx: &InsertEmitCtx, insertion: &
for column_mapping in insertion for column_mapping in insertion
.col_mappings .col_mappings
.iter() .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 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; continue;
} }
program.emit_insn(Insn::HaltIfNull { program.emit_insn(Insn::HaltIfNull {
@@ -918,7 +918,7 @@ fn bind_insert(
values = table values = table
.columns() .columns()
.iter() .iter()
.filter(|c| !c.hidden) .filter(|c| !c.hidden())
.map(|c| { .map(|c| {
c.default c.default
.clone() .clone()
@@ -1133,7 +1133,7 @@ fn init_source_emission<'a>(
ctx.table ctx.table
.columns .columns
.iter() .iter()
.filter(|col| !col.hidden) .filter(|col| !col.hidden())
.map(|col| col.affinity().aff_mask()) .map(|col| col.affinity().aff_mask())
.collect::<String>() .collect::<String>()
} else { } else {
@@ -1237,18 +1237,18 @@ pub struct AutoincMeta {
table_name_reg: usize, table_name_reg: usize,
} }
pub const ROWID_COLUMN: &Column = &Column { pub const ROWID_COLUMN: Column = Column::new(
name: None, None, // name
ty: schema::Type::Integer, String::new(), // type string
ty_str: String::new(), None, // default
primary_key: true, schema::Type::Integer,
is_rowid_alias: true, None,
notnull: true, true, // primary key
default: None, true, // rowid alias
unique: false, true, // notnull
collation: None, false, // unique
hidden: false, false, // hidden
}; );
/// Represents how a table should be populated during an INSERT. /// Represents how a table should be populated during an INSERT.
#[derive(Debug)] #[derive(Debug)]
@@ -1389,7 +1389,7 @@ fn build_insertion<'a>(
if columns.is_empty() { if columns.is_empty() {
// Case 1: No columns specified - map values to columns in order // 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!( crate::bail_parse_error!(
"table {} has {} columns but {} values were supplied", "table {} has {} columns but {} values were supplied",
&table.get_name(), &table.get_name(),
@@ -1399,11 +1399,11 @@ fn build_insertion<'a>(
} }
let mut value_idx = 0; let mut value_idx = 0;
for (i, col) in table_columns.iter().enumerate() { for (i, col) in table_columns.iter().enumerate() {
if col.hidden { if col.hidden() {
// Hidden columns are not taken into account. // Hidden columns are not taken into account.
continue; continue;
} }
if col.is_rowid_alias { if col.is_rowid_alias() {
insertion_key = InsertionKey::RowidAlias(ColMapping { insertion_key = InsertionKey::RowidAlias(ColMapping {
column: col, column: col,
value_index: Some(value_idx), value_index: Some(value_idx),
@@ -1421,7 +1421,7 @@ fn build_insertion<'a>(
let column_name = normalize_ident(column_name.as_str()); 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) { if let Some((idx_in_table, col_in_table)) = table.get_column_by_name(&column_name) {
// Named column // Named column
if col_in_table.is_rowid_alias { if col_in_table.is_rowid_alias() {
insertion_key = InsertionKey::RowidAlias(ColMapping { insertion_key = InsertionKey::RowidAlias(ColMapping {
column: col_in_table, column: col_in_table,
value_index: Some(value_index), value_index: Some(value_index),
@@ -1435,7 +1435,7 @@ fn build_insertion<'a>(
.any(|s| s.eq_ignore_ascii_case(&column_name)) .any(|s| s.eq_ignore_ascii_case(&column_name))
{ {
// Explicit use of the 'rowid' keyword // 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 { insertion_key = InsertionKey::RowidAlias(ColMapping {
column: col_in_table, column: col_in_table,
value_index: Some(value_index), value_index: Some(value_index),
@@ -1574,7 +1574,7 @@ fn translate_key(
register, register,
} => translate_column( } => translate_column(
program, program,
ROWID_COLUMN, &ROWID_COLUMN,
*register, *register,
*value_index, *value_index,
&mut translate_value_fn, &mut translate_value_fn,
@@ -1594,13 +1594,13 @@ fn translate_column(
) -> Result<()> { ) -> Result<()> {
if let Some(value_index) = value_index { if let Some(value_index) = value_index {
translate_value_fn(program, value_index, column_register)?; 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, // Although a non-NULL integer key is used for the insertion key,
// the rowid alias column is emitted as NULL. // the rowid alias column is emitted as NULL.
program.emit_insn(Insn::SoftNull { program.emit_insn(Insn::SoftNull {
reg: column_register, reg: column_register,
}); });
} else if column.hidden { } else if column.hidden() {
// Emit NULL for not-explicitly-mentioned hidden columns, even ignoring DEFAULT. // Emit NULL for not-explicitly-mentioned hidden columns, even ignoring DEFAULT.
program.emit_insn(Insn::Null { program.emit_insn(Insn::Null {
dest: column_register, dest: column_register,
@@ -1609,7 +1609,7 @@ fn translate_column(
} else if let Some(default_expr) = column.default.as_ref() { } else if let Some(default_expr) = column.default.as_ref() {
translate_expr(program, None, default_expr, column_register, resolver)?; translate_expr(program, None, default_expr, column_register, resolver)?;
} else { } else {
let nullable = !column.notnull && !column.primary_key && !column.unique; let nullable = !column.notnull() && !column.primary_key() && !column.unique();
if !nullable { if !nullable {
crate::bail_parse_error!( crate::bail_parse_error!(
"column {} is not nullable", "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)) { if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(name)) {
Some(insertion.key_register()) Some(insertion.key_register())
} else if let Some(c) = insertion.get_col_mapping_by_name(name) { } 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()) Some(insertion.key_register())
} else { } else {
Some(c.register) Some(c.register)
@@ -2231,7 +2231,7 @@ pub fn emit_fk_child_insert_checks(
let fk_ok = program.allocate_label(); let fk_ok = program.allocate_label();
for cname in &fk_ref.child_cols { for cname in &fk_ref.child_cols {
let (i, col) = child_tbl.get_column(cname).unwrap(); 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 new_rowid_reg
} else { } else {
new_start_reg + i new_start_reg + i
@@ -2250,7 +2250,7 @@ pub fn emit_fk_child_insert_checks(
// first child col carries rowid // first child col carries rowid
let (i_child, col_child) = child_tbl.get_column(&fk_ref.child_cols[0]).unwrap(); 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 new_rowid_reg
} else { } else {
new_start_reg + i_child 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() { for (k, cname) in fk_ref.child_cols.iter().enumerate() {
let (i, col) = child_tbl.get_column(cname).unwrap(); let (i, col) = child_tbl.get_column(cname).unwrap();
program.emit_insn(Insn::Copy { program.emit_insn(Insn::Copy {
src_reg: if col.is_rowid_alias { src_reg: if col.is_rowid_alias() {
new_rowid_reg new_rowid_reg
} else { } else {
new_start_reg + i new_start_reg + i
@@ -2333,7 +2333,7 @@ pub fn emit_fk_child_insert_checks(
for (i, pname) in parent_cols.iter().enumerate() { for (i, pname) in parent_cols.iter().enumerate() {
let (pos, col) = child_tbl.get_column(pname).unwrap(); let (pos, col) = child_tbl.get_column(pname).unwrap();
program.emit_insn(Insn::Copy { program.emit_insn(Insn::Copy {
src_reg: if col.is_rowid_alias { src_reg: if col.is_rowid_alias() {
new_rowid_reg new_rowid_reg
} else { } else {
new_start_reg + pos new_start_reg + pos

View File

@@ -2273,7 +2273,7 @@ impl<'a> LogicalPlanBuilder<'a> {
if let Some(ref name) = col.name { if let Some(ref name) = col.name {
columns.push(ColumnInfo { columns.push(ColumnInfo {
name: name.clone(), name: name.clone(),
ty: col.ty, ty: col.ty(),
database: database.clone(), database: database.clone(),
table: Some(actual_table.clone()), table: Some(actual_table.clone()),
table_alias: alias.map(|s| s.to_string()), 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)], primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)],
foreign_keys: vec![], foreign_keys: vec![],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_text(Some("name".to_string()), "TEXT".to_string(), None),
name: Some("name".to_string()), SchemaColumn::new_default_integer(
ty: Type::Text, Some("age".to_string()),
ty_str: "TEXT".to_string(), "INTEGER".to_string(),
primary_key: false, None,
is_rowid_alias: false, ),
notnull: false, SchemaColumn::new_default_text(Some("email".to_string()), "TEXT".to_string(), None),
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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -2455,54 +2426,40 @@ mod tests {
root_page: 3, root_page: 3,
primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_integer(
name: Some("user_id".to_string()), Some("user_id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: false, ),
is_rowid_alias: false, SchemaColumn::new_default_text(
notnull: false, Some("product".to_string()),
default: None, "TEXT".to_string(),
unique: false, None,
collation: None, ),
hidden: false, SchemaColumn::new(
}, Some("amount".to_string()),
SchemaColumn { "REAL".to_string(),
name: Some("product".to_string()), None,
ty: Type::Text, Type::Real,
ty_str: "TEXT".to_string(), None,
primary_key: false, false,
is_rowid_alias: false, false,
notnull: false, false,
default: None, false,
unique: false, 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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,
@@ -2520,54 +2477,36 @@ mod tests {
root_page: 4, root_page: 4,
primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)], primary_key_columns: vec![("id".to_string(), turso_parser::ast::SortOrder::Asc)],
columns: vec![ columns: vec![
SchemaColumn { SchemaColumn::new(
name: Some("id".to_string()), Some("id".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: true, Type::Integer,
is_rowid_alias: true, None,
notnull: true, true,
default: None, true,
unique: false, true,
collation: None, false,
hidden: false, false,
}, ),
SchemaColumn { SchemaColumn::new_default_text(Some("name".to_string()), "TEXT".to_string(), None),
name: Some("name".to_string()), SchemaColumn::new(
ty: Type::Text, Some("price".to_string()),
ty_str: "TEXT".to_string(), "REAL".to_string(),
primary_key: false, None,
is_rowid_alias: false, Type::Real,
notnull: false, None,
default: None, false,
unique: false, false,
collation: None, false,
hidden: false, false,
}, false,
SchemaColumn { ),
name: Some("price".to_string()), SchemaColumn::new_default_integer(
ty: Type::Real, Some("product_id".to_string()),
ty_str: "REAL".to_string(), "INTEGER".to_string(),
primary_key: false, None,
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,
},
], ],
has_rowid: true, has_rowid: true,
is_strict: false, is_strict: false,

View File

@@ -1488,9 +1488,10 @@ fn emit_seek_termination(
affinity = if let Some(table_ref) = tables affinity = if let Some(table_ref) = tables
.joined_tables() .joined_tables()
.iter() .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()) Some(table_ref.columns()[rowid_col_idx].affinity())
} else { } else {
Some(Affinity::Numeric) Some(Affinity::Numeric)

View File

@@ -101,7 +101,7 @@ fn find_best_access_method_for_btree(
index: None, index: None,
constraint_refs: vec![], 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. // 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() { for candidate in rhs_constraints.candidates.iter() {

View File

@@ -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 { fn estimate_selectivity(column: &Column, op: ast::Operator) -> f64 {
match op { match op {
ast::Operator::Equals => { ast::Operator::Equals => {
if column.is_rowid_alias || column.primary_key { if column.is_rowid_alias() || column.primary_key() {
SELECTIVITY_UNIQUE_EQUALITY SELECTIVITY_UNIQUE_EQUALITY
} else { } else {
SELECTIVITY_EQ SELECTIVITY_EQ
@@ -197,7 +197,7 @@ pub fn constraints_from_where_clause(
let rowid_alias_column = table_reference let rowid_alias_column = table_reference
.columns() .columns()
.iter() .iter()
.position(|c| c.is_rowid_alias); .position(|c| c.is_rowid_alias());
let mut cs = TableConstraints { let mut cs = TableConstraints {
table_id: table_reference.internal_id, 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 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() { for (i, constraint) in cs.constraints.iter_mut().enumerate() {
let constrained_column = &table_reference.table.columns()[constraint.table_col_pos]; 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); let constraining_expr = constraint.get_constraining_expr_ref(where_clause);
// Index seek keys must use the same collation as the constrained column. // Index seek keys must use the same collation as the constrained column.
match get_collseq_from_expr(constraining_expr, table_references)? { match get_collseq_from_expr(constraining_expr, table_references)? {

View File

@@ -1670,18 +1670,18 @@ mod tests {
} }
fn _create_column(c: &TestColumn) -> Column { fn _create_column(c: &TestColumn) -> Column {
Column { Column::new(
name: Some(c.name.clone()), Some(c.name.clone()),
ty: c.ty, c.ty.to_string(),
ty_str: c.ty.to_string(), None,
is_rowid_alias: c.is_rowid_alias, c.ty,
primary_key: false, None,
notnull: false, c.is_rowid_alias,
default: None, false,
unique: false, false,
collation: None, false,
hidden: false, false,
} )
} }
fn _create_column_of_type(name: &str, ty: Type) -> Column { fn _create_column_of_type(name: &str, ty: Type) -> Column {
_create_column(&TestColumn { _create_column(&TestColumn {

View File

@@ -16,8 +16,9 @@ use turso_ext::{ConstraintInfo, ConstraintUsage};
use turso_parser::ast::{self, Expr, SortOrder}; use turso_parser::ast::{self, Expr, SortOrder};
use crate::{ use crate::{
schema::{BTreeTable, Column, Index, IndexColumn, Schema, Table, Type, ROWID_SENTINEL}, schema::{BTreeTable, Index, IndexColumn, Schema, Table, ROWID_SENTINEL},
translate::{ translate::{
insert::ROWID_COLUMN,
optimizer::{ optimizer::{
access_method::AccessMethodParams, access_method::AccessMethodParams,
constraints::{RangeConstraintRef, SeekRangeConstraint, TableConstraints}, constraints::{RangeConstraintRef, SeekRangeConstraint, TableConstraints},
@@ -167,7 +168,7 @@ fn optimize_update_plan(
}; };
let Some(index) = table_ref.op.index() else { let Some(index) = table_ref.op.index() else {
let rowid_alias_used = plan.set_clauses.iter().fold(false, |accum, (idx, _)| { 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 { if rowid_alias_used {
break 'requires true; break 'requires true;
@@ -217,18 +218,7 @@ fn add_ephemeral_table_to_update_plan(
has_rowid: true, has_rowid: true,
has_autoincrement: false, has_autoincrement: false,
primary_key_columns: vec![], primary_key_columns: vec![],
columns: vec![Column { columns: vec![ROWID_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,
}],
is_strict: false, is_strict: false,
unique_sets: vec![], unique_sets: vec![],
foreign_keys: vec![], foreign_keys: vec![],
@@ -1034,7 +1024,7 @@ impl Optimizable for ast::Expr {
.expect("table not found"); .expect("table not found");
let columns = table_ref.columns(); let columns = table_ref.columns();
let column = &columns[*column]; let column = &columns[*column];
column.primary_key || column.notnull column.primary_key() || column.notnull()
} }
Expr::RowId { .. } => true, Expr::RowId { .. } => true,
Expr::InList { lhs, rhs, .. } => { Expr::InList { lhs, rhs, .. } => {
@@ -1265,7 +1255,7 @@ fn ephemeral_index_build(
name: c.name.clone().unwrap(), name: c.name.clone().unwrap(),
order: SortOrder::Asc, order: SortOrder::Asc,
pos_in_table: i, pos_in_table: i,
collation: c.collation, collation: c.collation_opt(),
default: c.default.clone(), default: c.default.clone(),
}) })
// only include columns that are used in the query // only include columns that are used in the query

View File

@@ -184,7 +184,7 @@ pub fn plan_satisfies_order_target(
.table .table
.columns() .columns()
.iter() .iter()
.position(|c| c.is_rowid_alias); .position(|c| c.is_rowid_alias());
let Some(rowid_alias_col) = rowid_alias_col else { let Some(rowid_alias_col) = rowid_alias_col else {
return false; return false;
}; };

View File

@@ -87,7 +87,7 @@ pub fn init_order_by(
name: pos_in_table.to_string(), name: pos_in_table.to_string(),
order: SortOrder::Asc, order: SortOrder::Asc,
pos_in_table, pos_in_table,
collation: Some(CollationSeq::Binary), collation: None,
default: None, default: None,
}); });
for _ in remappings.iter().filter(|r| !r.deduplicated) { for _ in remappings.iter().filter(|r| !r.deduplicated) {

View File

@@ -512,7 +512,7 @@ pub fn select_star(tables: &[JoinedTable], out_columns: &mut Vec<ResultSetColumn
.columns() .columns()
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, col)| !col.hidden) .filter(|(_, col)| !col.hidden())
.filter(|(_, col)| { .filter(|(_, col)| {
// If we are joining with USING, we need to deduplicate the columns from the right table // If we are joining with USING, we need to deduplicate the columns from the right table
// that are also present in the USING clause. // that are also present in the USING clause.
@@ -532,7 +532,7 @@ pub fn select_star(tables: &[JoinedTable], out_columns: &mut Vec<ResultSetColumn
database: None, database: None,
table: table.internal_id, table: table.internal_id,
column: i, column: i,
is_rowid_alias: col.is_rowid_alias, is_rowid_alias: col.is_rowid_alias(),
}, },
contains_aggregates: false, contains_aggregates: false,
}), }),
@@ -913,23 +913,27 @@ impl JoinedTable {
let mut columns = plan let mut columns = plan
.result_columns .result_columns
.iter() .iter()
.map(|rc| Column { .map(|rc| {
name: rc.name(&plan.table_references).map(String::from), Column::new(
ty: Type::Blob, // FIXME: infer proper type rc.name(&plan.table_references).map(String::from),
ty_str: "BLOB".to_string(), "BLOB".to_string(),
is_rowid_alias: false, None,
primary_key: false, Type::Blob, // FIXME: infer proper type
notnull: false, None,
default: None, false,
unique: false, false,
collation: None, false,
hidden: false, false,
false,
)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for (i, column) in columns.iter_mut().enumerate() { for (i, column) in columns.iter_mut().enumerate() {
column.collation = column.set_collation(get_collseq_from_expr(
get_collseq_from_expr(&plan.result_columns[i].expr, &plan.table_references)?; &plan.result_columns[i].expr,
&plan.table_references,
)?);
} }
let table = Table::FromClauseSubquery(FromClauseSubquery { let table = Table::FromClauseSubquery(FromClauseSubquery {

View File

@@ -593,7 +593,7 @@ fn transform_args_into_where_terms(
let mut args_iter = args.iter(); let mut args_iter = args.iter();
let mut hidden_count = 0; let mut hidden_count = 0;
for (i, col) in table.columns().iter().enumerate() { for (i, col) in table.columns().iter().enumerate() {
if !col.hidden { if !col.hidden() {
continue; continue;
} }
hidden_count += 1; hidden_count += 1;
@@ -603,7 +603,7 @@ fn transform_args_into_where_terms(
database: None, database: None,
table: internal_id, table: internal_id,
column: i, column: i,
is_rowid_alias: col.is_rowid_alias, is_rowid_alias: col.is_rowid_alias(),
}; };
let expr = match arg_expr.as_ref() { let expr = match arg_expr.as_ref() {
Expr::Literal(Null) => Expr::IsNull(Box::new(column_expr)), Expr::Literal(Null) => Expr::IsNull(Box::new(column_expr)),
@@ -1087,14 +1087,14 @@ fn parse_join(
let mut distinct_names: Vec<ast::Name> = vec![]; let mut distinct_names: Vec<ast::Name> = vec![];
// TODO: O(n^2) maybe not great for large tables or big multiway joins // 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 // 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; let mut found_match = false;
for left_table in table_references for left_table in table_references
.joined_tables() .joined_tables()
.iter() .iter()
.take(table_references.joined_tables().len() - 1) .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 { if left_col.name == right_col.name {
distinct_names.push(ast::Name::exact( distinct_names.push(ast::Name::exact(
left_col.name.clone().expect("column name is None"), left_col.name.clone().expect("column name is None"),
@@ -1154,7 +1154,7 @@ fn parse_join(
.columns() .columns()
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, col)| !natural || !col.hidden) .filter(|(_, col)| !natural || !col.hidden())
.find(|(_, col)| { .find(|(_, col)| {
col.name col.name
.as_ref() .as_ref()
@@ -1189,14 +1189,14 @@ fn parse_join(
database: None, database: None,
table: left_table_id, table: left_table_id,
column: left_col_idx, column: left_col_idx,
is_rowid_alias: left_col.is_rowid_alias, is_rowid_alias: left_col.is_rowid_alias(),
}), }),
ast::Operator::Equals, ast::Operator::Equals,
Box::new(Expr::Column { Box::new(Expr::Column {
database: None, database: None,
table: right_table.internal_id, table: right_table.internal_id,
column: right_col_idx, column: right_col_idx,
is_rowid_alias: right_col.is_rowid_alias, is_rowid_alias: right_col.is_rowid_alias(),
}), }),
); );

View File

@@ -743,7 +743,7 @@ fn emit_columns_for_table_info(
// According to the SQLite documentation: "The 'cid' column should not be taken to // According to the SQLite documentation: "The 'cid' column should not be taken to
// mean more than 'rank within the current result set'." // mean more than 'rank within the current result set'."
// Therefore, we enumerate only after filtering out hidden columns. // 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 // cid
program.emit_int(i as i64, base_reg); program.emit_int(i as i64, base_reg);
// name // name
@@ -753,7 +753,7 @@ fn emit_columns_for_table_info(
program.emit_string8(column.ty_str.clone(), base_reg + 2); program.emit_string8(column.ty_str.clone(), base_reg + 2);
// notnull // notnull
program.emit_bool(column.notnull, base_reg + 3); program.emit_bool(column.notnull(), base_reg + 3);
// dflt_value // dflt_value
match &column.default { match &column.default {
@@ -766,7 +766,7 @@ fn emit_columns_for_table_info(
} }
// pk // 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); program.emit_result_row(base_reg, 6);
} }

View File

@@ -2,25 +2,16 @@ use std::sync::Arc;
use crate::ast; use crate::ast;
use crate::ext::VTabImpl; use crate::ext::VTabImpl;
use crate::schema::create_table; use crate::schema::{create_table, BTreeTable, Column, Table, Type, RESERVED_TABLE_PREFIXES};
use crate::schema::BTreeTable;
use crate::schema::Column;
use crate::schema::Table;
use crate::schema::Type;
use crate::schema::RESERVED_TABLE_PREFIXES;
use crate::storage::pager::CreateBTreeFlags; use crate::storage::pager::CreateBTreeFlags;
use crate::translate::emitter::emit_cdc_full_record; use crate::translate::emitter::{
use crate::translate::emitter::emit_cdc_insns; emit_cdc_full_record, emit_cdc_insns, prepare_cdc_if_necessary, OperationMode, Resolver,
use crate::translate::emitter::prepare_cdc_if_necessary; };
use crate::translate::emitter::OperationMode; use crate::translate::{ProgramBuilder, ProgramBuilderOpts};
use crate::translate::emitter::Resolver;
use crate::translate::ProgramBuilder;
use crate::translate::ProgramBuilderOpts;
use crate::util::normalize_ident; use crate::util::normalize_ident;
use crate::util::PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX; use crate::util::PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX;
use crate::vdbe::builder::CursorType; use crate::vdbe::builder::CursorType;
use crate::vdbe::insn::Cookie; use crate::vdbe::insn::{CmpInsFlags, Cookie, InsertFlags, Insn};
use crate::vdbe::insn::{CmpInsFlags, InsertFlags, Insn};
use crate::Connection; use crate::Connection;
use crate::{bail_parse_error, Result}; use crate::{bail_parse_error, Result};
@@ -435,7 +426,7 @@ fn collect_autoindexes(
}; };
let needs_index = if us.is_primary_key { let needs_index = if us.is_primary_key {
!(col.primary_key && col.is_rowid_alias) !(col.primary_key() && col.is_rowid_alias())
} else { } else {
// UNIQUE single needs an index // UNIQUE single needs an index
true true
@@ -841,18 +832,18 @@ pub fn translate_drop_table(
has_rowid: true, has_rowid: true,
has_autoincrement: false, has_autoincrement: false,
primary_key_columns: vec![], primary_key_columns: vec![],
columns: vec![Column { columns: vec![Column::new(
name: Some("rowid".to_string()), Some("rowid".to_string()),
ty: Type::Integer, "INTEGER".to_string(),
ty_str: "INTEGER".to_string(), None,
primary_key: false, Type::Integer,
is_rowid_alias: false, None,
notnull: false, false,
default: None, false,
unique: false, false,
collation: None, false,
hidden: false, false,
}], )],
is_strict: false, is_strict: false,
unique_sets: vec![], unique_sets: vec![],
foreign_keys: vec![], foreign_keys: vec![],

View File

@@ -236,14 +236,14 @@ fn prepare_one_select_plan(
ResultColumn::Star => table_references ResultColumn::Star => table_references
.joined_tables() .joined_tables()
.iter() .iter()
.map(|t| t.columns().iter().filter(|col| !col.hidden).count()) .map(|t| t.columns().iter().filter(|col| !col.hidden()).count())
.sum(), .sum(),
// Guess 5 columns if we can't find the table using the identifier (maybe it's in [brackets] or `tick_quotes`, or miXeDcAse) // 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 ResultColumn::TableStar(n) => table_references
.joined_tables() .joined_tables()
.iter() .iter()
.find(|t| t.identifier == n.as_str()) .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), .unwrap_or(5),
// Otherwise allocate space for 1 column // Otherwise allocate space for 1 column
ResultColumn::Expr(_, _) => 1, ResultColumn::Expr(_, _) => 1,
@@ -317,7 +317,7 @@ fn prepare_one_select_plan(
for table in plan.table_references.joined_tables_mut() { for table in plan.table_references.joined_tables_mut() {
for idx in 0..table.columns().len() { for idx in 0..table.columns().len() {
let column = &table.columns()[idx]; let column = &table.columns()[idx];
if column.hidden { if column.hidden() {
continue; continue;
} }
table.mark_column_used(idx); table.mark_column_used(idx);
@@ -339,7 +339,7 @@ fn prepare_one_select_plan(
let num_columns = table.columns().len(); let num_columns = table.columns().len();
for idx in 0..num_columns { for idx in 0..num_columns {
let column = &table.columns()[idx]; let column = &table.columns()[idx];
if column.hidden { if column.hidden() {
continue; continue;
} }
plan.result_columns.push(ResultSetColumn { plan.result_columns.push(ResultSetColumn {
@@ -347,7 +347,7 @@ fn prepare_one_select_plan(
database: None, // TODO: support different databases database: None, // TODO: support different databases
table: table.internal_id, table: table.internal_id,
column: idx, column: idx,
is_rowid_alias: column.is_rowid_alias, is_rowid_alias: column.is_rowid_alias(),
}, },
alias: None, alias: None,
contains_aggregates: false, contains_aggregates: false,

View File

@@ -241,7 +241,7 @@ pub fn prepare_update_plan(
.columns() .columns()
.iter() .iter()
.enumerate() .enumerate()
.find(|(_i, c)| c.is_rowid_alias) .find(|(_i, c)| c.is_rowid_alias())
{ {
// Use the rowid alias column index // Use the rowid alias column index
match set_clauses.iter_mut().find(|(i, _)| i == &idx) { match set_clauses.iter_mut().find(|(i, _)| i == &idx) {
@@ -320,7 +320,7 @@ pub fn prepare_update_plan(
let updated_cols: HashSet<usize> = set_clauses.iter().map(|(i, _)| *i).collect(); let updated_cols: HashSet<usize> = set_clauses.iter().map(|(i, _)| *i).collect();
let rowid_alias_used = set_clauses let rowid_alias_used = set_clauses
.iter() .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 { let indexes_to_update = if rowid_alias_used {
// If the rowid alias is used in the SET clause, we need to update all indexes // If the rowid alias is used in the SET clause, we need to update all indexes
indexes.cloned().collect() indexes.cloned().collect()

View File

@@ -5,7 +5,7 @@ use std::{collections::HashMap, sync::Arc};
use turso_parser::ast::{self, Upsert}; use turso_parser::ast::{self, Upsert};
use crate::error::SQLITE_CONSTRAINT_PRIMARYKEY; 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::emitter::UpdateRowSource;
use crate::translate::expr::{walk_expr, WalkControl}; use crate::translate::expr::{walk_expr, WalkControl};
use crate::translate::fkeys::{emit_fk_child_update_counters, emit_parent_key_change_checks}; use crate::translate::fkeys::{emit_fk_child_update_counters, emit_parent_key_change_checks};
@@ -16,7 +16,7 @@ use crate::Connection;
use crate::{ use crate::{
bail_parse_error, bail_parse_error,
error::SQLITE_CONSTRAINT_NOTNULL, error::SQLITE_CONSTRAINT_NOTNULL,
schema::{Index, IndexColumn, Schema, Table}, schema::{Index, Schema, Table},
translate::{ translate::{
emitter::{ emitter::{
emit_cdc_full_record, emit_cdc_insns, emit_cdc_patch_record, OperationMode, Resolver, 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 // Otherwise use the table default, or default to BINARY
table table
.get_column_by_name(&idx_col.name) .get_column_by_name(&idx_col.name)
.map(|s| { .map(|s| s.1.collation().to_string())
s.1.collation
.map(|c| c.to_string().to_ascii_lowercase())
.unwrap_or_else(|| "binary".to_string())
})
.unwrap_or_else(|| "binary".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; return false;
} }
// Only treat as PK if the PK is the rowid alias (INTEGER PRIMARY KEY) // 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 { if let Some(pkcol) = pk {
extract_target_key(&t.targets[0].expr).is_some_and(|tk| { extract_target_key(&t.targets[0].expr).is_some_and(|tk| {
tk.col_name tk.col_name
@@ -152,7 +148,7 @@ fn collect_changed_cols(
let mut rowid_changed = false; let mut rowid_changed = false;
for (col_idx, _) in set_pairs { for (col_idx, _) in set_pairs {
if let Some(c) = table.columns().get(*col_idx) { if let Some(c) = table.columns().get(*col_idx) {
if c.is_rowid_alias { if c.is_rowid_alias() {
rowid_changed = true; rowid_changed = true;
} else { } else {
cols_changed.insert(*col_idx); cols_changed.insert(*col_idx);
@@ -356,7 +352,7 @@ pub fn emit_upsert(
let num_cols = ctx.table.columns.len(); let num_cols = ctx.table.columns.len();
let current_start = program.alloc_registers(num_cols); let current_start = program.alloc_registers(num_cols);
for (i, col) in ctx.table.columns.iter().enumerate() { for (i, col) in ctx.table.columns.iter().enumerate() {
if col.is_rowid_alias { if col.is_rowid_alias() {
program.emit_insn(Insn::RowId { program.emit_insn(Insn::RowId {
cursor_id: ctx.cursor_id, cursor_id: ctx.cursor_id,
dest: current_start + i, dest: current_start + i,
@@ -433,14 +429,14 @@ pub fn emit_upsert(
NoConstantOptReason::RegisterReuse, NoConstantOptReason::RegisterReuse,
)?; )?;
let col = &table.columns()[*col_idx]; 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 { program.emit_insn(Insn::HaltIfNull {
target_reg: new_start + *col_idx, target_reg: new_start + *col_idx,
err_code: SQLITE_CONSTRAINT_NOTNULL, err_code: SQLITE_CONSTRAINT_NOTNULL,
description: String::from(table.get_name()) + col.name.as_ref().unwrap(), 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 // Must be integer; remember the NEW rowid value
let r = program.alloc_register(); let r = program.alloc_register();
program.emit_insn(Insn::Copy { 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 (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 let has_direct_rowid_update = set_pairs
.iter() .iter()
.any(|(idx, _)| *idx == rowid_alias_idx.unwrap_or(ROWID_SENTINEL)); .any(|(idx, _)| *idx == rowid_alias_idx.unwrap_or(ROWID_SENTINEL));
@@ -713,7 +709,7 @@ pub fn emit_upsert(
table table
.columns() .columns()
.iter() .iter()
.find(|c| c.is_rowid_alias) .find(|c| c.is_rowid_alias())
.and_then(|c| c.name.as_ref()) .and_then(|c| c.name.as_ref())
.unwrap_or(&"rowid".to_string()) .unwrap_or(&"rowid".to_string())
), ),
@@ -943,7 +939,7 @@ fn rewrite_expr_to_registers(
return Some(rowid_reg); return Some(rowid_reg);
} }
let (idx, c) = table.get_column_by_name(name)?; let (idx, c) = table.get_column_by_name(name)?;
if c.is_rowid_alias { if c.is_rowid_alias() {
Some(rowid_reg) Some(rowid_reg)
} else { } else {
Some(base_start + idx) Some(base_start + idx)

View File

@@ -354,7 +354,7 @@ pub fn simple_bind_expr(
database: None, database: None,
table: internal_id, table: internal_id,
column: col_idx, column: col_idx,
is_rowid_alias: col.is_rowid_alias, is_rowid_alias: col.is_rowid_alias(),
}; };
} else { } else {
// only if we haven't found a match, check for explicit rowid reference // 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 { columns.push(ViewColumn {
table_index: table_index.unwrap_or(usize::MAX), table_index: table_index.unwrap_or(usize::MAX),
column: Column { column: Column::new_default_text(Some(col_name), "TEXT".to_string(), None),
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,
},
}); });
} }
ast::ResultColumn::Star => { ast::ResultColumn::Star => {
@@ -1392,18 +1381,18 @@ pub fn extract_view_columns(
columns.push(ViewColumn { columns.push(ViewColumn {
table_index: table_idx, table_index: table_idx,
column: Column { column: Column::new(
name: Some(final_name), Some(final_name),
ty: table_column.ty, table_column.ty_str.clone(),
ty_str: table_column.ty_str.clone(), None,
primary_key: false, table_column.ty(),
is_rowid_alias: false, table_column.collation_opt(),
notnull: false, false,
default: None, false,
unique: false, false,
collation: table_column.collation, false,
hidden: false, false,
}, ),
}); });
} }
} }
@@ -1413,18 +1402,11 @@ pub fn extract_view_columns(
if tables.is_empty() { if tables.is_empty() {
columns.push(ViewColumn { columns.push(ViewColumn {
table_index: usize::MAX, table_index: usize::MAX,
column: Column { column: Column::new_default_text(
name: Some("*".to_string()), Some("*".to_string()),
ty: Type::Text, "TEXT".to_string(),
ty_str: "TEXT".to_string(), None,
primary_key: false, ),
is_rowid_alias: false,
notnull: false,
default: None,
unique: false,
collation: None,
hidden: false,
},
}); });
} }
} }
@@ -1449,36 +1431,29 @@ pub fn extract_view_columns(
columns.push(ViewColumn { columns.push(ViewColumn {
table_index: table_idx, table_index: table_idx,
column: Column { column: Column::new(
name: Some(final_name), Some(final_name),
ty: table_column.ty, table_column.ty_str.clone(),
ty_str: table_column.ty_str.clone(), None,
primary_key: false, table_column.ty(),
is_rowid_alias: false, table_column.collation_opt(),
notnull: false, false,
default: None, false,
unique: false, false,
collation: table_column.collation, false,
hidden: false, false,
}, ),
}); });
} }
} else { } else {
// Table not found, create placeholder // Table not found, create placeholder
columns.push(ViewColumn { columns.push(ViewColumn {
table_index: usize::MAX, table_index: usize::MAX,
column: Column { column: Column::new_default_text(
name: Some(format!("{table_name_str}.*")), Some(format!("{table_name_str}.*")),
ty: Type::Text, "TEXT".to_string(),
ty_str: "TEXT".to_string(), None,
primary_key: false, ),
is_rowid_alias: false,
notnull: false,
default: None,
unique: false,
collation: None,
hidden: false,
},
}); });
} }
} }

View File

@@ -1028,7 +1028,7 @@ impl ProgramBuilder {
.columns .columns
.get(column) .get(column)
.expect("column index out of bounds"); .expect("column index out of bounds");
if column_def.is_rowid_alias { if column_def.is_rowid_alias() {
self.emit_insn(Insn::RowId { self.emit_insn(Insn::RowId {
cursor_id, cursor_id,
dest: out, dest: out,

View File

@@ -1920,14 +1920,15 @@ pub fn op_type_check(
.zip(table_reference.columns.iter()) .zip(table_reference.columns.iter())
.try_for_each(|(reg, col)| { .try_for_each(|(reg, col)| {
// INT PRIMARY KEY is not row_id_alias so we throw error if this col is NULL // 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!( bail_constraint_error!(
"NOT NULL constraint failed: {}.{} ({})", "NOT NULL constraint failed: {}.{} ({})",
&table_reference.name, &table_reference.name,
col.name.as_deref().unwrap_or(""), col.name.as_deref().unwrap_or(""),
SQLITE_CONSTRAINT 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) // Handle INTEGER PRIMARY KEY for null as usual (Rowid will be auto-assigned)
return Ok(()); return Ok(());
} }
@@ -6011,7 +6012,7 @@ pub fn op_insert(
// Fix rowid alias columns: replace Null with actual rowid value // Fix rowid alias columns: replace Null with actual rowid value
if let Some(table) = schema.get_table(table_name) { if let Some(table) = schema.get_table(table_name) {
for (i, col) in table.columns().iter().enumerate() { 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); values[i] = Value::Integer(key);
} }
} }
@@ -6162,7 +6163,7 @@ pub fn op_insert(
let schema = program.connection.schema.read(); let schema = program.connection.schema.read();
if let Some(table) = schema.get_table(table_name) { if let Some(table) = schema.get_table(table_name) {
for (i, col) in table.columns().iter().enumerate() { 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); new_values[i] = Value::Integer(key);
} }
} }
@@ -6280,7 +6281,7 @@ pub fn op_delete(
// Fix rowid alias columns: replace Null with actual rowid value // Fix rowid alias columns: replace Null with actual rowid value
if let Some(table) = schema.get_table(table_name) { if let Some(table) = schema.get_table(table_name) {
for (i, col) in table.columns().iter().enumerate() { 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); values[i] = Value::Integer(key);
} }
} }
@@ -8666,7 +8667,7 @@ pub fn op_alter_column(
// Maintain rowid-alias bit after change/rename (INTEGER PRIMARY KEY) // Maintain rowid-alias bit after change/rename (INTEGER PRIMARY KEY)
if !*rename { if !*rename {
// recompute alias from `new_column` // 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 tables OWN foreign keys // Update this tables OWN foreign keys