From e05850dfe22457acf0e13977998431c06500f194 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 20 May 2024 09:10:38 +0200 Subject: [PATCH] Introduce ColFlags --- src/parser/ast/check.rs | 22 ++--- src/parser/ast/mod.rs | 188 +++++++++++++++++++++++++--------------- src/parser/parse.y | 6 +- 3 files changed, 131 insertions(+), 85 deletions(-) diff --git a/src/parser/ast/check.rs b/src/parser/ast/check.rs index e16ac9d..f6df992 100644 --- a/src/parser/ast/check.rs +++ b/src/parser/ast/check.rs @@ -104,12 +104,10 @@ impl Stmt { Ok(()) } Stmt::AlterTable(.., AlterTableBody::AddColumn(cd)) => { - for c in cd { - if let ColumnConstraint::PrimaryKey { .. } = c { - return Err(custom_err!("Cannot add a PRIMARY KEY column")); - } else if let ColumnConstraint::Unique(..) = c { - return Err(custom_err!("Cannot add a UNIQUE column")); - } + if cd.col_flags.contains(ColFlags::PRIMKEY) { + return Err(custom_err!("Cannot add a PRIMARY KEY column")); + } else if cd.col_flags.contains(ColFlags::UNIQUE) { + return Err(custom_err!("Cannot add a UNIQUE column")); } Ok(()) } @@ -203,10 +201,8 @@ impl CreateTableBody { { let mut generated_count = 0; for c in columns.values() { - for cs in c.constraints.iter() { - if let ColumnConstraint::Generated { .. } = cs.constraint { - generated_count += 1; - } + if c.col_flags.intersects(ColFlags::GENERATED) { + generated_count += 1; } } if generated_count == columns.len() { @@ -260,10 +256,8 @@ impl CreateTableBody { } = self { for col in columns.values() { - for c in col { - if let ColumnConstraint::PrimaryKey { .. } = c { - return true; - } + if col.col_flags.contains(ColFlags::PRIMKEY) { + return true; } } if let Some(constraints) = constraints { diff --git a/src/parser/ast/mod.rs b/src/parser/ast/mod.rs index 939a552..0875ee0 100644 --- a/src/parser/ast/mod.rs +++ b/src/parser/ast/mod.rs @@ -1331,6 +1331,35 @@ impl CreateTableBody { } } +bitflags::bitflags! { + /// Column definition flags + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct ColFlags: u16 { + /// Column is part of the primary key + const PRIMKEY = 0x0001; + // A hidden column in a virtual table + //const HIDDEN = 0x0002; + /// Type name follows column name + const HASTYPE = 0x0004; + /// Column def contains "UNIQUE" or "PK" + const UNIQUE = 0x0008; + //const SORTERREF = 0x0010; /* Use sorter-refs with this column */ + /// GENERATED ALWAYS AS ... VIRTUAL + const VIRTUAL = 0x0020; + /// GENERATED ALWAYS AS ... STORED + const STORED = 0x0040; + //const NOTAVAIL = 0x0080; /* STORED column not yet calculated */ + //const BUSY = 0x0100; /* Blocks recursion on GENERATED columns */ + /// Has collating sequence name in zCnName + const HASCOLL = 0x0200; + //const NOEXPAND = 0x0400; /* Omit this column when expanding "*" */ + /// Combo: STORED, VIRTUAL + const GENERATED = Self::STORED.bits() | Self::VIRTUAL.bits(); + // Combo: HIDDEN, STORED, VIRTUAL + //const NOINSERT = Self::HIDDEN.bits() | Self::STORED.bits() | Self::VIRTUAL.bits(); + } +} + /// Table column definition // https://sqlite.org/syntax/column-def.html #[derive(Clone, Debug, PartialEq, Eq)] @@ -1341,89 +1370,112 @@ pub struct ColumnDefinition { pub col_type: Option, /// column constraints pub constraints: Vec, + /// column flags + pub col_flags: ColFlags, } impl ColumnDefinition { /// Constructor - pub fn add_column( - columns: &mut IndexMap, - mut cd: ColumnDefinition, - ) -> Result<(), ParserError> { - let col_name = &cd.col_name; - if columns.contains_key(col_name) { - // TODO unquote - return Err(custom_err!("duplicate column name: {}", col_name)); - } - // https://github.com/sqlite/sqlite/blob/e452bf40a14aca57fd9047b330dff282f3e4bbcc/src/build.c#L1511-L1514 - if let Some(ref mut col_type) = cd.col_type { - let mut split = col_type.name.split_ascii_whitespace(); - let truncate = if split - .next_back() - .map_or(false, |s| s.eq_ignore_ascii_case("ALWAYS")) - && split - .next_back() - .map_or(false, |s| s.eq_ignore_ascii_case("GENERATED")) - { - let mut generated = false; - for constraint in &cd.constraints { - if let ColumnConstraint::Generated { .. } = constraint.constraint { - generated = true; - break; + pub fn new( + col_name: Name, + mut col_type: Option, + constraints: Vec, + ) -> Result { + let mut col_flags = ColFlags::empty(); + let mut default = false; + for constraint in &constraints { + match &constraint.constraint { + ColumnConstraint::Default(..) => { + default = true; + } + ColumnConstraint::Collate { .. } => { + col_flags |= ColFlags::HASCOLL; + } + ColumnConstraint::Generated { typ, .. } => { + col_flags |= ColFlags::VIRTUAL; + if let Some(id) = typ { + if id.0.eq_ignore_ascii_case("STORED") { + col_flags |= ColFlags::STORED; + } } } - generated - } else { - false - }; - if truncate { - // str_split_whitespace_remainder - let new_type: Vec<&str> = split.collect(); - col_type.name = new_type.join(" "); - } - } - let (mut primary_key, mut generated, mut default) = (false, false, false); - for constraint in &cd.constraints { - if let ColumnConstraint::PrimaryKey { auto_increment, .. } = &constraint.constraint { - if *auto_increment - && cd - .col_type - .as_ref() - .map_or(true, |t| !t.name.eq_ignore_ascii_case("INTEGER")) - { - return Err(custom_err!( - "AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY" - )); + ColumnConstraint::ForeignKey { + clause: + ForeignKeyClause { + tbl_name, columns, .. + }, + .. + } => { + // The child table may reference the primary key of the parent without specifying the primary key column + if columns.as_ref().map_or(0, |v| v.len()) > 1 { + return Err(custom_err!( + "foreign key on {} should reference only one column of table {}", + col_name, + tbl_name + )); + } } - primary_key = true; - } else if let ColumnConstraint::ForeignKey { - clause: - ForeignKeyClause { - tbl_name, columns, .. - }, - .. - } = &constraint.constraint - { - // The child table may reference the primary key of the parent without specifying the primary key column - if columns.as_ref().map_or(0, |v| v.len()) > 1 { - return Err(custom_err!( - "foreign key on {} should reference only one column of table {}", - col_name, - tbl_name - )); + ColumnConstraint::PrimaryKey { auto_increment, .. } => { + if *auto_increment + && col_type + .as_ref() + .map_or(true, |t| !t.name.eq_ignore_ascii_case("INTEGER")) + { + return Err(custom_err!( + "AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY" + )); + } + col_flags |= ColFlags::PRIMKEY | ColFlags::UNIQUE; + } + ColumnConstraint::Unique(..) => { + col_flags |= ColFlags::UNIQUE; } - } else if let ColumnConstraint::Generated { .. } = &constraint.constraint { - generated = true; - } else if let ColumnConstraint::Default(..) = &constraint.constraint { - default = true; + _ => {} } } - if primary_key && generated { + if col_flags.contains(ColFlags::PRIMKEY) && col_flags.intersects(ColFlags::GENERATED) { return Err(custom_err!( "generated columns cannot be part of the PRIMARY KEY" )); - } else if default && generated { + } else if default && col_flags.intersects(ColFlags::GENERATED) { return Err(custom_err!("cannot use DEFAULT on a generated column")); } + if col_flags.intersects(ColFlags::GENERATED) { + // https://github.com/sqlite/sqlite/blob/e452bf40a14aca57fd9047b330dff282f3e4bbcc/src/build.c#L1511-L1514 + if let Some(ref mut col_type) = col_type { + let mut split = col_type.name.split_ascii_whitespace(); + if split + .next_back() + .map_or(false, |s| s.eq_ignore_ascii_case("ALWAYS")) + && split + .next_back() + .map_or(false, |s| s.eq_ignore_ascii_case("GENERATED")) + { + // str_split_whitespace_remainder + let new_type: Vec<&str> = split.collect(); + col_type.name = new_type.join(" "); + } + } + } + if col_type.as_ref().map_or(false, |t| !t.name.is_empty()) { + col_flags |= ColFlags::HASTYPE; + } + Ok(ColumnDefinition { + col_name, + col_type, + constraints, + col_flags, + }) + } + /// Constructor + pub fn add_column( + columns: &mut IndexMap, + cd: ColumnDefinition, + ) -> Result<(), ParserError> { + let col_name = &cd.col_name; + if columns.contains_key(col_name) { + return Err(custom_err!("duplicate column name: {}", col_name)); + } columns.insert(col_name.clone(), cd); Ok(()) } diff --git a/src/parser/parse.y b/src/parser/parse.y index 4c3aff1..f368122 100644 --- a/src/parser/parse.y +++ b/src/parser/parse.y @@ -160,12 +160,12 @@ table_option(A) ::= nm(X). { %type columnlist {IndexMap} columnlist(A) ::= columnlist(A) COMMA columnname(X) carglist(Y). { let col = X; - let cd = ColumnDefinition{ col_name: col.0, col_type: col.1, constraints: Y }; + let cd = ColumnDefinition::new(col.0, col.1, Y)?; ColumnDefinition::add_column(A, cd)?; } columnlist(A) ::= columnname(X) carglist(Y). { let col = X; - let cd = ColumnDefinition{ col_name: col.0, col_type: col.1, constraints: Y }; + let cd = ColumnDefinition::new(col.0, col.1, Y)?; let mut map = IndexMap::new(); ColumnDefinition::add_column(&mut map, cd)?; A = map; @@ -1316,7 +1316,7 @@ cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). { cmd ::= ALTER TABLE fullname(X) ADD kwcolumn_opt columnname(Y) carglist(C). { let (col_name, col_type) = Y; - let cd = ColumnDefinition{ col_name, col_type, constraints: C }; + let cd = ColumnDefinition::new(col_name, col_type, C)?; self.ctx.stmt = Some(Stmt::AlterTable(X, AlterTableBody::AddColumn(cd))); } cmd ::= ALTER TABLE fullname(X) RENAME kwcolumn_opt nm(Y) TO nm(Z). {