Files
turso/parser/src/ast.rs
Pekka Enberg 433b60555f Add BEGIN CONCURRENT support for MVCC mode
Currently, when MVCC is enabled, every transaction mode supports
concurrent reads and writes, which makes it hard to adopt for existing
applications that use `BEGIN DEFERRED` or `BEGIN IMMEDIATE`.

Therefore, add support for `BEGIN CONCURRENT` transactions when MVCC is
enabled. The transaction mode allows multiple concurrent read/write
transactions that don't block each other, with conflicts resolved at
commit time. Furthermore, implement the correct semantics for `BEGIN
DEFERRED` and `BEGIN IMMEDIATE` by taking advantage of the pager level
write lock when transaction upgrades to write. This means that now
concurrent MVCC transactions are serialized against the legacy ones when
needed.

The implementation includes:

- Parser support for CONCURRENT keyword in BEGIN statements

- New Concurrent variant in TransactionMode to distinguish from regular
  read/write transactions

- MVCC store tracking of exclusive transactions to support IMMEDIATE and
  EXCLUSIVE modes alongside CONCURRENT

- Proper transaction state management for all transaction types in MVCC

This enables better concurrency for applications that can handle
optimistic concurrency control, while still supporting traditional
SQLite transaction semantics via IMMEDIATE and EXCLUSIVE modes.
2025-09-11 16:05:52 +03:00

1678 lines
43 KiB
Rust

