mod display;
pub mod rebase;
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
pub start: u32,
pub end: u32,
}
impl Span {
#[must_use]
pub const fn new(start: u32, end: u32) -> Self {
Self { start, end }
}
pub const ZERO: Self = Self { start: 0, end: 0 };
#[must_use]
pub const fn merge(self, other: Self) -> Self {
let start = if self.start < other.start {
self.start
} else {
other.start
};
let end = if self.end > other.end {
self.end
} else {
other.end
};
Self { start, end }
}
#[must_use]
pub const fn len(self) -> u32 {
self.end - self.start
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.start == self.end
}
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Statement {
Select(SelectStatement),
Insert(InsertStatement),
Update(UpdateStatement),
Delete(DeleteStatement),
CreateTable(CreateTableStatement),
CreateIndex(CreateIndexStatement),
CreateView(CreateViewStatement),
CreateTrigger(CreateTriggerStatement),
CreateVirtualTable(CreateVirtualTableStatement),
Drop(DropStatement),
AlterTable(AlterTableStatement),
Begin(BeginStatement),
Commit,
Rollback(RollbackStatement),
Savepoint(String),
Release(String),
Attach(AttachStatement),
Detach(String),
Pragma(PragmaStatement),
Vacuum(VacuumStatement),
Reindex(Option<QualifiedName>),
Analyze(Option<QualifiedName>),
Explain { query_plan: bool, stmt: Box<Self> },
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct QualifiedName {
pub schema: Option<String>,
pub name: String,
}
impl QualifiedName {
#[must_use]
pub fn bare(name: impl Into<String>) -> Self {
Self {
schema: None,
name: name.into(),
}
}
#[must_use]
pub fn qualified(schema: impl Into<String>, name: impl Into<String>) -> Self {
Self {
schema: Some(schema.into()),
name: name.into(),
}
}
}
impl fmt::Display for QualifiedName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref s) = self.schema {
write!(f, "{s}.{}", self.name)
} else {
f.write_str(&self.name)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeName {
pub name: String,
pub arg1: Option<String>,
pub arg2: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Integer(i64),
Float(f64),
String(String),
Blob(Vec<u8>),
Null,
True,
False,
CurrentTime,
CurrentDate,
CurrentTimestamp,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ColumnRef {
pub table: Option<String>,
pub column: String,
}
impl ColumnRef {
#[must_use]
pub fn bare(column: impl Into<String>) -> Self {
Self {
table: None,
column: column.into(),
}
}
#[must_use]
pub fn qualified(table: impl Into<String>, column: impl Into<String>) -> Self {
Self {
table: Some(table.into()),
column: column.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BinaryOp {
Add,
Subtract,
Multiply,
Divide,
Modulo,
Concat,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
Is,
IsNot,
And,
Or,
BitAnd,
BitOr,
ShiftLeft,
ShiftRight,
}
impl fmt::Display for BinaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Add => "+",
Self::Subtract => "-",
Self::Multiply => "*",
Self::Divide => "/",
Self::Modulo => "%",
Self::Concat => "||",
Self::Eq => "=",
Self::Ne => "!=",
Self::Lt => "<",
Self::Le => "<=",
Self::Gt => ">",
Self::Ge => ">=",
Self::Is => "IS",
Self::IsNot => "IS NOT",
Self::And => "AND",
Self::Or => "OR",
Self::BitAnd => "&",
Self::BitOr => "|",
Self::ShiftLeft => "<<",
Self::ShiftRight => ">>",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UnaryOp {
Negate,
Plus,
BitNot,
Not,
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Negate => "-",
Self::Plus => "+",
Self::BitNot => "~",
Self::Not => "NOT",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LikeOp {
Like,
Glob,
Match,
Regexp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JsonArrow {
Arrow,
DoubleArrow,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Literal(Literal, Span),
Column(ColumnRef, Span),
BinaryOp {
left: Box<Self>,
op: BinaryOp,
right: Box<Self>,
span: Span,
},
UnaryOp {
op: UnaryOp,
expr: Box<Self>,
span: Span,
},
Between {
expr: Box<Self>,
low: Box<Self>,
high: Box<Self>,
not: bool,
span: Span,
},
In {
expr: Box<Self>,
set: InSet,
not: bool,
span: Span,
},
Like {
expr: Box<Self>,
pattern: Box<Self>,
escape: Option<Box<Self>>,
op: LikeOp,
not: bool,
span: Span,
},
Case {
operand: Option<Box<Self>>,
whens: Vec<(Self, Self)>,
else_expr: Option<Box<Self>>,
span: Span,
},
Cast {
expr: Box<Self>,
type_name: TypeName,
span: Span,
},
Exists {
subquery: Box<SelectStatement>,
not: bool,
span: Span,
},
Subquery(Box<SelectStatement>, Span),
FunctionCall {
name: String,
args: FunctionArgs,
distinct: bool,
filter: Option<Box<Self>>,
over: Option<WindowSpec>,
span: Span,
},
Collate {
expr: Box<Self>,
collation: String,
span: Span,
},
IsNull {
expr: Box<Self>,
not: bool,
span: Span,
},
Raise {
action: RaiseAction,
message: Option<String>,
span: Span,
},
JsonAccess {
expr: Box<Self>,
path: Box<Self>,
arrow: JsonArrow,
span: Span,
},
RowValue(Vec<Self>, Span),
Placeholder(PlaceholderType, Span),
}
impl Expr {
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Literal(_, s)
| Self::Column(_, s)
| Self::Subquery(_, s)
| Self::RowValue(_, s)
| Self::Placeholder(_, s) => *s,
Self::BinaryOp { span, .. }
| Self::UnaryOp { span, .. }
| Self::Between { span, .. }
| Self::In { span, .. }
| Self::Like { span, .. }
| Self::Case { span, .. }
| Self::Cast { span, .. }
| Self::Exists { span, .. }
| Self::FunctionCall { span, .. }
| Self::Collate { span, .. }
| Self::IsNull { span, .. }
| Self::Raise { span, .. }
| Self::JsonAccess { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum InSet {
List(Vec<Expr>),
Subquery(Box<SelectStatement>),
Table(QualifiedName),
}
#[derive(Debug, Clone, PartialEq)]
pub enum FunctionArgs {
Star,
List(Vec<Expr>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PlaceholderType {
Anonymous,
Numbered(u32),
ColonNamed(String),
AtNamed(String),
DollarNamed(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RaiseAction {
Ignore,
Rollback,
Abort,
Fail,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WindowSpec {
pub base_window: Option<String>,
pub partition_by: Vec<Expr>,
pub order_by: Vec<OrderingTerm>,
pub frame: Option<FrameSpec>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FrameSpec {
pub frame_type: FrameType,
pub start: FrameBound,
pub end: Option<FrameBound>,
pub exclude: Option<FrameExclude>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FrameType {
Rows,
Range,
Groups,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FrameBound {
UnboundedPreceding,
Preceding(Box<Expr>),
CurrentRow,
Following(Box<Expr>),
UnboundedFollowing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FrameExclude {
NoOthers,
CurrentRow,
Group,
Ties,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectStatement {
pub with: Option<WithClause>,
pub body: SelectBody,
pub order_by: Vec<OrderingTerm>,
pub limit: Option<LimitClause>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WithClause {
pub recursive: bool,
pub ctes: Vec<Cte>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Cte {
pub name: String,
pub columns: Vec<String>,
pub materialized: Option<CteMaterialized>,
pub query: SelectStatement,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CteMaterialized {
Materialized,
NotMaterialized,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SelectBody {
pub select: SelectCore,
pub compounds: Vec<(CompoundOp, SelectCore)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CompoundOp {
Union,
UnionAll,
Intersect,
Except,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SelectCore {
Select {
distinct: Distinctness,
columns: Vec<ResultColumn>,
from: Option<FromClause>,
where_clause: Option<Box<Expr>>,
group_by: Vec<Expr>,
having: Option<Box<Expr>>,
windows: Vec<WindowDef>,
},
Values(Vec<Vec<Expr>>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Distinctness {
#[default]
All,
Distinct,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResultColumn {
Star,
TableStar(String),
Expr { expr: Expr, alias: Option<String> },
}
#[derive(Debug, Clone, PartialEq)]
pub struct FromClause {
pub source: TableOrSubquery,
pub joins: Vec<JoinClause>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TableOrSubquery {
Table {
name: QualifiedName,
alias: Option<String>,
index_hint: Option<IndexHint>,
},
Subquery {
query: Box<SelectStatement>,
alias: Option<String>,
},
TableFunction {
name: String,
args: Vec<Expr>,
alias: Option<String>,
},
ParenJoin(Box<FromClause>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IndexHint {
IndexedBy(String),
NotIndexed,
}
#[derive(Debug, Clone, PartialEq)]
pub struct JoinClause {
pub join_type: JoinType,
pub table: TableOrSubquery,
pub constraint: Option<JoinConstraint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct JoinType {
pub natural: bool,
pub kind: JoinKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JoinKind {
Cross,
Inner,
Left,
Right,
Full,
}
#[derive(Debug, Clone, PartialEq)]
pub enum JoinConstraint {
On(Expr),
Using(Vec<String>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct WindowDef {
pub name: String,
pub spec: WindowSpec,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrderingTerm {
pub expr: Expr,
pub direction: Option<SortDirection>,
pub nulls: Option<NullsOrder>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SortDirection {
Asc,
Desc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NullsOrder {
First,
Last,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LimitClause {
pub limit: Expr,
pub offset: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct InsertStatement {
pub with: Option<WithClause>,
pub or_conflict: Option<ConflictAction>,
pub table: QualifiedName,
pub alias: Option<String>,
pub columns: Vec<String>,
pub source: InsertSource,
pub upsert: Vec<UpsertClause>,
pub returning: Vec<ResultColumn>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum InsertSource {
Values(Vec<Vec<Expr>>),
Select(Box<SelectStatement>),
DefaultValues,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConflictAction {
Rollback,
Abort,
Fail,
Ignore,
Replace,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpsertClause {
pub target: Option<UpsertTarget>,
pub action: UpsertAction,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpsertTarget {
pub columns: Vec<IndexedColumn>,
pub where_clause: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum UpsertAction {
Nothing,
Update {
assignments: Vec<Assignment>,
where_clause: Option<Box<Expr>>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpdateStatement {
pub with: Option<WithClause>,
pub or_conflict: Option<ConflictAction>,
pub table: QualifiedTableRef,
pub assignments: Vec<Assignment>,
pub from: Option<FromClause>,
pub where_clause: Option<Expr>,
pub returning: Vec<ResultColumn>,
pub order_by: Vec<OrderingTerm>,
pub limit: Option<LimitClause>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Assignment {
pub target: AssignmentTarget,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AssignmentTarget {
Column(String),
ColumnList(Vec<String>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QualifiedTableRef {
pub name: QualifiedName,
pub alias: Option<String>,
pub index_hint: Option<IndexHint>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeleteStatement {
pub with: Option<WithClause>,
pub table: QualifiedTableRef,
pub where_clause: Option<Expr>,
pub returning: Vec<ResultColumn>,
pub order_by: Vec<OrderingTerm>,
pub limit: Option<LimitClause>,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::struct_excessive_bools)]
pub struct CreateTableStatement {
pub if_not_exists: bool,
pub temporary: bool,
pub name: QualifiedName,
pub body: CreateTableBody,
pub without_rowid: bool,
pub strict: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CreateTableBody {
Columns {
columns: Vec<ColumnDef>,
constraints: Vec<TableConstraint>,
},
AsSelect(Box<SelectStatement>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ColumnDef {
pub name: String,
pub type_name: Option<TypeName>,
pub constraints: Vec<ColumnConstraint>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ColumnConstraint {
pub name: Option<String>,
pub kind: ColumnConstraintKind,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ColumnConstraintKind {
PrimaryKey {
direction: Option<SortDirection>,
conflict: Option<ConflictAction>,
autoincrement: bool,
},
NotNull {
conflict: Option<ConflictAction>,
},
Unique {
conflict: Option<ConflictAction>,
},
Check(Expr),
Default(DefaultValue),
Collate(String),
ForeignKey(ForeignKeyClause),
Generated {
expr: Expr,
storage: Option<GeneratedStorage>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum DefaultValue {
Expr(Expr),
ParenExpr(Expr),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GeneratedStorage {
Stored,
Virtual,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TableConstraint {
pub name: Option<String>,
pub kind: TableConstraintKind,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TableConstraintKind {
PrimaryKey {
columns: Vec<IndexedColumn>,
conflict: Option<ConflictAction>,
},
Unique {
columns: Vec<IndexedColumn>,
conflict: Option<ConflictAction>,
},
Check(Expr),
ForeignKey {
columns: Vec<String>,
clause: ForeignKeyClause,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct IndexedColumn {
pub expr: Expr,
pub collation: Option<String>,
pub direction: Option<SortDirection>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ForeignKeyClause {
pub table: String,
pub columns: Vec<String>,
pub actions: Vec<ForeignKeyAction>,
pub deferrable: Option<Deferrable>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ForeignKeyAction {
pub trigger: ForeignKeyTrigger,
pub action: ForeignKeyActionType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ForeignKeyTrigger {
OnDelete,
OnUpdate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ForeignKeyActionType {
SetNull,
SetDefault,
Cascade,
Restrict,
NoAction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Deferrable {
pub not: bool,
pub initially: Option<DeferrableInitially>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeferrableInitially {
Deferred,
Immediate,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateIndexStatement {
pub unique: bool,
pub if_not_exists: bool,
pub name: QualifiedName,
pub table: String,
pub columns: Vec<IndexedColumn>,
pub where_clause: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateViewStatement {
pub if_not_exists: bool,
pub temporary: bool,
pub name: QualifiedName,
pub columns: Vec<String>,
pub query: SelectStatement,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateTriggerStatement {
pub if_not_exists: bool,
pub temporary: bool,
pub name: QualifiedName,
pub timing: TriggerTiming,
pub event: TriggerEvent,
pub table: String,
pub for_each_row: bool,
pub when: Option<Expr>,
pub body: Vec<Statement>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TriggerTiming {
Before,
After,
InsteadOf,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TriggerEvent {
Insert,
Delete,
Update(Vec<String>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateVirtualTableStatement {
pub if_not_exists: bool,
pub name: QualifiedName,
pub module: String,
pub args: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DropStatement {
pub object_type: DropObjectType,
pub if_exists: bool,
pub name: QualifiedName,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DropObjectType {
Table,
View,
Index,
Trigger,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AlterTableStatement {
pub table: QualifiedName,
pub action: AlterTableAction,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AlterTableAction {
RenameTo(String),
RenameColumn { old: String, new: String },
AddColumn(ColumnDef),
DropColumn(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BeginStatement {
pub mode: Option<TransactionMode>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TransactionMode {
Deferred,
Immediate,
Exclusive,
Concurrent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RollbackStatement {
pub to_savepoint: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AttachStatement {
pub expr: Expr,
pub schema: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PragmaStatement {
pub name: QualifiedName,
pub value: Option<PragmaValue>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PragmaValue {
Assign(Expr),
Call(Expr),
}
#[derive(Debug, Clone, PartialEq)]
pub struct VacuumStatement {
pub schema: Option<String>,
pub into: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedColumn {
pub table_idx: usize,
pub column_idx: usize,
pub table_name: String,
pub column_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableSchema {
pub name: String,
pub alias: Option<String>,
pub columns: Vec<String>,
}
impl TableSchema {
#[must_use]
pub fn effective_name(&self) -> &str {
self.alias.as_deref().unwrap_or(&self.name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveError {
NoSuchTable { name: String, span: Span },
NoSuchColumn {
table: String,
column: String,
span: Span,
},
AmbiguousColumn {
column: String,
candidates: Vec<String>,
span: Span,
},
ColumnNotFound { column: String, span: Span },
NoOuterTable { name: String, span: Span },
}
impl fmt::Display for ResolveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoSuchTable { name, span } => {
write!(f, "no such table: {name} at {span}")
}
Self::NoSuchColumn {
table,
column,
span,
} => {
write!(f, "no such column: {table}.{column} at {span}")
}
Self::AmbiguousColumn {
column,
candidates,
span,
} => {
write!(
f,
"ambiguous column name: {column} (candidates: {}) at {span}",
candidates.join(", ")
)
}
Self::ColumnNotFound { column, span } => {
write!(f, "no such column: {column} at {span}")
}
Self::NoOuterTable { name, span } => {
write!(f, "no such table in outer scope: {name} at {span}")
}
}
}
}
impl std::error::Error for ResolveError {}
#[derive(Debug, Clone)]
pub struct ResolverScope {
pub tables: Vec<TableSchema>,
pub parent: Option<Box<Self>>,
}
impl ResolverScope {
#[must_use]
pub fn new(tables: Vec<TableSchema>) -> Self {
Self {
tables,
parent: None,
}
}
#[must_use]
pub fn child(self, tables: Vec<TableSchema>) -> Self {
Self {
tables,
parent: Some(Box::new(self)),
}
}
pub fn resolve(&self, col: &ColumnRef, span: Span) -> Result<ResolvedColumn, ResolveError> {
match &col.table {
Some(table_name) => self.resolve_qualified(table_name, &col.column, span),
None => self.resolve_unqualified(&col.column, span),
}
}
fn resolve_qualified(
&self,
table_name: &str,
column: &str,
span: Span,
) -> Result<ResolvedColumn, ResolveError> {
for (idx, table) in self.tables.iter().enumerate() {
if table.effective_name().eq_ignore_ascii_case(table_name) {
return match table
.columns
.iter()
.position(|c| c.eq_ignore_ascii_case(column))
{
Some(col_idx) => Ok(ResolvedColumn {
table_idx: idx,
column_idx: col_idx,
table_name: table.effective_name().to_owned(),
column_name: table.columns[col_idx].clone(),
}),
None => Err(ResolveError::NoSuchColumn {
table: table_name.to_owned(),
column: column.to_owned(),
span,
}),
};
}
}
if let Some(ref parent) = self.parent {
return parent.resolve_qualified(table_name, column, span);
}
Err(ResolveError::NoSuchTable {
name: table_name.to_owned(),
span,
})
}
fn resolve_unqualified(
&self,
column: &str,
span: Span,
) -> Result<ResolvedColumn, ResolveError> {
let mut found: Option<ResolvedColumn> = None;
let mut candidates = Vec::new();
for (idx, table) in self.tables.iter().enumerate() {
if let Some(col_idx) = table
.columns
.iter()
.position(|c| c.eq_ignore_ascii_case(column))
{
candidates.push(table.effective_name().to_owned());
found = Some(ResolvedColumn {
table_idx: idx,
column_idx: col_idx,
table_name: table.effective_name().to_owned(),
column_name: table.columns[col_idx].clone(),
});
}
}
match candidates.len() {
0 => {
if let Some(ref parent) = self.parent {
return parent.resolve_unqualified(column, span);
}
Err(ResolveError::ColumnNotFound {
column: column.to_owned(),
span,
})
}
1 => Ok(found.expect("candidates len is 1")),
_ => Err(ResolveError::AmbiguousColumn {
column: column.to_owned(),
candidates,
span,
}),
}
}
#[must_use]
pub fn expand_star(&self) -> Vec<(String, String)> {
let mut result = Vec::new();
for table in &self.tables {
for col in &table.columns {
result.push((table.effective_name().to_owned(), col.clone()));
}
}
result
}
pub fn expand_table_star(
&self,
table_name: &str,
span: Span,
) -> Result<Vec<(String, String)>, ResolveError> {
for table in &self.tables {
if table.effective_name().eq_ignore_ascii_case(table_name) {
return Ok(table
.columns
.iter()
.map(|c| (table.effective_name().to_owned(), c.clone()))
.collect());
}
}
Err(ResolveError::NoSuchTable {
name: table_name.to_owned(),
span,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ast_statement_variants_dml() {
let _ = Statement::Select(SelectStatement {
with: None,
body: SelectBody {
select: SelectCore::Values(vec![vec![Expr::Literal(
Literal::Integer(1),
Span::ZERO,
)]]),
compounds: vec![],
},
order_by: vec![],
limit: None,
});
let _ = Statement::Insert(InsertStatement {
with: None,
or_conflict: None,
table: QualifiedName::bare("t"),
alias: None,
columns: vec![],
source: InsertSource::DefaultValues,
upsert: vec![],
returning: vec![],
});
let table_ref = QualifiedTableRef {
name: QualifiedName::bare("t"),
alias: None,
index_hint: None,
};
let _ = Statement::Update(UpdateStatement {
with: None,
or_conflict: None,
table: table_ref.clone(),
assignments: vec![],
from: None,
where_clause: None,
returning: vec![],
order_by: vec![],
limit: None,
});
let _ = Statement::Delete(DeleteStatement {
with: None,
table: table_ref,
where_clause: None,
returning: vec![],
order_by: vec![],
limit: None,
});
}
#[test]
fn test_ast_statement_variants_ddl() {
let _ = Statement::CreateTable(CreateTableStatement {
if_not_exists: false,
temporary: false,
name: QualifiedName::bare("t"),
body: CreateTableBody::Columns {
columns: vec![],
constraints: vec![],
},
without_rowid: false,
strict: false,
});
let _ = Statement::CreateIndex(CreateIndexStatement {
unique: false,
if_not_exists: false,
name: QualifiedName::bare("idx"),
table: "t".to_owned(),
columns: vec![],
where_clause: None,
});
let _ = Statement::CreateView(CreateViewStatement {
if_not_exists: false,
temporary: false,
name: QualifiedName::bare("v"),
columns: vec![],
query: SelectStatement {
with: None,
body: SelectBody {
select: SelectCore::Values(vec![]),
compounds: vec![],
},
order_by: vec![],
limit: None,
},
});
let _ = Statement::CreateTrigger(CreateTriggerStatement {
if_not_exists: false,
temporary: false,
name: QualifiedName::bare("tr"),
timing: TriggerTiming::Before,
event: TriggerEvent::Insert,
table: "t".to_owned(),
for_each_row: true,
when: None,
body: vec![],
});
let _ = Statement::CreateVirtualTable(CreateVirtualTableStatement {
if_not_exists: false,
name: QualifiedName::bare("vt"),
module: "fts5".to_owned(),
args: vec!["content".to_owned()],
});
let _ = Statement::Drop(DropStatement {
object_type: DropObjectType::Table,
if_exists: false,
name: QualifiedName::bare("t"),
});
let _ = Statement::AlterTable(AlterTableStatement {
table: QualifiedName::bare("t"),
action: AlterTableAction::RenameTo("t2".to_owned()),
});
}
#[test]
fn test_ast_statement_variants_txn_and_misc() {
let _ = Statement::Begin(BeginStatement { mode: None });
let _ = Statement::Commit;
let _ = Statement::Rollback(RollbackStatement { to_savepoint: None });
let _ = Statement::Savepoint("sp1".to_owned());
let _ = Statement::Release("sp1".to_owned());
let _ = Statement::Attach(AttachStatement {
expr: Expr::Literal(Literal::String("file.db".to_owned()), Span::ZERO),
schema: "aux".to_owned(),
});
let _ = Statement::Detach("aux".to_owned());
let _ = Statement::Pragma(PragmaStatement {
name: QualifiedName::bare("cache_size"),
value: None,
});
let _ = Statement::Vacuum(VacuumStatement {
schema: None,
into: None,
});
let _ = Statement::Reindex(None);
let _ = Statement::Analyze(None);
let _ = Statement::Explain {
query_plan: true,
stmt: Box::new(Statement::Commit),
};
}
#[test]
fn test_ast_select_body_with_compounds() {
let core1 = SelectCore::Values(vec![vec![Expr::Literal(Literal::Integer(1), Span::ZERO)]]);
let core2 = SelectCore::Values(vec![vec![Expr::Literal(Literal::Integer(2), Span::ZERO)]]);
let core3 = SelectCore::Values(vec![vec![Expr::Literal(Literal::Integer(3), Span::ZERO)]]);
let body = SelectBody {
select: core1,
compounds: vec![(CompoundOp::Union, core2), (CompoundOp::Intersect, core3)],
};
assert_eq!(body.compounds.len(), 2);
assert_eq!(body.compounds[0].0, CompoundOp::Union);
assert_eq!(body.compounds[1].0, CompoundOp::Intersect);
}
#[test]
fn test_ast_values_as_first_class() {
let values = SelectCore::Values(vec![
vec![
Expr::Literal(Literal::Integer(1), Span::ZERO),
Expr::Literal(Literal::Integer(2), Span::ZERO),
],
vec![
Expr::Literal(Literal::Integer(3), Span::ZERO),
Expr::Literal(Literal::Integer(4), Span::ZERO),
],
]);
assert!(matches!(values, SelectCore::Values(ref rows) if rows.len() == 2));
let select = SelectCore::Select {
distinct: Distinctness::All,
columns: vec![],
from: None,
where_clause: None,
group_by: vec![],
having: None,
windows: vec![],
};
assert!(!matches!(select, SelectCore::Values(_)));
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_ast_expr_variants_core() {
let span = Span::new(0, 10);
let dummy = || Box::new(Expr::Literal(Literal::Null, span));
let exprs: Vec<Expr> = vec![
Expr::Literal(Literal::Integer(42), span),
Expr::Column(ColumnRef::bare("x"), span),
Expr::BinaryOp {
left: dummy(),
op: BinaryOp::Add,
right: dummy(),
span,
},
Expr::UnaryOp {
op: UnaryOp::Negate,
expr: dummy(),
span,
},
Expr::Between {
expr: dummy(),
low: dummy(),
high: dummy(),
not: false,
span,
},
Expr::In {
expr: dummy(),
set: InSet::List(vec![]),
not: false,
span,
},
Expr::Like {
expr: dummy(),
pattern: dummy(),
escape: None,
op: LikeOp::Like,
not: false,
span,
},
Expr::Case {
operand: None,
whens: vec![],
else_expr: None,
span,
},
Expr::Cast {
expr: dummy(),
type_name: TypeName {
name: "INTEGER".to_owned(),
arg1: None,
arg2: None,
},
span,
},
Expr::Collate {
expr: dummy(),
collation: "NOCASE".to_owned(),
span,
},
Expr::IsNull {
expr: dummy(),
not: false,
span,
},
Expr::JsonAccess {
expr: dummy(),
path: dummy(),
arrow: JsonArrow::Arrow,
span,
},
Expr::RowValue(vec![], span),
Expr::Placeholder(PlaceholderType::Anonymous, span),
];
for expr in &exprs {
assert_eq!(expr.span(), span);
}
}
#[test]
fn test_ast_expr_variants_subqueries_and_calls() {
let span = Span::new(0, 10);
let dummy = || Box::new(Expr::Literal(Literal::Null, span));
let empty_select = SelectStatement {
with: None,
body: SelectBody {
select: SelectCore::Values(vec![]),
compounds: vec![],
},
order_by: vec![],
limit: None,
};
let exprs: Vec<Expr> = vec![
Expr::Exists {
subquery: Box::new(empty_select.clone()),
not: false,
span,
},
Expr::Subquery(Box::new(empty_select), span),
Expr::FunctionCall {
name: "count".to_owned(),
args: FunctionArgs::Star,
distinct: false,
filter: None,
over: None,
span,
},
Expr::Raise {
action: RaiseAction::Abort,
message: Some("error".to_owned()),
span,
},
Expr::UnaryOp {
op: UnaryOp::Negate,
expr: dummy(),
span,
},
];
for expr in &exprs {
assert_eq!(expr.span(), span);
}
}
#[test]
fn test_ast_function_call_with_window() {
let span = Span::new(0, 30);
let expr = Expr::FunctionCall {
name: "row_number".to_owned(),
args: FunctionArgs::List(vec![]),
distinct: false,
filter: None,
over: Some(WindowSpec {
base_window: None,
partition_by: vec![Expr::Column(ColumnRef::bare("dept"), span)],
order_by: vec![OrderingTerm {
expr: Expr::Column(ColumnRef::bare("salary"), span),
direction: Some(SortDirection::Desc),
nulls: None,
}],
frame: Some(FrameSpec {
frame_type: FrameType::Rows,
start: FrameBound::UnboundedPreceding,
end: Some(FrameBound::CurrentRow),
exclude: None,
}),
}),
span,
};
assert!(matches!(expr, Expr::FunctionCall { over: Some(_), .. }));
if let Expr::FunctionCall {
over: Some(ref win),
..
} = expr
{
assert_eq!(win.partition_by.len(), 1);
assert_eq!(win.order_by.len(), 1);
assert!(win.frame.is_some());
}
}
#[test]
fn test_ast_like_with_escape() {
let span = Span::ZERO;
let expr = Expr::Like {
expr: Box::new(Expr::Column(ColumnRef::bare("name"), span)),
pattern: Box::new(Expr::Literal(Literal::String("foo%".to_owned()), span)),
escape: Some(Box::new(Expr::Literal(
Literal::String("\\".to_owned()),
span,
))),
op: LikeOp::Like,
not: false,
span,
};
assert!(matches!(
expr,
Expr::Like {
escape: Some(_),
..
}
));
if let Expr::Like {
escape: Some(ref esc),
..
} = expr
{
assert!(matches!(esc.as_ref(), Expr::Literal(Literal::String(_), _)));
}
}
#[test]
fn test_ast_json_access_arrow_types() {
let span = Span::ZERO;
let arrow = Expr::JsonAccess {
expr: Box::new(Expr::Column(ColumnRef::bare("data"), span)),
path: Box::new(Expr::Literal(Literal::String("$.name".to_owned()), span)),
arrow: JsonArrow::Arrow,
span,
};
let double_arrow = Expr::JsonAccess {
expr: Box::new(Expr::Column(ColumnRef::bare("data"), span)),
path: Box::new(Expr::Literal(Literal::String("$.name".to_owned()), span)),
arrow: JsonArrow::DoubleArrow,
span,
};
assert!(matches!(
(&arrow, &double_arrow),
(
Expr::JsonAccess {
arrow: JsonArrow::Arrow,
..
},
Expr::JsonAccess {
arrow: JsonArrow::DoubleArrow,
..
}
)
));
}
#[test]
fn test_ast_row_value() {
let span = Span::ZERO;
let rv = Expr::RowValue(
vec![
Expr::Column(ColumnRef::bare("a"), span),
Expr::Column(ColumnRef::bare("b"), span),
Expr::Column(ColumnRef::bare("c"), span),
],
span,
);
assert!(matches!(rv, Expr::RowValue(_, _)));
if let Expr::RowValue(ref elems, _) = rv {
assert_eq!(elems.len(), 3);
}
}
fn make_scope_t1_t2() -> ResolverScope {
ResolverScope::new(vec![
TableSchema {
name: "t1".to_owned(),
alias: None,
columns: vec!["a".to_owned(), "b".to_owned()],
},
TableSchema {
name: "t2".to_owned(),
alias: None,
columns: vec!["c".to_owned(), "d".to_owned()],
},
])
}
#[test]
fn test_resolve_unambiguous_column() {
let scope = make_scope_t1_t2();
let result = scope
.resolve(&ColumnRef::bare("a"), Span::ZERO)
.expect("should resolve");
assert_eq!(result.table_name, "t1");
assert_eq!(result.column_name, "a");
assert_eq!(result.table_idx, 0);
assert_eq!(result.column_idx, 0);
}
#[test]
fn test_resolve_ambiguous_column_error() {
let scope = ResolverScope::new(vec![
TableSchema {
name: "t1".to_owned(),
alias: None,
columns: vec!["x".to_owned(), "y".to_owned()],
},
TableSchema {
name: "t2".to_owned(),
alias: None,
columns: vec!["x".to_owned(), "z".to_owned()],
},
]);
let err = scope
.resolve(&ColumnRef::bare("x"), Span::ZERO)
.unwrap_err();
assert!(matches!(err, ResolveError::AmbiguousColumn { .. }));
if let ResolveError::AmbiguousColumn {
column, candidates, ..
} = err
{
assert_eq!(column, "x");
assert_eq!(candidates, vec!["t1", "t2"]);
}
}
#[test]
fn test_resolve_qualified_column() {
let scope = make_scope_t1_t2();
let result = scope
.resolve(&ColumnRef::qualified("t1", "a"), Span::ZERO)
.expect("should resolve");
assert_eq!(result.table_name, "t1");
assert_eq!(result.column_name, "a");
let err = scope
.resolve(&ColumnRef::qualified("t1", "nonexistent"), Span::ZERO)
.unwrap_err();
assert!(matches!(err, ResolveError::NoSuchColumn { .. }));
}
#[test]
fn test_resolve_alias_binding() {
let scope = ResolverScope::new(vec![TableSchema {
name: "users".to_owned(),
alias: Some("u".to_owned()),
columns: vec!["id".to_owned(), "name".to_owned()],
}]);
let result = scope
.resolve(&ColumnRef::qualified("u", "name"), Span::ZERO)
.expect("should resolve via alias");
assert_eq!(result.table_name, "u");
assert_eq!(result.column_name, "name");
}
#[test]
fn test_resolve_star_expansion() {
let scope = make_scope_t1_t2();
let expanded = scope.expand_star();
assert_eq!(
expanded,
vec![
("t1".to_owned(), "a".to_owned()),
("t1".to_owned(), "b".to_owned()),
("t2".to_owned(), "c".to_owned()),
("t2".to_owned(), "d".to_owned()),
]
);
}
#[test]
fn test_resolve_qualified_star() {
let scope = make_scope_t1_t2();
let expanded = scope.expand_table_star("t1", Span::ZERO).unwrap();
assert_eq!(
expanded,
vec![
("t1".to_owned(), "a".to_owned()),
("t1".to_owned(), "b".to_owned()),
]
);
}
#[test]
fn test_resolve_subquery_scope() {
let outer = ResolverScope::new(vec![TableSchema {
name: "t1".to_owned(),
alias: None,
columns: vec!["a".to_owned(), "b".to_owned()],
}]);
let inner = outer.child(vec![TableSchema {
name: "t2".to_owned(),
alias: None,
columns: vec!["c".to_owned(), "d".to_owned()],
}]);
let result = inner
.resolve(&ColumnRef::qualified("t2", "c"), Span::ZERO)
.expect("inner table");
assert_eq!(result.table_name, "t2");
let result = inner
.resolve(&ColumnRef::qualified("t1", "a"), Span::ZERO)
.expect("correlated outer reference");
assert_eq!(result.table_name, "t1");
assert_eq!(result.column_name, "a");
}
#[test]
fn test_resolve_scope_shadowing() {
let outer = ResolverScope::new(vec![TableSchema {
name: "t1".to_owned(),
alias: None,
columns: vec!["outer_col".to_owned()],
}]);
let inner = outer.child(vec![TableSchema {
name: "t1".to_owned(),
alias: None,
columns: vec!["inner_col".to_owned()],
}]);
let result = inner
.resolve(&ColumnRef::qualified("t1", "inner_col"), Span::ZERO)
.expect("inner shadows outer");
assert_eq!(result.column_name, "inner_col");
let err = inner
.resolve(&ColumnRef::qualified("t1", "outer_col"), Span::ZERO)
.unwrap_err();
assert!(matches!(err, ResolveError::NoSuchColumn { .. }));
}
#[test]
fn test_resolve_nonexistent_table_error() {
let scope = make_scope_t1_t2();
let err = scope
.resolve(&ColumnRef::qualified("nonexistent", "a"), Span::ZERO)
.unwrap_err();
assert!(matches!(err, ResolveError::NoSuchTable { .. }));
}
#[test]
fn test_resolve_unqualified_column_not_found() {
let scope = make_scope_t1_t2();
let err = scope
.resolve(&ColumnRef::bare("nonexistent"), Span::ZERO)
.unwrap_err();
assert!(matches!(err, ResolveError::ColumnNotFound { .. }));
if let ResolveError::ColumnNotFound { column, .. } = err {
assert_eq!(column, "nonexistent");
}
}
#[test]
fn test_resolve_column_in_order_by() {
let scope = ResolverScope::new(vec![TableSchema {
name: "result".to_owned(),
alias: None,
columns: vec!["total".to_owned()],
}]);
let result = scope
.resolve(&ColumnRef::bare("total"), Span::ZERO)
.expect("order by alias");
assert_eq!(result.column_name, "total");
}
#[test]
fn test_span_merge() {
let a = Span::new(5, 10);
let b = Span::new(15, 20);
let merged = a.merge(b);
assert_eq!(merged.start, 5);
assert_eq!(merged.end, 20);
}
#[test]
fn test_span_len_is_empty() {
let s = Span::new(10, 20);
assert_eq!(s.len(), 10);
assert!(!s.is_empty());
assert!(Span::ZERO.is_empty());
}
#[test]
fn test_qualified_name_display() {
let bare = QualifiedName::bare("users");
assert_eq!(bare.to_string(), "users");
let qual = QualifiedName::qualified("main", "users");
assert_eq!(qual.to_string(), "main.users");
}
#[test]
fn test_binary_op_display() {
assert_eq!(BinaryOp::Add.to_string(), "+");
assert_eq!(BinaryOp::Concat.to_string(), "||");
assert_eq!(BinaryOp::And.to_string(), "AND");
assert_eq!(BinaryOp::IsNot.to_string(), "IS NOT");
}
#[test]
fn test_unary_op_display() {
assert_eq!(UnaryOp::Negate.to_string(), "-");
assert_eq!(UnaryOp::Not.to_string(), "NOT");
}
#[test]
fn test_unary_op_display_all_variants() {
assert_eq!(UnaryOp::Plus.to_string(), "+");
assert_eq!(UnaryOp::BitNot.to_string(), "~");
}
#[test]
fn test_binary_op_display_all_variants() {
assert_eq!(BinaryOp::Subtract.to_string(), "-");
assert_eq!(BinaryOp::Multiply.to_string(), "*");
assert_eq!(BinaryOp::Divide.to_string(), "/");
assert_eq!(BinaryOp::Modulo.to_string(), "%");
assert_eq!(BinaryOp::Eq.to_string(), "=");
assert_eq!(BinaryOp::Ne.to_string(), "!=");
assert_eq!(BinaryOp::Lt.to_string(), "<");
assert_eq!(BinaryOp::Le.to_string(), "<=");
assert_eq!(BinaryOp::Gt.to_string(), ">");
assert_eq!(BinaryOp::Ge.to_string(), ">=");
assert_eq!(BinaryOp::Is.to_string(), "IS");
assert_eq!(BinaryOp::Or.to_string(), "OR");
assert_eq!(BinaryOp::BitAnd.to_string(), "&");
assert_eq!(BinaryOp::BitOr.to_string(), "|");
assert_eq!(BinaryOp::ShiftLeft.to_string(), "<<");
assert_eq!(BinaryOp::ShiftRight.to_string(), ">>");
}
#[test]
fn test_span_debug_format() {
let s = Span::new(10, 25);
assert_eq!(format!("{s:?}"), "10..25");
}
#[test]
fn test_span_display_format() {
let s = Span::new(0, 42);
assert_eq!(format!("{s}"), "0..42");
}
#[test]
fn test_span_zero_properties() {
assert_eq!(Span::ZERO.start, 0);
assert_eq!(Span::ZERO.end, 0);
assert_eq!(Span::ZERO.len(), 0);
assert!(Span::ZERO.is_empty());
}
#[test]
fn test_span_merge_overlapping() {
let a = Span::new(5, 15);
let b = Span::new(10, 20);
let merged = a.merge(b);
assert_eq!(merged.start, 5);
assert_eq!(merged.end, 20);
}
#[test]
fn test_span_merge_reversed_order() {
let a = Span::new(20, 30);
let b = Span::new(5, 10);
let merged = a.merge(b);
assert_eq!(merged.start, 5);
assert_eq!(merged.end, 30);
}
#[test]
fn test_table_schema_effective_name_with_alias() {
let schema = TableSchema {
name: "users".to_owned(),
alias: Some("u".to_owned()),
columns: vec!["id".to_owned()],
};
assert_eq!(schema.effective_name(), "u");
}
#[test]
fn test_table_schema_effective_name_without_alias() {
let schema = TableSchema {
name: "users".to_owned(),
alias: None,
columns: vec!["id".to_owned()],
};
assert_eq!(schema.effective_name(), "users");
}
#[test]
fn test_resolve_case_insensitive_table() {
let scope = ResolverScope::new(vec![TableSchema {
name: "Users".to_owned(),
alias: None,
columns: vec!["Id".to_owned(), "Name".to_owned()],
}]);
let result = scope
.resolve(&ColumnRef::qualified("users", "id"), Span::ZERO)
.expect("case-insensitive table match");
assert_eq!(result.table_name, "Users");
assert_eq!(result.column_name, "Id");
}
#[test]
fn test_resolve_case_insensitive_unqualified() {
let scope = ResolverScope::new(vec![TableSchema {
name: "T".to_owned(),
alias: None,
columns: vec!["COL_A".to_owned()],
}]);
let result = scope
.resolve(&ColumnRef::bare("col_a"), Span::ZERO)
.expect("case-insensitive unqualified match");
assert_eq!(result.column_name, "COL_A");
}
#[test]
fn test_expand_table_star_nonexistent() {
let scope = make_scope_t1_t2();
let err = scope
.expand_table_star("nonexistent", Span::ZERO)
.unwrap_err();
assert!(matches!(err, ResolveError::NoSuchTable { .. }));
}
#[test]
fn test_resolve_error_display_no_such_table() {
let err = ResolveError::NoSuchTable {
name: "foo".to_owned(),
span: Span::new(5, 8),
};
assert_eq!(err.to_string(), "no such table: foo at 5..8");
}
#[test]
fn test_resolve_error_display_no_such_column() {
let err = ResolveError::NoSuchColumn {
table: "t1".to_owned(),
column: "bar".to_owned(),
span: Span::new(10, 16),
};
assert_eq!(err.to_string(), "no such column: t1.bar at 10..16");
}
#[test]
fn test_resolve_error_display_ambiguous() {
let err = ResolveError::AmbiguousColumn {
column: "id".to_owned(),
candidates: vec!["users".to_owned(), "orders".to_owned()],
span: Span::ZERO,
};
let msg = err.to_string();
assert!(msg.contains("ambiguous column name: id"));
assert!(msg.contains("users, orders"));
}
#[test]
fn test_resolve_error_display_column_not_found() {
let err = ResolveError::ColumnNotFound {
column: "xyz".to_owned(),
span: Span::new(0, 3),
};
assert_eq!(err.to_string(), "no such column: xyz at 0..3");
}
#[test]
fn test_resolve_error_display_no_outer_table() {
let err = ResolveError::NoOuterTable {
name: "outer_t".to_owned(),
span: Span::new(1, 8),
};
assert_eq!(
err.to_string(),
"no such table in outer scope: outer_t at 1..8"
);
}
#[test]
fn test_resolve_error_is_std_error() {
let err: Box<dyn std::error::Error> = Box::new(ResolveError::ColumnNotFound {
column: "x".to_owned(),
span: Span::ZERO,
});
assert!(!err.to_string().is_empty());
}
#[test]
fn test_resolve_unqualified_from_parent_scope() {
let outer = ResolverScope::new(vec![TableSchema {
name: "outer_t".to_owned(),
alias: None,
columns: vec!["outer_col".to_owned()],
}]);
let inner = outer.child(vec![TableSchema {
name: "inner_t".to_owned(),
alias: None,
columns: vec!["inner_col".to_owned()],
}]);
let result = inner
.resolve(&ColumnRef::bare("outer_col"), Span::ZERO)
.expect("correlated unqualified from parent");
assert_eq!(result.table_name, "outer_t");
assert_eq!(result.column_name, "outer_col");
}
#[test]
fn test_distinctness_default_is_all() {
assert_eq!(Distinctness::default(), Distinctness::All);
}
#[test]
fn test_transaction_mode_concurrent() {
let begin = BeginStatement {
mode: Some(TransactionMode::Concurrent),
};
assert_eq!(begin.mode, Some(TransactionMode::Concurrent));
}
#[test]
fn test_transaction_mode_all_variants() {
let modes = [
TransactionMode::Deferred,
TransactionMode::Immediate,
TransactionMode::Exclusive,
TransactionMode::Concurrent,
];
for (i, a) in modes.iter().enumerate() {
for (j, b) in modes.iter().enumerate() {
assert_eq!(i == j, a == b, "modes {i} and {j} distinctness");
}
}
}
#[test]
fn test_conflict_action_all_variants() {
let actions = [
ConflictAction::Rollback,
ConflictAction::Abort,
ConflictAction::Fail,
ConflictAction::Ignore,
ConflictAction::Replace,
];
assert_eq!(actions.len(), 5);
for (i, a) in actions.iter().enumerate() {
for (j, b) in actions.iter().enumerate() {
assert_eq!(i == j, a == b);
}
}
}
#[test]
fn test_compound_op_all_variants() {
let ops = [
CompoundOp::Union,
CompoundOp::UnionAll,
CompoundOp::Intersect,
CompoundOp::Except,
];
assert_eq!(ops.len(), 4);
assert_ne!(CompoundOp::Union, CompoundOp::UnionAll);
}
#[test]
fn test_drop_object_type_variants() {
let types = [
DropObjectType::Table,
DropObjectType::View,
DropObjectType::Index,
DropObjectType::Trigger,
];
assert_eq!(types.len(), 4);
assert_ne!(DropObjectType::Table, DropObjectType::View);
}
#[test]
fn test_like_op_variants() {
let ops = [LikeOp::Like, LikeOp::Glob, LikeOp::Match, LikeOp::Regexp];
assert_eq!(ops.len(), 4);
assert_ne!(LikeOp::Like, LikeOp::Glob);
}
#[test]
fn test_placeholder_type_variants() {
let _ = PlaceholderType::Anonymous;
let _ = PlaceholderType::Numbered(1);
let _ = PlaceholderType::ColonNamed("param".to_owned());
let _ = PlaceholderType::AtNamed("param".to_owned());
let _ = PlaceholderType::DollarNamed("param".to_owned());
assert_ne!(PlaceholderType::Anonymous, PlaceholderType::Numbered(1));
assert_ne!(
PlaceholderType::ColonNamed("a".to_owned()),
PlaceholderType::AtNamed("a".to_owned()),
);
}
#[test]
fn test_raise_action_variants() {
let actions = [
RaiseAction::Ignore,
RaiseAction::Rollback,
RaiseAction::Abort,
RaiseAction::Fail,
];
assert_eq!(actions.len(), 4);
assert_ne!(RaiseAction::Ignore, RaiseAction::Rollback);
}
#[test]
fn test_trigger_timing_variants() {
let timings = [
TriggerTiming::Before,
TriggerTiming::After,
TriggerTiming::InsteadOf,
];
assert_eq!(timings.len(), 3);
assert_ne!(TriggerTiming::Before, TriggerTiming::After);
}
#[test]
fn test_trigger_event_update_with_columns() {
let ev = TriggerEvent::Update(vec!["col1".to_owned(), "col2".to_owned()]);
assert!(matches!(ev, TriggerEvent::Update(ref cols) if cols.len() == 2));
assert_ne!(TriggerEvent::Insert, TriggerEvent::Delete);
}
#[test]
fn test_frame_type_variants() {
let types = [FrameType::Rows, FrameType::Range, FrameType::Groups];
assert_eq!(types.len(), 3);
assert_ne!(FrameType::Rows, FrameType::Groups);
}
#[test]
fn test_frame_exclude_variants() {
let excludes = [
FrameExclude::NoOthers,
FrameExclude::CurrentRow,
FrameExclude::Group,
FrameExclude::Ties,
];
assert_eq!(excludes.len(), 4);
}
#[test]
fn test_sort_direction_and_nulls_order() {
assert_ne!(SortDirection::Asc, SortDirection::Desc);
assert_ne!(NullsOrder::First, NullsOrder::Last);
}
#[test]
fn test_generated_storage_variants() {
assert_ne!(GeneratedStorage::Stored, GeneratedStorage::Virtual);
}
#[test]
fn test_cte_materialized_variants() {
assert_ne!(
CteMaterialized::Materialized,
CteMaterialized::NotMaterialized
);
}
#[test]
fn test_in_set_table_variant() {
let set = InSet::Table(QualifiedName::bare("lookup"));
assert!(matches!(set, InSet::Table(ref n) if n.name == "lookup"));
}
#[test]
fn test_function_args_star_vs_list() {
let star = FunctionArgs::Star;
let list = FunctionArgs::List(vec![]);
assert_ne!(star, list);
}
#[test]
fn test_insert_source_default_values() {
let src = InsertSource::DefaultValues;
assert!(matches!(src, InsertSource::DefaultValues));
assert_ne!(InsertSource::DefaultValues, InsertSource::Values(vec![]),);
}
#[test]
fn test_pragma_value_variants() {
let span = Span::ZERO;
let assign = PragmaValue::Assign(Expr::Literal(Literal::Integer(100), span));
let call = PragmaValue::Call(Expr::Literal(Literal::Integer(100), span));
assert_ne!(assign, call);
}
#[test]
fn test_column_ref_constructors() {
let bare = ColumnRef::bare("col");
assert!(bare.table.is_none());
assert_eq!(bare.column, "col");
let qual = ColumnRef::qualified("tbl", "col");
assert_eq!(qual.table.as_deref(), Some("tbl"));
assert_eq!(qual.column, "col");
}
#[test]
fn test_qualified_name_constructors() {
let bare = QualifiedName::bare("t");
assert!(bare.schema.is_none());
assert_eq!(bare.name, "t");
let qual = QualifiedName::qualified("main", "t");
assert_eq!(qual.schema.as_deref(), Some("main"));
assert_eq!(qual.name, "t");
}
#[test]
fn test_deferrable_initially_variants() {
let deferred = Deferrable {
not: false,
initially: Some(DeferrableInitially::Deferred),
};
let immediate = Deferrable {
not: false,
initially: Some(DeferrableInitially::Immediate),
};
assert_ne!(deferred, immediate);
let not_deferrable = Deferrable {
not: true,
initially: None,
};
assert_ne!(deferred, not_deferrable);
}
#[test]
fn test_foreign_key_action_types() {
let types = [
ForeignKeyActionType::SetNull,
ForeignKeyActionType::SetDefault,
ForeignKeyActionType::Cascade,
ForeignKeyActionType::Restrict,
ForeignKeyActionType::NoAction,
];
assert_eq!(types.len(), 5);
assert_ne!(
ForeignKeyActionType::Cascade,
ForeignKeyActionType::Restrict
);
}
#[test]
fn test_foreign_key_trigger_variants() {
assert_ne!(ForeignKeyTrigger::OnDelete, ForeignKeyTrigger::OnUpdate);
}
#[test]
fn test_index_hint_variants() {
let indexed = IndexHint::IndexedBy("idx_name".to_owned());
let not_indexed = IndexHint::NotIndexed;
assert_ne!(indexed, not_indexed);
}
#[test]
fn test_join_kind_all_variants() {
let kinds = [
JoinKind::Cross,
JoinKind::Inner,
JoinKind::Left,
JoinKind::Right,
JoinKind::Full,
];
assert_eq!(kinds.len(), 5);
assert_ne!(JoinKind::Left, JoinKind::Right);
}
#[test]
fn test_join_type_natural_flag() {
let natural_inner = JoinType {
natural: true,
kind: JoinKind::Inner,
};
let regular_inner = JoinType {
natural: false,
kind: JoinKind::Inner,
};
assert_ne!(natural_inner, regular_inner);
}
#[test]
fn test_alter_table_all_actions() {
let rename = AlterTableAction::RenameTo("new_name".to_owned());
let rename_col = AlterTableAction::RenameColumn {
old: "old_col".to_owned(),
new: "new_col".to_owned(),
};
let add_col = AlterTableAction::AddColumn(ColumnDef {
name: "new_col".to_owned(),
type_name: Some(TypeName {
name: "INTEGER".to_owned(),
arg1: None,
arg2: None,
}),
constraints: vec![],
});
let drop_col = AlterTableAction::DropColumn("old_col".to_owned());
assert_ne!(rename, rename_col);
assert_ne!(add_col, drop_col);
}
#[test]
#[allow(clippy::approx_constant)]
fn test_literal_all_variants() {
let _ = Literal::Integer(42);
let _ = Literal::Float(3.14);
let _ = Literal::String("hello".to_owned());
let _ = Literal::Blob(vec![0xDE, 0xAD]);
let _ = Literal::Null;
let _ = Literal::True;
let _ = Literal::False;
let _ = Literal::CurrentTime;
let _ = Literal::CurrentDate;
let _ = Literal::CurrentTimestamp;
assert_ne!(Literal::True, Literal::False);
assert_ne!(Literal::CurrentTime, Literal::CurrentDate);
}
#[test]
fn test_json_arrow_variants() {
assert_ne!(JsonArrow::Arrow, JsonArrow::DoubleArrow);
}
#[test]
fn test_upsert_action_nothing_vs_update() {
let nothing = UpsertAction::Nothing;
let update = UpsertAction::Update {
assignments: vec![],
where_clause: None,
};
assert_ne!(nothing, update);
}
#[test]
fn test_assignment_target_variants() {
let single = AssignmentTarget::Column("col".to_owned());
let multi = AssignmentTarget::ColumnList(vec!["a".to_owned(), "b".to_owned()]);
assert_ne!(single, multi);
}
#[test]
fn test_type_name_with_args() {
let simple = TypeName {
name: "INTEGER".to_owned(),
arg1: None,
arg2: None,
};
let varchar = TypeName {
name: "VARCHAR".to_owned(),
arg1: Some("255".to_owned()),
arg2: None,
};
let decimal = TypeName {
name: "DECIMAL".to_owned(),
arg1: Some("10".to_owned()),
arg2: Some("2".to_owned()),
};
assert_ne!(simple, varchar);
assert_ne!(varchar, decimal);
}
#[test]
fn test_frame_bound_variants() {
let span = Span::ZERO;
let _ = FrameBound::UnboundedPreceding;
let _ = FrameBound::Preceding(Box::new(Expr::Literal(Literal::Integer(1), span)));
let _ = FrameBound::CurrentRow;
let _ = FrameBound::Following(Box::new(Expr::Literal(Literal::Integer(1), span)));
let _ = FrameBound::UnboundedFollowing;
assert_ne!(FrameBound::UnboundedPreceding, FrameBound::CurrentRow);
}
#[test]
fn test_result_column_variants() {
let span = Span::ZERO;
let star = ResultColumn::Star;
let table_star = ResultColumn::TableStar("t1".to_owned());
let expr = ResultColumn::Expr {
expr: Expr::Literal(Literal::Integer(1), span),
alias: Some("one".to_owned()),
};
assert_ne!(star, table_star);
assert!(matches!(expr, ResultColumn::Expr { alias: Some(_), .. }));
}
}