mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-14 05:34:20 +01:00
Closes #1282 # Support for WHERE clause subqueries This PR implements support for subqueries that appear in the WHERE clause of SELECT statements. ## What are those lol 1. **EXISTS subqueries**: `WHERE EXISTS (SELECT ...)` 2. **Row value subqueries**: `WHERE x = (SELECT ...)` or `WHERE (x, y) = (SELECT ...)`. The latter are not yet supported - only the single-column ("scalar subquery") case is. 3. **IN subqueries**: `WHERE x IN (SELECT ...)` or `WHERE (x, y) IN (SELECT ...)` ## Correlated vs Uncorrelated Subqueries - **Uncorrelated subqueries** reference only their own tables and can be evaluated once. - **Correlated subqueries** reference columns from the outer query (e.g., `WHERE EXISTS (SELECT * FROM t2 WHERE t2.id = t1.id)`) and must be re-evaluated for each row of the outer query ## Implementation ### Planning During query planning, the WHERE clause is walked to find subquery expressions (`Expr::Exists`, `Expr::Subquery`, `Expr::InSelect`). Each subquery is: 1. Assigned a unique internal ID 2. Compiled into its own `SelectPlan` with outer query tables provided as available references 3. Replaced in the AST with an `Expr::SubqueryResult` node that references the subquery with its internal ID 4. Stored in a `Vec<NonFromClauseSubquery>` on the `SelectPlan` For IN subqueries, an ephemeral index is created to store the subquery results; for other kinds, the results are stored in register(s). ### Translation Before emitting bytecode, we need to determine when each subquery should be evaluated: - **Uncorrelated**: Evaluated once before opening any table cursors - **Correlated**: Evaluated at the appropriate nested loop depth after all referenced outer tables are in scope This is calculated by examining which outer query tables the subquery references and finding the right-most (innermost) loop that opens those tables - using similar mechanisms that we use for figuring out when to evaluate other `WhereTerm`s too. ### Code Generation - **EXISTS**: Sets a register to 1 if any row is produced, 0 otherwise. Has new `QueryDestination::ExistsSubqueryResult` variant. - **IN**: Results stored in an ephemeral index and the index is probed. - **RowValue**: Results stored in a range of registers. Has new `QueryDestination::RowValueSubqueryResult` variant. ## Annoying details ### Which cursor to read from in a subquery? Sometimes a query will use a covering index, i.e. skip opening the table cursor at all if the index contains All The Needed Stuff. Correlated subqueries reading columns from outer tables is a bit problematic in this regard: with our current translation code, the subquery doesn't know whether the outer query opened a table cursor, index cursor, or both. So, for now, we try to find a table cursor first, then fall back to finding any index cursor for that table. Reviewed-by: Preston Thorpe <preston@turso.tech> Closes #3847
1828 lines
48 KiB
Rust
1828 lines
48 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,
|
|
/// USING module
|
|
using: Option<Name>,
|
|
/// indexed columns or expressions
|
|
columns: Vec<SortedColumn>,
|
|
/// WITH parameters
|
|
with_clause: Vec<(Name, Box<Expr>)>,
|
|
/// 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),
|
|
/// Subqueries from e.g. the WHERE clause are planned separately
|
|
/// and their results will be placed in registers or in an ephemeral index
|
|
/// pointed to by this type.
|
|
SubqueryResult {
|
|
/// Internal "opaque" identifier for the subquery. When the translator encounters
|
|
/// a [Expr::SubqueryResult], it needs to know which subquery in the corresponding
|
|
/// query plan it references.
|
|
subquery_id: TableInternalId,
|
|
/// Left-hand side expression for IN subqueries.
|
|
/// This property plus 'not_in' are only relevant for IN subqueries,
|
|
/// and the reason they are not included in the [SubqueryType] enum is so that
|
|
/// we don't have to clone this Box.
|
|
lhs: Option<Box<Expr>>,
|
|
/// Whether the IN subquery is a NOT IN subquery.
|
|
not_in: bool,
|
|
/// The type of subquery.
|
|
query_type: SubqueryType,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub enum SubqueryType {
|
|
/// EXISTS subquery; result is stored in a single register.
|
|
Exists { result_reg: usize },
|
|
/// Row value subquery; result is stored in a range of registers.
|
|
/// Example: x = (SELECT ...) or (x, y) = (SELECT ...)
|
|
RowValue {
|
|
result_reg_start: usize,
|
|
num_regs: usize,
|
|
},
|
|
/// IN subquery; result is stored in an ephemeral index.
|
|
/// Example: x <NOT> IN (SELECT ...)
|
|
In { cursor_id: usize },
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
pub fn can_be_null(&self) -> bool {
|
|
// todo: better handling columns. Check sqlite3ExprCanBeNull
|
|
match self {
|
|
Expr::Literal(literal) => !matches!(
|
|
literal,
|
|
Literal::Numeric(_) | Literal::String(_) | Literal::Blob(_)
|
|
),
|
|
_ => true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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)]
|
|
pub struct Name {
|
|
quote: Option<char>,
|
|
value: String,
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl serde::Serialize for Name {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_str(&self.value)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<'de> serde::Deserialize<'de> for Name {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
struct NameVisitor;
|
|
impl<'de> serde::de::Visitor<'de> for NameVisitor {
|
|
type Value = Name;
|
|
|
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
formatter.write_str("a string")
|
|
}
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: serde::de::Error,
|
|
{
|
|
Ok(Name::from_bytes(v.as_bytes()))
|
|
}
|
|
}
|
|
deserializer.deserialize_str(NameVisitor)
|
|
}
|
|
}
|
|
|
|
impl Name {
|
|
/// Create name which will have exactly the value of given string
|
|
/// (e.g. if s = "\"str\"" - the name value will contain quotes and translation to SQL will give us """str""")
|
|
pub fn exact(s: String) -> Self {
|
|
Self {
|
|
value: s,
|
|
quote: None,
|
|
}
|
|
}
|
|
/// Parse name from the bytes (e.g. handle quoting and handle escaped quotes)
|
|
pub fn from_bytes(s: &[u8]) -> Self {
|
|
Self::from_string(unsafe { std::str::from_utf8_unchecked(s) })
|
|
}
|
|
/// Parse name from the string (e.g. handle quoting and handle escaped quotes)
|
|
pub fn from_string(s: impl AsRef<str>) -> Self {
|
|
let s = s.as_ref();
|
|
let bytes = s.as_bytes();
|
|
|
|
if s.is_empty() {
|
|
return Name::exact(s.to_string());
|
|
}
|
|
|
|
if matches!(bytes[0], b'"' | b'\'' | b'`') {
|
|
assert!(s.len() >= 2);
|
|
assert!(bytes[bytes.len() - 1] == bytes[0]);
|
|
let s = match bytes[0] {
|
|
b'"' => s[1..s.len() - 1].replace("\"\"", "\""),
|
|
b'\'' => s[1..s.len() - 1].replace("''", "'"),
|
|
b'`' => s[1..s.len() - 1].replace("``", "`"),
|
|
_ => unreachable!(),
|
|
};
|
|
Name {
|
|
value: s,
|
|
quote: Some(bytes[0] as char),
|
|
}
|
|
} else if bytes[0] == b'[' {
|
|
assert!(s.len() >= 2);
|
|
assert!(bytes[bytes.len() - 1] == b']');
|
|
Name::exact(s[1..s.len() - 1].to_string())
|
|
} else {
|
|
Name::exact(s.to_string())
|
|
}
|
|
}
|
|
|
|
/// Return string value of the name
|
|
pub fn as_str(&self) -> &str {
|
|
&self.value
|
|
}
|
|
|
|
/// Convert value to the string literal (e.g. single-quoted string with escaped single quotes)
|
|
pub fn as_literal(&self) -> String {
|
|
format!("'{}'", self.value.replace("'", "''"))
|
|
}
|
|
|
|
/// Convert value to the name string (e.g. double-quoted string with escaped double quotes)
|
|
pub fn as_ident(&self) -> String {
|
|
// let's keep original quotes if they were set
|
|
// (parser.rs tests validates that behaviour)
|
|
if let Some(quote) = self.quote {
|
|
let single = quote.to_string();
|
|
let double = single.clone() + &single;
|
|
return format!("{}{}{}", quote, self.value.replace(&single, &double), quote);
|
|
}
|
|
let value = self.value.as_bytes();
|
|
let safe_char = |&c: &u8| c.is_ascii_alphanumeric() || c == b'_';
|
|
if !value.is_empty() && value.iter().all(safe_char) {
|
|
self.value.clone()
|
|
} else {
|
|
format!("\"{}\"", self.value.replace("\"", "\"\""))
|
|
}
|
|
}
|
|
|
|
/// Checks if a name represents a quoted string that should get fallback behavior
|
|
/// Need to detect legacy conversion of double quoted keywords to string literals
|
|
/// (see https://sqlite.org/lang_keywords.html)
|
|
///
|
|
/// Also, used to detect string literals in PRAGMA cases
|
|
pub fn quoted_with(&self, quote: char) -> bool {
|
|
self.quote == Some(quote)
|
|
}
|
|
|
|
pub fn quoted(&self) -> bool {
|
|
self.quote.is_some()
|
|
}
|
|
}
|
|
|
|
/// 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))]
|
|
#[cfg_attr(feature = "simulator", derive(strum::EnumDiscriminants))]
|
|
#[cfg_attr(
|
|
feature = "simulator",
|
|
strum_discriminants(derive(strum::VariantArray))
|
|
)]
|
|
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`
|
|
defer_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`
|
|
defer_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,
|
|
/// set the busy_timeout (see https://www.sqlite.org/pragma.html#pragma_busy_timeout)
|
|
BusyTimeout,
|
|
/// `cache_size` pragma
|
|
CacheSize,
|
|
/// encryption cipher algorithm name for encrypted databases
|
|
#[strum(serialize = "cipher")]
|
|
#[cfg_attr(feature = "serde", serde(rename = "cipher"))]
|
|
EncryptionCipher,
|
|
/// Control fsync error retry behavior (0 = off/panic, 1 = on/retry)
|
|
DataSyncRetry,
|
|
/// List databases
|
|
DatabaseList,
|
|
/// Encoding - only support utf8
|
|
Encoding,
|
|
/// Current free page count.
|
|
FreelistCount,
|
|
/// Enable or disable foreign key constraint enforcement
|
|
ForeignKeys,
|
|
/// 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,
|
|
/// Sets or queries the threshold (in bytes) at which MVCC triggers an automatic checkpoint.
|
|
MvccCheckpointThreshold,
|
|
}
|
|
|
|
/// `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,
|
|
}
|