pub mod check;
pub mod fmt;
use strum_macros::{EnumIter, EnumString};
/// `?` or `$` Prepared statement arg placeholder(s)
#[derive(Default)]
pub struct ParameterInfo {
/// Number of SQL parameters in a prepared statement, like `sqlite3_bind_parameter_count`
pub count: u32,
/// Parameter name(s) if any
pub names: Vec<String>,
}
/// Statement or Explain statement
// https://sqlite.org/syntax/sql-stmt.html
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Cmd {
/// `EXPLAIN` statement
Explain(Stmt),
/// `EXPLAIN QUERY PLAN` statement
ExplainQueryPlan(Stmt),
/// statement
Stmt(Stmt),
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CreateVirtualTable {
/// `IF NOT EXISTS`
pub if_not_exists: bool,
/// table name
pub tbl_name: QualifiedName,
/// module name
pub module_name: Name,
/// args
pub args: Vec<String>, // TODO smol str
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Update {
/// CTE
pub with: Option<With>,
/// `OR`
pub or_conflict: Option<ResolveType>,
/// table name
pub tbl_name: QualifiedName,
/// `INDEXED`
pub indexed: Option<Indexed>,
/// `SET` assignments
pub sets: Vec<Set>,
/// `FROM`
pub from: Option<FromClause>,
/// `WHERE` clause
pub where_clause: Option<Box<Expr>>,
/// `RETURNING`
pub returning: Vec<ResultColumn>,
/// `ORDER BY`
pub order_by: Vec<SortedColumn>,
/// `LIMIT`
pub limit: Option<Limit>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AlterTable {
// table name
pub name: QualifiedName,
// `ALTER TABLE` body
pub body: AlterTableBody,
}
/// SQL statement
// https://sqlite.org/syntax/sql-stmt.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Stmt {
/// `ALTER TABLE`: table name, body
AlterTable(AlterTable),
/// `ANALYSE`: object name
Analyze {
// object name
name: Option<QualifiedName>,
},
/// `ATTACH DATABASE`
Attach {
/// filename
// TODO distinction between ATTACH and ATTACH DATABASE
expr: Box<Expr>,
/// schema name
db_name: Box<Expr>,
/// password
key: Option<Box<Expr>>,
},
/// `BEGIN`: tx type, tx name
Begin {
// transaction type
typ: Option<TransactionType>,
// transaction name
name: Option<Name>,
},
/// `COMMIT`/`END`: tx name
Commit {
// tx name
name: Option<Name>,
}, // TODO distinction between COMMIT and END
/// `CREATE INDEX`
CreateIndex {
/// `UNIQUE`
unique: bool,
/// `IF NOT EXISTS`
if_not_exists: bool,
/// index name
idx_name: QualifiedName,
/// table name
tbl_name: Name,
/// indexed columns or expressions
columns: Vec<SortedColumn>,
/// partial index
where_clause: Option<Box<Expr>>,
},
/// `CREATE TABLE`
CreateTable {
/// `TEMPORARY`
temporary: bool, // TODO distinction between TEMP and TEMPORARY
/// `IF NOT EXISTS`
if_not_exists: bool,
/// table name
tbl_name: QualifiedName,
/// table body
body: CreateTableBody,
},
/// `CREATE TRIGGER`
CreateTrigger {
/// `TEMPORARY`
temporary: bool,
/// `IF NOT EXISTS`
if_not_exists: bool,
/// trigger name
trigger_name: QualifiedName,
/// `BEFORE`/`AFTER`/`INSTEAD OF`
time: Option<TriggerTime>,
/// `DELETE`/`INSERT`/`UPDATE`
event: TriggerEvent,
/// table name
tbl_name: QualifiedName,
/// `FOR EACH ROW`
for_each_row: bool,
/// `WHEN`
when_clause: Option<Box<Expr>>,
/// statements
commands: Vec<TriggerCmd>,
},
/// `CREATE VIEW`
CreateView {
/// `TEMPORARY`
temporary: bool,
/// `IF NOT EXISTS`
if_not_exists: bool,
/// view name
view_name: QualifiedName,
/// columns
columns: Vec<IndexedColumn>,
/// query
select: Select,
},
/// `CREATE MATERIALIZED VIEW`
CreateMaterializedView {
/// `IF NOT EXISTS`
if_not_exists: bool,
/// view name
view_name: QualifiedName,
/// columns
columns: Vec<IndexedColumn>,
/// query
select: Select,
},
/// `CREATE VIRTUAL TABLE`
CreateVirtualTable(CreateVirtualTable),
/// `DELETE`
Delete {
/// CTE
with: Option<With>,
/// `FROM` table name
tbl_name: QualifiedName,
/// `INDEXED`
indexed: Option<Indexed>,
/// `WHERE` clause
where_clause: Option<Box<Expr>>,
/// `RETURNING`
returning: Vec<ResultColumn>,
/// `ORDER BY`
order_by: Vec<SortedColumn>,
/// `LIMIT`
limit: Option<Limit>,
},
/// `DETACH DATABASE`: db name
Detach {
// db name
name: Box<Expr>,
}, // TODO distinction between DETACH and DETACH DATABASE
/// `DROP INDEX`
DropIndex {
/// `IF EXISTS`
if_exists: bool,
/// index name
idx_name: QualifiedName,
},
/// `DROP TABLE`
DropTable {
/// `IF EXISTS`
if_exists: bool,
/// table name
tbl_name: QualifiedName,
},
/// `DROP TRIGGER`
DropTrigger {
/// `IF EXISTS`
if_exists: bool,
/// trigger name
trigger_name: QualifiedName,
},
/// `DROP VIEW`
DropView {
/// `IF EXISTS`
if_exists: bool,
/// view name
view_name: QualifiedName,
},
/// `INSERT`
Insert {
/// CTE
with: Option<With>,
/// `OR`
or_conflict: Option<ResolveType>, // TODO distinction between REPLACE and INSERT OR REPLACE
/// table name
tbl_name: QualifiedName,
/// `COLUMNS`
columns: Vec<Name>,
/// `VALUES` or `SELECT`
body: InsertBody,
/// `RETURNING`
returning: Vec<ResultColumn>,
},
/// `PRAGMA`: pragma name, body
Pragma {
// pragma name
name: QualifiedName,
// pragma body
body: Option<PragmaBody>,
},
/// `REINDEX`
Reindex {
/// collation or index or table name
name: Option<QualifiedName>,
},
/// `RELEASE`: savepoint name
Release {
// savepoint name
name: Name,
}, // TODO distinction between RELEASE and RELEASE SAVEPOINT
/// `ROLLBACK`
Rollback {
/// transaction name
tx_name: Option<Name>,
/// savepoint name
savepoint_name: Option<Name>, // TODO distinction between TO and TO SAVEPOINT
},
/// `SAVEPOINT`: savepoint name
Savepoint {
// savepoint name
name: Name,
},
/// `SELECT`
Select(Select),
/// `UPDATE`
Update(Update),
/// `VACUUM`: database name, into expr
Vacuum {
// database name
name: Option<Name>,
// into expression
into: Option<Box<Expr>>,
},
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Internal ID of a table reference.
///
/// Used by [Expr::Column] and [Expr::RowId] to refer to a table.
/// E.g. in 'SELECT * FROM t UNION ALL SELECT * FROM t', there are two table references,
/// so there are two TableInternalIds.
///
/// FIXME: rename this to TableReferenceId.
pub struct TableInternalId(usize);
impl Default for TableInternalId {
fn default() -> Self {
Self(1)
}
}
impl From<usize> for TableInternalId {
fn from(value: usize) -> Self {
Self(value)
}
}
impl std::ops::AddAssign<usize> for TableInternalId {
fn add_assign(&mut self, rhs: usize) {
self.0 += rhs;
}
}
impl From<TableInternalId> for usize {
fn from(value: TableInternalId) -> Self {
value.0
}
}
impl std::fmt::Display for TableInternalId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "t{}", self.0)
}
}
/// SQL expression
// https://sqlite.org/syntax/expr.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Expr {
/// `BETWEEN`
Between {
/// expression
lhs: Box<Expr>,
/// `NOT`
not: bool,
/// start
start: Box<Expr>,
/// end
end: Box<Expr>,
},
/// binary expression
Binary(Box<Expr>, Operator, Box<Expr>),
/// Register reference for DBSP expression compilation
/// This is not part of SQL syntax but used internally for incremental computation
Register(usize),
/// `CASE` expression
Case {
/// operand
base: Option<Box<Expr>>,
/// `WHEN` condition `THEN` result
when_then_pairs: Vec<(Box<Expr>, Box<Expr>)>,
/// `ELSE` result
else_expr: Option<Box<Expr>>,
},
/// CAST expression
Cast {
/// expression
expr: Box<Expr>,
/// `AS` type name
type_name: Option<Type>,
},
/// `COLLATE`: expression
Collate(Box<Expr>, Name),
/// schema-name.table-name.column-name
DoublyQualified(Name, Name, Name),
/// `EXISTS` subquery
Exists(Select),
/// call to a built-in function
FunctionCall {
/// function name
name: Name,
/// `DISTINCT`
distinctness: Option<Distinctness>,
/// arguments
args: Vec<Box<Expr>>,
/// `ORDER BY`
order_by: Vec<SortedColumn>,
/// `FILTER`
filter_over: FunctionTail,
},
/// Function call expression with '*' as arg
FunctionCallStar {
/// function name
name: Name,
/// `FILTER`
filter_over: FunctionTail,
},
/// Identifier
Id(Name),
/// Column
Column {
/// the x in `x.y.z`. index of the db in catalog.
database: Option<usize>,
/// the y in `x.y.z`. index of the table in catalog.
table: TableInternalId,
/// the z in `x.y.z`. index of the column in the table.
column: usize,
/// is the column a rowid alias
is_rowid_alias: bool,
},
/// `ROWID`
RowId {
/// the x in `x.y.z`. index of the db in catalog.
database: Option<usize>,
/// the y in `x.y.z`. index of the table in catalog.
table: TableInternalId,
},
/// `IN`
InList {
/// expression
lhs: Box<Expr>,
/// `NOT`
not: bool,
/// values
rhs: Vec<Box<Expr>>,
},
/// `IN` subselect
InSelect {
/// expression
lhs: Box<Expr>,
/// `NOT`
not: bool,
/// subquery
rhs: Select,
},
/// `IN` table name / function
InTable {
/// expression
lhs: Box<Expr>,
/// `NOT`
not: bool,
/// table name
rhs: QualifiedName,
/// table function arguments
args: Vec<Box<Expr>>,
},
/// `IS NULL`
IsNull(Box<Expr>),
/// `LIKE`
Like {
/// expression
lhs: Box<Expr>,
/// `NOT`
not: bool,
/// operator
op: LikeOperator,
/// pattern
rhs: Box<Expr>,
/// `ESCAPE` char
escape: Option<Box<Expr>>,
},
/// Literal expression
Literal(Literal),
/// Name
Name(Name),
/// `NOT NULL` or `NOTNULL`
NotNull(Box<Expr>),
/// Parenthesized subexpression
Parenthesized(Vec<Box<Expr>>),
/// Qualified name
Qualified(Name, Name),
/// `RAISE` function call
Raise(ResolveType, Option<Box<Expr>>),
/// Subquery expression
Subquery(Select),
/// Unary expression
Unary(UnaryOperator, Box<Expr>),
/// Parameters
Variable(String),
}
impl Expr {
pub fn into_boxed(self) -> Box<Expr> {
Box::new(self)
}
pub fn unary(operator: UnaryOperator, expr: Expr) -> Expr {
Expr::Unary(operator, Box::new(expr))
}
pub fn binary(lhs: Expr, operator: Operator, rhs: Expr) -> Expr {
Expr::Binary(Box::new(lhs), operator, Box::new(rhs))
}
pub fn not_null(expr: Expr) -> Expr {
Expr::NotNull(Box::new(expr))
}
pub fn between(lhs: Expr, not: bool, start: Expr, end: Expr) -> Expr {
Expr::Between {
lhs: Box::new(lhs),
not,
start: Box::new(start),
end: Box::new(end),
}
}
pub fn in_select(lhs: Expr, not: bool, select: Select) -> Expr {
Expr::InSelect {
lhs: Box::new(lhs),
not,
rhs: select,
}
}
pub fn like(
lhs: Expr,
not: bool,
operator: LikeOperator,
rhs: Expr,
escape: Option<Expr>,
) -> Expr {
Expr::Like {
lhs: Box::new(lhs),
not,
op: operator,
rhs: Box::new(rhs),
escape: escape.map(Box::new),
}
}
pub fn is_null(expr: Expr) -> Expr {
Expr::IsNull(Box::new(expr))
}
pub fn collate(expr: Expr, name: Name) -> Expr {
Expr::Collate(Box::new(expr), name)
}
pub fn cast(expr: Expr, type_name: Option<Type>) -> Expr {
Expr::Cast {
expr: Box::new(expr),
type_name,
}
}
pub fn raise(resolve_type: ResolveType, expr: Option<Expr>) -> Expr {
Expr::Raise(resolve_type, expr.map(Box::new))
}
}
/// SQL literal
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Literal {
/// Number
Numeric(String),
/// String
// TODO Check that string is already quoted and correctly escaped
String(String),
/// BLOB
// TODO Check that string is valid (only hexa)
Blob(String),
/// Keyword
Keyword(String),
/// `NULL`
Null,
/// `CURRENT_DATE`
CurrentDate,
/// `CURRENT_TIME`
CurrentTime,
/// `CURRENT_TIMESTAMP`
CurrentTimestamp,
}
/// Textual comparison operator in an expression
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LikeOperator {
/// `GLOB`
Glob,
/// `LIKE`
Like,
/// `MATCH`
Match,
/// `REGEXP`
Regexp,
}
/// SQL operators
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Operator {
/// `+`
Add,
/// `AND`
And,
/// `->`
ArrowRight,
/// `->>`
ArrowRightShift,
/// `&`
BitwiseAnd,
/// `|`
BitwiseOr,
/// `~`
BitwiseNot,
/// String concatenation (`||`)
Concat,
/// `=` or `==`
Equals,
/// `/`
Divide,
/// `>`
Greater,
/// `>=`
GreaterEquals,
/// `IS`
Is,
/// `IS NOT`
IsNot,
/// `<<`
LeftShift,
/// `<`
Less,
/// `<=`
LessEquals,
/// `%`
Modulus,
/// `*`
Multiply,
/// `!=` or `<>`
NotEquals,
/// `OR`
Or,
/// `>>`
RightShift,
/// `-`
Subtract,
}
impl Operator {
/// returns whether order of operations can be ignored
pub fn is_commutative(&self) -> bool {
matches!(
self,
Operator::Add
| Operator::Multiply
| Operator::BitwiseAnd
| Operator::BitwiseOr
| Operator::Equals
| Operator::NotEquals
)
}
/// Returns true if this operator is a comparison operator that may need affinity conversion
pub fn is_comparison(&self) -> bool {
matches!(
self,
Self::Equals
| Self::NotEquals
| Self::Less
| Self::LessEquals
| Self::Greater
| Self::GreaterEquals
| Self::Is
| Self::IsNot
)
}
}
/// Unary operators
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UnaryOperator {
/// bitwise negation (`~`)
BitwiseNot,
/// negative-sign
Negative,
/// `NOT`
Not,
/// positive-sign
Positive,
}
/// `SELECT` statement
// https://sqlite.org/lang_select.html
// https://sqlite.org/syntax/factored-select-stmt.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Select {
/// CTE
pub with: Option<With>,
/// body
pub body: SelectBody,
/// `ORDER BY`
pub order_by: Vec<SortedColumn>, // ORDER BY term does not match any column in the result set
/// `LIMIT`
pub limit: Option<Limit>,
}
/// `SELECT` body
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SelectBody {
/// first select
pub select: OneSelect,
/// compounds
pub compounds: Vec<CompoundSelect>,
}
/// Compound select
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CompoundSelect {
/// operator
pub operator: CompoundOperator,
/// select
pub select: OneSelect,
}
/// Compound operators
// https://sqlite.org/syntax/compound-operator.html
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CompoundOperator {
/// `UNION`
Union,
/// `UNION ALL`
UnionAll,
/// `EXCEPT`
Except,
/// `INTERSECT`
Intersect,
}
/// `SELECT` core
// https://sqlite.org/syntax/select-core.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum OneSelect {
/// `SELECT`
Select {
/// `DISTINCT`
distinctness: Option<Distinctness>,
/// columns
columns: Vec<ResultColumn>,
/// `FROM` clause
from: Option<FromClause>,
/// `WHERE` clause
where_clause: Option<Box<Expr>>,
/// `GROUP BY`
group_by: Option<GroupBy>,
/// `WINDOW` definition
window_clause: Vec<WindowDef>,
},
/// `VALUES`
Values(Vec<Vec<Box<Expr>>>),
}
/// `SELECT` ... `FROM` clause
// https://sqlite.org/syntax/join-clause.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FromClause {
/// table
pub select: Box<SelectTable>, // FIXME mandatory
/// `JOIN`ed tabled
pub joins: Vec<JoinedSelectTable>,
}
/// `SELECT` distinctness
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Distinctness {
/// `DISTINCT`
Distinct,
/// `ALL`
All,
}
/// `SELECT` or `RETURNING` result column
// https://sqlite.org/syntax/result-column.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ResultColumn {
/// expression
Expr(Box<Expr>, Option<As>),
/// `*`
Star,
/// table name.`*`
TableStar(Name),
}
/// Alias
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum As {
/// `AS`
As(Name),
/// no `AS`
Elided(Name), // FIXME Ids
}
/// `JOIN` clause
// https://sqlite.org/syntax/join-clause.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct JoinedSelectTable {
/// operator
pub operator: JoinOperator,
/// table
pub table: Box<SelectTable>,
/// constraint
pub constraint: Option<JoinConstraint>,
}
/// Table or subquery
// https://sqlite.org/syntax/table-or-subquery.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SelectTable {
/// table
Table(QualifiedName, Option<As>, Option<Indexed>),
/// table function call
TableCall(QualifiedName, Vec<Box<Expr>>, Option<As>),
/// `SELECT` subquery
Select(Select, Option<As>),
/// subquery
Sub(FromClause, Option<As>),
}
/// Join operators
// https://sqlite.org/syntax/join-operator.html
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum JoinOperator {
/// `,`
Comma,
/// `JOIN`
TypedJoin(Option<JoinType>),
}
// https://github.com/sqlite/sqlite/blob/80511f32f7e71062026edd471913ef0455563964/src/select.c#L197-L257
bitflags::bitflags! {
/// `JOIN` types
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct JoinType: u8 {
/// `INNER`
const INNER = 0x01;
/// `CROSS` => INNER|CROSS
const CROSS = 0x02;
/// `NATURAL`
const NATURAL = 0x04;
/// `LEFT` => LEFT|OUTER
const LEFT = 0x08;
/// `RIGHT` => RIGHT|OUTER
const RIGHT = 0x10;
/// `OUTER`
const OUTER = 0x20;
}
}
/// `JOIN` constraint
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum JoinConstraint {
/// `ON`
On(Box<Expr>),
/// `USING`: col names
Using(Vec<Name>),
}
/// `GROUP BY`
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GroupBy {
/// expressions
pub exprs: Vec<Box<Expr>>,
/// `HAVING`
pub having: Option<Box<Expr>>, // HAVING clause on a non-aggregate query
}
/// identifier or string or `CROSS` or `FULL` or `INNER` or `LEFT` or `NATURAL` or `OUTER` or `RIGHT`.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Name {
/// Identifier
Ident(String),
/// Quoted values
Quoted(String),
}
impl Name {
pub fn new(s: impl AsRef<str>) -> Self {
let s = s.as_ref();
let bytes = s.as_bytes();
if s.is_empty() {
return Name::Ident(s.to_string());
}
match bytes[0] {
b'"' | b'\'' | b'`' | b'[' => Name::Quoted(s.to_string()),
_ => Name::Ident(s.to_string()),
}
}
pub fn as_str(&self) -> &str {
match self {
Name::Ident(s) | Name::Quoted(s) => s.as_str(),
}
}
/// Checks if a name represents a double-quoted string that should get fallback behavior
pub fn is_double_quoted(&self) -> bool {
if let Self::Quoted(ident) = self {
return ident.starts_with("\"");
}
false
}
}
/// Qualified name
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct QualifiedName {
/// schema
pub db_name: Option<Name>,
/// object name
pub name: Name,
/// alias
pub alias: Option<Name>, // FIXME restrict alias usage (fullname vs xfullname)
}
impl QualifiedName {
/// Constructor
pub fn single(name: Name) -> Self {
Self {
db_name: None,
name,
alias: None,
}
}
/// Constructor
pub fn fullname(db_name: Name, name: Name) -> Self {
Self {
db_name: Some(db_name),
name,
alias: None,
}
}
/// Constructor
pub fn xfullname(db_name: Name, name: Name, alias: Name) -> Self {
Self {
db_name: Some(db_name),
name,
alias: Some(alias),
}
}
/// Constructor
pub fn alias(name: Name, alias: Name) -> Self {
Self {
db_name: None,
name,
alias: Some(alias),
}
}
}
/// `ALTER TABLE` body
// https://sqlite.org/lang_altertable.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AlterTableBody {
/// `RENAME TO`: new table name
RenameTo(Name),
/// `ADD COLUMN`
AddColumn(ColumnDefinition), // TODO distinction between ADD and ADD COLUMN
/// `ALTER COLUMN`
AlterColumn { old: Name, new: ColumnDefinition },
/// `RENAME COLUMN`
RenameColumn {
/// old name
old: Name,
/// new name
new: Name,
},
/// `DROP COLUMN`
DropColumn(Name), // TODO distinction between DROP and DROP COLUMN
}
/// `CREATE TABLE` body
// https://sqlite.org/lang_createtable.html
// https://sqlite.org/syntax/create-table-stmt.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CreateTableBody {
/// columns and constraints
ColumnsAndConstraints {
/// table column definitions
columns: Vec<ColumnDefinition>,
/// table constraints
constraints: Vec<NamedTableConstraint>,
/// table options
options: TableOptions,
},
/// `AS` select
AsSelect(Select),
}
/// Table column definition
// https://sqlite.org/syntax/column-def.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ColumnDefinition {
/// column name
pub col_name: Name,
/// column type
pub col_type: Option<Type>,
/// column constraints
pub constraints: Vec<NamedColumnConstraint>,
}
/// Named column constraint
// https://sqlite.org/syntax/column-constraint.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NamedColumnConstraint {
/// constraint name
pub name: Option<Name>,
/// constraint
pub constraint: ColumnConstraint,
}
/// Column constraint
// https://sqlite.org/syntax/column-constraint.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ColumnConstraint {
/// `PRIMARY KEY`
PrimaryKey {
/// `ASC` / `DESC`
order: Option<SortOrder>,
/// `ON CONFLICT` clause
conflict_clause: Option<ResolveType>,
/// `AUTOINCREMENT`
auto_increment: bool,
},
/// `NULL`
NotNull {
/// `NOT`
nullable: bool,
/// `ON CONFLICT` clause
conflict_clause: Option<ResolveType>,
},
/// `UNIQUE`
Unique(Option<ResolveType>),
/// `CHECK`
Check(Box<Expr>),
/// `DEFAULT`
Default(Box<Expr>),
/// `COLLATE`
Collate {
/// collation name
collation_name: Name, // FIXME Ids
},
/// `REFERENCES` foreign-key clause
ForeignKey {
/// clause
clause: ForeignKeyClause,
/// `DEFERRABLE`
deref_clause: Option<DeferSubclause>,
},
/// `GENERATED`
Generated {
/// expression
expr: Box<Expr>,
/// `STORED` / `VIRTUAL`
typ: Option<Name>,
},
}
/// Named table constraint
// https://sqlite.org/syntax/table-constraint.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NamedTableConstraint {
/// constraint name
pub name: Option<Name>,
/// constraint
pub constraint: TableConstraint,
}
/// Table constraint
// https://sqlite.org/syntax/table-constraint.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TableConstraint {
/// `PRIMARY KEY`
PrimaryKey {
/// columns
columns: Vec<SortedColumn>,
/// `AUTOINCREMENT`
auto_increment: bool,
/// `ON CONFLICT` clause
conflict_clause: Option<ResolveType>,
},
/// `UNIQUE`
Unique {
/// columns
columns: Vec<SortedColumn>,
/// `ON CONFLICT` clause
conflict_clause: Option<ResolveType>,
},
/// `CHECK`
Check(Box<Expr>),
/// `FOREIGN KEY`
ForeignKey {
/// columns
columns: Vec<IndexedColumn>,
/// `REFERENCES`
clause: ForeignKeyClause,
/// `DEFERRABLE`
deref_clause: Option<DeferSubclause>,
},
}
bitflags::bitflags! {
/// `CREATE TABLE` options
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TableOptions: u8 {
/// None
const NONE = 0;
/// `WITHOUT ROWID`
const WITHOUT_ROWID = 1;
/// `STRICT`
const STRICT = 2;
}
}
/// Sort orders
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SortOrder {
/// `ASC`
Asc,
/// `DESC`
Desc,
}
/// `NULLS FIRST` or `NULLS LAST`
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NullsOrder {
/// `NULLS FIRST`
First,
/// `NULLS LAST`
Last,
}
/// `REFERENCES` clause
// https://sqlite.org/syntax/foreign-key-clause.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ForeignKeyClause {
/// foreign table name
pub tbl_name: Name,
/// foreign table columns
pub columns: Vec<IndexedColumn>,
/// referential action(s) / deferrable option(s)
pub args: Vec<RefArg>,
}
/// foreign-key reference args
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RefArg {
/// `ON DELETE`
OnDelete(RefAct),
/// `ON INSERT`
OnInsert(RefAct),
/// `ON UPDATE`
OnUpdate(RefAct),
/// `MATCH`
Match(Name),
}
/// foreign-key reference actions
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RefAct {
/// `SET NULL`
SetNull,
/// `SET DEFAULT`
SetDefault,
/// `CASCADE`
Cascade,
/// `RESTRICT`
Restrict,
/// `NO ACTION`
NoAction,
}
/// foreign-key defer clause
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DeferSubclause {
/// `DEFERRABLE`
pub deferrable: bool,
/// `INITIALLY` `DEFERRED` / `IMMEDIATE`
pub init_deferred: Option<InitDeferredPred>,
}
/// `INITIALLY` `DEFERRED` / `IMMEDIATE`
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum InitDeferredPred {
/// `INITIALLY DEFERRED`
InitiallyDeferred,
/// `INITIALLY IMMEDIATE`
InitiallyImmediate, // default
}
/// Indexed column
// https://sqlite.org/syntax/indexed-column.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IndexedColumn {
/// column name
pub col_name: Name,
/// `COLLATE`
pub collation_name: Option<Name>, // FIXME Ids
/// `ORDER BY`
pub order: Option<SortOrder>,
}
/// `INDEXED BY` / `NOT INDEXED`
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Indexed {
/// `INDEXED BY`: idx name
IndexedBy(Name),
/// `NOT INDEXED`
NotIndexed,
}
/// Sorted column
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SortedColumn {
/// expression
pub expr: Box<Expr>,
/// `ASC` / `DESC`
pub order: Option<SortOrder>,
/// `NULLS FIRST` / `NULLS LAST`
pub nulls: Option<NullsOrder>,
}
/// `LIMIT`
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Limit {
/// count
pub expr: Box<Expr>,
/// `OFFSET`
pub offset: Option<Box<Expr>>, // TODO distinction between LIMIT offset, count and LIMIT count OFFSET offset
}
/// `INSERT` body
// https://sqlite.org/lang_insert.html
// https://sqlite.org/syntax/insert-stmt.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(clippy::large_enum_variant)]
pub enum InsertBody {
/// `SELECT` or `VALUES`
Select(Select, Option<Box<Upsert>>),
/// `DEFAULT VALUES`
DefaultValues,
}
/// `UPDATE ... SET`
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Set {
/// column name(s)
pub col_names: Vec<Name>,
/// expression
pub expr: Box<Expr>,
}
/// `PRAGMA` body
// https://sqlite.org/syntax/pragma-stmt.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PragmaBody {
/// `=`
Equals(PragmaValue),
/// function call
Call(PragmaValue),
}
/// `PRAGMA` value
// https://sqlite.org/syntax/pragma-value.html
pub type PragmaValue = Box<Expr>; // TODO
/// `PRAGMA` value
// https://sqlite.org/pragma.html
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, EnumString, strum::Display)]
#[strum(serialize_all = "snake_case")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PragmaName {
/// Returns the application ID of the database file.
ApplicationId,
/// set the autovacuum mode
AutoVacuum,
/// `cache_size` pragma
CacheSize,
/// encryption cipher algorithm name for encrypted databases
#[strum(serialize = "cipher")]
#[cfg_attr(feature = "serde", serde(rename = "cipher"))]
EncryptionCipher,
/// List databases
DatabaseList,
/// Encoding - only support utf8
Encoding,
/// Current free page count.
FreelistCount,
/// Run integrity check on the database file
IntegrityCheck,
/// `journal_mode` pragma
JournalMode,
/// encryption key for encrypted databases, specified as hexadecimal string.
#[strum(serialize = "hexkey")]
#[cfg_attr(feature = "serde", serde(rename = "hexkey"))]
EncryptionKey,
/// Noop as per SQLite docs
LegacyFileFormat,
/// Set or get the maximum number of pages in the database file.
MaxPageCount,
/// `module_list` pragma
/// `module_list` lists modules used by virtual tables.
ModuleList,
/// Return the total number of pages in the database file.
PageCount,
/// Return the page size of the database in bytes.
PageSize,
/// make connection query only
QueryOnly,
/// Returns schema version of the database file.
SchemaVersion,
/// Control database synchronization mode (OFF | FULL | NORMAL | EXTRA)
Synchronous,
/// returns information about the columns of a table
TableInfo,
/// enable capture-changes logic for the connection
UnstableCaptureDataChangesConn,
/// Returns the user version of the database file.
UserVersion,
/// trigger a checkpoint to run on database(s) if WAL is enabled
WalCheckpoint,
}
/// `CREATE TRIGGER` time
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TriggerTime {
/// `BEFORE`
Before, // default
/// `AFTER`
After,
/// `INSTEAD OF`
InsteadOf,
}
/// `CREATE TRIGGER` event
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TriggerEvent {
/// `DELETE`
Delete,
/// `INSERT`
Insert,
/// `UPDATE`
Update,
/// `UPDATE OF`: col names
UpdateOf(Vec<Name>),
}
/// `CREATE TRIGGER` command
// https://sqlite.org/lang_createtrigger.html
// https://sqlite.org/syntax/create-trigger-stmt.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TriggerCmd {
/// `UPDATE`
Update {
/// `OR`
or_conflict: Option<ResolveType>,
/// table name
tbl_name: Name,
/// `SET` assignments
sets: Vec<Set>,
/// `FROM`
from: Option<FromClause>,
/// `WHERE` clause
where_clause: Option<Box<Expr>>,
},
/// `INSERT`
Insert {
/// `OR`
or_conflict: Option<ResolveType>,
/// table name
tbl_name: Name,
/// `COLUMNS`
col_names: Vec<Name>,
/// `SELECT` or `VALUES`
select: Select,
/// `ON CONFLICT` clause
upsert: Option<Box<Upsert>>,
/// `RETURNING`
returning: Vec<ResultColumn>,
},
/// `DELETE`
Delete {
/// table name
tbl_name: Name,
/// `WHERE` clause
where_clause: Option<Box<Expr>>,
},
/// `SELECT`
Select(Select),
}
/// Conflict resolution types
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ResolveType {
/// `ROLLBACK`
Rollback,
/// `ABORT`
Abort, // default
/// `FAIL`
Fail,
/// `IGNORE`
Ignore,
/// `REPLACE`
Replace,
}
impl ResolveType {
/// Get the OE_XXX bit value
pub fn bit_value(&self) -> usize {
match self {
ResolveType::Rollback => 1,
ResolveType::Abort => 2,
ResolveType::Fail => 3,
ResolveType::Ignore => 4,
ResolveType::Replace => 5,
}
}
}
/// `WITH` clause
// https://sqlite.org/lang_with.html
// https://sqlite.org/syntax/with-clause.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct With {
/// `RECURSIVE`
pub recursive: bool,
/// CTEs
pub ctes: Vec<CommonTableExpr>,
}
/// CTE materialization
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Materialized {
/// No hint
Any,
/// `MATERIALIZED`
Yes,
/// `NOT MATERIALIZED`
No,
}
/// CTE
// https://sqlite.org/syntax/common-table-expression.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CommonTableExpr {
/// table name
pub tbl_name: Name,
/// table columns
pub columns: Vec<IndexedColumn>, // check no duplicate
/// `MATERIALIZED`
pub materialized: Materialized,
/// query
pub select: Select,
}
/// Column type
// https://sqlite.org/syntax/type-name.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Type {
/// type name
pub name: String, // TODO Validate: Ids+
/// type size
pub size: Option<TypeSize>,
}
/// Column type size limit(s)
// https://sqlite.org/syntax/type-name.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TypeSize {
/// maximum size
MaxSize(Box<Expr>),
/// precision
TypeSize(Box<Expr>, Box<Expr>),
}
/// Transaction types
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TransactionType {
/// `DEFERRED`
Deferred, // default
/// `IMMEDIATE`
Immediate,
/// `EXCLUSIVE`
Exclusive,
/// `CONCURRENT`,
Concurrent,
}
/// Upsert clause
// https://sqlite.org/lang_upsert.html
// https://sqlite.org/syntax/upsert-clause.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Upsert {
/// conflict targets
pub index: Option<UpsertIndex>,
/// `DO` clause
pub do_clause: UpsertDo,
/// next upsert
pub next: Option<Box<Upsert>>,
}
/// Upsert conflict targets
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UpsertIndex {
/// columns
pub targets: Vec<SortedColumn>,
/// `WHERE` clause
pub where_clause: Option<Box<Expr>>,
}
/// Upsert `DO` action
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UpsertDo {
/// `SET`
Set {
/// assignments
sets: Vec<Set>,
/// `WHERE` clause
where_clause: Option<Box<Expr>>,
},
/// `NOTHING`
Nothing,
}
/// Function call tail
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FunctionTail {
/// `FILTER` clause
pub filter_clause: Option<Box<Expr>>,
/// `OVER` clause
pub over_clause: Option<Over>,
}
/// Function call `OVER` clause
// https://sqlite.org/syntax/over-clause.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Over {
/// Window definition
Window(Window),
/// Window name
Name(Name),
}
/// `OVER` window definition
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WindowDef {
/// window name
pub name: Name,
/// window definition
pub window: Window,
}
/// Window definition
// https://sqlite.org/syntax/window-defn.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Window {
/// base window name
pub base: Option<Name>,
/// `PARTITION BY`
pub partition_by: Vec<Box<Expr>>,
/// `ORDER BY`
pub order_by: Vec<SortedColumn>,
/// frame spec
pub frame_clause: Option<FrameClause>,
}
/// Frame specification
// https://sqlite.org/syntax/frame-spec.html
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FrameClause {
/// unit
pub mode: FrameMode,
/// start bound
pub start: FrameBound,
/// end bound
pub end: Option<FrameBound>,
/// `EXCLUDE`
pub exclude: Option<FrameExclude>,
}
/// Frame modes
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FrameMode {
/// `GROUPS`
Groups,
/// `RANGE`
Range,
/// `ROWS`
Rows,
}
/// Frame bounds
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FrameBound {
/// `CURRENT ROW`
CurrentRow,
/// `FOLLOWING`
Following(Box<Expr>),
/// `PRECEDING`
Preceding(Box<Expr>),
/// `UNBOUNDED FOLLOWING`
UnboundedFollowing,
/// `UNBOUNDED PRECEDING`
UnboundedPreceding,
}
/// Frame exclusions
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FrameExclude {
/// `NO OTHERS`
NoOthers,
/// `CURRENT ROW`
CurrentRow,
/// `GROUP`
Group,
/// `TIES`
Ties,
}