Skip to content

Commit

Permalink
Introduce ColFlags
Browse files Browse the repository at this point in the history
  • Loading branch information
gwenn committed May 20, 2024
1 parent e9906d5 commit e05850d
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 85 deletions.
22 changes: 8 additions & 14 deletions src/parser/ast/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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 {
Expand Down
188 changes: 120 additions & 68 deletions src/parser/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -1341,89 +1370,112 @@ pub struct ColumnDefinition {
pub col_type: Option<Type>,
/// column constraints
pub constraints: Vec<NamedColumnConstraint>,
/// column flags
pub col_flags: ColFlags,
}

impl ColumnDefinition {
/// Constructor
pub fn add_column(
columns: &mut IndexMap<Name, ColumnDefinition>,
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<Type>,
constraints: Vec<NamedColumnConstraint>,
) -> Result<ColumnDefinition, ParserError> {
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<Name, ColumnDefinition>,
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(())
}
Expand Down
6 changes: 3 additions & 3 deletions src/parser/parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,12 @@ table_option(A) ::= nm(X). {
%type columnlist {IndexMap<Name,ColumnDefinition>}
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;
Expand Down Expand Up @@ -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). {
Expand Down

0 comments on commit e05850d

Please sign in to comment.