use inillucent_base::limits::Limits;
use inillucent_value::Collation;
use crate::ast::{self, ConflictAction};
use crate::bind::{
no_such_column, refused, unsupported, Binder, BoundExpr, BoundOrderTerm, BoundResultColumn,
BoundSelect, BoundSource,
};
use crate::catalog_view::{IndexInfo, TableInfo, TableKind, TriggerEventInfo, TriggerInfo};
use crate::diagnostic::ParseError;
use crate::lexer::Span;
use crate::parser::parse_expression;
const WRITABLE_INTERNAL: [&[u8]; 4] = [
b"sqlite_sequence",
b"sqlite_stat1",
b"sqlite_schema",
b"sqlite_master",
];
#[derive(Clone, Debug, PartialEq)]
pub enum ColumnSource {
Row(usize),
Expr(BoundExpr),
Generated(BoundExpr),
}
#[derive(Clone, Debug, PartialEq)]
pub enum BoundInsertSource {
Values(Vec<Vec<BoundExpr>>),
Select(Box<BoundSelect>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct BoundCheck {
pub name: Option<Vec<u8>>,
pub expr: BoundExpr,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BoundDefault {
pub column: u16,
pub expr: BoundExpr,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BoundIndexExprs {
pub position: usize,
pub predicate: Option<BoundExpr>,
pub keys: Vec<Option<BoundExpr>>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum BoundTriggerStatement {
Insert(Box<BoundInsert>),
Update(Box<BoundUpdate>),
Delete(Box<BoundDelete>),
Select(Box<BoundSelect>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct BoundTrigger {
pub name: Vec<u8>,
pub table: Vec<u8>,
pub time: ast::TriggerTime,
pub when: Option<BoundExpr>,
pub body: Vec<BoundTriggerStatement>,
pub foreign_key: bool,
pub self_referencing: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BoundInsert {
pub table: TableInfo,
pub target_source: usize,
pub columns: Vec<ColumnSource>,
pub rowid: Option<ColumnSource>,
pub named_rowid: Option<usize>,
pub source: BoundInsertSource,
pub arity: usize,
pub on_conflict: Option<ConflictAction>,
pub checks: Vec<BoundCheck>,
pub not_null_defaults: Vec<BoundDefault>,
pub index_exprs: Vec<BoundIndexExprs>,
pub upsert: Vec<BoundUpsert>,
pub sequence_root: u32,
pub returning: Vec<BoundResultColumn>,
pub triggers: Vec<BoundTrigger>,
pub replace_triggers: Vec<BoundTrigger>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BoundUpsert {
pub target: Vec<u16>,
pub assignments: Vec<BoundAssignment>,
pub do_update: bool,
pub filter: Option<BoundExpr>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BoundAssignment {
pub column: u16,
pub rowid: bool,
pub value: BoundExpr,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BoundUpdate {
pub table: TableInfo,
pub source: usize,
pub from: Vec<crate::bind::BoundSource>,
pub assignments: Vec<BoundAssignment>,
pub generated: Vec<BoundAssignment>,
pub filter: Option<BoundExpr>,
pub on_conflict: Option<ConflictAction>,
pub checks: Vec<BoundCheck>,
pub not_null_defaults: Vec<BoundDefault>,
pub index_exprs: Vec<BoundIndexExprs>,
pub index_hint: crate::bind::IndexChoice,
pub returning: Vec<BoundResultColumn>,
pub order_by: Vec<BoundOrderTerm>,
pub limit: Option<BoundExpr>,
pub offset: Option<BoundExpr>,
pub triggers: Vec<BoundTrigger>,
pub view_rows: Option<Box<BoundSelect>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BoundDelete {
pub table: TableInfo,
pub index_exprs: Vec<BoundIndexExprs>,
pub index_hint: crate::bind::IndexChoice,
pub source: usize,
pub filter: Option<BoundExpr>,
pub returning: Vec<BoundResultColumn>,
pub order_by: Vec<BoundOrderTerm>,
pub limit: Option<BoundExpr>,
pub offset: Option<BoundExpr>,
pub triggers: Vec<BoundTrigger>,
pub view_rows: Option<Box<BoundSelect>>,
}
fn can_replace(table: &TableInfo, statement: Option<ConflictAction>) -> bool {
if statement == Some(ConflictAction::Replace) {
return true;
}
table
.indexes
.iter()
.any(|index| index.conflict == Some(ConflictAction::Replace))
|| table.columns.iter().any(|column| {
column.not_null_conflict == Some(ConflictAction::Replace)
|| column.primary_key_conflict == Some(ConflictAction::Replace)
})
}
fn fault_applies(
planned: &crate::catalog_view::ForeignKeyTrigger,
event: &TriggerEventInfo,
) -> bool {
match event {
TriggerEventInfo::Insert => planned.is_check,
TriggerEventInfo::Delete => !planned.is_check,
TriggerEventInfo::Update(_) => true,
}
}
fn report_as_foreign_key(trigger: &mut BoundTrigger) {
trigger.foreign_key = true;
for statement in &mut trigger.body {
let BoundTriggerStatement::Select(select) = statement else {
continue;
};
for column in &mut select.columns {
if let BoundExpr::Raise { foreign_key, .. } = &mut column.expr {
*foreign_key = true;
}
}
}
}
fn has_instead_of(table: &TableInfo, event: &TriggerEventInfo) -> bool {
table
.triggers
.iter()
.any(|trigger| trigger.time == ast::TriggerTime::InsteadOf && trigger.fires_for(event, &[]))
}
const ROWID_TARGET: u16 = u16::MAX;
fn is_rowid_name(folded: &[u8]) -> bool {
matches!(folded, b"rowid" | b"oid" | b"_rowid_")
}
pub const MAX_TRIGGER_DEPTH: usize = 1000;
pub const MAX_FOREIGN_KEY_DEPTH: usize = 64;
pub const MAX_FOREIGN_KEY_STATEMENTS: usize = 256;
impl<'a> Binder<'a> {
pub fn bind_insert(&mut self, insert: &ast::Insert) -> Result<BoundInsert, ParseError> {
let pushed = self.push_ctes(&insert.with)?;
let bound = self.bind_insert_body(insert);
if pushed {
self.pop_ctes();
}
bound
}
fn bind_insert_body(&mut self, insert: &ast::Insert) -> Result<BoundInsert, ParseError> {
let table = self.writable_target(
insert.database,
insert.table,
Span::default(),
&TriggerEventInfo::Insert,
)?;
let alias = match insert.alias {
Some(alias) => self.ast.text(alias).to_vec(),
None => table.name.clone(),
};
let target_source = self.push_write_source(table.clone(), alias);
let targets = match insert.source {
ast::InsertSource::DefaultValues => Vec::new(),
ast::InsertSource::Select(_) => self.insert_targets(&table, &insert.columns)?,
};
let (source, arity) = self.bind_insert_source(&insert.source, &table, &targets)?;
if arity != targets.len() {
return Err(refused(
format!("{} values for {} columns", arity, targets.len()),
Span::default(),
));
}
let (columns, rowid) = self.column_sources(&table, &targets)?;
let named_rowid = targets.iter().position(|target| *target == ROWID_TARGET);
let checks = self.bind_checks(&table)?;
let not_null_defaults = self.bind_not_null_defaults(&table)?;
let index_exprs = self.bind_index_exprs(&table)?;
let upsert = self.bind_upsert(&table, insert)?;
let returning = self.bind_returning(&insert.returning)?;
let mut triggers = self.bind_triggers(&table, TriggerEventInfo::Insert, &[])?;
triggers.extend(self.bind_foreign_keys(&table, TriggerEventInfo::Insert, &[])?);
let replace_triggers = if can_replace(&table, insert.on_conflict) {
self.bind_foreign_keys(&table, TriggerEventInfo::Delete, &[])?
} else {
Vec::new()
};
let sequence_root = if table.autoincrement {
self.catalog
.find_table(None, b"sqlite_sequence")
.map_or(0, |sequence| sequence.root)
} else {
0
};
Ok(BoundInsert {
table,
index_exprs,
target_source,
columns,
rowid,
named_rowid,
source,
arity,
on_conflict: insert.on_conflict,
checks,
not_null_defaults,
upsert,
sequence_root,
returning,
triggers,
replace_triggers,
})
}
pub fn bind_update(&mut self, update: &ast::Update) -> Result<BoundUpdate, ParseError> {
let pushed = self.push_ctes(&update.with)?;
let bound = self.bind_update_body(update);
if pushed {
self.pop_ctes();
}
bound
}
fn bind_update_from(
&mut self,
from: &[ast::FromTermId],
) -> Result<(Vec<crate::bind::BoundSource>, Vec<BoundExpr>), ParseError> {
let before = self.sources.len();
for term in from {
self.bind_from_term(*term)?;
}
self.desugar_join_constraints(from)?;
let arguments = core::mem::take(&mut self.pending_constraints);
let joined: Vec<crate::bind::BoundSource> = self
.scope()
.iter()
.filter(|id| **id >= before)
.filter_map(|id| self.sources.get(*id).cloned())
.collect();
Ok((joined, arguments))
}
fn bind_update_body(&mut self, update: &ast::Update) -> Result<BoundUpdate, ParseError> {
if let Some(refusal) = order_without_limit(update.limited_at, update.limit, "UPDATE") {
return Err(refusal);
}
let (table, source) =
self.write_target_from_term(update.target, &TriggerEventInfo::Update(Vec::new()))?;
let (joined, arguments) = self.bind_update_from(&update.from)?;
let mut assignments = Vec::new();
for (names, value) in &update.assignments {
let bound = self.bind_expr(*value)?;
for name in names {
let folded = self.ast.folded(*name).to_vec();
if table.is_rowid_name(&folded) {
if assignments.iter().any(|held: &BoundAssignment| held.rowid) {
return Err(refused(
format!(
"column {} is assigned twice",
String::from_utf8_lossy(self.ast.text(*name))
),
Span::default(),
));
}
assignments.push(BoundAssignment {
column: 0,
rowid: true,
value: bound.clone(),
});
continue;
}
let Some(position) = table.column_position(&folded) else {
return Err(no_such_column(self.ast.text(*name), Span::default()));
};
self.refuse_generated(&table, position, "UPDATE", Span::default())?;
if assignments
.iter()
.any(|existing: &BoundAssignment| existing.column == position)
{
return Err(refused(
format!(
"column {} is assigned twice",
String::from_utf8_lossy(self.ast.text(*name))
),
Span::default(),
));
}
assignments.push(BoundAssignment {
column: position,
rowid: false,
value: bound.clone(),
});
}
}
assignments.sort_by_key(|assignment| (assignment.rowid, assignment.column));
let mut filter = match update.filter {
Some(expr) => Some(self.bind_expr(expr)?),
None => None,
};
for constraint in arguments {
filter = Some(match filter.take() {
Some(existing) => BoundExpr::And(Box::new(existing), Box::new(constraint)),
None => constraint,
});
}
let generated = self.bind_stored_generated(&table)?;
let checks = self.bind_checks(&table)?;
let not_null_defaults = self.bind_not_null_defaults(&table)?;
let index_exprs = self.bind_index_exprs(&table)?;
let returning = self.bind_returning(&update.returning)?;
let order_by = self.bind_aggregate_order(&update.order_by)?;
let limit = match update.limit {
Some(expr) => Some(self.bind_expr(expr)?),
None => None,
};
let offset = match update.offset {
Some(expr) => Some(self.bind_expr(expr)?),
None => None,
};
let changed: Vec<Vec<u8>> = assignments
.iter()
.filter(|assignment| !assignment.rowid)
.filter_map(|assignment| table.column(assignment.column))
.map(|column| column.folded.clone())
.collect();
let mut triggers =
self.bind_triggers(&table, TriggerEventInfo::Update(Vec::new()), &changed)?;
triggers.extend(self.bind_foreign_keys(
&table,
TriggerEventInfo::Update(Vec::new()),
&changed,
)?);
let view_rows = self
.view_rows(&table, filter.clone())
.map(|rows| limit_view_rows(rows, &order_by, &limit, &offset));
let index_hint = self.write_hint(source, &index_exprs, filter.as_ref(), &joined)?;
Ok(BoundUpdate {
table,
index_exprs,
index_hint,
source,
from: joined,
assignments,
generated,
filter,
on_conflict: update.on_conflict,
checks,
not_null_defaults,
returning,
order_by,
limit,
offset,
triggers,
view_rows,
})
}
pub fn bind_delete(&mut self, delete: &ast::Delete) -> Result<BoundDelete, ParseError> {
let pushed = self.push_ctes(&delete.with)?;
let bound = self.bind_delete_body(delete);
if pushed {
self.pop_ctes();
}
bound
}
fn bind_delete_body(&mut self, delete: &ast::Delete) -> Result<BoundDelete, ParseError> {
if let Some(refusal) = order_without_limit(delete.limited_at, delete.limit, "DELETE") {
return Err(refusal);
}
let (table, source) =
self.write_target_from_term(delete.target, &TriggerEventInfo::Delete)?;
let index_exprs = self.bind_index_exprs(&table)?;
let filter = match delete.filter {
Some(expr) => Some(self.bind_expr(expr)?),
None => None,
};
let returning = self.bind_returning(&delete.returning)?;
let order_by = self.bind_aggregate_order(&delete.order_by)?;
let limit = match delete.limit {
Some(expr) => Some(self.bind_expr(expr)?),
None => None,
};
let offset = match delete.offset {
Some(expr) => Some(self.bind_expr(expr)?),
None => None,
};
let mut triggers = self.bind_triggers(&table, TriggerEventInfo::Delete, &[])?;
triggers.extend(self.bind_foreign_keys(&table, TriggerEventInfo::Delete, &[])?);
let view_rows = self
.view_rows(&table, filter.clone())
.map(|rows| limit_view_rows(rows, &order_by, &limit, &offset));
let index_hint = self.write_hint(source, &index_exprs, filter.as_ref(), &[])?;
Ok(BoundDelete {
table,
index_exprs,
index_hint,
source,
filter,
returning,
order_by,
limit,
offset,
triggers,
view_rows,
})
}
fn bind_triggers(
&mut self,
table: &TableInfo,
event: TriggerEventInfo,
changed: &[Vec<u8>],
) -> Result<Vec<BoundTrigger>, ParseError> {
let catalog = self.catalog;
let database = catalog.database_name(table.database).to_vec();
let Some(live) = catalog.find_table(Some(database.as_slice()), &table.folded) else {
return Ok(Vec::new());
};
let (old, new) = match event {
TriggerEventInfo::Insert => (false, true),
TriggerEventInfo::Delete => (true, false),
TriggerEventInfo::Update(_) => (true, true),
};
let mut bound = Vec::new();
for trigger in live.triggers.iter().rev() {
if !trigger.fires_for(&event, changed) {
continue;
}
if self.firing.contains(&trigger.folded) {
continue;
}
if self.firing.len() >= self.trigger_depth {
return Err(refused(
format!(
"too many levels of trigger recursion: the limit is {}",
self.trigger_depth
),
Span::default(),
));
}
self.firing.push(trigger.folded.clone());
let saved_ast = self.ast;
let saved_scopes = core::mem::take(&mut self.scopes);
let saved_aliases = self.row_aliases.take();
let saved_target = self.view_target.take();
let saved_site = self.call_site;
self.call_site = crate::function::CallSite::Schema;
self.ast = &trigger.ast;
self.row_aliases = Some(crate::bind::RowAliases {
table: table.clone(),
old,
new,
});
let result = self.bind_trigger_body(trigger, table);
self.call_site = saved_site;
self.ast = saved_ast;
self.scopes = saved_scopes;
self.row_aliases = saved_aliases;
self.view_target = saved_target;
self.firing.pop();
bound.push(result?);
}
Ok(bound)
}
fn bind_foreign_keys(
&mut self,
table: &TableInfo,
event: TriggerEventInfo,
changed: &[Vec<u8>],
) -> Result<Vec<BoundTrigger>, ParseError> {
if !self.foreign_keys || table.kind != TableKind::Table {
return Ok(Vec::new());
}
let catalog = self.catalog;
let database = catalog.database_name(table.database).to_vec();
let Some(live) = catalog.find_table(Some(database.as_slice()), &table.folded) else {
return Ok(Vec::new());
};
let mut bound = Vec::new();
for planned in &live.foreign_key_triggers {
if planned.is_check && (planned.deferred || self.defer_foreign_keys) {
continue;
}
let Some(trigger) = planned.trigger.as_ref() else {
if fault_applies(planned, &event) {
return Err(crate::bind::schema_refused(
String::from_utf8_lossy(&planned.fault).into_owned(),
Span::default(),
));
}
continue;
};
if !trigger.fires_for(&event, changed) {
continue;
}
if self.firing_foreign_keys.contains(&trigger.folded) {
continue;
}
let mut one = self.bind_foreign_key_trigger(table, trigger, &event)?;
one.self_referencing = planned.self_referencing;
bound.push(one);
}
Ok(bound)
}
fn bind_foreign_key_trigger(
&mut self,
table: &TableInfo,
trigger: &'a TriggerInfo,
event: &TriggerEventInfo,
) -> Result<BoundTrigger, ParseError> {
if self.foreign_key_depth >= MAX_FOREIGN_KEY_DEPTH || self.foreign_key_budget == 0 {
return Err(refused(
"too many levels of foreign key recursion",
Span::default(),
));
}
self.foreign_key_depth = self.foreign_key_depth.saturating_add(1);
self.foreign_key_budget = self.foreign_key_budget.saturating_sub(1);
self.firing_foreign_keys.push(trigger.folded.clone());
let (old, new) = match event {
TriggerEventInfo::Insert => (false, true),
TriggerEventInfo::Delete => (true, false),
TriggerEventInfo::Update(_) => (true, true),
};
let saved_ast = self.ast;
let saved_scopes = core::mem::take(&mut self.scopes);
let saved_aliases = self.row_aliases.take();
let saved_target = self.view_target.take();
let saved_site = self.call_site;
self.call_site = crate::function::CallSite::Schema;
self.ast = &trigger.ast;
self.row_aliases = Some(crate::bind::RowAliases {
table: table.clone(),
old,
new,
});
let result = self.bind_trigger_body(trigger, table);
self.call_site = saved_site;
self.ast = saved_ast;
self.scopes = saved_scopes;
self.row_aliases = saved_aliases;
self.view_target = saved_target;
self.foreign_key_depth = self.foreign_key_depth.saturating_sub(1);
self.firing_foreign_keys.pop();
let mut bound = result?;
report_as_foreign_key(&mut bound);
Ok(bound)
}
fn bind_trigger_body(
&mut self,
trigger: &TriggerInfo,
table: &TableInfo,
) -> Result<BoundTrigger, ParseError> {
let when = match trigger.when {
Some(expr) => Some(self.bind_expr(expr)?),
None => None,
};
let mut body = Vec::new();
for statement in &trigger.body {
let saved = core::mem::take(&mut self.scopes);
let one = self.bind_trigger_statement(statement);
self.scopes = saved;
body.push(one?);
}
Ok(BoundTrigger {
name: trigger.name.clone(),
table: table.folded.clone(),
time: trigger.time,
when,
body,
foreign_key: false,
self_referencing: false,
})
}
pub(crate) fn bind_trigger_statement(
&mut self,
statement: &ast::Statement,
) -> Result<BoundTriggerStatement, ParseError> {
match statement {
ast::Statement::Insert(insert) => {
if !insert.returning.is_empty() {
return Err(refused(
"RETURNING is not allowed on a trigger body statement",
Span::default(),
));
}
Ok(BoundTriggerStatement::Insert(Box::new(
self.bind_insert(insert)?,
)))
}
ast::Statement::Update(update) => {
if !update.returning.is_empty() {
return Err(refused(
"RETURNING is not allowed on a trigger body statement",
Span::default(),
));
}
Ok(BoundTriggerStatement::Update(Box::new(
self.bind_update(update)?,
)))
}
ast::Statement::Delete(delete) => {
if !delete.returning.is_empty() {
return Err(refused(
"RETURNING is not allowed on a trigger body statement",
Span::default(),
));
}
Ok(BoundTriggerStatement::Delete(Box::new(
self.bind_delete(delete)?,
)))
}
ast::Statement::Select(select) => Ok(BoundTriggerStatement::Select(Box::new(
self.bind_select(*select)?,
))),
_ => Err(unsupported(
"that statement in a trigger body",
Span::default(),
)),
}
}
fn writable_target(
&mut self,
database: Option<ast::NameId>,
name: ast::NameId,
span: Span,
event: &TriggerEventInfo,
) -> Result<TableInfo, ParseError> {
let qualifier = database.map(|id| self.ast.folded(id).to_vec());
let folded = self.ast.folded(name).to_vec();
let Some(table) = self
.catalog
.find_table(qualifier.as_deref(), &folded)
.cloned()
else {
return Err(crate::bind::no_such_table(self.ast.text(name), span));
};
match table.kind {
TableKind::View => {
if !has_instead_of(&table, event) {
return Err(unsupported("writing to a view", span));
}
let expanded = self.expanded_view(&table, span)?;
self.record_write_dependency(table.database);
return Ok(expanded);
}
TableKind::Virtual => {
if table.columns.is_empty() {
return Err(unsupported("that virtual table's module", span));
}
self.record_write_dependency(table.database);
return Ok(table);
}
TableKind::Subquery => return Err(unsupported("writing to a subquery", span)),
TableKind::Table => {}
}
if table.folded.starts_with(b"sqlite_")
&& !WRITABLE_INTERNAL.contains(&table.folded.as_slice())
{
return Err(unsupported(
"writing to a table whose name begins with sqlite_",
span,
));
}
self.record_write_dependency(table.database);
Ok(table)
}
fn write_target_from_term(
&mut self,
id: ast::FromTermId,
event: &TriggerEventInfo,
) -> Result<(TableInfo, usize), ParseError> {
let Some(term) = self.ast.from_term(id) else {
return Err(unsupported("missing target", Span::default()));
};
let ast::FromSource::Table {
database,
name,
indexed_by,
..
} = term.source
else {
return Err(unsupported("a target that is not a table", term.span));
};
let table = self.writable_target(database, name, term.span, event)?;
if let ast::IndexHint::IndexedBy(index) = indexed_by {
let folded = self.ast.folded(index).to_vec();
if !table.indexes.iter().any(|held| held.folded == folded) {
return Err(crate::bind::no_such_index(self.ast.text(index), term.span));
}
}
let alias = match term.alias {
Some(alias) => self.ast.text(alias).to_vec(),
None => table.name.clone(),
};
if table.kind == TableKind::View {
let inner = self.view_query(&table, term.span)?;
let source = BoundSource {
index_hint: crate::bind::IndexChoice::Any,
id: self.sources.len(),
rows: crate::bind::SourceRows::Subquery(Box::new(inner)),
table: std::rc::Rc::new(table.clone()),
alias,
join: ast::JoinKind::Comma,
constraint: None,
suppressed: Vec::new(),
index_exprs: Vec::new(),
};
self.view_target = Some(source.id);
let scope = source.id;
self.sources.push(source);
self.scopes.push(vec![scope]);
return Ok((table, scope));
}
let scope = self.push_write_source(table.clone(), alias);
let choice = self.index_choice(indexed_by);
if let Some(source) = self.sources.get_mut(scope) {
source.index_hint = choice;
}
Ok((table, scope))
}
pub(crate) fn expanded_view(
&mut self,
table: &TableInfo,
span: Span,
) -> Result<TableInfo, ParseError> {
let bound = self.view_query(table, span)?;
let mut expanded = table.clone();
expanded.columns = crate::bind::subquery_columns(&bound, &[]);
Ok(expanded)
}
fn view_query(&mut self, table: &TableInfo, span: Span) -> Result<BoundSelect, ParseError> {
let catalog = self.catalog;
let database = catalog.database_name(table.database).to_vec();
let Some(live) = catalog.find_table(Some(database.as_slice()), &table.folded) else {
return Err(crate::bind::no_such_table(&table.name, span));
};
let Some(body) = live.view.as_ref() else {
return Err(unsupported(
"a view whose definition could not be parsed",
span,
));
};
let names = body.columns.clone();
let saved_ast = self.ast;
let saved_scopes = core::mem::take(&mut self.scopes);
self.ast = &body.ast;
let bound = self.bind_select(body.select);
self.ast = saved_ast;
self.scopes = saved_scopes;
let mut bound = bound?;
for (position, name) in names.iter().enumerate() {
if let Some(column) = bound.columns.get_mut(position) {
column.name = name.clone();
}
}
Ok(bound)
}
fn view_rows(
&mut self,
table: &TableInfo,
filter: Option<BoundExpr>,
) -> Option<Box<BoundSelect>> {
if table.kind != TableKind::View {
return None;
}
let id = self.view_target.take()?;
let source = self.sources.get(id)?.clone();
let columns = table
.columns
.iter()
.enumerate()
.map(|(position, column)| BoundResultColumn {
expr: BoundExpr::Column {
source: id,
column: position as u16,
slot: position as u16,
affinity: column.affinity,
collation: Collation::from_name(
core::str::from_utf8(&column.collation).unwrap_or("BINARY"),
)
.unwrap_or(Collation::Binary),
},
name: column.name.clone(),
origin: None,
declared_type: column.declared_type.clone(),
})
.collect();
Some(Box::new(crate::bind::block_over(source, filter, columns)))
}
fn write_hint(
&self,
source: usize,
index_exprs: &[BoundIndexExprs],
filter: Option<&BoundExpr>,
joined: &[BoundSource],
) -> Result<crate::bind::IndexChoice, ParseError> {
let Some(target) = self.sources.get(source) else {
return Ok(crate::bind::IndexChoice::Any);
};
if target.index_hint == crate::bind::IndexChoice::Any {
return Ok(crate::bind::IndexChoice::Any);
}
let mut probe = target.clone();
probe.index_exprs = index_exprs.to_vec();
let mut block = crate::bind::block_over(probe, filter.cloned(), Vec::new());
block.sources.extend(joined.iter().cloned());
if crate::plan::unanswerable_index_hint(&block).is_some() {
return Err(crate::bind::no_query_solution(Span::default()));
}
Ok(target.index_hint.clone())
}
fn push_write_source(&mut self, table: TableInfo, alias: Vec<u8>) -> usize {
let id = self.sources.len();
self.sources.push(BoundSource {
index_hint: crate::bind::IndexChoice::Any,
id,
rows: crate::bind::SourceRows::Table,
table: std::rc::Rc::new(table),
alias,
join: ast::JoinKind::Comma,
constraint: None,
suppressed: Vec::new(),
index_exprs: Vec::new(),
});
self.scopes.push(vec![id]);
id
}
fn refuse_generated(
&self,
table: &TableInfo,
position: u16,
verb: &str,
span: Span,
) -> Result<(), ParseError> {
let Some(column) = table.column(position) else {
return Ok(());
};
if !column.generated {
return Ok(());
}
Err(refused(
format!(
"cannot {verb} generated column \"{}\"",
String::from_utf8_lossy(&column.name)
),
span,
))
}
fn insert_targets(
&self,
table: &TableInfo,
columns: &[ast::NameId],
) -> Result<Vec<u16>, ParseError> {
if columns.is_empty() {
return Ok((0..table.columns.len() as u16)
.filter(|position| {
table
.column(*position)
.is_some_and(|column| !column.generated && !column.hidden)
})
.collect());
}
let mut targets = Vec::with_capacity(columns.len());
for name in columns {
let folded = self.ast.folded(*name).to_vec();
let position = match table.column_position(&folded) {
Some(position) => position,
None if table.has_rowid() && is_rowid_name(&folded) => ROWID_TARGET,
None => return Err(no_such_column(self.ast.text(*name), Span::default())),
};
if targets.contains(&position) {
return Err(refused(
format!(
"column {} is named twice",
String::from_utf8_lossy(self.ast.text(*name))
),
Span::default(),
));
}
if position != ROWID_TARGET {
self.refuse_generated(table, position, "INSERT into", Span::default())?;
}
targets.push(position);
}
Ok(targets)
}
fn bind_insert_source(
&mut self,
source: &ast::InsertSource,
table: &TableInfo,
targets: &[u16],
) -> Result<(BoundInsertSource, usize), ParseError> {
match source {
ast::InsertSource::DefaultValues => {
let _ = (table, targets);
Ok((BoundInsertSource::Values(vec![Vec::new()]), 0))
}
ast::InsertSource::Select(id) => {
let saved = core::mem::take(&mut self.scopes);
let select = self.bind_select(*id);
let bound = match select {
Ok(bound) => bound,
Err(error) => {
self.scopes = saved;
return Err(error);
}
};
self.scopes = saved;
if bound.values.is_empty() {
let arity = bound.columns.len();
return Ok((BoundInsertSource::Select(Box::new(bound)), arity));
}
let arity = bound.values.first().map_or(0, Vec::len);
for row in &bound.values {
if row.len() != arity {
return Err(unsupported(
"all VALUES rows must have the same number of columns",
Span::default(),
));
}
}
Ok((BoundInsertSource::Values(bound.values), arity))
}
}
}
fn column_sources(
&mut self,
table: &TableInfo,
targets: &[u16],
) -> Result<(Vec<ColumnSource>, Option<ColumnSource>), ParseError> {
let mut columns = Vec::with_capacity(table.columns.len());
for position in 0..table.columns.len() as u16 {
if let Some(expr) = self.generated_expr(table, position)? {
columns.push(ColumnSource::Generated(expr));
continue;
}
let source = match targets.iter().position(|target| *target == position) {
Some(index) => ColumnSource::Row(index),
None => ColumnSource::Expr(self.default_expr(table, position)?),
};
columns.push(source);
}
let rowid = match table.rowid_alias {
Some(position) => columns.get(position as usize).cloned(),
None => None,
};
Ok((columns, rowid))
}
fn generated_expr(
&mut self,
table: &TableInfo,
position: u16,
) -> Result<Option<BoundExpr>, ParseError> {
let Some(column) = table.column(position) else {
return Ok(None);
};
if !column.generated {
return Ok(None);
}
let Some(sql) = column.generated_sql.clone() else {
return Ok(Some(BoundExpr::Null));
};
Ok(Some(self.bind_schema_expr(&sql)?))
}
fn bind_stored_generated(
&mut self,
table: &TableInfo,
) -> Result<Vec<BoundAssignment>, ParseError> {
let mut generated = Vec::new();
for position in 0..table.columns.len() as u16 {
let Some(column) = table.column(position) else {
continue;
};
if !column.generated || !column.stored {
continue;
}
let Some(expr) = self.generated_expr(table, position)? else {
continue;
};
generated.push(BoundAssignment {
column: position,
rowid: false,
value: expr,
});
}
Ok(generated)
}
fn default_expr(&mut self, table: &TableInfo, position: u16) -> Result<BoundExpr, ParseError> {
let Some(column) = table.column(position) else {
return Ok(BoundExpr::Null);
};
let Some(sql) = column.default_sql.as_ref() else {
return Ok(BoundExpr::Null);
};
if sql.is_empty() {
return Ok(BoundExpr::Null);
}
self.bind_schema_expr(sql)
}
fn bind_not_null_defaults(
&mut self,
table: &TableInfo,
) -> Result<Vec<BoundDefault>, ParseError> {
let mut defaults = Vec::new();
for (position, column) in table.columns.iter().enumerate() {
if !column.not_null || Some(position as u16) == table.rowid_alias {
continue;
}
let Some(sql) = column.default_sql.as_ref() else {
continue;
};
if sql.is_empty() {
continue;
}
let expr = self.bind_schema_expr(&sql.clone())?;
defaults.push(BoundDefault {
column: position as u16,
expr,
});
}
Ok(defaults)
}
fn bind_checks(&mut self, table: &TableInfo) -> Result<Vec<BoundCheck>, ParseError> {
let mut checks = Vec::with_capacity(table.checks.len());
for check in &table.checks {
checks.push(BoundCheck {
name: check.name.clone(),
expr: self.bind_schema_expr(&check.expr_sql)?,
});
}
Ok(checks)
}
fn bind_index_exprs(&mut self, table: &TableInfo) -> Result<Vec<BoundIndexExprs>, ParseError> {
let mut bound = Vec::new();
for (position, index) in table.indexes.iter().enumerate() {
let needs = index.partial_sql.is_some()
|| index.columns.iter().any(|key| key.expr_sql.is_some());
if !needs {
continue;
}
let predicate = match index.partial_sql.as_ref() {
Some(sql) => Some(self.bind_schema_expr(sql)?),
None => None,
};
let mut keys = Vec::with_capacity(index.columns.len());
for key in &index.columns {
keys.push(match key.expr_sql.as_ref() {
Some(sql) => Some(self.bind_schema_expr(sql)?),
None => None,
});
}
bound.push(BoundIndexExprs {
position,
predicate,
keys,
});
}
Ok(bound)
}
pub fn bind_schema_expr(&mut self, sql: &[u8]) -> Result<BoundExpr, ParseError> {
let limits = Limits::default();
let (ast, expr) = parse_expression(sql, &limits)?;
let mut nested = Binder::new(self.catalog, &ast, self.authorizer);
nested.trigger_depth = self.trigger_depth;
nested.externals = self.externals;
nested.collations = self.collations;
nested.trusted_schema = self.trusted_schema;
nested.call_site = crate::function::CallSite::Schema;
nested.sources = self.sources.clone();
nested.scopes = self.scopes.clone();
let bound = nested.bind_expr(expr)?;
Ok(bound)
}
fn bind_upsert(
&mut self,
table: &TableInfo,
insert: &ast::Insert,
) -> Result<Vec<BoundUpsert>, ParseError> {
if insert.upserts.is_empty() {
return Ok(Vec::new());
}
for upsert in &insert.upserts {
if upsert.target_filter.is_some() {
return Err(unsupported(
"a partial-index conflict target",
Span::default(),
));
}
}
if let Some(position) = insert
.upserts
.iter()
.position(|upsert| upsert.target.is_empty())
{
if position + 1 < insert.upserts.len() {
return Err(crate::bind::schema_refused(
"ON CONFLICT clause with no conflict target must be last",
Span::default(),
));
}
}
self.excluded = Some(table.clone());
let mut bound = Vec::with_capacity(insert.upserts.len());
for upsert in &insert.upserts {
match self.bind_upsert_body(table, upsert) {
Ok(Some(one)) => bound.push(one),
Ok(None) => {}
Err(error) => {
self.excluded = None;
return Err(error);
}
}
}
self.excluded = None;
Ok(bound)
}
fn bind_upsert_body(
&mut self,
table: &TableInfo,
upsert: &ast::Upsert,
) -> Result<Option<BoundUpsert>, ParseError> {
let mut target = Vec::new();
for column in &upsert.target {
let Some(name) = bare_indexed_column(self.ast, column) else {
return Err(unsupported(
"an expression in a conflict target",
Span::default(),
));
};
let Some(position) = table.column_position(&name) else {
return Err(no_such_column(&name, Span::default()));
};
target.push(position);
}
target.sort_unstable();
let mut assignments = Vec::new();
for (names, value) in &upsert.assignments {
let bound = self.bind_expr(*value)?;
for name in names {
let folded = self.ast.folded(*name).to_vec();
let Some(position) = table.column_position(&folded) else {
return Err(no_such_column(self.ast.text(*name), Span::default()));
};
assignments.push(BoundAssignment {
column: position,
rowid: false,
value: bound.clone(),
});
}
}
assignments.sort_by_key(|assignment| assignment.column);
let filter = match upsert.filter {
Some(expr) => Some(self.bind_expr(expr)?),
None => None,
};
Ok(Some(BoundUpsert {
target,
assignments,
do_update: upsert.do_update,
filter,
}))
}
fn bind_returning(
&mut self,
columns: &[ast::ResultColumn],
) -> Result<Vec<BoundResultColumn>, ParseError> {
if columns.is_empty() {
return Ok(Vec::new());
}
self.bind_result_columns_public(columns)
}
}
fn bare_indexed_column(ast: &crate::Ast, column: &ast::IndexedColumn) -> Option<Vec<u8>> {
match ast.expr(column.expr) {
Some(ast::Expr::Column {
table: None,
column: name,
..
}) => Some(ast.folded(*name).to_vec()),
_ => None,
}
}
pub mod codes {
pub const CHECK: i32 = 275;
pub const DATATYPE: i32 = 3091;
pub const NOT_NULL: i32 = 1299;
pub const PRIMARY_KEY: i32 = 1555;
pub const UNIQUE: i32 = 2067;
pub const ROWID: i32 = 2579;
pub const MISMATCH: i32 = 20;
pub const TRIGGER: i32 = 1811;
pub const FOREIGN_KEY: i32 = 787;
}
pub fn unique_message(table: &TableInfo, index: &IndexInfo) -> String {
let names: Vec<String> = index
.columns
.iter()
.filter_map(|key| key.column)
.filter_map(|column| table.column(column))
.map(|column| {
format!(
"{}.{}",
String::from_utf8_lossy(&table.name),
String::from_utf8_lossy(&column.name)
)
})
.collect();
format!("UNIQUE constraint failed: {}", names.join(", "))
}
pub fn rowid_message(table: &TableInfo) -> (i32, String) {
if table.without_rowid {
if let Some(index) = table.indexes.iter().find(|index| index.root == table.root) {
return (codes::PRIMARY_KEY, unique_message(table, index));
}
}
match table.rowid_alias.and_then(|column| table.column(column)) {
Some(column) => (
codes::PRIMARY_KEY,
format!(
"UNIQUE constraint failed: {}.{}",
String::from_utf8_lossy(&table.name),
String::from_utf8_lossy(&column.name)
),
),
None => (
codes::ROWID,
format!(
"UNIQUE constraint failed: {}.rowid",
String::from_utf8_lossy(&table.name)
),
),
}
}
fn limit_view_rows(
mut rows: Box<BoundSelect>,
order_by: &[BoundOrderTerm],
limit: &Option<BoundExpr>,
offset: &Option<BoundExpr>,
) -> Box<BoundSelect> {
rows.order_by = order_by.to_vec();
rows.limit = limit.clone();
rows.offset = offset.clone();
rows
}
fn order_without_limit(
limited: Option<(ast::Limited, Span)>,
limit: Option<ast::ExprId>,
statement: &str,
) -> Option<ParseError> {
let (word, span) = limited?;
if word != ast::Limited::OrderBy || limit.is_some() {
return None;
}
Some(refused(
format!("ORDER BY without LIMIT on {statement}"),
span,
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::catalog_view::{
ColumnInfo, IndexColumnInfo, IndexInfo, IndexOrigin, TableInfo, TableKind,
};
use inillucent_value::Affinity;
fn a_column(name: &str) -> ColumnInfo {
ColumnInfo {
name: name.as_bytes().to_vec(),
folded: name.to_ascii_lowercase().into_bytes(),
declared_type: b"INTEGER".to_vec(),
affinity: Affinity::Integer,
collation: b"binary".to_vec(),
not_null: false,
not_null_conflict: None,
primary_key_conflict: None,
default_sql: None,
primary_key_position: None,
hidden: false,
generated: false,
stored: false,
generated_sql: None,
}
}
fn a_table(name: &str, columns: &[&str]) -> TableInfo {
TableInfo {
name: name.as_bytes().to_vec(),
folded: name.to_ascii_lowercase().into_bytes(),
database: 0,
root: 2,
columns: columns.iter().map(|held| a_column(held)).collect(),
rowid_alias: None,
without_rowid: false,
strict: false,
autoincrement: false,
kind: TableKind::Table,
create_sql: Vec::new(),
indexes: Vec::new(),
view: None,
triggers: Vec::new(),
analysed_rows: None,
foreign_key_triggers: Vec::new(),
foreign_keys: Vec::new(),
checks: Vec::new(),
module: None,
}
}
fn an_index(name: &str, root: u32, columns: &[u16]) -> IndexInfo {
IndexInfo {
name: name.as_bytes().to_vec(),
folded: name.to_ascii_lowercase().into_bytes(),
root,
unique: true,
columns: columns
.iter()
.map(|held| IndexColumnInfo {
column: Some(*held),
expr_sql: None,
collation: b"binary".to_vec(),
descending: false,
declared_descending: false,
})
.collect(),
partial_sql: None,
origin: IndexOrigin::Unique,
conflict: None,
prefix_rows: Vec::new(),
analysed_rows: None,
metric: None,
}
}
#[test]
fn the_rowid_has_three_names() {
assert!(is_rowid_name(b"rowid"));
assert!(is_rowid_name(b"oid"));
assert!(is_rowid_name(b"_rowid_"));
assert!(!is_rowid_name(b"row_id"));
assert!(!is_rowid_name(b"id"));
assert!(
!is_rowid_name(b"ROWID"),
"the argument is already folded, so an unfolded name is not one this asks about"
);
}
#[test]
fn a_unique_violation_names_every_column_of_the_index() {
let table = a_table("t", &["a", "b", "c"]);
let one = an_index("by_a", 3, &[0]);
assert_eq!(
unique_message(&table, &one),
"UNIQUE constraint failed: t.a"
);
let two = an_index("by_a_b", 4, &[0, 1]);
assert_eq!(
unique_message(&table, &two),
"UNIQUE constraint failed: t.a, t.b",
"both columns, in key order, separated the way the reference separates them"
);
}
#[test]
fn a_rowid_collision_names_the_column_that_aliases_it() {
let hidden = a_table("t", &["a"]);
assert_eq!(
rowid_message(&hidden),
(
codes::ROWID,
"UNIQUE constraint failed: t.rowid".to_string()
)
);
let mut aliased = a_table("t", &["id", "a"]);
aliased.rowid_alias = Some(0);
assert_eq!(
rowid_message(&aliased),
(
codes::PRIMARY_KEY,
"UNIQUE constraint failed: t.id".to_string()
)
);
}
#[test]
fn a_without_rowid_collision_names_the_primary_key() {
let mut table = a_table("t", &["a", "b"]);
table.without_rowid = true;
table.indexes = vec![an_index("sqlite_autoindex_t_1", table.root, &[0, 1])];
assert_eq!(
rowid_message(&table),
(
codes::PRIMARY_KEY,
"UNIQUE constraint failed: t.a, t.b".to_string()
)
);
}
#[test]
fn a_constraint_can_make_a_plain_insert_replace() {
let plain = a_table("t", &["a"]);
assert!(!can_replace(&plain, None));
assert!(can_replace(&plain, Some(ConflictAction::Replace)));
let mut on_the_index = a_table("t", &["a"]);
let mut index = an_index("by_a", 3, &[0]);
index.conflict = Some(ConflictAction::Replace);
on_the_index.indexes = vec![index];
assert!(
can_replace(&on_the_index, None),
"`a UNIQUE ON CONFLICT REPLACE` replaces without the statement saying so"
);
let mut on_the_column = a_table("t", &["a"]);
if let Some(column) = on_the_column.columns.first_mut() {
column.not_null_conflict = Some(ConflictAction::Replace);
}
assert!(can_replace(&on_the_column, None));
}
}