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

View File

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

View File

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

View File

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

View File

@@ -109,10 +109,10 @@ fn emit_collseq_if_needed(
if let ast::Expr::Column { table, column, .. } = expr {
if let Some((_, table_ref)) = referenced_tables.find_table_by_internal_id(*table) {
if let Some(table_column) = table_ref.get_column_at(*column) {
if let Some(collation) = &table_column.collation {
if let Some(c) = table_column.collation_opt() {
program.emit_insn(Insn::CollSeq {
reg: None,
collation: *collation,
collation: c,
});
}
}

View File

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

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");
}
if !target_btree.has_rowid {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1488,9 +1488,10 @@ fn emit_seek_termination(
affinity = if let Some(table_ref) = tables
.joined_tables()
.iter()
.find(|t| t.columns().iter().any(|c| c.is_rowid_alias))
.find(|t| t.columns().iter().any(|c| c.is_rowid_alias()))
{
if let Some(rowid_col_idx) = table_ref.columns().iter().position(|c| c.is_rowid_alias())
{
if let Some(rowid_col_idx) = table_ref.columns().iter().position(|c| c.is_rowid_alias) {
Some(table_ref.columns()[rowid_col_idx].affinity())
} else {
Some(Affinity::Numeric)

View File

@@ -101,7 +101,7 @@ fn find_best_access_method_for_btree(
index: None,
constraint_refs: vec![],
};
let rowid_column_idx = rhs_table.columns().iter().position(|c| c.is_rowid_alias);
let rowid_column_idx = rhs_table.columns().iter().position(|c| c.is_rowid_alias());
// Estimate cost for each candidate index (including the rowid index) and replace best_access_method if the cost is lower.
for candidate in rhs_constraints.candidates.iter() {

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 {
match op {
ast::Operator::Equals => {
if column.is_rowid_alias || column.primary_key {
if column.is_rowid_alias() || column.primary_key() {
SELECTIVITY_UNIQUE_EQUALITY
} else {
SELECTIVITY_EQ
@@ -197,7 +197,7 @@ pub fn constraints_from_where_clause(
let rowid_alias_column = table_reference
.columns()
.iter()
.position(|c| c.is_rowid_alias);
.position(|c| c.is_rowid_alias());
let mut cs = TableConstraints {
table_id: table_reference.internal_id,
@@ -313,7 +313,7 @@ pub fn constraints_from_where_clause(
// For each constraint we found, add a reference to it for each index that may be able to use it.
for (i, constraint) in cs.constraints.iter_mut().enumerate() {
let constrained_column = &table_reference.table.columns()[constraint.table_col_pos];
let column_collation = constrained_column.collation.unwrap_or_default();
let column_collation = constrained_column.collation();
let constraining_expr = constraint.get_constraining_expr_ref(where_clause);
// Index seek keys must use the same collation as the constrained column.
match get_collseq_from_expr(constraining_expr, table_references)? {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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
// mean more than 'rank within the current result set'."
// Therefore, we enumerate only after filtering out hidden columns.
for (i, column) in columns.iter().filter(|col| !col.hidden).enumerate() {
for (i, column) in columns.iter().filter(|col| !col.hidden()).enumerate() {
// cid
program.emit_int(i as i64, base_reg);
// name
@@ -753,7 +753,7 @@ fn emit_columns_for_table_info(
program.emit_string8(column.ty_str.clone(), base_reg + 2);
// notnull
program.emit_bool(column.notnull, base_reg + 3);
program.emit_bool(column.notnull(), base_reg + 3);
// dflt_value
match &column.default {
@@ -766,7 +766,7 @@ fn emit_columns_for_table_info(
}
// pk
program.emit_bool(column.primary_key, base_reg + 5);
program.emit_bool(column.primary_key(), base_reg + 5);
program.emit_result_row(base_reg, 6);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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