pub mod datetime;
pub mod eval;
pub mod func;
pub mod json;
pub mod vdbe;
mod window;
use crate::btree::{
clear_index, clear_table, create_index_root, create_table_root, delete_table, free_tree,
insert_index, insert_table, table_has_empty_leaf, IndexCursor, TableCursor,
};
use crate::error::{Error, Result};
use crate::format::record::{decode_record, encode_record};
use crate::pager::{AutoVacuum, PageSource, WritePager};
use crate::schema::Schema;
use crate::sql::ast::*;
use crate::sql::{self};
use crate::value::Value;
use crate::vfs::{OpenFlags, Vfs};
use crate::vtab::{
ConstraintOp, DynVTabModule, IndexConstraint, IndexPlan, VTabChange, VTabRegistry, VTabStore,
};
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use eval::{ColumnInfo, EvalCtx, Params};
#[derive(Debug, Clone, PartialEq)]
pub struct QueryResult {
pub columns: Vec<String>,
pub rows: Vec<Vec<Value>>,
}
enum Backend {
Write(Box<WritePager>),
Read(Box<dyn PageSource>),
}
impl Backend {
fn source(&self) -> &dyn PageSource {
match self {
Backend::Write(w) => w.as_ref(),
Backend::Read(r) => r.as_ref(),
}
}
fn writer(&mut self) -> Result<&mut WritePager> {
match self {
Backend::Write(w) => Ok(w),
Backend::Read(_) => Err(Error::Error("database is read-only".into())),
}
}
fn wal_mode(&self) -> bool {
matches!(self, Backend::Write(w) if w.wal_mode())
}
}
pub struct Connection {
backend: Backend,
schema: Schema,
main_file: String,
attached: Vec<AttachedDb>,
temp_db: Option<AttachedDb>,
in_tx: bool,
cte_env: core::cell::RefCell<Vec<CteBinding>>,
outer_scope: core::cell::RefCell<Vec<OuterFrame>>,
foreign_keys: bool,
trigger_depth: core::cell::Cell<usize>,
stmt_keep_partial: core::cell::Cell<bool>,
stmt_rollback_tx: core::cell::Cell<bool>,
raise_ignore: core::cell::Cell<bool>,
recursive_triggers: bool,
returning_rows: core::cell::RefCell<Vec<Vec<Value>>>,
open_savepoints: usize,
last_insert_rowid: core::cell::Cell<i64>,
changes: core::cell::Cell<i64>,
total_changes: core::cell::Cell<i64>,
read_default: core::cell::Cell<DbRef>,
vtab_registry: VTabRegistry,
rng_state: core::cell::Cell<u64>,
cache_size: core::cell::Cell<i64>,
functions: alloc::collections::BTreeMap<String, ScalarFunction>,
aggregates: alloc::collections::BTreeMap<String, AggregateFactory>,
fts5_rank: core::cell::RefCell<Option<Fts5QueryCtx>>,
}
pub type ScalarFunction = Box<dyn Fn(&[Value]) -> Result<Value>>;
pub trait AggregateFunction {
fn step(&mut self, args: &[Value]) -> Result<()>;
fn finalize(&mut self) -> Result<Value>;
}
pub type AggregateFactory = Box<dyn Fn() -> Box<dyn AggregateFunction>>;
fn initial_rng_seed() -> u64 {
#[cfg(feature = "std")]
{
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
nanos ^ 0x9E37_79B9_7F4A_7C15
}
#[cfg(not(feature = "std"))]
{
0x9E37_79B9_7F4A_7C15
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum DbRef {
Main,
Temp,
Attached(usize),
}
struct AttachedDb {
name: String,
file: String,
backend: Backend,
schema: Schema,
}
struct CteBinding {
name: String,
columns: Vec<ColumnInfo>,
rows: Vec<InputRow>,
}
struct OuterFrame {
columns: Vec<ColumnInfo>,
row: Vec<Value>,
rowid: Option<i64>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum TrigEvent {
Insert,
Update,
Delete,
}
impl Connection {
fn from_pager(db: WritePager) -> Result<Connection> {
let backend = Backend::Write(Box::new(db));
let schema = Schema::read(backend.source())?;
Ok(Connection {
backend,
schema,
main_file: String::new(),
attached: Vec::new(),
temp_db: None,
in_tx: false,
cte_env: core::cell::RefCell::new(Vec::new()),
outer_scope: core::cell::RefCell::new(Vec::new()),
foreign_keys: false,
trigger_depth: core::cell::Cell::new(0),
stmt_keep_partial: core::cell::Cell::new(false),
stmt_rollback_tx: core::cell::Cell::new(false),
raise_ignore: core::cell::Cell::new(false),
recursive_triggers: false,
returning_rows: core::cell::RefCell::new(Vec::new()),
open_savepoints: 0,
last_insert_rowid: core::cell::Cell::new(0),
changes: core::cell::Cell::new(0),
total_changes: core::cell::Cell::new(0),
read_default: core::cell::Cell::new(DbRef::Main),
vtab_registry: VTabRegistry::with_builtins(),
rng_state: core::cell::Cell::new(initial_rng_seed()),
cache_size: core::cell::Cell::new(-2000),
functions: alloc::collections::BTreeMap::new(),
aggregates: alloc::collections::BTreeMap::new(),
fts5_rank: core::cell::RefCell::new(None),
})
}
fn from_read_backend(backend: Box<dyn PageSource>) -> Result<Connection> {
let backend = Backend::Read(backend);
let schema = Schema::read(backend.source())?;
Ok(Connection {
backend,
schema,
main_file: String::new(),
attached: Vec::new(),
temp_db: None,
in_tx: false,
cte_env: core::cell::RefCell::new(Vec::new()),
outer_scope: core::cell::RefCell::new(Vec::new()),
foreign_keys: false,
trigger_depth: core::cell::Cell::new(0),
stmt_keep_partial: core::cell::Cell::new(false),
stmt_rollback_tx: core::cell::Cell::new(false),
raise_ignore: core::cell::Cell::new(false),
recursive_triggers: false,
returning_rows: core::cell::RefCell::new(Vec::new()),
open_savepoints: 0,
last_insert_rowid: core::cell::Cell::new(0),
changes: core::cell::Cell::new(0),
total_changes: core::cell::Cell::new(0),
read_default: core::cell::Cell::new(DbRef::Main),
vtab_registry: VTabRegistry::with_builtins(),
rng_state: core::cell::Cell::new(initial_rng_seed()),
cache_size: core::cell::Cell::new(-2000),
functions: alloc::collections::BTreeMap::new(),
aggregates: alloc::collections::BTreeMap::new(),
fts5_rank: core::cell::RefCell::new(None),
})
}
pub fn open_vfs(vfs: &dyn Vfs, path: &str) -> Result<Connection> {
let main = vfs.open(path, OpenFlags::READ_WRITE)?;
let journal = vfs.open(&journal_path(path), OpenFlags::READ_WRITE_CREATE)?;
let wal = vfs.open(&wal_path(path), OpenFlags::READ_WRITE_CREATE)?;
let mut c = Connection::from_pager(WritePager::open_wal(main, Some(journal), Some(wal))?)?;
c.main_file = path.to_string();
Ok(c)
}
pub fn open_readonly_vfs(vfs: &dyn Vfs, path: &str) -> Result<Connection> {
let main = vfs.open(path, OpenFlags::READ_ONLY)?;
let wal_path = wal_path(path);
if vfs.exists(&wal_path)? {
let mut wal = vfs.open(&wal_path, OpenFlags::READ_ONLY)?;
let reader = crate::pager::WalReader::open(main, wal.as_mut())?;
let mut c = Connection::from_read_backend(Box::new(reader))?;
c.main_file = path.to_string();
return Ok(c);
}
let mut c = Connection::from_read_backend(Box::new(WritePager::open(main, None)?))?;
c.main_file = path.to_string();
Ok(c)
}
pub fn create_vfs(vfs: &dyn Vfs, path: &str, page_size: u32) -> Result<Connection> {
let main = vfs.open(path, OpenFlags::READ_WRITE_CREATE)?;
let journal = vfs.open(&journal_path(path), OpenFlags::READ_WRITE_CREATE)?;
let wal = vfs.open(&wal_path(path), OpenFlags::READ_WRITE_CREATE)?;
let mut db = WritePager::create_wal(main, Some(journal), Some(wal), page_size)?;
db.commit()?;
let mut c = Connection::from_pager(db)?;
c.main_file = path.to_string();
Ok(c)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn open(path: &str) -> Result<Connection> {
Connection::open_vfs(&crate::vfs::std_file::StdVfs::new(), path)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn open_readonly(path: &str) -> Result<Connection> {
Connection::open_readonly_vfs(&crate::vfs::std_file::StdVfs::new(), path)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn create(path: &str) -> Result<Connection> {
Connection::create_vfs(&crate::vfs::std_file::StdVfs::new(), path, 4096)
}
pub fn open_memory() -> Result<Connection> {
let vfs = crate::vfs::memory::MemoryVfs::new();
let main = vfs.open("main", OpenFlags::READ_WRITE_CREATE)?;
let mut db = WritePager::create(main, None, 4096)?;
db.commit()?;
Connection::from_pager(db)
}
pub fn schema(&self) -> &Schema {
&self.schema
}
pub fn query(&self, sql: &str) -> Result<QueryResult> {
self.query_params(sql, &Params::default())
}
pub fn query_vdbe(&self, sql: &str) -> Result<QueryResult> {
let Statement::Select(sel) = sql::parse_one(sql)? else {
return Err(Error::Unsupported("query_vdbe expects SELECT"));
};
let Some(from) = &sel.from else {
let prog = vdbe::compile_const_select(&sel)?;
let rows = vdbe::run(&prog)?;
return Ok(QueryResult {
columns: prog.columns,
rows,
});
};
if !from.joins.is_empty() || from.first.subquery.is_some() || from.first.tvf_args.is_some()
{
return Err(Error::Unsupported("VDBE: only a single plain table"));
}
let meta = self.table_meta(&from.first.name, from.first.alias.as_deref())?;
let col_names: Vec<String> = meta.columns.iter().map(|c| c.name.clone()).collect();
let prog = vdbe::compile_table_select(&sel, &col_names)?;
let rows: Vec<Vec<Value>> = if meta.without_rowid {
self.scan_without_rowid(&meta)?
} else {
self.scan_table(&meta)?
.into_iter()
.map(|(_, v)| v)
.collect()
};
let result = vdbe::run_rows(&prog, &rows)?;
Ok(QueryResult {
columns: prog.columns,
rows: result,
})
}
pub fn query_params(&self, sql: &str, params: &Params) -> Result<QueryResult> {
match sql::parse_one(sql)? {
Statement::Select(sel) => self.run_select(&sel, params),
Statement::Pragma(p) => self.run_pragma(&p),
Statement::Explain { query_plan, stmt } => {
if !query_plan {
return Err(Error::Unsupported(
"plain EXPLAIN (VDBE bytecode); use EXPLAIN QUERY PLAN",
));
}
self.explain_query_plan(&stmt, params)
}
_ => Err(Error::Unsupported(
"use execute() for non-SELECT statements",
)),
}
}
fn run_pragma(&self, p: &Pragma) -> Result<QueryResult> {
let name = p.name.to_ascii_lowercase();
let header = self.backend.source().header();
let single = |col: &str, v: Value| QueryResult {
columns: alloc::vec![String::from(col)],
rows: alloc::vec![alloc::vec![v]],
};
match name.as_str() {
"page_size" => Ok(single("page_size", Value::Integer(header.page_size as i64))),
"page_count" => Ok(single(
"page_count",
Value::Integer(self.backend.source().page_count() as i64),
)),
"user_version" => Ok(single(
"user_version",
Value::Integer(header.user_version as i32 as i64),
)),
"schema_version" => Ok(single(
"schema_version",
Value::Integer(header.schema_cookie as i64),
)),
"encoding" => Ok(single(
"encoding",
Value::Text(
match header.text_encoding {
crate::format::TextEncoding::Utf8 => "UTF-8",
crate::format::TextEncoding::Utf16Le => "UTF-16le",
crate::format::TextEncoding::Utf16Be => "UTF-16be",
}
.into(),
),
)),
"freelist_count" => Ok(single(
"freelist_count",
Value::Integer(header.freelist_count as i64),
)),
"auto_vacuum" => Ok(single(
"auto_vacuum",
Value::Integer(auto_vacuum_mode(header) as i64),
)),
"application_id" => Ok(single(
"application_id",
Value::Integer(header.application_id as i32 as i64),
)),
"data_version" => Ok(single("data_version", Value::Integer(1))),
"table_info" => self.pragma_table_info(p, false),
"table_xinfo" => self.pragma_table_info(p, true),
"index_list" => self.pragma_index_list(p),
"index_info" => self.pragma_index_info(p, false),
"index_xinfo" => self.pragma_index_info(p, true),
"database_list" => Ok(self.pragma_database_list()),
"table_list" => self.pragma_table_list(p),
"collation_list" => Ok(QueryResult {
columns: alloc::vec!["seq".into(), "name".into()],
rows: ["RTRIM", "NOCASE", "BINARY"]
.iter()
.enumerate()
.map(|(i, n)| alloc::vec![Value::Integer(i as i64), Value::Text((*n).into())])
.collect(),
}),
"foreign_key_list" => self.pragma_foreign_key_list(p),
"foreign_key_check" => self.pragma_foreign_key_check(p),
"integrity_check" | "quick_check" => self.pragma_integrity_check(),
"foreign_keys" => Ok(single(
"foreign_keys",
Value::Integer(self.foreign_keys as i64),
)),
"recursive_triggers" => Ok(single(
"recursive_triggers",
Value::Integer(self.recursive_triggers as i64),
)),
"journal_mode" => {
let mode = if self.backend.wal_mode() {
"wal"
} else {
"delete"
};
Ok(single("journal_mode", Value::Text(mode.into())))
}
"cache_size" => Ok(single("cache_size", Value::Integer(self.cache_size.get()))),
"mmap_size" => Ok(QueryResult {
columns: alloc::vec![String::from("mmap_size")],
rows: Vec::new(),
}),
"synchronous" => Ok(single("synchronous", Value::Integer(2))),
"temp_store" => Ok(single("temp_store", Value::Integer(0))),
"secure_delete" => Ok(single("secure_delete", Value::Integer(0))),
"read_uncommitted" => Ok(single("read_uncommitted", Value::Integer(0))),
"cell_size_check" => Ok(single("cell_size_check", Value::Integer(0))),
"checkpoint_fullfsync" => Ok(single("checkpoint_fullfsync", Value::Integer(0))),
"fullfsync" => Ok(single("fullfsync", Value::Integer(0))),
"busy_timeout" => Ok(single("timeout", Value::Integer(0))),
"wal_autocheckpoint" => Ok(single("wal_autocheckpoint", Value::Integer(1000))),
"max_page_count" => Ok(single("max_page_count", Value::Integer(4294967294))),
"locking_mode" => Ok(single("locking_mode", Value::Text("normal".into()))),
_ => Err(Error::Unsupported("this PRAGMA")),
}
}
fn pragma_table_list(&self, p: &Pragma) -> Result<QueryResult> {
use crate::schema::ObjectType;
let filter = match &p.value {
Some(Expr::Column { column, .. }) => Some(column.clone()),
Some(Expr::Literal(Literal::Str(s))) => Some(s.clone()),
_ => None,
};
let params = Params::default();
let mut dbs: Vec<(String, DbRef, &str)> = alloc::vec![
("main".into(), DbRef::Main, "sqlite_schema"),
("temp".into(), DbRef::Temp, "sqlite_temp_schema"),
];
for (i, d) in self.attached.iter().enumerate() {
dbs.push((d.name.clone(), DbRef::Attached(i), "sqlite_schema"));
}
let matches = |n: &str| filter.as_deref().is_none_or(|f| f.eq_ignore_ascii_case(n));
let mut rows: Vec<Vec<Value>> = Vec::new();
for (db_name, db, schema_tab) in &dbs {
let objects: &[crate::schema::SchemaObject] =
if matches!(db, DbRef::Temp) && self.temp_db.is_none() {
&[]
} else {
self.db_parts(*db).0.objects()
};
for obj in objects {
let typ = match obj.obj_type {
ObjectType::Table => "table",
ObjectType::View => "view",
_ => continue,
};
if !matches(&obj.name) {
continue;
}
let (ncol, wr, strict) = self.table_list_dims(*db, obj, ¶ms);
rows.push(alloc::vec![
Value::Text(db_name.clone()),
Value::Text(obj.name.clone()),
Value::Text(typ.into()),
Value::Integer(ncol),
Value::Integer(wr),
Value::Integer(strict),
]);
}
if matches(schema_tab)
|| filter
.as_deref()
.is_some_and(|f| f.eq_ignore_ascii_case("sqlite_master"))
{
rows.push(alloc::vec![
Value::Text(db_name.clone()),
Value::Text((*schema_tab).into()),
Value::Text("table".into()),
Value::Integer(5),
Value::Integer(0),
Value::Integer(0),
]);
}
}
Ok(QueryResult {
columns: alloc::vec![
"schema".into(),
"name".into(),
"type".into(),
"ncol".into(),
"wr".into(),
"strict".into(),
],
rows,
})
}
fn table_list_dims(
&self,
db: DbRef,
obj: &crate::schema::SchemaObject,
params: &Params,
) -> (i64, i64, i64) {
use crate::schema::ObjectType;
match obj.obj_type {
ObjectType::Table => {
let (schema, _) = self.db_parts(db);
match self.table_meta_in(schema, &obj.name, None) {
Ok(m) => (
m.columns.len() as i64,
m.without_rowid as i64,
m.strict_types.is_some() as i64,
),
Err(_) => (0, 0, 0),
}
}
ObjectType::View => {
let ncol = self
.scan_db_view(db, &obj.name, None, params)
.ok()
.flatten()
.map_or(0, |(c, _)| c.len() as i64);
(ncol, 0, 0)
}
_ => (0, 0, 0),
}
}
fn pragma_database_list(&self) -> QueryResult {
let mut rows = alloc::vec![alloc::vec![
Value::Integer(0),
Value::Text("main".into()),
Value::Text(self.main_file.clone()),
]];
if self.temp_db.is_some() {
rows.push(alloc::vec![
Value::Integer(1),
Value::Text("temp".into()),
Value::Text(String::new()),
]);
}
for (i, db) in self.attached.iter().enumerate() {
rows.push(alloc::vec![
Value::Integer((i + 2) as i64),
Value::Text(db.name.clone()),
Value::Text(db.file.clone()),
]);
}
QueryResult {
columns: alloc::vec!["seq".into(), "name".into(), "file".into()],
rows,
}
}
fn pragma_table_info(&self, p: &Pragma, extended: bool) -> Result<QueryResult> {
let table = match &p.value {
Some(Expr::Column { column, .. }) => column.clone(),
Some(Expr::Literal(Literal::Str(s))) => s.clone(),
_ => {
return Err(Error::Error(
"PRAGMA table_info requires a table name".into(),
))
}
};
if matches!(
table.to_ascii_lowercase().as_str(),
"sqlite_master" | "sqlite_schema" | "sqlite_temp_master" | "sqlite_temp_schema"
) {
let cols = [
("type", "TEXT"),
("name", "TEXT"),
("tbl_name", "TEXT"),
("rootpage", "INT"),
("sql", "TEXT"),
];
let mut rows = Vec::new();
for (i, (name, ty)) in cols.iter().enumerate() {
let mut row = alloc::vec![
Value::Integer(i as i64),
Value::Text((*name).into()),
Value::Text((*ty).into()),
Value::Integer(0),
Value::Null,
Value::Integer(0),
];
if extended {
row.push(Value::Integer(0));
}
rows.push(row);
}
let columns = table_info_columns(extended);
return Ok(QueryResult { columns, rows });
}
if let Some(vobj) = self.schema.objects().iter().find(|o| {
o.obj_type == crate::schema::ObjectType::View && o.name.eq_ignore_ascii_case(&table)
}) {
if let Some(sql) = &vobj.sql {
if let Statement::CreateView(cv) = sql::parse_one(sql)? {
return self.view_table_info(&cv, &table, extended);
}
}
}
if self.is_virtual_table(&table) {
let (_, _, schema) = self.vtab_meta(&table)?;
let rows = schema
.columns
.iter()
.enumerate()
.map(|(i, name)| {
let ty = schema.types.get(i).cloned().unwrap_or_default();
let mut row = alloc::vec![
Value::Integer(i as i64),
Value::Text(name.clone()),
Value::Text(ty),
Value::Integer(0),
Value::Null,
Value::Integer(0),
];
if extended {
row.push(Value::Integer(0));
}
row
})
.collect();
return Ok(QueryResult {
columns: table_info_columns(extended),
rows,
});
}
let obj = self
.schema
.table(&table)
.ok_or_else(|| Error::Error(format!("no such table: {table}")))?;
let sql = obj.sql.as_deref().unwrap_or("");
let Statement::CreateTable(ct) = sql::parse_one(sql)? else {
return Err(Error::Corrupt("schema sql is not CREATE TABLE".into()));
};
let pk_positions = primary_key_positions(&ct);
let mut rows = Vec::new();
for (i, col) in ct.columns.iter().enumerate() {
let generated = col.constraints.iter().find_map(|c| match c {
ColumnConstraint::Generated { stored, .. } => Some(*stored),
_ => None,
});
if generated.is_some() && !extended {
continue;
}
let hidden = match generated {
None => 0,
Some(false) => 2,
Some(true) => 3,
};
let notnull = col
.constraints
.iter()
.any(|c| matches!(c, ColumnConstraint::NotNull));
let dflt = col.constraints.iter().find_map(|c| match c {
ColumnConstraint::Default(e) => Some(sql::print::expr(e)),
_ => None,
});
let pk = pk_positions
.iter()
.position(|&pos| pos == i)
.map_or(0, |n| n as i64 + 1);
let mut row = alloc::vec![
Value::Integer(i as i64),
Value::Text(col.name.clone()),
Value::Text(col.type_name.clone().unwrap_or_default()),
Value::Integer(notnull as i64),
dflt.map(Value::Text).unwrap_or(Value::Null),
Value::Integer(pk),
];
if extended {
row.push(Value::Integer(hidden));
}
rows.push(row);
}
Ok(QueryResult {
columns: table_info_columns(extended),
rows,
})
}
fn view_table_info(
&self,
cv: &CreateView,
view_name: &str,
extended: bool,
) -> Result<QueryResult> {
let mut cols: NamedColumns = match self.resolved_view_columns(&cv.select) {
Some(c) => c,
None => self
.view_columns(view_name, &Params::default())?
.into_iter()
.map(|c| (c.name, None))
.collect(),
};
if !cv.columns.is_empty() && cv.columns.len() == cols.len() {
for (slot, name) in cols.iter_mut().zip(&cv.columns) {
slot.0 = name.clone();
}
}
let rows = cols
.into_iter()
.enumerate()
.map(|(i, (name, ty))| {
let mut row = alloc::vec![
Value::Integer(i as i64),
Value::Text(name),
Value::Text(ty.unwrap_or_default()),
Value::Integer(0),
Value::Null,
Value::Integer(0),
];
if extended {
row.push(Value::Integer(0));
}
row
})
.collect();
Ok(QueryResult {
columns: table_info_columns(extended),
rows,
})
}
fn resolved_view_columns(&self, select: &Select) -> Option<NamedColumns> {
let mut sources: Vec<(String, NamedColumns)> = Vec::new();
if let Some(fc) = &select.from {
let mut refs = alloc::vec![&fc.first];
let mut coalesced = false;
for j in &fc.joins {
refs.push(&j.table);
if j.natural || !j.using.is_empty() {
coalesced = true;
}
}
let has_wild = select
.columns
.iter()
.any(|c| matches!(c, ResultColumn::Wildcard | ResultColumn::TableWildcard(_)));
if coalesced && has_wild {
return None; }
for tref in refs {
let label = tref.alias.clone().unwrap_or_else(|| tref.name.clone());
sources.push((label, self.source_columns_of(tref)?));
}
}
let lookup = |table: Option<&str>, col: &str| -> Option<String> {
for (label, cols) in &sources {
if table.is_some_and(|t| !t.eq_ignore_ascii_case(label)) {
continue;
}
if let Some((_, ty)) = cols.iter().find(|(n, _)| n.eq_ignore_ascii_case(col)) {
return ty.clone();
}
}
None
};
let mut out = Vec::new();
for rc in &select.columns {
match rc {
ResultColumn::Wildcard => {
for (_, cols) in &sources {
out.extend(cols.iter().cloned());
}
}
ResultColumn::TableWildcard(t) => {
let (_, cols) = sources.iter().find(|(l, _)| l.eq_ignore_ascii_case(t))?;
out.extend(cols.iter().cloned());
}
ResultColumn::Expr {
expr,
alias,
source,
} => {
let name = result_column_label(expr, alias, source);
let ty = match expr {
Expr::Column { table, column } => lookup(table.as_deref(), column),
_ => None,
};
out.push((name, ty));
}
}
}
Some(out)
}
fn source_columns_of(&self, tref: &TableRef) -> Option<NamedColumns> {
if tref.tvf_args.is_some() {
return None;
}
if let Some(sub) = &tref.subquery {
return self.resolved_view_columns(sub);
}
if let Some(o) = self.schema.objects().iter().find(|o| {
o.obj_type == crate::schema::ObjectType::View && o.name.eq_ignore_ascii_case(&tref.name)
}) {
if let Some(Ok(Statement::CreateView(cv))) = o.sql.as_deref().map(sql::parse_one) {
return self.resolved_view_columns(&cv.select);
}
return None;
}
let obj = self.schema.table(&tref.name)?;
let Ok(Statement::CreateTable(ct)) = sql::parse_one(obj.sql.as_deref()?) else {
return None;
};
Some(
ct.columns
.iter()
.map(|c| {
let ty = c.type_name.clone().unwrap_or_else(|| String::from("BLOB"));
(c.name.clone(), Some(ty))
})
.collect(),
)
}
fn pragma_arg_name(p: &Pragma) -> Result<String> {
match &p.value {
Some(Expr::Column { column, .. }) => Ok(column.clone()),
Some(Expr::Literal(Literal::Str(s))) => Ok(s.clone()),
_ => Err(Error::Error("PRAGMA requires a name argument".into())),
}
}
fn pragma_index_list(&self, p: &Pragma) -> Result<QueryResult> {
let table = Self::pragma_arg_name(p)?;
let objs: Vec<_> = self.schema.indexes_on(&table).collect();
let pk_set: Vec<usize> = self
.schema
.table(&table)
.and_then(|o| o.sql.as_deref())
.and_then(|sql| sql::parse_one(sql).ok())
.and_then(|st| match st {
Statement::CreateTable(ct) => {
let ipk = find_integer_primary_key(&ct);
let pk = primary_key_positions(&ct);
if pk.is_empty() || (pk.len() == 1 && Some(pk[0]) == ipk) {
None
} else {
Some(pk)
}
}
_ => None,
})
.unwrap_or_default();
let tmeta = self.table_meta(&table, None).ok();
let mut rows = Vec::new();
for obj in objs.iter().rev() {
let (unique, origin, partial) = match &obj.sql {
Some(sql) => match sql::parse_one(sql) {
Ok(Statement::CreateIndex(ci)) => {
(ci.unique as i64, "c", ci.where_clause.is_some() as i64)
}
_ => (0, "c", 0),
},
None => {
let cols = autoindex_number(&obj.name, &table)
.and_then(|n| tmeta.as_ref().and_then(|m| m.unique.get(n - 1)))
.cloned()
.unwrap_or_default();
let origin = if !pk_set.is_empty() && cols == pk_set {
"pk"
} else {
"u"
};
(1, origin, 0)
}
};
rows.push(alloc::vec![
Value::Integer(rows.len() as i64),
Value::Text(obj.name.clone()),
Value::Integer(unique),
Value::Text(origin.into()),
Value::Integer(partial),
]);
}
if tmeta.as_ref().is_some_and(|m| m.without_rowid) && !pk_set.is_empty() {
rows.push(alloc::vec![
Value::Integer(rows.len() as i64),
Value::Text(alloc::format!("sqlite_autoindex_{table}_1")),
Value::Integer(1),
Value::Text("pk".into()),
Value::Integer(0),
]);
}
Ok(QueryResult {
columns: ["seq", "name", "unique", "origin", "partial"]
.iter()
.map(|s| String::from(*s))
.collect(),
rows,
})
}
fn pragma_index_info(&self, p: &Pragma, extended: bool) -> Result<QueryResult> {
let index = Self::pragma_arg_name(p)?;
let obj = self
.schema
.index(&index)
.ok_or_else(|| Error::Error(format!("no such index: {index}")))?;
let tmeta = self.table_meta(&obj.tbl_name, None)?;
let descs: Vec<bool> = match &obj.sql {
Some(sql) => match sql::parse_one(sql)? {
Statement::CreateIndex(ci) => ci.columns.iter().map(|c| c.descending).collect(),
_ => Vec::new(),
},
None => Vec::new(),
};
let cols: Vec<usize> = match &obj.sql {
Some(sql) => match sql::parse_one(sql)? {
Statement::CreateIndex(ci) => self.index_columns(&tmeta, &ci)?,
_ => Vec::new(),
},
None => autoindex_number(&obj.name, &obj.tbl_name)
.and_then(|n| tmeta.unique.get(n - 1).cloned())
.unwrap_or_default(),
};
let mut rows = Vec::new();
for (seqno, &cid) in cols.iter().enumerate() {
if extended {
let coll = match tmeta.columns[cid].collation {
crate::value::Collation::NoCase => "NOCASE",
crate::value::Collation::RTrim => "RTRIM",
crate::value::Collation::Binary => "BINARY",
};
rows.push(alloc::vec![
Value::Integer(seqno as i64),
Value::Integer(cid as i64),
Value::Text(tmeta.columns[cid].name.clone()),
Value::Integer(descs.get(seqno).copied().unwrap_or(false) as i64),
Value::Text(coll.into()),
Value::Integer(1), ]);
} else {
rows.push(alloc::vec![
Value::Integer(seqno as i64),
Value::Integer(cid as i64),
Value::Text(tmeta.columns[cid].name.clone()),
]);
}
}
if extended && !tmeta.without_rowid {
rows.push(alloc::vec![
Value::Integer(cols.len() as i64),
Value::Integer(-1),
Value::Null,
Value::Integer(0),
Value::Text("BINARY".into()),
Value::Integer(0),
]);
}
let columns: Vec<String> = if extended {
["seqno", "cid", "name", "desc", "coll", "key"]
.iter()
.map(|s| String::from(*s))
.collect()
} else {
["seqno", "cid", "name"]
.iter()
.map(|s| String::from(*s))
.collect()
};
Ok(QueryResult { columns, rows })
}
fn pragma_foreign_key_list(&self, p: &Pragma) -> Result<QueryResult> {
let table = Self::pragma_arg_name(p)?;
let obj = self
.schema
.table(&table)
.ok_or_else(|| Error::Error(format!("no such table: {table}")))?;
let Statement::CreateTable(ct) = sql::parse_one(obj.sql.as_deref().unwrap_or(""))? else {
return Ok(QueryResult {
columns: [
"id",
"seq",
"table",
"from",
"to",
"on_update",
"on_delete",
"match",
]
.iter()
.map(|s| String::from(*s))
.collect(),
rows: Vec::new(),
});
};
let action = |a: FkAction| -> &'static str {
match a {
FkAction::NoAction => "NO ACTION",
FkAction::Restrict => "RESTRICT",
FkAction::Cascade => "CASCADE",
FkAction::SetNull => "SET NULL",
FkAction::SetDefault => "SET DEFAULT",
}
};
let mut fks: Vec<(Vec<String>, &ForeignKey)> = Vec::new();
for col in &ct.columns {
for c in &col.constraints {
if let ColumnConstraint::References(fk) = c {
fks.push((alloc::vec![col.name.clone()], fk));
}
}
}
for c in &ct.constraints {
if let TableConstraint::ForeignKey(fk) = c {
fks.push((fk.columns.clone(), fk));
}
}
let mut rows = Vec::new();
let n = fks.len();
for (i, (from_cols, fk)) in fks.iter().enumerate() {
let id = (n - 1 - i) as i64;
for (seq, from) in from_cols.iter().enumerate() {
let to = fk.ref_columns.get(seq).cloned().unwrap_or_default();
rows.push(alloc::vec![
Value::Integer(id),
Value::Integer(seq as i64),
Value::Text(fk.ref_table.clone()),
Value::Text(from.clone()),
if to.is_empty() {
Value::Null
} else {
Value::Text(to)
},
Value::Text(action(fk.on_update).into()),
Value::Text(action(fk.on_delete).into()),
Value::Text("NONE".into()),
]);
}
}
Ok(QueryResult {
columns: [
"id",
"seq",
"table",
"from",
"to",
"on_update",
"on_delete",
"match",
]
.iter()
.map(|s| String::from(*s))
.collect(),
rows,
})
}
fn pragma_foreign_key_check(&self, p: &Pragma) -> Result<QueryResult> {
use crate::schema::ObjectType;
let tables: Vec<String> = match &p.value {
Some(_) => alloc::vec![Self::pragma_arg_name(p)?],
None => self
.schema
.objects()
.iter()
.filter(|o| o.obj_type == ObjectType::Table && !o.name.starts_with("sqlite_"))
.map(|o| o.name.clone())
.collect(),
};
let mut rows = Vec::new();
for table in &tables {
let meta = self.table_meta(table, None)?;
if meta.without_rowid {
continue; }
let fks = self.foreign_keys_of(table)?;
if fks.is_empty() {
continue;
}
let n = fks.len();
for (rowid, values) in self.scan_table(&meta)? {
for (i, fk) in fks.iter().enumerate() {
let Some(key) = self.child_key_values(&meta, fk, &values) else {
continue; };
if !self.parent_has_key(fk, &key)? {
rows.push(alloc::vec![
Value::Text(table.clone()),
Value::Integer(rowid),
Value::Text(fk.ref_table.clone()),
Value::Integer((n - 1 - i) as i64),
]);
}
}
}
}
Ok(QueryResult {
columns: ["table", "rowid", "parent", "fkid"]
.iter()
.map(|s| String::from(*s))
.collect(),
rows,
})
}
fn pragma_integrity_check(&self) -> Result<QueryResult> {
use crate::schema::ObjectType;
let single = |v: Value| QueryResult {
columns: alloc::vec![String::from("integrity_check")],
rows: alloc::vec![alloc::vec![v]],
};
let tables: Vec<String> = self
.schema
.objects()
.iter()
.filter(|o| {
o.obj_type == ObjectType::Table
&& !o.name.starts_with("sqlite_")
&& !matches!(
o.sql.as_deref().map(sql::parse_one),
Some(Ok(Statement::CreateVirtualTable(_)))
)
})
.map(|o| o.name.clone())
.collect();
let mut problems = Vec::new();
for table in &tables {
let meta = self.table_meta(table, None)?;
let rows: Vec<Vec<Value>> = if meta.without_rowid {
self.scan_without_rowid(&meta)?
} else {
self.scan_table(&meta)?
.into_iter()
.map(|(_, v)| v)
.collect()
};
let no_params = Params::default();
for idx in self.indexes_of(table)? {
let expected = rows
.iter()
.filter_map(|r| self.row_in_index(&idx, &meta, r, None, &no_params).ok())
.filter(|&keep| keep)
.count();
let mut cur = crate::btree::IndexCursor::new(self.backend.source(), idx.root);
let mut got = 0usize;
while cur.next()?.is_some() {
got += 1;
}
if got != expected {
problems.push(alloc::format!("wrong # of entries in index {}", idx.name));
}
}
}
if problems.is_empty() {
Ok(single(Value::Text("ok".into())))
} else {
Ok(QueryResult {
columns: alloc::vec![String::from("integrity_check")],
rows: problems
.into_iter()
.map(|p| alloc::vec![Value::Text(p)])
.collect(),
})
}
}
pub fn execute(&mut self, sql: &str) -> Result<usize> {
self.execute_params(sql, &Params::default())
}
pub fn register_module(
&mut self,
name: &str,
module: impl DynVTabModule + 'static,
) -> Result<()> {
self.vtab_registry.register(name, Box::new(module))
}
pub fn register_function(
&mut self,
name: &str,
f: impl Fn(&[Value]) -> Result<Value> + 'static,
) {
self.functions
.insert(name.to_ascii_lowercase(), Box::new(f));
}
pub fn register_aggregate_function(
&mut self,
name: &str,
factory: impl Fn() -> Box<dyn AggregateFunction> + 'static,
) {
self.aggregates
.insert(name.to_ascii_lowercase(), Box::new(factory));
}
pub fn execute_batch(&mut self, sql: &str) -> Result<()> {
for stmt in split_sql_script(sql) {
if matches!(sql::parse_one(stmt), Ok(Statement::Select(_))) {
self.query(stmt)?;
} else {
self.execute_params(stmt, &Params::default())?;
}
}
Ok(())
}
pub fn execute_params(&mut self, sql: &str, params: &Params) -> Result<usize> {
let stmt = sql::parse_one(sql)?;
match &stmt {
Statement::Begin => {
if self.in_tx {
return Err(Error::Error(
"cannot start a transaction within a transaction".into(),
));
}
self.in_tx = true;
return Ok(0);
}
Statement::Commit => {
if !self.in_tx && self.open_savepoints == 0 {
return Err(Error::Error(
"cannot commit - no transaction is active".into(),
));
}
self.check_deferred_fks()?;
self.backend.writer()?.commit()?;
self.commit_attached()?;
self.in_tx = false;
self.open_savepoints = 0;
return Ok(0);
}
Statement::Savepoint(name) => {
self.backend.writer()?.savepoint(name);
self.savepoint_attached(name)?;
self.open_savepoints += 1;
return Ok(0);
}
Statement::Release(name) => {
self.backend.writer()?.release_savepoint(name)?;
self.release_attached(name)?;
self.open_savepoints = self.backend.writer()?.savepoint_depth();
if self.open_savepoints == 0 && !self.in_tx {
self.check_deferred_fks()?;
self.backend.writer()?.commit()?;
self.commit_attached()?;
self.schema = Schema::read(self.backend.source())?;
}
return Ok(0);
}
Statement::RollbackTo(name) => {
self.backend.writer()?.rollback_to_savepoint(name)?;
self.rollback_to_attached(name)?;
self.open_savepoints = self.backend.writer()?.savepoint_depth();
self.schema = Schema::read(self.backend.source())?;
return Ok(0);
}
Statement::Rollback => {
if !self.in_tx && self.open_savepoints == 0 {
return Err(Error::Error(
"cannot rollback - no transaction is active".into(),
));
}
self.backend.writer()?.rollback();
self.rollback_attached()?;
self.in_tx = false;
self.open_savepoints = 0;
self.schema = Schema::read(self.backend.source())?;
return Ok(0);
}
_ => {}
}
let target = self.target_db(&stmt)?;
if target == DbRef::Temp {
self.ensure_temp()?;
}
match target {
DbRef::Main => self.exec_parsed(stmt, sql, params),
other => {
self.swap_db(other);
let r = self.exec_parsed(stmt, sql, params);
self.swap_db(other);
r
}
}
}
fn target_db(&self, stmt: &Statement) -> Result<DbRef> {
let resolved = |s: Option<&str>, name: &str| -> Result<DbRef> {
match s {
Some(_) => self.resolve_db(s),
None => Ok(self.unqualified_db(name)),
}
};
match stmt {
Statement::CreateTable(s) => self.resolve_db(s.schema.as_deref()),
Statement::Insert(s) => resolved(s.schema.as_deref(), &s.table),
Statement::Update(s) => resolved(s.schema.as_deref(), &s.table),
Statement::Delete(s) => resolved(s.schema.as_deref(), &s.table),
Statement::Drop(s) => resolved(s.schema.as_deref(), &s.name),
Statement::Alter(a) => resolved(a.schema.as_deref(), &a.table),
Statement::CreateIndex(ci) => resolved(ci.schema.as_deref(), &ci.table),
Statement::CreateView(cv) => self.resolve_db(cv.schema.as_deref()),
Statement::CreateTrigger(ct) => resolved(ct.schema.as_deref(), &ct.table),
Statement::CreateVirtualTable(cvt) => self.resolve_db(cvt.schema.as_deref()),
_ => Ok(DbRef::Main),
}
}
fn commit_attached(&mut self) -> Result<()> {
if let Some(t) = &mut self.temp_db {
t.backend.writer()?.commit()?;
t.schema = Schema::read(t.backend.source())?;
}
for d in &mut self.attached {
d.backend.writer()?.commit()?;
d.schema = Schema::read(d.backend.source())?;
}
Ok(())
}
fn rollback_attached(&mut self) -> Result<()> {
if let Some(t) = &mut self.temp_db {
t.backend.writer()?.rollback();
t.schema = Schema::read(t.backend.source())?;
}
for d in &mut self.attached {
d.backend.writer()?.rollback();
d.schema = Schema::read(d.backend.source())?;
}
Ok(())
}
fn savepoint_attached(&mut self, name: &str) -> Result<()> {
if let Some(t) = &mut self.temp_db {
t.backend.writer()?.savepoint(name);
}
for d in &mut self.attached {
d.backend.writer()?.savepoint(name);
}
Ok(())
}
fn release_attached(&mut self, name: &str) -> Result<()> {
if let Some(t) = &mut self.temp_db {
let _ = t.backend.writer()?.release_savepoint(name);
}
for d in &mut self.attached {
let _ = d.backend.writer()?.release_savepoint(name);
}
Ok(())
}
fn rollback_to_attached(&mut self, name: &str) -> Result<()> {
if let Some(t) = &mut self.temp_db {
let did = t.backend.writer()?.rollback_to_savepoint(name).is_ok();
if did {
t.schema = Schema::read(t.backend.source())?;
}
}
for d in &mut self.attached {
let did = d.backend.writer()?.rollback_to_savepoint(name).is_ok();
if did {
d.schema = Schema::read(d.backend.source())?;
}
}
Ok(())
}
fn swap_db(&mut self, db: DbRef) {
match db {
DbRef::Main => {}
DbRef::Temp => {
let t = self.temp_db.as_mut().expect("temp db exists");
core::mem::swap(&mut self.backend, &mut t.backend);
core::mem::swap(&mut self.schema, &mut t.schema);
}
DbRef::Attached(i) => self.swap_attached(i),
}
}
fn swap_attached(&mut self, i: usize) {
core::mem::swap(&mut self.backend, &mut self.attached[i].backend);
core::mem::swap(&mut self.schema, &mut self.attached[i].schema);
}
fn exec_parsed(&mut self, stmt: Statement, sql: &str, params: &Params) -> Result<usize> {
let is_dml = matches!(
stmt,
Statement::Insert(_) | Statement::Update(_) | Statement::Delete(_)
);
if is_dml {
self.stmt_keep_partial.set(false);
self.stmt_rollback_tx.set(false);
return self.run_dml_atomic(stmt, params);
}
let affected = match stmt {
Statement::CreateTable(ct) => {
self.exec_create_table(&ct, ddl_text(sql))?;
0
}
Statement::Insert(_) | Statement::Delete(_) | Statement::Update(_) => unreachable!(),
Statement::CreateIndex(ci) => {
self.exec_create_index(&ci, ddl_text(sql))?;
0
}
Statement::CreateView(cv) => {
self.exec_create_view(&cv, ddl_text(sql))?;
0
}
Statement::CreateTrigger(ct) => {
self.exec_create_trigger(&ct, ddl_text(sql))?;
0
}
Statement::CreateVirtualTable(cvt) => {
self.exec_create_virtual_table(&cvt, ddl_text(sql))?;
0
}
Statement::Drop(d) => {
self.exec_drop(&d)?;
0
}
Statement::Alter(a) => {
self.exec_alter(&a)?;
0
}
Statement::Pragma(p) => {
self.exec_pragma(&p, params)?;
0
}
Statement::Vacuum => {
self.exec_vacuum()?;
0
}
Statement::Reindex => 0,
Statement::Analyze(target) => {
self.exec_analyze(target.as_deref())?;
0
}
Statement::Attach { file, name } => {
self.exec_attach(&file, &name, params)?;
0
}
Statement::Detach(name) => {
self.exec_detach(&name)?;
0
}
Statement::Select(_) => return Err(Error::Unsupported("use query() for SELECT")),
Statement::Explain { .. } => return Err(Error::Unsupported("use query() for EXPLAIN")),
Statement::Begin
| Statement::Commit
| Statement::Rollback
| Statement::Savepoint(_)
| Statement::Release(_)
| Statement::RollbackTo(_) => unreachable!(),
};
if !self.in_tx && self.open_savepoints == 0 {
self.backend.writer()?.commit()?;
self.schema = Schema::read(self.backend.source())?;
}
Ok(affected)
}
fn conflict_error(&self, oc: OnConflict, msg: &str) -> Error {
match oc {
OnConflict::Fail => self.stmt_keep_partial.set(true),
OnConflict::Rollback => self.stmt_rollback_tx.set(true),
_ => {}
}
Error::Constraint(String::from(msg))
}
fn run_dml_atomic(&mut self, stmt: Statement, params: &Params) -> Result<usize> {
const SP: &str = "\u{0}graphite_stmt";
self.backend.writer()?.savepoint(SP);
self.savepoint_attached(SP)?;
let result = match stmt {
Statement::Insert(ins) => self.exec_insert(&ins, params),
Statement::Delete(del) => self.exec_delete(&del, params),
Statement::Update(upd) => self.exec_update(&upd, params),
_ => unreachable!("run_dml_atomic only handles DML"),
};
match result {
Ok(affected) => {
let _ = self.backend.writer()?.release_savepoint(SP);
let _ = self.release_attached(SP);
self.changes.set(affected as i64);
self.total_changes
.set(self.total_changes.get() + affected as i64);
if !self.in_tx && self.open_savepoints == 0 {
self.backend.writer()?.commit()?;
self.schema = Schema::read(self.backend.source())?;
}
Ok(affected)
}
Err(e) => {
if self.stmt_rollback_tx.get() {
self.backend.writer()?.rollback();
self.rollback_attached()?;
self.in_tx = false;
self.open_savepoints = 0;
self.schema = Schema::read(self.backend.source())?;
} else if self.stmt_keep_partial.get() {
let _ = self.backend.writer()?.release_savepoint(SP);
let _ = self.release_attached(SP);
if !self.in_tx && self.open_savepoints == 0 {
self.backend.writer()?.commit()?;
self.schema = Schema::read(self.backend.source())?;
}
} else {
let _ = self.backend.writer()?.rollback_to_savepoint(SP);
let _ = self.backend.writer()?.release_savepoint(SP);
let _ = self.rollback_to_attached(SP);
let _ = self.release_attached(SP);
if !self.in_tx && self.open_savepoints == 0 {
self.backend.writer()?.rollback();
self.rollback_attached()?;
self.schema = Schema::read(self.backend.source())?;
}
}
Err(e)
}
}
}
pub fn execute_returning(&mut self, sql: &str, params: &Params) -> Result<QueryResult> {
let stmt = sql::parse_one(sql)?;
let returning: &[ResultColumn] = match &stmt {
Statement::Insert(i) => &i.returning,
Statement::Update(u) => &u.returning,
Statement::Delete(d) => &d.returning,
_ => {
return Err(Error::Unsupported(
"execute_returning expects INSERT/UPDATE/DELETE",
))
}
};
if returning.is_empty() {
self.execute_params(sql, params)?;
return Ok(QueryResult {
columns: Vec::new(),
rows: Vec::new(),
});
}
let table = match &stmt {
Statement::Insert(i) => &i.table,
Statement::Update(u) => &u.table,
Statement::Delete(d) => &d.table,
_ => unreachable!(),
};
let meta = self.table_meta(table, None)?;
let columns = returning_labels(returning, &meta.columns);
self.returning_rows.borrow_mut().clear();
self.execute_params(sql, params)?;
let rows = core::mem::take(&mut *self.returning_rows.borrow_mut());
Ok(QueryResult { columns, rows })
}
fn exec_attach(&mut self, file: &Expr, name: &str, params: &Params) -> Result<()> {
let path = {
let ctx = EvalCtx::rowless(params).with_subqueries(self);
eval::to_text(&eval::eval(file, &ctx)?)
};
if name.eq_ignore_ascii_case("main")
|| name.eq_ignore_ascii_case("temp")
|| self
.attached
.iter()
.any(|d| d.name.eq_ignore_ascii_case(name))
{
return Err(Error::Error(alloc::format!(
"database {name} is already in use"
)));
}
let (backend, file) = if path.is_empty() || path.eq_ignore_ascii_case(":memory:") {
let vfs = crate::vfs::memory::MemoryVfs::new();
let f = vfs.open(name, OpenFlags::READ_WRITE_CREATE)?;
let mut db = WritePager::create(f, None, 4096)?;
db.commit()?;
(Backend::Write(Box::new(db)), String::new())
} else {
(self.open_attached_file(&path)?, path)
};
let schema = Schema::read(backend.source())?;
self.attached.push(AttachedDb {
name: name.to_string(),
file,
backend,
schema,
});
Ok(())
}
#[cfg(feature = "std")]
fn open_attached_file(&self, path: &str) -> Result<Backend> {
let vfs = crate::vfs::std_file::StdVfs::new();
let main = vfs.open(path, OpenFlags::READ_WRITE_CREATE)?;
let journal = vfs.open(&journal_path(path), OpenFlags::READ_WRITE_CREATE)?;
let db = if main.size()? == 0 {
let mut db = WritePager::create(main, Some(journal), 4096)?;
db.commit()?;
db
} else {
WritePager::open(main, Some(journal))?
};
Ok(Backend::Write(Box::new(db)))
}
#[cfg(not(feature = "std"))]
fn open_attached_file(&self, _path: &str) -> Result<Backend> {
Err(Error::Unsupported("ATTACH of a file database requires std"))
}
fn exec_detach(&mut self, name: &str) -> Result<()> {
if name.eq_ignore_ascii_case("main") || name.eq_ignore_ascii_case("temp") {
return Err(Error::Error(alloc::format!(
"cannot detach database {name}"
)));
}
match self
.attached
.iter()
.position(|d| d.name.eq_ignore_ascii_case(name))
{
Some(i) => {
self.attached.remove(i);
Ok(())
}
None => Err(Error::Error(alloc::format!("no such database: {name}"))),
}
}
fn exec_vacuum(&mut self) -> Result<()> {
use crate::schema::ObjectType;
if !matches!(self.backend, Backend::Write(_)) {
return Ok(());
}
if self.backend.wal_mode() {
self.backend.writer()?.checkpoint()?;
}
let user_version = self.backend.source().header().user_version;
let objs: Vec<(ObjectType, String, Option<String>)> = self
.schema
.objects()
.iter()
.map(|o| (o.obj_type, o.name.clone(), o.sql.clone()))
.collect();
let quote = |n: &str| alloc::format!("\"{}\"", n.replace('"', "\"\""));
let is_vtab = |sql: &Option<String>| {
matches!(
sql.as_deref().map(sql::parse_one),
Some(Ok(Statement::CreateVirtualTable(_)))
)
};
let vtab_names: alloc::collections::BTreeSet<String> = objs
.iter()
.filter(|(ty, _, sql)| *ty == ObjectType::Table && is_vtab(sql))
.map(|(_, n, _)| n.clone())
.collect();
let table_names: alloc::collections::BTreeSet<String> = objs
.iter()
.filter(|(ty, _, _)| *ty == ObjectType::Table)
.map(|(_, n, _)| n.clone())
.collect();
let is_backing = |name: &str| {
name.strip_suffix("_data")
.is_some_and(|p| vtab_names.contains(p))
};
let persistent_vtab = |name: &str| {
vtab_names.contains(name) && table_names.contains(&alloc::format!("{name}_data"))
};
let mut tmp = Connection::open_memory()?;
for (ty, name, sql) in &objs {
if *ty == ObjectType::Table && !is_backing(name) {
if let Some(s) = sql {
tmp.execute(s)?;
}
}
}
for (ty, _, sql) in &objs {
if *ty == ObjectType::Index {
if let Some(s) = sql {
tmp.execute(s)?;
}
}
}
for (ty, name, _) in &objs {
if *ty != ObjectType::Table
|| is_backing(name)
|| (vtab_names.contains(name) && !persistent_vtab(name))
{
continue;
}
let result = self.query(&alloc::format!("SELECT * FROM {}", quote(name)))?;
let ncols = result.columns.len();
if ncols == 0 {
continue;
}
let placeholders = (1..=ncols)
.map(|i| alloc::format!("?{i}"))
.collect::<Vec<_>>()
.join(",");
let stmt = alloc::format!("INSERT INTO {} VALUES ({placeholders})", quote(name));
for row in result.rows {
let params = Params {
positional: row,
named: Vec::new(),
};
tmp.execute_params(&stmt, ¶ms)?;
}
}
for (ty, _, sql) in &objs {
if *ty == ObjectType::View {
if let Some(s) = sql {
tmp.execute(s)?;
}
}
}
for (ty, _, sql) in &objs {
if *ty == ObjectType::Trigger {
if let Some(s) = sql {
tmp.execute(s)?;
}
}
}
let count = tmp.backend.source().page_count();
let mut image = Vec::with_capacity(count as usize);
for n in 1..=count {
image.push(tmp.backend.source().page(n)?.data().to_vec());
}
self.backend.writer()?.replace_image(image)?;
if user_version != 0 {
self.backend.writer()?.header_mut().user_version = user_version;
let mut page1 = self.backend.writer()?.read_page(1)?;
self.backend.writer()?.header().write_to(&mut page1)?;
self.backend.writer()?.write_page(1, page1)?;
self.backend.writer()?.commit()?;
}
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn exec_analyze(&mut self, target: Option<&str>) -> Result<()> {
use crate::schema::ObjectType;
let analyze: Vec<String> = match target {
None => self
.schema
.objects()
.iter()
.filter(|o| o.obj_type == ObjectType::Table && !o.name.starts_with("sqlite_"))
.map(|o| o.name.clone())
.collect(),
Some(name) => {
if let Some(t) = self.schema.table(name) {
alloc::vec![t.name.clone()]
} else if let Some(ix) = self.schema.index(name) {
alloc::vec![ix.tbl_name.clone()]
} else {
return Ok(()); }
}
};
let mut new_rows: Vec<(String, Option<String>, String)> = Vec::new();
for tname in &analyze {
let meta = self.table_meta(tname, None)?;
let rows: Vec<Vec<Value>> = if meta.without_rowid {
self.scan_without_rowid(&meta)?
} else {
self.scan_table(&meta)?
.into_iter()
.map(|(_, v)| v)
.collect()
};
let n = rows.len();
let indexes = self.indexes_of(tname)?;
if indexes.is_empty() {
if n > 0 {
new_rows.push((tname.clone(), None, alloc::format!("{n}")));
}
} else {
for idx in &indexes {
if n == 0 {
continue; }
let stat = index_stat_string(&idx.cols, &idx.collations, &rows);
new_rows.push((tname.clone(), Some(idx.name.clone()), stat));
}
}
}
if self.schema.table("sqlite_stat1").is_none() {
const STAT1_SQL: &str = "CREATE TABLE sqlite_stat1(tbl,idx,stat)";
let Statement::CreateTable(ct) = sql::parse_one(STAT1_SQL)? else {
unreachable!()
};
self.exec_create_table(&ct, STAT1_SQL)?;
}
let stat_root = self.schema.table("sqlite_stat1").unwrap().rootpage;
let stat_meta = self.table_meta("sqlite_stat1", None)?;
let victims: Vec<i64> = self
.scan_table(&stat_meta)?
.into_iter()
.filter(
|(_, vals)| matches!(&vals[0], Value::Text(t) if analyze.iter().any(|a| a == t)),
)
.map(|(rid, _)| rid)
.collect();
for rid in victims {
delete_table(self.backend.writer()?, stat_root, rid)?;
}
let base = self.next_rowid(stat_root)?;
for (i, (tbl, idx, stat)) in new_rows.into_iter().enumerate() {
let rec = encode_record(&[
Value::Text(tbl),
idx.map_or(Value::Null, Value::Text),
Value::Text(stat),
]);
insert_table(self.backend.writer()?, stat_root, base + i as i64, &rec)?;
}
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn exec_create_table(&mut self, ct: &CreateTable, sql_text: &str) -> Result<()> {
if let Some(select) = &ct.as_select {
return self.exec_create_table_as_select(ct, select);
}
let reprinted;
let sql_text = if ct.schema.is_some() {
reprinted = sql::print::create_table(ct);
reprinted.as_str()
} else {
sql_text
};
if self.schema.table(&ct.name).is_some() {
if ct.if_not_exists {
return Ok(());
}
return Err(Error::Error(format!("table {} already exists", ct.name)));
}
if ct.strict {
for c in &ct.columns {
if strict_column_type(c.type_name.as_deref()).is_none() {
return Err(match &c.type_name {
Some(t) => Error::Error(format!(
"unknown datatype for {}.{}: \"{t}\"",
ct.name, c.name
)),
None => {
Error::Error(format!("missing datatype for {}.{}", ct.name, c.name))
}
});
}
}
}
for c in &ct.columns {
for k in &c.constraints {
match k {
ColumnConstraint::Check(e) if expr_has_subquery(e) => {
return Err(Error::Error(
"subqueries prohibited in CHECK constraints".into(),
));
}
ColumnConstraint::Generated { expr, .. } if expr_has_subquery(expr) => {
return Err(Error::Error(
"subqueries prohibited in generated columns".into(),
));
}
ColumnConstraint::Generated { expr, .. } if expr_is_nondeterministic(expr) => {
return Err(Error::Error(
"non-deterministic functions prohibited in generated columns".into(),
));
}
_ => {}
}
}
}
for tc in &ct.constraints {
if let TableConstraint::Check(e) = tc {
if expr_has_subquery(e) {
return Err(Error::Error(
"subqueries prohibited in CHECK constraints".into(),
));
}
}
}
if !ct.columns.is_empty()
&& ct.columns.iter().all(|c| {
c.constraints
.iter()
.any(|k| matches!(k, ColumnConstraint::Generated { .. }))
})
{
return Err(Error::Error(
"must have at least one non-generated column".into(),
));
}
for (i, c) in ct.columns.iter().enumerate() {
if ct.columns[..i]
.iter()
.any(|p| p.name.eq_ignore_ascii_case(&c.name))
{
return Err(Error::Error(alloc::format!(
"duplicate column name: {}",
c.name
)));
}
}
let pk_count = ct
.columns
.iter()
.flat_map(|c| &c.constraints)
.filter(|k| matches!(k, ColumnConstraint::PrimaryKey { .. }))
.count()
+ ct.constraints
.iter()
.filter(|tc| matches!(tc, TableConstraint::PrimaryKey(_)))
.count();
if pk_count > 1 {
return Err(Error::Error(alloc::format!(
"table {} has more than one primary key",
ct.name
)));
}
for tc in &ct.constraints {
let cols = match tc {
TableConstraint::PrimaryKey(cols) | TableConstraint::Unique(cols) => cols,
_ => continue,
};
for name in cols {
if !ct.columns.iter().any(|c| c.name.eq_ignore_ascii_case(name)) {
return Err(Error::Error(alloc::format!("no such column: {name}")));
}
}
}
let ipk = find_integer_primary_key(ct);
let has_autoinc = |i: usize| {
ct.columns[i].constraints.iter().any(|k| {
matches!(
k,
ColumnConstraint::PrimaryKey {
autoincrement: true,
..
}
)
})
};
if (0..ct.columns.len()).any(has_autoinc) {
if ct.without_rowid {
return Err(Error::Error(
"AUTOINCREMENT not allowed on WITHOUT ROWID tables".into(),
));
}
if !(0..ct.columns.len()).any(|i| has_autoinc(i) && Some(i) == ipk) {
return Err(Error::Error(
"AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY".into(),
));
}
}
let root = if ct.without_rowid {
if primary_key_positions(ct).is_empty() {
return Err(Error::Error(
"WITHOUT ROWID table must have a PRIMARY KEY".into(),
));
}
create_index_root(self.backend.writer()?)?
} else {
create_table_root(self.backend.writer()?)?
};
let next = self.next_rowid(crate::schema::SCHEMA_ROOT_PAGE)?;
let row = encode_record(&[
Value::Text("table".into()),
Value::Text(ct.name.clone()),
Value::Text(ct.name.clone()),
Value::Integer(root as i64),
Value::Text(sql_text.into()),
]);
insert_table(
self.backend.writer()?,
crate::schema::SCHEMA_ROOT_PAGE,
next,
&row,
)?;
let ipk = if ct.without_rowid {
None
} else {
find_integer_primary_key(ct)
};
let unique = collect_unique_sets(ct, ipk);
let pk = if ct.without_rowid {
primary_key_positions(ct)
} else {
Vec::new()
};
let mut schema_rowid = next + 1;
for (n, set) in unique.iter().enumerate() {
if ct.without_rowid && *set == pk {
continue;
}
let idx_root = create_index_root(self.backend.writer()?)?;
let idx_row = encode_record(&[
Value::Text("index".into()),
Value::Text(alloc::format!("sqlite_autoindex_{}_{}", ct.name, n + 1)),
Value::Text(ct.name.clone()),
Value::Integer(idx_root as i64),
Value::Null, ]);
insert_table(
self.backend.writer()?,
crate::schema::SCHEMA_ROOT_PAGE,
schema_rowid,
&idx_row,
)?;
schema_rowid += 1;
}
let cookie = self
.backend
.writer()?
.header()
.schema_cookie
.wrapping_add(1);
self.backend.writer()?.header_mut().schema_cookie = cookie;
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn exec_create_table_as_select(&mut self, ct: &CreateTable, select: &Select) -> Result<()> {
if self.schema.table(&ct.name).is_some() {
if ct.if_not_exists {
return Ok(());
}
return Err(Error::Error(format!("table {} already exists", ct.name)));
}
let result = self.run_select(select, &Params::default())?;
let cols = result
.columns
.iter()
.map(|c| crate::sql::print::ident(c))
.collect::<Vec<_>>()
.join(", ");
let create_sql = format!(
"CREATE TABLE {}({cols})",
crate::sql::print::ident(&ct.name)
);
let Statement::CreateTable(syn) = sql::parse_one(&create_sql)? else {
return Err(Error::Corrupt("generated CTAS schema is invalid".into()));
};
self.exec_create_table(&syn, &create_sql)?;
if !result.rows.is_empty() {
let value_rows: Vec<Vec<Expr>> = result
.rows
.into_iter()
.map(|row| {
row.into_iter()
.map(|v| Expr::Literal(value_to_literal(v)))
.collect()
})
.collect();
let ins = Insert {
table: ct.name.clone(),
schema: None,
columns: Vec::new(),
source: InsertSource::Values(value_rows),
on_conflict: OnConflict::Abort,
upsert: Vec::new(),
returning: Vec::new(),
};
self.exec_insert(&ins, &Params::default())?;
}
Ok(())
}
fn exec_pragma(&mut self, p: &Pragma, params: &Params) -> Result<()> {
if p.name.eq_ignore_ascii_case("foreign_keys") {
if let Some(e) = &p.value {
self.foreign_keys = pragma_truth(e, params);
}
} else if p.name.eq_ignore_ascii_case("recursive_triggers") {
if let Some(e) = &p.value {
self.recursive_triggers = pragma_truth(e, params);
}
} else if p.name.eq_ignore_ascii_case("cache_size") {
if let Some(e) = &p.value {
self.cache_size
.set(eval::to_i64(&eval::eval(e, &EvalCtx::rowless(params))?));
}
} else if p.name.eq_ignore_ascii_case("journal_mode") {
if let Some(e) = &p.value {
if pragma_text(e).eq_ignore_ascii_case("wal") {
self.backend.writer()?.set_wal_mode()?;
}
}
} else if p.name.eq_ignore_ascii_case("wal_checkpoint") {
self.backend.writer()?.checkpoint()?;
} else if p.name.eq_ignore_ascii_case("user_version") {
if let Some(e) = &p.value {
let v = eval::to_i64(&eval::eval(e, &EvalCtx::rowless(params))?) as u32;
self.backend.writer()?.header_mut().user_version = v;
}
} else if p.name.eq_ignore_ascii_case("application_id") {
if let Some(e) = &p.value {
let v = eval::to_i64(&eval::eval(e, &EvalCtx::rowless(params))?) as u32;
self.backend.writer()?.header_mut().application_id = v;
}
} else if p.name.eq_ignore_ascii_case("auto_vacuum") {
if let Some(e) = &p.value {
let mode = match pragma_text(e).to_ascii_lowercase().as_str() {
"none" => 0,
"full" => 1,
"incremental" => 2,
_ => eval::to_i64(&eval::eval(e, &EvalCtx::rowless(params))?),
};
let target = match mode {
0 => AutoVacuum::None,
1 => AutoVacuum::Full,
2 => AutoVacuum::Incremental,
_ => return Err(Error::Error(format!("invalid auto_vacuum mode {mode}"))),
};
self.backend.writer()?.set_auto_vacuum_if_empty(target)?;
}
} else if p.name.eq_ignore_ascii_case("incremental_vacuum") {
let n = match &p.value {
Some(e) => eval::to_i64(&eval::eval(e, &EvalCtx::rowless(params))?),
None => 0,
};
self.backend.writer()?.incremental_vacuum(n)?;
}
Ok(())
}
fn foreign_keys_of(&self, table: &str) -> Result<Vec<ForeignKey>> {
let Some(obj) = self.schema.table(table) else {
return Ok(Vec::new());
};
let Some(sql) = &obj.sql else {
return Ok(Vec::new());
};
let Statement::CreateTable(ct) = sql::parse_one(sql)? else {
return Ok(Vec::new());
};
let mut out = Vec::new();
for col in &ct.columns {
for c in &col.constraints {
if let ColumnConstraint::References(fk) = c {
out.push(self.resolve_fk(fk)?);
}
}
}
for c in &ct.constraints {
if let TableConstraint::ForeignKey(fk) = c {
out.push(self.resolve_fk(fk)?);
}
}
Ok(out)
}
fn resolve_fk(&self, fk: &ForeignKey) -> Result<ForeignKey> {
let mut fk = fk.clone();
if fk.ref_columns.is_empty() {
fk.ref_columns = self.primary_key_columns(&fk.ref_table)?;
}
Ok(fk)
}
fn primary_key_columns(&self, table: &str) -> Result<Vec<String>> {
let Some(obj) = self.schema.table(table) else {
return Err(Error::Error(format!("no such table: {table}")));
};
let sql = obj.sql.as_deref().unwrap_or("");
let Statement::CreateTable(ct) = sql::parse_one(sql)? else {
return Ok(Vec::new());
};
for col in &ct.columns {
if col
.constraints
.iter()
.any(|c| matches!(c, ColumnConstraint::PrimaryKey { .. }))
{
return Ok(alloc::vec![col.name.clone()]);
}
}
for c in &ct.constraints {
if let TableConstraint::PrimaryKey(cols) = c {
return Ok(cols.clone());
}
}
Ok(Vec::new())
}
fn check_fk_child(&self, table: &str, meta: &TableMeta, values: &[Value]) -> Result<()> {
if !self.foreign_keys {
return Ok(());
}
for fk in self.foreign_keys_of(table)? {
if fk.initially_deferred && self.in_tx {
continue;
}
let key = match self.child_key_values(meta, &fk, values) {
Some(k) => k,
None => continue, };
if !self.parent_has_key(&fk, &key)? {
return Err(Error::Constraint("FOREIGN KEY constraint failed".into()));
}
}
Ok(())
}
fn check_deferred_fks(&self) -> Result<()> {
if !self.foreign_keys {
return Ok(());
}
for obj in self.schema.objects() {
if obj.obj_type != crate::schema::ObjectType::Table {
continue;
}
let fks: Vec<ForeignKey> = self
.foreign_keys_of(&obj.name)?
.into_iter()
.filter(|fk| fk.initially_deferred)
.collect();
if fks.is_empty() {
continue;
}
let meta = self.table_meta(&obj.name, None)?;
for (_, row) in self.scan_table(&meta)? {
for fk in &fks {
if let Some(key) = self.child_key_values(&meta, fk, &row) {
if !self.parent_has_key(fk, &key)? {
return Err(Error::Constraint("FOREIGN KEY constraint failed".into()));
}
}
}
}
}
Ok(())
}
fn child_key_values(
&self,
meta: &TableMeta,
fk: &ForeignKey,
values: &[Value],
) -> Option<Vec<Value>> {
let mut key = Vec::with_capacity(fk.columns.len());
for cname in &fk.columns {
let pos = meta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(cname))?;
let v = values.get(pos)?;
if matches!(v, Value::Null) {
return None;
}
key.push(v.clone());
}
Some(key)
}
fn parent_has_key(&self, fk: &ForeignKey, key: &[Value]) -> Result<bool> {
let pmeta = self.table_meta(&fk.ref_table, None)?;
let positions = self.column_positions(&pmeta, &fk.ref_columns)?;
for (_, row) in self.scan_table(&pmeta)? {
if positions
.iter()
.zip(key)
.all(|(&p, k)| eval::compare(&row[p], k) == core::cmp::Ordering::Equal)
{
return Ok(true);
}
}
Ok(false)
}
fn column_positions(&self, meta: &TableMeta, names: &[String]) -> Result<Vec<usize>> {
names
.iter()
.map(|n| {
meta.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(n))
.ok_or_else(|| Error::Error(format!("no such column: {n}")))
})
.collect()
}
fn enforce_parent_change(
&mut self,
parent_table: &str,
old_vals: &[Value],
new_vals: Option<&[Value]>,
params: &Params,
) -> Result<()> {
if !self.foreign_keys {
return Ok(());
}
let table_names: Vec<String> = self
.schema
.objects()
.iter()
.filter(|o| o.obj_type == crate::schema::ObjectType::Table)
.map(|o| o.name.clone())
.collect();
let mut referencing: Vec<(String, ForeignKey)> = Vec::new();
for name in table_names {
for fk in self.foreign_keys_of(&name)? {
if fk.ref_table.eq_ignore_ascii_case(parent_table) {
referencing.push((name.clone(), fk));
}
}
}
if referencing.is_empty() {
return Ok(());
}
let pmeta = self.table_meta(parent_table, None)?;
for (child_table, fk) in referencing {
let ppos = self.column_positions(&pmeta, &fk.ref_columns)?;
let old_key: Vec<Value> = ppos.iter().map(|&p| old_vals[p].clone()).collect();
if old_key.iter().any(|v| matches!(v, Value::Null)) {
continue;
}
let is_delete = new_vals.is_none();
let action = if is_delete {
fk.on_delete
} else {
fk.on_update
};
if action == FkAction::NoAction && fk.initially_deferred && self.in_tx {
continue;
}
if let Some(nv) = new_vals {
let new_key: Vec<Value> = ppos.iter().map(|&p| nv[p].clone()).collect();
if new_key
.iter()
.zip(&old_key)
.all(|(a, b)| eval::compare(a, b) == core::cmp::Ordering::Equal)
{
continue;
}
}
self.apply_fk_action(&child_table, &fk, &old_key, new_vals, &ppos, action, params)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn apply_fk_action(
&mut self,
child_table: &str,
fk: &ForeignKey,
old_key: &[Value],
new_parent: Option<&[Value]>,
parent_pos: &[usize],
action: FkAction,
params: &Params,
) -> Result<()> {
let cmeta = self.table_meta(child_table, None)?;
let cpos = self.column_positions(&cmeta, &fk.columns)?;
let mut matches: Vec<i64> = Vec::new();
for (rowid, row) in self.scan_table(&cmeta)? {
if cpos
.iter()
.zip(old_key)
.all(|(&p, k)| eval::compare(&row[p], k) == core::cmp::Ordering::Equal)
{
matches.push(rowid);
}
}
if matches.is_empty() {
return Ok(());
}
match action {
FkAction::NoAction | FkAction::Restrict => {
Err(Error::Constraint("FOREIGN KEY constraint failed".into()))
}
FkAction::Cascade if new_parent.is_none() => {
for rowid in matches {
self.delete_row_cascade(child_table, &cmeta, rowid, params)?;
}
Ok(())
}
FkAction::Cascade => {
let new_parent = new_parent.unwrap();
let new_key: Vec<Value> =
parent_pos.iter().map(|&p| new_parent[p].clone()).collect();
for rowid in matches {
self.update_child_key(&cmeta, child_table, rowid, &cpos, &new_key)?;
}
Ok(())
}
FkAction::SetNull => {
let nulls = alloc::vec![Value::Null; cpos.len()];
for rowid in matches {
self.update_child_key(&cmeta, child_table, rowid, &cpos, &nulls)?;
}
Ok(())
}
FkAction::SetDefault => {
let defaults: Vec<Value> = cpos
.iter()
.map(|&p| match &cmeta.defaults[p] {
Some(e) => eval::eval(e, &EvalCtx::rowless(params)).unwrap_or(Value::Null),
None => Value::Null,
})
.collect();
for rowid in matches {
self.update_child_key(&cmeta, child_table, rowid, &cpos, &defaults)?;
}
Ok(())
}
}
}
fn delete_row_cascade(
&mut self,
table: &str,
meta: &TableMeta,
rowid: i64,
params: &Params,
) -> Result<()> {
let old = self.read_row(meta, rowid)?;
if let Some(old) = old {
self.enforce_parent_change(table, &old, None, params)?;
}
delete_table(self.backend.writer()?, meta.root, rowid)?;
let indexes = self.indexes_of(table)?;
if !indexes.is_empty() {
self.rebuild_indexes(meta, &indexes)?;
}
Ok(())
}
fn update_child_key(
&mut self,
meta: &TableMeta,
table: &str,
rowid: i64,
positions: &[usize],
new_vals: &[Value],
) -> Result<()> {
let Some(mut row) = self.read_row(meta, rowid)? else {
return Ok(());
};
for (&p, v) in positions.iter().zip(new_vals) {
row[p] = v.clone();
}
let mut stored = row.clone();
if let Some(ipk) = meta.ipk {
stored[ipk] = Value::Null;
}
let record = encode_record(&stored);
insert_table(self.backend.writer()?, meta.root, rowid, &record)?;
let indexes = self.indexes_of(table)?;
if !indexes.is_empty() {
self.rebuild_indexes(meta, &indexes)?;
}
Ok(())
}
fn read_row(&self, meta: &TableMeta, rowid: i64) -> Result<Option<Vec<Value>>> {
let encoding = self.backend.source().header().text_encoding;
let mut cur = TableCursor::new(self.backend.source(), meta.root);
if cur.seek(rowid)? {
let values = self.decode_full_row(meta, rowid, &cur.payload()?, encoding)?;
Ok(Some(values))
} else {
Ok(None)
}
}
fn exec_create_trigger(&mut self, ct: &CreateTrigger, sql_text: &str) -> Result<()> {
let stripped;
let sql_text = match ct.schema.as_deref() {
Some(s) => {
stripped = strip_schema_qualifier(sql_text, s)?;
stripped.as_str()
}
None => sql_text,
};
if self
.schema
.objects()
.iter()
.any(|o| o.name.eq_ignore_ascii_case(&ct.name))
{
if ct.if_not_exists {
return Ok(());
}
return Err(Error::Error(format!("trigger {} already exists", ct.name)));
}
let table_in_other = self
.temp_db
.as_ref()
.is_some_and(|t| t.schema.table(&ct.table).is_some());
if self.schema.table(&ct.table).is_none() && !table_in_other && !self.is_view(&ct.table) {
return Err(Error::Error(format!("no such table: {}", ct.table)));
}
let next = self.next_rowid(crate::schema::SCHEMA_ROOT_PAGE)?;
let row = encode_record(&[
Value::Text("trigger".into()),
Value::Text(ct.name.clone()),
Value::Text(ct.table.clone()),
Value::Integer(0),
Value::Text(sql_text.into()),
]);
insert_table(
self.backend.writer()?,
crate::schema::SCHEMA_ROOT_PAGE,
next,
&row,
)?;
let cookie = self
.backend
.writer()?
.header()
.schema_cookie
.wrapping_add(1);
self.backend.writer()?.header_mut().schema_cookie = cookie;
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn triggers_for(
&self,
table: &str,
kind: TrigEvent,
timing: TriggerTiming,
) -> Result<Vec<CreateTrigger>> {
let mut out = Vec::new();
self.collect_triggers(self.schema.objects(), table, kind, timing, &mut out);
if let Some(t) = &self.temp_db {
self.collect_triggers(t.schema.objects(), table, kind, timing, &mut out);
}
Ok(out)
}
fn collect_triggers(
&self,
objects: &[crate::schema::SchemaObject],
table: &str,
kind: TrigEvent,
timing: TriggerTiming,
out: &mut Vec<CreateTrigger>,
) {
for obj in objects {
if obj.obj_type != crate::schema::ObjectType::Trigger
|| !obj.tbl_name.eq_ignore_ascii_case(table)
{
continue;
}
let Some(sql) = &obj.sql else { continue };
let Ok(Statement::CreateTrigger(ct)) = sql::parse_one(sql) else {
continue;
};
let event_ok = matches!(
(&ct.event, kind),
(TriggerEvent::Insert, TrigEvent::Insert)
| (TriggerEvent::Delete, TrigEvent::Delete)
| (TriggerEvent::Update(_), TrigEvent::Update)
);
if ct.timing == timing && event_ok {
out.push(ct);
}
}
}
#[allow(clippy::too_many_arguments)]
fn fire_triggers(
&mut self,
table: &str,
kind: TrigEvent,
timing: TriggerTiming,
columns: &[ColumnInfo],
old: Option<(&[Value], i64)>,
new: Option<(&[Value], i64)>,
params: &Params,
changed_cols: Option<&[String]>,
) -> Result<bool> {
let depth = self.trigger_depth.get();
let limit = if self.recursive_triggers { 1000 } else { 1 };
if depth >= limit {
return if self.recursive_triggers {
Err(Error::Error("too many levels of trigger recursion".into()))
} else {
Ok(false)
};
}
let mut trigs = self.triggers_for(table, kind, timing)?;
if let Some(changed) = changed_cols {
trigs.retain(|t| match &t.event {
TriggerEvent::Update(cols) if !cols.is_empty() => cols
.iter()
.any(|c| changed.iter().any(|ch| ch.eq_ignore_ascii_case(c))),
_ => true,
});
}
if trigs.is_empty() {
return Ok(false);
}
self.trigger_depth.set(depth + 1);
let base = self.outer_scope.borrow().len();
if let Some((vals, rid)) = old {
self.push_row_frame("old", columns, vals, rid);
}
if let Some((vals, rid)) = new {
self.push_row_frame("new", columns, vals, rid);
}
let result = self.run_trigger_bodies(&trigs, params);
self.outer_scope.borrow_mut().truncate(base);
self.trigger_depth.set(depth);
result.map(|()| true)
}
fn push_row_frame(&self, label: &str, columns: &[ColumnInfo], values: &[Value], rowid: i64) {
let columns = columns
.iter()
.map(|c| ColumnInfo {
name: c.name.clone(),
table: String::from(label),
affinity: c.affinity,
collation: c.collation,
})
.collect();
self.outer_scope.borrow_mut().push(OuterFrame {
columns,
row: values.to_vec(),
rowid: Some(rowid),
});
}
fn is_view(&self, name: &str) -> bool {
self.temp_has_view(name)
|| self.schema.objects().iter().any(|o| {
o.obj_type == crate::schema::ObjectType::View && o.name.eq_ignore_ascii_case(name)
})
}
fn temp_has_view(&self, name: &str) -> bool {
self.temp_db.as_ref().is_some_and(|t| {
t.schema.objects().iter().any(|o| {
o.obj_type == crate::schema::ObjectType::View && o.name.eq_ignore_ascii_case(name)
})
})
}
fn view_columns(&self, name: &str, params: &Params) -> Result<Vec<ColumnInfo>> {
match self.try_view(name, None, params)? {
Some((cols, _)) => Ok(cols),
None => Err(Error::Error(format!("no such view: {name}"))),
}
}
fn exec_view_insert(
&mut self,
ins: &Insert,
rows: &[Vec<Expr>],
params: &Params,
) -> Result<usize> {
let cols = self.view_columns(&ins.table, params)?;
if self
.triggers_for(&ins.table, TrigEvent::Insert, TriggerTiming::InsteadOf)?
.is_empty()
{
return Err(Error::Error(format!(
"cannot modify {} — it is a view",
ins.table
)));
}
let target: Vec<usize> = if ins.columns.is_empty() {
(0..cols.len()).collect()
} else {
ins.columns
.iter()
.map(|name| {
cols.iter()
.position(|c| c.name.eq_ignore_ascii_case(name))
.ok_or_else(|| Error::Error(format!("no such column: {name}")))
})
.collect::<Result<_>>()?
};
let mut affected = 0;
for row_exprs in rows {
let ctx = EvalCtx::rowless(params).with_subqueries(self);
let mut new = alloc::vec![Value::Null; cols.len()];
for (i, e) in row_exprs.iter().enumerate() {
new[target[i]] = eval::eval(e, &ctx)?;
}
self.fire_triggers(
&ins.table,
TrigEvent::Insert,
TriggerTiming::InsteadOf,
&cols,
None,
Some((&new, 0)),
params,
None,
)?;
if self.raise_ignore.replace(false) {
continue;
}
affected += 1;
}
Ok(affected)
}
fn exec_view_delete(&mut self, del: &Delete, params: &Params) -> Result<usize> {
let (cols, rows) = self
.try_view(&del.table, None, params)?
.ok_or_else(|| Error::Error(format!("no such view: {}", del.table)))?;
if self
.triggers_for(&del.table, TrigEvent::Delete, TriggerTiming::InsteadOf)?
.is_empty()
{
return Err(Error::Error(format!(
"cannot modify {} — it is a view",
del.table
)));
}
let mut affected = 0;
for row in rows {
if let Some(p) = &del.where_clause {
let ctx = row_ctx(&row.values, &cols, None, params).with_subqueries(self);
if eval::truth(&eval::eval(p, &ctx)?) != Some(true) {
continue;
}
}
self.fire_triggers(
&del.table,
TrigEvent::Delete,
TriggerTiming::InsteadOf,
&cols,
Some((&row.values, 0)),
None,
params,
None,
)?;
if self.raise_ignore.replace(false) {
continue;
}
affected += 1;
}
Ok(affected)
}
fn exec_view_update(&mut self, upd: &Update, params: &Params) -> Result<usize> {
let (cols, rows) = self
.try_view(&upd.table, None, params)?
.ok_or_else(|| Error::Error(format!("no such view: {}", upd.table)))?;
if self
.triggers_for(&upd.table, TrigEvent::Update, TriggerTiming::InsteadOf)?
.is_empty()
{
return Err(Error::Error(format!(
"cannot modify {} — it is a view",
upd.table
)));
}
let changed: Vec<String> = upd.assignments.iter().map(|(c, _)| c.clone()).collect();
let mut affected = 0;
for row in rows {
let old = row.values.clone();
if let Some(p) = &upd.where_clause {
let ctx = row_ctx(&old, &cols, None, params).with_subqueries(self);
if eval::truth(&eval::eval(p, &ctx)?) != Some(true) {
continue;
}
}
let mut new = old.clone();
for (col, expr) in &upd.assignments {
let pos = cols
.iter()
.position(|c| c.name.eq_ignore_ascii_case(col))
.ok_or_else(|| Error::Error(format!("no such column: {col}")))?;
let ctx = row_ctx(&old, &cols, None, params).with_subqueries(self);
new[pos] = eval::eval(expr, &ctx)?;
}
self.fire_triggers(
&upd.table,
TrigEvent::Update,
TriggerTiming::InsteadOf,
&cols,
Some((&old, 0)),
Some((&new, 0)),
params,
Some(&changed),
)?;
if self.raise_ignore.replace(false) {
continue;
}
affected += 1;
}
Ok(affected)
}
fn run_trigger_bodies(&mut self, trigs: &[CreateTrigger], params: &Params) -> Result<()> {
for trig in trigs {
if let Some(when) = &trig.when {
let fires = {
let ctx = EvalCtx::rowless(params).with_subqueries(self);
eval::truth(&eval::eval(when, &ctx)?) == Some(true)
};
if !fires {
continue;
}
}
for stmt in &trig.body {
match stmt {
Statement::Insert(ins) => {
self.exec_insert(ins, params)?;
}
Statement::Update(u) => {
self.exec_update(u, params)?;
}
Statement::Delete(d) => {
self.exec_delete(d, params)?;
}
Statement::Select(sel) => {
self.run_trigger_select(sel, params)?;
if self.raise_ignore.get() {
return Ok(());
}
}
_ => return Err(Error::Unsupported("statement type in trigger body")),
}
}
}
Ok(())
}
fn run_trigger_select(&self, sel: &Select, params: &Params) -> Result<()> {
for col in &sel.columns {
if let ResultColumn::Expr { expr, .. } = col {
self.eval_raise_expr(expr, params)?;
if self.raise_ignore.get() {
return Ok(());
}
}
}
Ok(())
}
fn eval_raise_expr(&self, expr: &Expr, params: &Params) -> Result<()> {
match expr {
Expr::Function { name, args, .. } if name.eq_ignore_ascii_case("raise") => {
self.fire_raise(args, params)
}
Expr::Paren(inner) => self.eval_raise_expr(inner, params),
Expr::Case {
operand,
when_then,
else_result,
} => {
let ctx = EvalCtx::rowless(params).with_subqueries(self);
let base = match operand {
Some(op) => Some(eval::eval(op, &ctx)?),
None => None,
};
for (when, then) in when_then {
let hit = match &base {
Some(b) => {
let w = eval::eval(when, &ctx)?;
crate::value::cmp_values(b, &w) == core::cmp::Ordering::Equal
}
None => eval::truth(&eval::eval(when, &ctx)?) == Some(true),
};
if hit {
return self.eval_raise_expr(then, params);
}
}
if let Some(e) = else_result {
return self.eval_raise_expr(e, params);
}
Ok(())
}
_ => Ok(()),
}
}
fn fire_raise(&self, args: &[Expr], params: &Params) -> Result<()> {
let action = match args.first() {
Some(Expr::Literal(Literal::Str(s))) => s.as_str(),
_ => return Err(Error::Error("malformed RAISE()".into())),
};
if action == "ignore" {
self.raise_ignore.set(true);
return Ok(());
}
let ctx = EvalCtx::rowless(params).with_subqueries(self);
let msg = match args.get(1) {
Some(e) => match eval::eval(e, &ctx)? {
Value::Null => String::new(),
Value::Text(s) => s,
Value::Integer(i) => {
let mut s = String::new();
let _ = core::fmt::write(&mut s, format_args!("{i}"));
s
}
Value::Real(r) => eval::format_real(r),
Value::Blob(_) => String::new(),
},
None => String::new(),
};
match action {
"fail" => self.stmt_keep_partial.set(true),
"rollback" => self.stmt_rollback_tx.set(true),
_ => {} }
Err(Error::Constraint(msg))
}
fn exec_insert(&mut self, ins: &Insert, params: &Params) -> Result<usize> {
reject_schema_write(&ins.table)?;
if self.is_virtual_table(&ins.table) {
let rows: Vec<Vec<Expr>> = match &ins.source {
InsertSource::Values(rows) => rows.clone(),
InsertSource::DefaultValues => alloc::vec![Vec::new()],
InsertSource::Select(sel) => self
.run_select(sel, params)?
.rows
.into_iter()
.map(|row| row.into_iter().map(value_to_literal_expr).collect())
.collect(),
};
return self.exec_vtab_insert(ins, &rows, params);
}
if let InsertSource::Values(rows) = &ins.source {
if let Some(first) = rows.first() {
if rows.iter().any(|r| r.len() != first.len()) {
return Err(Error::Error(
"all VALUES must have the same number of terms".into(),
));
}
}
}
let (rows, is_default_values) = match &ins.source {
InsertSource::Values(rows) => (rows.clone(), false),
InsertSource::DefaultValues => (alloc::vec![Vec::new()], true),
InsertSource::Select(sel) => {
let result = self.run_select(sel, params)?;
let rows = result
.rows
.into_iter()
.map(|row| row.into_iter().map(value_to_literal_expr).collect())
.collect();
(rows, false)
}
};
if self.is_view(&ins.table) {
return self.exec_view_insert(ins, &rows, params);
}
let meta = self.table_meta(&ins.table, None)?;
if meta.without_rowid {
if !ins.upsert.is_empty() || !ins.returning.is_empty() {
return Err(Error::Unsupported(
"UPSERT / RETURNING on WITHOUT ROWID tables",
));
}
return self.exec_insert_without_rowid(ins, &meta, &rows, params);
}
let n_cols = meta.columns.len();
let target: Vec<usize> = if ins.columns.is_empty() {
(0..n_cols).collect()
} else {
let mut t = Vec::new();
for name in &ins.columns {
let pos = meta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(name))
.ok_or_else(|| Error::Error(format!("no such column: {name}")))?;
t.push(pos);
}
t
};
let indexes = self.indexes_of(&ins.table)?;
let mut next_auto = self.next_rowid(meta.root)?;
let mut affected = 0;
let mut replaced = false;
for row_exprs in &rows {
if !is_default_values && row_exprs.len() != target.len() {
return Err(Error::Error("INSERT column/value count mismatch".into()));
}
let ctx = EvalCtx::rowless(params).with_subqueries(self);
let mut values: Vec<Value> = meta
.defaults
.iter()
.map(|d| match d {
Some(e) => eval::eval(e, &ctx),
None => Ok(Value::Null),
})
.collect::<Result<_>>()?;
for (i, e) in row_exprs.iter().enumerate() {
if meta.is_generated(target[i]) {
return Err(Error::Error(format!(
"cannot INSERT into generated column \"{}\"",
meta.columns[target[i]].name
)));
}
values[target[i]] = eval::eval(e, &ctx)?;
}
apply_column_affinity(&meta, &mut values);
self.materialize_generated(&meta, &mut values, params)?;
let rowid = match meta.ipk {
Some(ipk) if !matches!(values[ipk], Value::Null) => {
let r = eval::to_i64(&values[ipk]);
next_auto = next_auto.max(r + 1);
r
}
_ => {
let r = next_auto;
next_auto += 1;
r
}
};
if let Some(ipk) = meta.ipk {
values[ipk] = Value::Integer(rowid);
}
{
let r = check_not_null(&meta, &values)
.and_then(|()| self.check_strict_types(&meta, &values))
.and_then(|()| self.check_constraints(&meta, &values, Some(rowid), params));
match r {
Ok(()) => {}
Err(Error::Constraint(_)) if ins.on_conflict == OnConflict::Ignore => continue,
Err(Error::Constraint(m)) => {
return Err(self.conflict_error(ins.on_conflict, &m))
}
Err(e) => return Err(e),
}
}
self.check_fk_child(&ins.table, &meta, &values)?;
let conflicts = self.find_conflicts(&ins.table, &meta, rowid, &values, None, params)?;
if !conflicts.is_empty() {
let mut matched = None;
for up in &ins.upsert {
if self.upsert_target_matches(&meta, up, &conflicts, &values, rowid, params)? {
matched = Some(up);
break;
}
}
if let Some(up) = matched {
match &up.action {
UpsertAction::Nothing => continue, UpsertAction::Update {
assignments,
where_clause,
} => {
if self.upsert_do_update(
&ins.table,
&meta,
conflicts[0],
&values,
assignments,
where_clause.as_ref(),
&ins.returning,
params,
)? {
affected += 1;
replaced = true; }
continue;
}
}
}
match ins.on_conflict {
oc @ (OnConflict::Abort | OnConflict::Fail | OnConflict::Rollback) => {
return Err(self.conflict_error(oc, "UNIQUE constraint failed"))
}
OnConflict::Ignore => continue, OnConflict::Replace => {
for cr in conflicts {
delete_table(self.backend.writer()?, meta.root, cr)?;
}
replaced = true;
}
}
}
let index_values = values.clone();
self.fire_triggers(
&ins.table,
TrigEvent::Insert,
TriggerTiming::Before,
&meta.columns,
None,
Some((&index_values, rowid)),
params,
None,
)?;
if self.raise_ignore.replace(false) {
continue;
}
let record = self.encode_table_record(&meta, &index_values);
insert_table(self.backend.writer()?, meta.root, rowid, &record)?;
self.last_insert_rowid.set(rowid);
for idx in &indexes {
if !self.row_in_index(idx, &meta, &index_values, Some(rowid), params)? {
continue; }
let key = self.index_key_bytes(idx, &meta, &index_values, rowid, params)?;
insert_index(self.backend.writer()?, idx.root, &key, &idx.collations)?;
}
self.fire_triggers(
&ins.table,
TrigEvent::Insert,
TriggerTiming::After,
&meta.columns,
None,
Some((&index_values, rowid)),
params,
None,
)?;
if !ins.returning.is_empty() {
self.collect_returning(&ins.returning, &meta, &index_values, Some(rowid), params)?;
}
affected += 1;
}
if replaced {
self.rebuild_indexes(&meta, &indexes)?;
}
Ok(affected)
}
#[allow(clippy::too_many_arguments)]
fn upsert_do_update(
&mut self,
table: &str,
meta: &TableMeta,
existing_rowid: i64,
proposed: &[Value],
assignments: &[(String, Expr)],
where_clause: Option<&Expr>,
returning: &[ResultColumn],
params: &Params,
) -> Result<bool> {
let Some(old_row) = self.read_row(meta, existing_rowid)? else {
return Ok(false);
};
let changed: Vec<String> = assignments.iter().map(|(c, _)| c.clone()).collect();
let mut cols: Vec<ColumnInfo> = meta.columns.clone();
cols.extend(meta.columns.iter().map(|c| ColumnInfo {
name: c.name.clone(),
table: String::from("excluded"),
affinity: c.affinity,
collation: c.collation,
}));
let mut values = old_row.clone();
{
let mut combined = old_row.clone();
combined.extend_from_slice(proposed);
let ctx = EvalCtx {
row: &combined,
columns: &cols,
rowid: Some(existing_rowid),
params,
anon_counter: core::cell::Cell::new(0),
subqueries: None,
}
.with_subqueries(self);
if let Some(w) = where_clause {
if eval::truth(&eval::eval(w, &ctx)?) != Some(true) {
return Ok(false);
}
}
for (col, e) in assignments {
let pos = meta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(col))
.ok_or_else(|| Error::Error(format!("no such column: {col}")))?;
if meta.is_generated(pos) {
return Err(Error::Error(format!(
"cannot UPDATE generated column \"{col}\""
)));
}
values[pos] = eval::eval(e, &ctx)?;
}
}
apply_column_affinity(meta, &mut values);
self.materialize_generated(meta, &mut values, params)?;
check_not_null(meta, &values)?;
self.check_strict_types(meta, &values)?;
self.check_constraints(meta, &values, Some(existing_rowid), params)?;
self.check_fk_child(table, meta, &values)?;
if self.foreign_keys {
self.enforce_parent_change(table, &old_row, Some(&values), params)?;
}
let new_rowid = match meta.ipk {
Some(ipk) => eval::to_i64(&values[ipk]),
None => existing_rowid,
};
self.fire_triggers(
table,
TrigEvent::Update,
TriggerTiming::Before,
&meta.columns,
Some((&old_row, existing_rowid)),
Some((&values, new_rowid)),
params,
Some(&changed),
)?;
if !self
.find_conflicts(
table,
meta,
new_rowid,
&values,
Some(existing_rowid),
params,
)?
.is_empty()
{
return Err(Error::Constraint("UNIQUE constraint failed".into()));
}
let new_full = values.clone();
let record = self.encode_table_record(meta, &new_full);
delete_table(self.backend.writer()?, meta.root, existing_rowid)?;
insert_table(self.backend.writer()?, meta.root, new_rowid, &record)?;
self.fire_triggers(
table,
TrigEvent::Update,
TriggerTiming::After,
&meta.columns,
Some((&old_row, existing_rowid)),
Some((&new_full, new_rowid)),
params,
Some(&changed),
)?;
if !returning.is_empty() {
self.collect_returning(returning, meta, &new_full, Some(new_rowid), params)?;
}
Ok(true)
}
fn collect_returning(
&self,
returning: &[ResultColumn],
meta: &TableMeta,
values: &[Value],
rowid: Option<i64>,
params: &Params,
) -> Result<()> {
let ctx = row_ctx(values, &meta.columns, rowid, params).with_subqueries(self);
let mut out = Vec::new();
for col in returning {
project_column(col, &meta.columns, &ctx, &mut out)?;
}
self.returning_rows.borrow_mut().push(out);
Ok(())
}
fn stat1_map(&self) -> alloc::collections::BTreeMap<String, Vec<u64>> {
let mut map = alloc::collections::BTreeMap::new();
if self.schema.table("sqlite_stat1").is_none() {
return map;
}
let Ok(meta) = self.table_meta("sqlite_stat1", None) else {
return map;
};
let Ok(rows) = self.scan_table(&meta) else {
return map;
};
for (_, vals) in rows {
if let (Some(Value::Text(idx)), Some(Value::Text(stat))) = (vals.get(1), vals.get(2)) {
let nums: Vec<u64> = stat
.split_whitespace()
.filter_map(|t| t.parse().ok())
.collect();
if !nums.is_empty() {
map.insert(idx.clone(), nums);
}
}
}
map
}
fn find_conflicts(
&self,
table: &str,
meta: &TableMeta,
rowid: i64,
values: &[Value],
exclude: Option<i64>,
params: &Params,
) -> Result<Vec<i64>> {
let uniq_idx: Vec<(IndexMeta, Vec<Value>)> = self
.indexes_of(table)?
.into_iter()
.filter(|i| i.unique && autoindex_number(&i.name, table).is_none())
.filter_map(|i| {
if !self
.row_in_index(&i, meta, values, Some(rowid), params)
.unwrap_or(false)
{
return None;
}
let key = self
.index_key_values(&i, meta, values, rowid, params)
.ok()?;
if key.iter().any(|v| matches!(v, Value::Null)) {
return None; }
Some((i, key))
})
.collect();
let mut out = Vec::new();
for (er, ev) in self.scan_table(meta)? {
if Some(er) == exclude {
continue;
}
if er == rowid {
out.push(er);
continue;
}
let mut conflicted = false;
for set in &meta.unique {
let new_tuple: Vec<&Value> = set.iter().map(|&i| &values[i]).collect();
if new_tuple.iter().any(|v| matches!(v, Value::Null)) {
continue; }
let conflict = set.iter().zip(&new_tuple).all(|(&i, nv)| {
crate::value::cmp_values_coll(&ev[i], nv, meta.columns[i].collation)
== core::cmp::Ordering::Equal
});
if conflict {
out.push(er);
conflicted = true;
break;
}
}
if conflicted {
continue;
}
for (idx, new_key) in &uniq_idx {
if !self.row_in_index(idx, meta, &ev, Some(er), params)? {
continue; }
let ex_key = self.index_key_values(idx, meta, &ev, er, params)?;
let conflict = ex_key.len() == new_key.len()
&& ex_key
.iter()
.zip(new_key)
.zip(&idx.collations)
.all(|((a, b), &coll)| {
crate::value::cmp_values_coll(a, b, coll) == core::cmp::Ordering::Equal
});
if conflict {
out.push(er);
break;
}
}
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
fn upsert_target_matches(
&self,
meta: &TableMeta,
up: &Upsert,
conflicts: &[i64],
values: &[Value],
rowid: i64,
params: &Params,
) -> Result<bool> {
if up.target.is_empty() {
return Ok(true);
}
let target_cols: Vec<usize> = up
.target
.iter()
.map(|name| {
meta.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(name))
.ok_or_else(|| Error::Error(format!("no such column: {name}")))
})
.collect::<Result<_>>()?;
if let Some(ipk) = meta.ipk {
if target_cols == [ipk] {
return Ok(conflicts.contains(&rowid));
}
}
for &er in conflicts {
let Some(existing) = self.read_row(meta, er)? else {
continue;
};
let collide = target_cols.iter().all(|&c| {
!matches!(values[c], Value::Null)
&& crate::value::cmp_values_coll(
&existing[c],
&values[c],
meta.columns[c].collation,
) == core::cmp::Ordering::Equal
});
if collide {
return Ok(true);
}
}
let _ = params;
Ok(false)
}
fn index_key_values(
&self,
idx: &IndexMeta,
meta: &TableMeta,
values: &[Value],
rowid: i64,
params: &Params,
) -> Result<Vec<Value>> {
match &idx.key_exprs {
None => Ok(idx.cols.iter().map(|&c| values[c].clone()).collect()),
Some(exprs) => {
let ctx = row_ctx(values, &meta.columns, Some(rowid), params).with_subqueries(self);
exprs.iter().map(|e| eval::eval(e, &ctx)).collect()
}
}
}
fn wr_index_collision(
&self,
table: &str,
meta: &TableMeta,
a: &[Value],
b: &[Value],
params: &Params,
) -> Result<bool> {
for idx in self
.indexes_of(table)?
.iter()
.filter(|i| i.unique && autoindex_number(&i.name, table).is_none())
{
if !self.row_in_index(idx, meta, a, None, params)?
|| !self.row_in_index(idx, meta, b, None, params)?
{
continue;
}
let ka = self.index_key_values(idx, meta, a, 0, params)?;
if ka.iter().any(|v| matches!(v, Value::Null)) {
continue; }
let kb = self.index_key_values(idx, meta, b, 0, params)?;
let eq = ka.len() == kb.len()
&& ka.iter().zip(&kb).zip(&idx.collations).all(|((x, y), &c)| {
crate::value::cmp_values_coll(x, y, c) == core::cmp::Ordering::Equal
});
if eq {
return Ok(true);
}
}
Ok(false)
}
fn exec_delete(&mut self, del: &Delete, params: &Params) -> Result<usize> {
reject_schema_write(&del.table)?;
if self.is_virtual_table(&del.table) {
return self.exec_vtab_delete(del, params);
}
if self.is_view(&del.table) {
return self.exec_view_delete(del, params);
}
let meta = self.table_meta(&del.table, None)?;
if meta.without_rowid {
if !del.returning.is_empty() {
return Err(Error::Unsupported("RETURNING on WITHOUT ROWID tables"));
}
return self.exec_delete_without_rowid(del, &meta, params);
}
let indexes = self.indexes_of(&del.table)?;
let mut victims = self.matching_rowids(&meta, del.where_clause.as_ref(), params)?;
if !del.order_by.is_empty() || del.limit.is_some() || del.offset.is_some() {
victims = self.order_limit_rowids(
&meta,
victims,
&del.order_by,
del.limit.as_ref(),
del.offset.as_ref(),
params,
)?;
}
let mut deleted = 0;
for rowid in &victims {
let old = self.read_row(&meta, *rowid)?;
if let Some(old) = &old {
self.fire_triggers(
&del.table,
TrigEvent::Delete,
TriggerTiming::Before,
&meta.columns,
Some((old, *rowid)),
None,
params,
None,
)?;
if self.raise_ignore.replace(false) {
continue;
}
if !del.returning.is_empty() {
self.collect_returning(&del.returning, &meta, old, Some(*rowid), params)?;
}
}
if self.foreign_keys {
if let Some(old) = &old {
self.enforce_parent_change(&del.table, old, None, params)?;
}
}
delete_table(self.backend.writer()?, meta.root, *rowid)?;
deleted += 1;
if let Some(old) = &old {
self.fire_triggers(
&del.table,
TrigEvent::Delete,
TriggerTiming::After,
&meta.columns,
Some((old, *rowid)),
None,
params,
None,
)?;
}
}
if deleted > 0 {
self.compact_table(&meta)?;
self.rebuild_indexes(&meta, &indexes)?;
}
Ok(deleted)
}
fn compact_table(&mut self, meta: &TableMeta) -> Result<()> {
if !table_has_empty_leaf(self.backend.source(), meta.root)? {
return Ok(());
}
let mut rows: Vec<(i64, Vec<u8>)> = Vec::new();
{
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut ok = cur.first()?;
while ok {
rows.push((cur.rowid()?, cur.payload()?));
ok = cur.next()?;
}
}
let w = self.backend.writer()?;
clear_table(w, meta.root)?;
for (rowid, payload) in &rows {
insert_table(w, meta.root, *rowid, payload)?;
}
Ok(())
}
fn exec_update(&mut self, upd: &Update, params: &Params) -> Result<usize> {
reject_schema_write(&upd.table)?;
if self.is_virtual_table(&upd.table) {
return self.exec_vtab_update(upd, params);
}
if self.is_view(&upd.table) {
return self.exec_view_update(upd, params);
}
let meta = self.table_meta(&upd.table, None)?;
if meta.without_rowid {
if !upd.returning.is_empty() {
return Err(Error::Unsupported("RETURNING on WITHOUT ROWID tables"));
}
if upd.from.is_some() {
return Err(Error::Unsupported("UPDATE … FROM on WITHOUT ROWID tables"));
}
return self.exec_update_without_rowid(upd, &meta, params);
}
let indexes = self.indexes_of(&upd.table)?;
let changed: Vec<String> = upd.assignments.iter().map(|(c, _)| c.clone()).collect();
let from_data = match &upd.from {
Some(fc) => {
let synth = Select {
ctes: Vec::new(),
compound: Vec::new(),
distinct: false,
columns: Vec::new(),
from: Some(fc.clone()),
where_clause: None,
group_by: Vec::new(),
having: None,
window_defs: Vec::new(),
order_by: Vec::new(),
limit: None,
offset: None,
};
let (cols, rows) = self.scan_source(&synth, params)?;
Some((cols, rows.into_iter().map(|r| r.values).collect::<Vec<_>>()))
}
None => None,
};
let combined_columns: Vec<ColumnInfo> = match &from_data {
Some((cols, _)) => meta.columns.iter().chain(cols).cloned().collect(),
None => Vec::new(),
};
let mut targets: Vec<(i64, Vec<Value>, Option<Vec<Value>>)> = Vec::new();
{
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let encoding = self.backend.source().header().text_encoding;
let mut ok = cur.first()?;
while ok {
let rowid = cur.rowid()?;
let values = self.decode_full_row(&meta, rowid, &cur.payload()?, encoding)?;
match &from_data {
Some((_, from_rows)) => {
let mut matched = None;
for fr in from_rows {
let mut combined = values.clone();
combined.extend_from_slice(fr);
let ok = match &upd.where_clause {
Some(p) => {
let ctx =
row_ctx(&combined, &combined_columns, Some(rowid), params)
.with_subqueries(self);
eval::truth(&eval::eval(p, &ctx)?) == Some(true)
}
None => true,
};
if ok {
matched = Some(fr.clone());
break;
}
}
if let Some(fr) = matched {
targets.push((rowid, values, Some(fr)));
}
}
None => {
let matches = match &upd.where_clause {
Some(p) => {
let ctx = row_ctx(&values, &meta.columns, Some(rowid), params)
.with_subqueries(self);
eval::truth(&eval::eval(p, &ctx)?) == Some(true)
}
None => true,
};
if matches {
targets.push((rowid, values, None));
}
}
}
ok = cur.next()?;
}
}
if !upd.order_by.is_empty() || upd.limit.is_some() || upd.offset.is_some() {
let rowids: Vec<i64> = targets.iter().map(|(r, _, _)| *r).collect();
let kept = self.order_limit_rowids(
&meta,
rowids,
&upd.order_by,
upd.limit.as_ref(),
upd.offset.as_ref(),
params,
)?;
let mut by_id: alloc::collections::BTreeMap<i64, (Vec<Value>, Option<Vec<Value>>)> =
targets.into_iter().map(|(r, v, f)| (r, (v, f))).collect();
targets = kept
.into_iter()
.filter_map(|r| by_id.remove(&r).map(|(v, f)| (r, v, f)))
.collect();
}
let mut affected = 0;
for (rowid, mut values, matched_from) in targets {
let old_row = values.clone();
for (col, expr) in &upd.assignments {
let pos = meta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(col))
.ok_or_else(|| Error::Error(format!("no such column: {col}")))?;
if meta.is_generated(pos) {
return Err(Error::Error(format!(
"cannot UPDATE generated column \"{col}\""
)));
}
let new = match &matched_from {
Some(fr) => {
let mut combined = old_row.clone();
combined.extend_from_slice(fr);
let ctx = row_ctx(&combined, &combined_columns, Some(rowid), params)
.with_subqueries(self);
eval::eval(expr, &ctx)?
}
None => {
let ctx = row_ctx(&old_row, &meta.columns, Some(rowid), params)
.with_subqueries(self);
eval::eval(expr, &ctx)?
}
};
values[pos] = new;
}
apply_column_affinity(&meta, &mut values);
self.materialize_generated(&meta, &mut values, params)?;
{
let r = check_not_null(&meta, &values)
.and_then(|()| self.check_strict_types(&meta, &values))
.and_then(|()| self.check_constraints(&meta, &values, Some(rowid), params));
match r {
Ok(()) => {}
Err(Error::Constraint(_)) if upd.on_conflict == OnConflict::Ignore => continue,
Err(Error::Constraint(m)) => {
return Err(self.conflict_error(upd.on_conflict, &m))
}
Err(e) => return Err(e),
}
}
self.check_fk_child(&upd.table, &meta, &values)?;
if self.foreign_keys {
self.enforce_parent_change(&upd.table, &old_row, Some(&values), params)?;
}
let new_rowid = match meta.ipk {
Some(ipk) => eval::to_i64(&values[ipk]),
None => rowid,
};
self.fire_triggers(
&upd.table,
TrigEvent::Update,
TriggerTiming::Before,
&meta.columns,
Some((&old_row, rowid)),
Some((&values, new_rowid)),
params,
Some(&changed),
)?;
if self.raise_ignore.replace(false) {
continue;
}
let conflicts =
self.find_conflicts(&upd.table, &meta, new_rowid, &values, Some(rowid), params)?;
if !conflicts.is_empty() {
match upd.on_conflict {
OnConflict::Ignore => continue,
OnConflict::Replace => {
for cr in conflicts {
delete_table(self.backend.writer()?, meta.root, cr)?;
}
}
oc @ (OnConflict::Abort | OnConflict::Fail | OnConflict::Rollback) => {
return Err(self.conflict_error(oc, "UNIQUE constraint failed"))
}
}
}
let new_full = values.clone();
let record = self.encode_table_record(&meta, &new_full);
delete_table(self.backend.writer()?, meta.root, rowid)?;
insert_table(self.backend.writer()?, meta.root, new_rowid, &record)?;
self.fire_triggers(
&upd.table,
TrigEvent::Update,
TriggerTiming::After,
&meta.columns,
Some((&old_row, rowid)),
Some((&new_full, new_rowid)),
params,
Some(&changed),
)?;
if !upd.returning.is_empty() {
self.collect_returning(&upd.returning, &meta, &new_full, Some(new_rowid), params)?;
}
affected += 1;
}
if affected > 0 {
self.compact_table(&meta)?;
self.rebuild_indexes(&meta, &indexes)?;
}
Ok(affected)
}
fn exec_create_index(&mut self, ci: &CreateIndex, sql_text: &str) -> Result<()> {
let reprinted;
let sql_text = if ci.schema.is_some() {
reprinted = sql::print::create_index(ci);
reprinted.as_str()
} else {
sql_text
};
if self.schema.index(&ci.name).is_some() {
if ci.if_not_exists {
return Ok(());
}
return Err(Error::Error(format!("index {} already exists", ci.name)));
}
if self.is_virtual_table(&ci.table) {
return Err(Error::Error("virtual tables may not be indexed".into()));
}
let tmeta = self.table_meta(&ci.table, None)?;
if ci.columns.iter().any(|t| expr_is_nondeterministic(&t.expr))
|| ci
.where_clause
.as_ref()
.is_some_and(expr_is_nondeterministic)
{
return Err(Error::Error(
"non-deterministic functions prohibited in index expressions".into(),
));
}
let (cols, key_exprs, colls) = self.index_key_spec(&tmeta, ci)?;
if key_exprs.is_some() && tmeta.without_rowid {
return Err(Error::Unsupported(
"expression indexes on WITHOUT ROWID tables",
));
}
let schema_next = self.next_rowid(crate::schema::SCHEMA_ROOT_PAGE)?;
let no_params = Params::default();
let keep_row = |values: &[Value], rowid: Option<i64>| -> Result<bool> {
match &ci.where_clause {
None => Ok(true),
Some(p) => {
let ctx =
row_ctx(values, &tmeta.columns, rowid, &no_params).with_subqueries(self);
Ok(eval::truth(&eval::eval(p, &ctx)?) == Some(true))
}
}
};
let root = if tmeta.without_rowid {
let rows = self.scan_without_rowid(&tmeta)?;
let keep: Vec<bool> = rows
.iter()
.map(|row| keep_row(row, None))
.collect::<Result<_>>()?;
let pk_cols = tmeta.storage_order[..tmeta.pk_len].to_vec();
let mut key_colls = colls.clone();
key_colls.extend(self.col_collations(&tmeta, &pk_cols));
let w = self.backend.writer()?;
let root = create_index_root(w)?;
for (row, &k) in rows.iter().zip(&keep) {
if k {
insert_index(w, root, &wr_index_key(&cols, &pk_cols, row), &key_colls)?;
}
}
root
} else {
let rows = self.scan_table(&tmeta)?;
let mut keys: Vec<Vec<u8>> = Vec::new();
for (rowid, values) in &rows {
if !keep_row(values, Some(*rowid))? {
continue;
}
keys.push(match &key_exprs {
None => index_key(&cols, values, *rowid),
Some(exprs) => {
let ctx = row_ctx(values, &tmeta.columns, Some(*rowid), &no_params)
.with_subqueries(self);
let mut k: Vec<Value> = exprs
.iter()
.map(|e| eval::eval(e, &ctx))
.collect::<Result<_>>()?;
k.push(Value::Integer(*rowid));
encode_record(&k)
}
});
}
let w = self.backend.writer()?;
let root = create_index_root(w)?;
for key in &keys {
insert_index(w, root, key, &colls)?;
}
root
};
let w = self.backend.writer()?;
let schema_row = encode_record(&[
Value::Text("index".into()),
Value::Text(ci.name.clone()),
Value::Text(ci.table.clone()),
Value::Integer(root as i64),
Value::Text(sql_text.into()),
]);
insert_table(w, crate::schema::SCHEMA_ROOT_PAGE, schema_next, &schema_row)?;
let cookie = w.header().schema_cookie.wrapping_add(1);
w.header_mut().schema_cookie = cookie;
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn exec_create_view(&mut self, cv: &CreateView, sql_text: &str) -> Result<()> {
let stripped;
let sql_text = match cv.schema.as_deref() {
Some(s) => {
stripped = strip_schema_qualifier(sql_text, s)?;
stripped.as_str()
}
None => sql_text,
};
let exists = self.schema.objects().iter().any(|o| o.name == cv.name);
if exists {
if cv.if_not_exists {
return Ok(());
}
return Err(Error::Error(format!("table {} already exists", cv.name)));
}
let next = self.next_rowid(crate::schema::SCHEMA_ROOT_PAGE)?;
let row = encode_record(&[
Value::Text("view".into()),
Value::Text(cv.name.clone()),
Value::Text(cv.name.clone()),
Value::Integer(0), Value::Text(sql_text.into()),
]);
insert_table(
self.backend.writer()?,
crate::schema::SCHEMA_ROOT_PAGE,
next,
&row,
)?;
let cookie = self
.backend
.writer()?
.header()
.schema_cookie
.wrapping_add(1);
self.backend.writer()?.header_mut().schema_cookie = cookie;
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn exec_create_virtual_table(
&mut self,
cvt: &CreateVirtualTable,
sql_text: &str,
) -> Result<()> {
let stripped;
let sql_text = match cvt.schema.as_deref() {
Some(s) => {
stripped = strip_schema_qualifier(sql_text, s)?;
stripped.as_str()
}
None => sql_text,
};
let exists = self.schema.objects().iter().any(|o| o.name == cvt.name);
if exists {
if cvt.if_not_exists {
return Ok(());
}
return Err(Error::Error(format!("table {} already exists", cvt.name)));
}
let module = self
.vtab_registry
.get(&cvt.module)
.ok_or_else(|| Error::Error(format!("no such module: {}", cvt.module)))?;
let arg_refs: Vec<&str> = cvt.args.iter().map(String::as_str).collect();
let schema = module.dyn_connect(&arg_refs)?;
let persistent = module.dyn_persistent();
let cols = schema.columns;
if persistent {
let coldefs = cols
.iter()
.map(|c| sql::print::ident(c))
.collect::<Vec<_>>()
.join(", ");
let backing_sql = format!(
"CREATE TABLE {}({coldefs})",
sql::print::ident(&format!("{}_data", cvt.name))
);
let Statement::CreateTable(ct) = sql::parse_one(&backing_sql)? else {
unreachable!("constructed a CREATE TABLE");
};
self.exec_create_table(&ct, &backing_sql)?;
}
let next = self.next_rowid(crate::schema::SCHEMA_ROOT_PAGE)?;
let row = encode_record(&[
Value::Text("table".into()),
Value::Text(cvt.name.clone()),
Value::Text(cvt.name.clone()),
Value::Integer(0), Value::Text(sql_text.into()),
]);
insert_table(
self.backend.writer()?,
crate::schema::SCHEMA_ROOT_PAGE,
next,
&row,
)?;
let cookie = self
.backend
.writer()?
.header()
.schema_cookie
.wrapping_add(1);
self.backend.writer()?.header_mut().schema_cookie = cookie;
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn is_virtual_table(&self, name: &str) -> bool {
self.schema
.objects()
.iter()
.filter(|o| {
o.obj_type == crate::schema::ObjectType::Table && o.name.eq_ignore_ascii_case(name)
})
.any(|o| {
matches!(
o.sql.as_deref().map(sql::parse_one),
Some(Ok(Statement::CreateVirtualTable(_)))
)
})
}
fn vtab_meta(&self, name: &str) -> Result<(String, Vec<String>, crate::vtab::VTabSchema)> {
use crate::schema::ObjectType;
let obj = self
.schema
.objects()
.iter()
.find(|o| o.obj_type == ObjectType::Table && o.name.eq_ignore_ascii_case(name))
.ok_or_else(|| Error::Error(format!("no such table: {name}")))?;
let Some(Ok(Statement::CreateVirtualTable(cvt))) = obj.sql.as_deref().map(sql::parse_one)
else {
return Err(Error::Error(format!("{name} is not a virtual table")));
};
let module = self
.vtab_registry
.get(&cvt.module)
.ok_or_else(|| Error::Error(format!("no such module: {}", cvt.module)))?;
let arg_refs: Vec<&str> = cvt.args.iter().map(String::as_str).collect();
let schema = module.dyn_connect(&arg_refs)?;
Ok((cvt.module.clone(), cvt.args.clone(), schema))
}
fn with_vtab_store<F>(
&mut self,
module_name: &str,
args: &[String],
table: &str,
f: F,
) -> Result<usize>
where
F: FnOnce(&dyn DynVTabModule, &mut dyn VTabStore, &[&str]) -> Result<usize>,
{
let module = self
.vtab_registry
.unregister(module_name)
.ok_or_else(|| Error::Error(format!("no such module: {module_name}")))?;
let backing = format!("{table}_data");
let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect();
let result = {
let mut store = ExecVTabStore {
conn: self,
backing: &backing,
};
f(&*module, &mut store, &arg_refs)
};
self.vtab_registry.register(module_name, module)?;
result
}
fn exec_vtab_insert(
&mut self,
ins: &Insert,
rows: &[Vec<Expr>],
params: &Params,
) -> Result<usize> {
if !ins.upsert.is_empty() || !ins.returning.is_empty() {
return Err(Error::Unsupported("UPSERT / RETURNING on a virtual table"));
}
let (module_name, args, schema) = self.vtab_meta(&ins.table)?;
let col_names = schema.columns;
let ncols = col_names.len();
let target: Vec<Option<usize>> = if ins.columns.is_empty() {
(0..ncols).map(Some).collect()
} else {
ins.columns
.iter()
.map(
|name| match col_names.iter().position(|c| c.eq_ignore_ascii_case(name)) {
Some(p) => Ok(Some(p)),
None if matches!(
name.to_ascii_lowercase().as_str(),
"rowid" | "_rowid_" | "oid"
) =>
{
Ok(None)
}
None => Err(Error::Error(format!("no such column: {name}"))),
},
)
.collect::<Result<_>>()?
};
let mut changes: Vec<(Option<i64>, Vec<Value>)> = Vec::with_capacity(rows.len());
for row in rows {
if row.len() != target.len() {
return Err(Error::Error(format!(
"{} values for {} columns",
row.len(),
target.len()
)));
}
let mut values = alloc::vec![Value::Null; ncols];
let mut rowid = None;
for (j, expr) in row.iter().enumerate() {
let ctx = EvalCtx::rowless(params).with_subqueries(self);
let v = eval::eval(expr, &ctx)?;
match target[j] {
Some(col) => values[col] = v,
None => rowid = Some(eval::to_i64(&v)),
}
}
changes.push((rowid, values));
}
self.with_vtab_store(
&module_name,
&args,
&ins.table,
|module, store, arg_refs| {
for (rowid, values) in &changes {
module.dyn_update(
arg_refs,
VTabChange::Insert {
rowid: *rowid,
values,
},
store,
)?;
}
Ok(changes.len())
},
)
}
fn exec_vtab_delete(&mut self, del: &Delete, params: &Params) -> Result<usize> {
if !del.returning.is_empty() {
return Err(Error::Unsupported("RETURNING on a virtual table"));
}
let (module_name, args, _) = self.vtab_meta(&del.table)?;
let (columns, rows) = self
.try_virtual_table(&del.table, None, None)?
.ok_or_else(|| Error::Error(format!("{} is not a virtual table", del.table)))?;
let mut victims: Vec<i64> = Vec::new();
for r in &rows {
if let Some(pred) = &del.where_clause {
let ctx = r.ctx(&columns, params).with_subqueries(self);
if eval::truth(&eval::eval(pred, &ctx)?) != Some(true) {
continue;
}
}
victims.push(
r.rowid
.ok_or_else(|| Error::Error("virtual-table row has no rowid".into()))?,
);
}
self.with_vtab_store(
&module_name,
&args,
&del.table,
|module, store, arg_refs| {
for rowid in &victims {
module.dyn_update(arg_refs, VTabChange::Delete { rowid: *rowid }, store)?;
}
Ok(victims.len())
},
)
}
fn exec_vtab_update(&mut self, upd: &Update, params: &Params) -> Result<usize> {
if !upd.returning.is_empty() {
return Err(Error::Unsupported("RETURNING on a virtual table"));
}
if upd.from.is_some() {
return Err(Error::Unsupported("UPDATE … FROM on a virtual table"));
}
let (module_name, args, schema) = self.vtab_meta(&upd.table)?;
let col_names = schema.columns;
let assigns: Vec<(usize, &Expr)> = upd
.assignments
.iter()
.map(|(name, value)| {
col_names
.iter()
.position(|c| c.eq_ignore_ascii_case(name))
.map(|pos| (pos, value))
.ok_or_else(|| Error::Error(format!("no such column: {name}")))
})
.collect::<Result<_>>()?;
let (columns, rows) = self
.try_virtual_table(&upd.table, None, None)?
.ok_or_else(|| Error::Error(format!("{} is not a virtual table", upd.table)))?;
let mut changes: Vec<(i64, Vec<Value>)> = Vec::new();
for r in &rows {
let ctx = r.ctx(&columns, params).with_subqueries(self);
if let Some(pred) = &upd.where_clause {
if eval::truth(&eval::eval(pred, &ctx)?) != Some(true) {
continue;
}
}
let mut values = r.values.clone();
for (pos, expr) in &assigns {
values[*pos] = eval::eval(expr, &ctx)?;
}
let rowid = r
.rowid
.ok_or_else(|| Error::Error("virtual-table row has no rowid".into()))?;
changes.push((rowid, values));
}
self.with_vtab_store(
&module_name,
&args,
&upd.table,
|module, store, arg_refs| {
for (rowid, values) in &changes {
module.dyn_update(
arg_refs,
VTabChange::Update {
rowid: *rowid,
new_rowid: *rowid,
values,
},
store,
)?;
}
Ok(changes.len())
},
)
}
fn try_virtual_table(
&self,
name: &str,
alias: Option<&str>,
pushdown: Option<(&Select, &Params)>,
) -> Result<Option<(Vec<ColumnInfo>, Vec<InputRow>)>> {
use crate::schema::ObjectType;
let obj = match self
.schema
.objects()
.iter()
.find(|o| o.obj_type == ObjectType::Table && o.name.eq_ignore_ascii_case(name))
{
Some(o) => o,
None => return Ok(None),
};
let sql = match obj.sql.as_deref() {
Some(s) => s,
None => return Ok(None),
};
let cvt = match sql::parse_one(sql) {
Ok(Statement::CreateVirtualTable(cvt)) => cvt,
_ => return Ok(None),
};
let module = self
.vtab_registry
.get(&cvt.module)
.ok_or_else(|| Error::Error(format!("no such module: {}", cvt.module)))?;
let arg_refs: Vec<&str> = cvt.args.iter().map(String::as_str).collect();
let schema = module.dyn_connect(&arg_refs)?;
let label = alias.unwrap_or(name).to_string();
let columns: Vec<ColumnInfo> = schema
.columns
.iter()
.map(|n| ColumnInfo {
name: n.clone(),
table: label.clone(),
affinity: eval::Affinity::Blob,
collation: crate::value::Collation::default(),
})
.collect();
if module.dyn_persistent() {
let backing = format!("{name}_data");
let bmeta = self.table_meta(&backing, None)?;
let rows = self
.scan_table(&bmeta)?
.into_iter()
.map(|(rowid, values)| InputRow {
values,
rowid: Some(rowid),
})
.collect();
return Ok(Some((columns, rows)));
}
let (constraints, bound_values) = match pushdown {
Some((sel, params)) => collect_vtab_constraints(sel, &columns, params),
None => (Vec::new(), Vec::new()),
};
let plan = module.dyn_best_index(&constraints)?;
let argv = order_vtab_argv(&plan, &bound_values);
let mut cursor = module.dyn_open(&arg_refs, &plan, &argv)?;
let ncols = columns.len();
let mut rows = Vec::new();
while let Some(row) = cursor.dyn_next()? {
let values = (0..ncols).map(|i| row.dyn_column(i)).collect();
rows.push(InputRow {
values,
rowid: Some(row.dyn_rowid()),
});
}
Ok(Some((columns, rows)))
}
fn push_ctes(&self, sel: &Select, params: &Params) -> Result<()> {
for cte in &sel.ctes {
let binding = if references_name(&cte.select, &cte.name) {
self.eval_recursive_cte(cte, params)?
} else {
self.materialize_plain_cte(cte, params)?
};
self.cte_env.borrow_mut().push(binding);
}
Ok(())
}
fn lookup_cte(
&self,
name: &str,
alias: Option<&str>,
) -> Option<(Vec<ColumnInfo>, Vec<InputRow>)> {
let env = self.cte_env.borrow();
let b = env
.iter()
.rev()
.find(|b| b.name.eq_ignore_ascii_case(name))?;
let label = alias.unwrap_or(&b.name);
let columns = b
.columns
.iter()
.map(|c| ColumnInfo {
name: c.name.clone(),
table: label.to_string(),
affinity: c.affinity,
collation: c.collation,
})
.collect();
Some((columns, b.rows.clone()))
}
fn cte_columns(&self, cte: &Cte, body_cols: &[String]) -> Result<Vec<ColumnInfo>> {
let names = if cte.columns.is_empty() {
body_cols.to_vec()
} else {
if cte.columns.len() != body_cols.len() {
return Err(Error::Error(alloc::format!(
"table {} has {} values for {} columns",
cte.name,
body_cols.len(),
cte.columns.len()
)));
}
cte.columns.clone()
};
Ok(names
.into_iter()
.map(|n| ColumnInfo {
name: n,
table: cte.name.clone(),
affinity: eval::Affinity::Blob,
collation: crate::value::Collation::default(),
})
.collect())
}
fn materialize_plain_cte(&self, cte: &Cte, params: &Params) -> Result<CteBinding> {
let result = self.run_select(&cte.select, params)?;
let columns = self.cte_columns(cte, &result.columns)?;
let rows = result
.rows
.into_iter()
.map(|values| InputRow {
values,
rowid: None,
})
.collect();
Ok(CteBinding {
name: cte.name.clone(),
columns,
rows,
})
}
fn eval_recursive_cte(&self, cte: &Cte, params: &Params) -> Result<CteBinding> {
let mut arms: Vec<(Option<CompoundOp>, Select)> = Vec::new();
let mut base = (*cte.select).clone();
let rec_limit = match &base.limit {
Some(e) => {
let n = must_be_int(eval::eval(
e,
&EvalCtx::rowless(params).with_subqueries(self),
)?)?;
(n >= 0).then_some(n as usize)
}
None => None,
};
let rec_offset = match &base.offset {
Some(e) => must_be_int(eval::eval(
e,
&EvalCtx::rowless(params).with_subqueries(self),
)?)?
.max(0) as usize,
None => 0,
};
let compound = core::mem::take(&mut base.compound);
base.order_by.clear();
base.limit = None;
base.offset = None;
arms.push((None, base));
for (op, mut s) in compound {
s.order_by.clear();
s.limit = None;
s.offset = None;
arms.push((Some(op), s));
}
let mut anchor: Vec<Select> = Vec::new();
let mut recursive: Vec<Select> = Vec::new();
let mut rec_distinct = false;
let mut in_rec = false;
for (op, s) in arms {
if !in_rec && references_name_select(&s, &cte.name) {
in_rec = true;
rec_distinct = matches!(op, Some(CompoundOp::Union));
}
if in_rec {
recursive.push(s);
} else {
anchor.push(s);
}
}
if anchor.is_empty() || recursive.is_empty() {
return Err(Error::Unsupported(
"recursive CTE must have a non-recursive anchor and a recursive term",
));
}
let mut anchor_rows: Vec<Vec<Value>> = Vec::new();
for a in &anchor {
let r = self.run_select(a, params)?;
anchor_rows.extend(r.rows);
}
let body_cols = self.run_select(&anchor[0], params)?.columns;
let columns = self.cte_columns(cte, &body_cols)?;
if rec_distinct {
dedup_rows(&mut anchor_rows);
}
let mut all_rows = anchor_rows.clone();
let mut working = anchor_rows;
let slot = self.cte_env.borrow().len();
self.cte_env.borrow_mut().push(CteBinding {
name: cte.name.clone(),
columns: columns.clone(),
rows: Vec::new(),
});
let mut guard = 0usize;
let result = loop {
guard += 1;
if guard > 1_000_000 {
self.cte_env.borrow_mut().truncate(slot);
return Err(Error::Error("recursive CTE did not terminate".into()));
}
self.cte_env.borrow_mut()[slot].rows = working
.iter()
.cloned()
.map(|values| InputRow {
values,
rowid: None,
})
.collect();
let mut produced: Vec<Vec<Value>> = Vec::new();
for r in &recursive {
match self.run_select(r, params) {
Ok(res) => produced.extend(res.rows),
Err(e) => {
self.cte_env.borrow_mut().truncate(slot);
return Err(e);
}
}
}
let mut fresh: Vec<Vec<Value>> = Vec::new();
for row in produced {
if rec_distinct && all_rows.iter().any(|s| rows_equal(s, &row)) {
continue;
}
fresh.push(row);
}
if fresh.is_empty() {
break Ok(());
}
all_rows.extend(fresh.iter().cloned());
working = fresh;
if let Some(lim) = rec_limit {
if all_rows.len() >= rec_offset.saturating_add(lim) {
break Ok(());
}
}
};
self.cte_env.borrow_mut().truncate(slot);
result?;
if rec_offset > 0 {
all_rows.drain(..rec_offset.min(all_rows.len()));
}
if let Some(lim) = rec_limit {
all_rows.truncate(lim);
}
let rows = all_rows
.into_iter()
.map(|values| InputRow {
values,
rowid: None,
})
.collect();
Ok(CteBinding {
name: cte.name.clone(),
columns,
rows,
})
}
fn try_view(
&self,
name: &str,
alias: Option<&str>,
params: &Params,
) -> Result<Option<(Vec<ColumnInfo>, Vec<InputRow>)>> {
use crate::schema::ObjectType;
if self.temp_has_view(name) {
return self.scan_db_view(DbRef::Temp, name, alias, params);
}
let obj = match self
.schema
.objects()
.iter()
.find(|o| o.obj_type == ObjectType::View && o.name.eq_ignore_ascii_case(name))
{
Some(o) => o.clone(),
None => return Ok(None),
};
let sql = obj
.sql
.as_deref()
.ok_or_else(|| Error::Corrupt("view has no CREATE statement".into()))?;
let Statement::CreateView(cv) = sql::parse_one(sql)? else {
return Err(Error::Corrupt("schema sql is not CREATE VIEW".into()));
};
let result = self.run_select(&cv.select, params)?;
let label = alias.unwrap_or(name).to_string();
let names = if cv.columns.is_empty() {
result.columns.clone()
} else {
cv.columns.clone()
};
let columns: Vec<ColumnInfo> = names
.into_iter()
.map(|n| ColumnInfo {
name: n,
table: label.clone(),
affinity: eval::Affinity::Blob,
collation: crate::value::Collation::default(),
})
.collect();
let rows = result
.rows
.into_iter()
.map(|values| InputRow {
values,
rowid: None,
})
.collect();
Ok(Some((columns, rows)))
}
fn exec_drop(&mut self, d: &Drop) -> Result<()> {
use crate::schema::ObjectType;
if matches!(d.kind, DropKind::Table) && self.is_virtual_table(&d.name) {
let backing = format!("{}_data", d.name);
if self.schema.table(&backing).is_some() {
self.exec_drop(&Drop {
kind: DropKind::Table,
if_exists: false,
name: backing,
schema: d.schema.clone(),
})?;
}
}
let want = match d.kind {
DropKind::Table => ObjectType::Table,
DropKind::Index => ObjectType::Index,
DropKind::View => ObjectType::View,
DropKind::Trigger => ObjectType::Trigger,
};
let target = self
.schema
.objects()
.iter()
.find(|o| o.obj_type == want && o.name == d.name)
.cloned();
let Some(obj) = target else {
if d.if_exists {
return Ok(());
}
if let Some(other) = self.schema.objects().iter().find(|o| o.name == d.name) {
match (d.kind, other.obj_type) {
(DropKind::Table, ObjectType::View) => {
return Err(Error::Error(format!(
"use DROP VIEW to delete view {}",
d.name
)))
}
(DropKind::View, ObjectType::Table) => {
return Err(Error::Error(format!(
"use DROP TABLE to delete table {}",
d.name
)))
}
_ => {}
}
}
let kind = match d.kind {
DropKind::Table => "table",
DropKind::Index => "index",
DropKind::View => "view",
DropKind::Trigger => "trigger",
};
return Err(Error::Error(format!("no such {kind}: {}", d.name)));
};
let mut roots_to_free = Vec::new();
let mut names_to_remove = Vec::new();
roots_to_free.push(obj.rootpage);
names_to_remove.push(obj.name.clone());
if want == ObjectType::Table {
for idx in self.schema.indexes_on(&obj.name) {
roots_to_free.push(idx.rootpage);
names_to_remove.push(idx.name.clone());
}
for o in self.schema.objects() {
if o.obj_type == ObjectType::Trigger && o.tbl_name.eq_ignore_ascii_case(&obj.name) {
roots_to_free.push(o.rootpage); names_to_remove.push(o.name.clone());
}
}
}
let victim_rowids = self.schema_rowids_for(&names_to_remove)?;
let w = self.backend.writer()?;
for root in roots_to_free {
if root != 0 {
free_tree(w, root)?;
}
}
for rid in victim_rowids {
delete_table(w, crate::schema::SCHEMA_ROOT_PAGE, rid)?;
}
let cookie = w.header().schema_cookie.wrapping_add(1);
w.header_mut().schema_cookie = cookie;
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn exec_alter(&mut self, a: &Alter) -> Result<()> {
if self.is_virtual_table(&a.table) {
if let AlterAction::RenameTable(new_name) = &a.action {
return self.rename_virtual_table(&a.table, new_name);
}
return Err(Error::Error("virtual tables may not be altered".into()));
}
let obj = self
.schema
.table(&a.table)
.cloned()
.ok_or_else(|| Error::Error(format!("no such table: {}", a.table)))?;
let sql = obj
.sql
.as_deref()
.ok_or_else(|| Error::Corrupt("table has no CREATE statement".into()))?;
let Statement::CreateTable(mut ct) = sql::parse_one(sql)? else {
return Err(Error::Corrupt("schema sql is not CREATE TABLE".into()));
};
if let AlterAction::DropColumn(name) = &a.action {
return self.exec_drop_column(a, ct, name);
}
match &a.action {
AlterAction::DropColumn(_) => unreachable!("handled above"),
AlterAction::AddColumn(cd) => {
if ct
.columns
.iter()
.any(|c| c.name.eq_ignore_ascii_case(&cd.name))
{
return Err(Error::Error(format!("duplicate column name: {}", cd.name)));
}
for k in &cd.constraints {
match k {
ColumnConstraint::Unique => {
return Err(Error::Error("Cannot add a UNIQUE column".into()));
}
ColumnConstraint::PrimaryKey { .. } => {
return Err(Error::Error("Cannot add a PRIMARY KEY column".into()));
}
_ => {}
}
}
let not_null = cd
.constraints
.iter()
.any(|k| matches!(k, ColumnConstraint::NotNull));
if not_null {
let default = cd.constraints.iter().find_map(|k| match k {
ColumnConstraint::Default(e) => Some(e),
_ => None,
});
let no_params = Params::default();
let default_is_null = match default {
None => true,
Some(e) => {
let ctx = EvalCtx::rowless(&no_params).with_subqueries(self);
matches!(eval::eval(e, &ctx), Ok(Value::Null) | Err(_))
}
};
if default_is_null && !self.table_is_empty(&a.table)? {
return Err(Error::Error(
"Cannot add a NOT NULL column with default value NULL".into(),
));
}
}
ct.columns.push(cd.clone());
let new_sql = sql::print::create_table(&ct);
let table = a.table.clone();
self.rewrite_schema_rows(|cols| {
if is_text(&cols[0], "table") && is_text(&cols[1], &table) {
cols[4] = Value::Text(new_sql.clone());
true
} else {
false
}
})?;
}
AlterAction::RenameTable(new_name) => {
if self
.schema
.objects()
.iter()
.any(|o| o.name.eq_ignore_ascii_case(new_name))
{
return Err(Error::Error(format!(
"there is already another table or index with this name: {new_name}"
)));
}
ct.name = new_name.clone();
let new_table_sql = sql::print::create_table(&ct);
let old = a.table.clone();
let new_name = new_name.clone();
self.rewrite_schema_rows(|cols| {
if is_text(&cols[0], "table") && is_text(&cols[1], &old) {
cols[1] = Value::Text(new_name.clone());
cols[2] = Value::Text(new_name.clone());
cols[4] = Value::Text(new_table_sql.clone());
true
} else if is_text(&cols[2], &old) {
cols[2] = Value::Text(new_name.clone());
if is_text(&cols[0], "index") {
if let Some(Value::Text(isql)) = cols.get(4).cloned() {
if let Ok(Statement::CreateIndex(mut ci)) = sql::parse_one(&isql) {
ci.table = new_name.clone();
cols[4] = Value::Text(sql::print::create_index(&ci));
}
}
}
true
} else if is_text(&cols[0], "view") {
match cols.get(4).cloned() {
Some(Value::Text(vsql)) if view_uses_table(&vsql, &old) => {
cols[4] = Value::Text(rewrite_ident_tokens(&vsql, &old, &new_name));
true
}
_ => false,
}
} else {
false
}
})?;
}
AlterAction::RenameColumn { old, new } => {
let pos = ct
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(old))
.ok_or_else(|| Error::Error(format!("no such column: {old}")))?;
if ct
.columns
.iter()
.enumerate()
.any(|(i, c)| i != pos && c.name.eq_ignore_ascii_case(new))
{
return Err(Error::Error(format!("duplicate column name: {new}")));
}
ct.columns[pos].name = new.clone();
let rename = |e: &mut Expr| rename_column_ref(e, &a.table, old, new);
for col in &mut ct.columns {
for k in &mut col.constraints {
match k {
ColumnConstraint::Check(e) | ColumnConstraint::Default(e) => rename(e),
ColumnConstraint::Generated { expr, .. } => rename(expr),
_ => {}
}
}
}
for tc in &mut ct.constraints {
match tc {
TableConstraint::PrimaryKey(n) | TableConstraint::Unique(n) => {
for nm in n {
if nm.eq_ignore_ascii_case(old) {
*nm = new.clone();
}
}
}
TableConstraint::Check(e) => rename(e),
TableConstraint::ForeignKey(fk) => {
for nm in &mut fk.columns {
if nm.eq_ignore_ascii_case(old) {
*nm = new.clone();
}
}
}
}
}
let new_table_sql = sql::print::create_table(&ct);
let table = a.table.clone();
let old = old.clone();
let new = new.clone();
self.rewrite_schema_rows(|cols| {
if is_text(&cols[0], "table") && is_text(&cols[1], &table) {
cols[4] = Value::Text(new_table_sql.clone());
true
} else if is_text(&cols[0], "index") && is_text(&cols[2], &table) {
if let Some(Value::Text(isql)) = cols.get(4).cloned() {
if let Ok(Statement::CreateIndex(mut ci)) = sql::parse_one(&isql) {
let mut changed = false;
for term in &mut ci.columns {
if let Expr::Column { column, .. } = &mut term.expr {
if column.eq_ignore_ascii_case(&old) {
*column = new.clone();
changed = true;
}
}
}
if changed {
cols[4] = Value::Text(sql::print::create_index(&ci));
return true;
}
}
}
false
} else {
false
}
})?;
}
}
let cookie = self
.backend
.writer()?
.header()
.schema_cookie
.wrapping_add(1);
self.backend.writer()?.header_mut().schema_cookie = cookie;
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn rename_virtual_table(&mut self, old: &str, new: &str) -> Result<()> {
if self
.schema
.objects()
.iter()
.any(|o| o.name.eq_ignore_ascii_case(new))
{
return Err(Error::Error(format!(
"there is already another table or index with this name: {new}"
)));
}
let backing_old = format!("{old}_data");
if self.schema.table(&backing_old).is_some() {
self.exec_alter(&Alter {
schema: None,
table: backing_old,
action: AlterAction::RenameTable(format!("{new}_data")),
})?;
}
let old_s = old.to_string();
let new_s = new.to_string();
self.rewrite_schema_rows(|cols| {
if is_text(&cols[0], "table") && is_text(&cols[1], &old_s) {
cols[1] = Value::Text(new_s.clone());
cols[2] = Value::Text(new_s.clone());
if let Some(Value::Text(s)) = cols.get(4).cloned() {
cols[4] = Value::Text(rewrite_ident_tokens(&s, &old_s, &new_s));
}
true
} else {
false
}
})?;
let cookie = self
.backend
.writer()?
.header()
.schema_cookie
.wrapping_add(1);
self.backend.writer()?.header_mut().schema_cookie = cookie;
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn exec_drop_column(&mut self, a: &Alter, mut ct: CreateTable, name: &str) -> Result<()> {
let pos = ct
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(name))
.ok_or_else(|| Error::Error(format!("no such column: \"{name}\"")))?;
if ct.columns.len() <= 1 {
return Err(Error::Error(format!(
"cannot drop column \"{name}\": no other columns exist"
)));
}
let cannot = |why: &str| {
Err(Error::Error(format!(
"cannot drop column \"{name}\": {why}"
)))
};
for c in &ct.columns[pos].constraints {
match c {
ColumnConstraint::PrimaryKey { .. } => return cannot("PRIMARY KEY"),
ColumnConstraint::Unique => return cannot("UNIQUE"),
ColumnConstraint::Check(_) => return cannot("CHECK"),
ColumnConstraint::References(_) => return cannot("FOREIGN KEY"),
ColumnConstraint::Generated { .. } => return cannot("generated"),
_ => {}
}
}
for tc in &ct.constraints {
match tc {
TableConstraint::PrimaryKey(n) | TableConstraint::Unique(n) => {
if n.iter().any(|x| x.eq_ignore_ascii_case(name)) {
return cannot("PRIMARY KEY or UNIQUE");
}
}
TableConstraint::Check(_) => return cannot("a table CHECK constraint exists"),
TableConstraint::ForeignKey(_) => {
return cannot("a table FOREIGN KEY constraint exists")
}
}
}
if ct.columns.iter().enumerate().any(|(i, c)| {
i != pos
&& c.constraints
.iter()
.any(|x| matches!(x, ColumnConstraint::Generated { .. }))
}) {
return cannot("a generated column exists");
}
let meta = self.table_meta(&a.table, None)?;
if meta.ipk == Some(pos) {
return cannot("PRIMARY KEY");
}
let indexes = self.indexes_of(&a.table)?;
if indexes
.iter()
.any(|i| i.cols.contains(&pos) || i.key_exprs.is_some() || i.partial.is_some())
{
return cannot("it is indexed");
}
let new_rows: Vec<(i64, Vec<Value>)> = self
.scan_table(&meta)?
.into_iter()
.map(|(rid, mut vals)| {
vals.remove(pos);
(rid, vals)
})
.collect();
ct.columns.remove(pos);
let new_sql = sql::print::create_table(&ct);
let table = a.table.clone();
self.rewrite_schema_rows(|cols| {
if is_text(&cols[0], "table") && is_text(&cols[1], &table) {
cols[4] = Value::Text(new_sql.clone());
true
} else {
false
}
})?;
self.schema = Schema::read(self.backend.source())?;
let new_meta = self.table_meta(&a.table, None)?;
clear_table(self.backend.writer()?, new_meta.root)?;
for (rid, vals) in &new_rows {
let mut stored = vals.clone();
if let Some(ipk) = new_meta.ipk {
stored[ipk] = Value::Null;
}
let record = encode_record(&stored);
insert_table(self.backend.writer()?, new_meta.root, *rid, &record)?;
}
let new_indexes = self.indexes_of(&a.table)?;
self.rebuild_indexes(&new_meta, &new_indexes)?;
let cookie = self
.backend
.writer()?
.header()
.schema_cookie
.wrapping_add(1);
self.backend.writer()?.header_mut().schema_cookie = cookie;
self.schema = Schema::read(self.backend.source())?;
Ok(())
}
fn rewrite_schema_rows(&mut self, mut f: impl FnMut(&mut Vec<Value>) -> bool) -> Result<()> {
let encoding = self.backend.source().header().text_encoding;
let mut changes: Vec<(i64, Vec<u8>)> = Vec::new();
{
let mut cur = TableCursor::new(self.backend.source(), crate::schema::SCHEMA_ROOT_PAGE);
let mut ok = cur.first()?;
while ok {
let mut cols = decode_record(&cur.payload()?, encoding)?;
cols.resize(5, Value::Null);
if f(&mut cols) {
changes.push((cur.rowid()?, encode_record(&cols)));
}
ok = cur.next()?;
}
}
let w = self.backend.writer()?;
for (rid, rec) in changes {
delete_table(w, crate::schema::SCHEMA_ROOT_PAGE, rid)?;
insert_table(w, crate::schema::SCHEMA_ROOT_PAGE, rid, &rec)?;
}
Ok(())
}
fn schema_rowids_for(&self, names: &[String]) -> Result<Vec<i64>> {
let encoding = self.backend.source().header().text_encoding;
let mut out = Vec::new();
let mut cur = TableCursor::new(self.backend.source(), crate::schema::SCHEMA_ROOT_PAGE);
let mut ok = cur.first()?;
while ok {
let cols = decode_record(&cur.payload()?, encoding)?;
if let Some(Value::Text(name)) = cols.get(1) {
if names.iter().any(|n| n == name) {
out.push(cur.rowid()?);
}
}
ok = cur.next()?;
}
Ok(out)
}
fn try_without_rowid_pk_seek(
&self,
meta: &TableMeta,
sel: &Select,
params: &Params,
) -> Result<Option<Vec<InputRow>>> {
let Some(where_expr) = &sel.where_clause else {
return Ok(None);
};
let hint = sel.from.as_ref().and_then(|f| f.first.index_hint.as_ref());
if matches!(hint, Some(IndexHint::NotIndexed)) {
return Ok(None);
}
let pk = &meta.storage_order[..meta.pk_len];
if pk.is_empty() {
return Ok(None);
}
let mut eqs: Vec<(usize, Value)> = Vec::new();
collect_eq_constraints(where_expr, &meta.columns, params, &mut eqs);
let storage_colls = wr_storage_collations(meta);
let mut key = Vec::new();
let mut colls = Vec::new();
for (i, &c) in pk.iter().enumerate() {
let Some((_, v)) = eqs.iter().find(|(col, _)| *col == c) else {
break;
};
if matches!(v, Value::Null) {
break; }
key.push(meta.columns[c].affinity.coerce(v.clone()));
colls.push(storage_colls[i]);
}
if key.is_empty() {
return Ok(None);
}
let records =
crate::btree::index_seek_records(self.backend.source(), meta.root, &key, &colls)?;
let mut out = Vec::with_capacity(records.len());
for storage in records {
let mut row = unpermute_row(meta, storage);
self.compute_generated(meta, &mut row, params)?;
out.push(InputRow {
values: row,
rowid: None,
});
}
Ok(Some(out))
}
fn try_without_rowid_pk_range(
&self,
meta: &TableMeta,
sel: &Select,
params: &Params,
) -> Result<Option<Vec<InputRow>>> {
let Some(where_expr) = &sel.where_clause else {
return Ok(None);
};
let hint = sel.from.as_ref().and_then(|f| f.first.index_hint.as_ref());
if matches!(hint, Some(IndexHint::NotIndexed)) {
return Ok(None);
}
let pk = &meta.storage_order[..meta.pk_len];
let Some(&lead) = pk.first() else {
return Ok(None);
};
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
let Some(b) = ranges.get(&lead) else {
return Ok(None);
};
let aff = meta.columns[lead].affinity;
let coll = wr_storage_collations(meta)[0];
let lower = b.lower.as_ref().map(|(v, i)| (aff.coerce(v.clone()), *i));
let upper = b.upper.as_ref().map(|(v, i)| (aff.coerce(v.clone()), *i));
let colls = [coll];
let lower_arg = lower
.as_ref()
.map(|(v, inc)| (core::slice::from_ref(v), *inc));
let upper_arg = upper
.as_ref()
.map(|(v, inc)| (core::slice::from_ref(v), *inc));
let records = crate::btree::index_range_records(
self.backend.source(),
meta.root,
lower_arg,
upper_arg,
&colls,
)?;
let mut out = Vec::with_capacity(records.len());
for storage in records {
let mut row = unpermute_row(meta, storage);
self.compute_generated(meta, &mut row, params)?;
out.push(InputRow {
values: row,
rowid: None,
});
}
Ok(Some(out))
}
fn try_without_rowid_index_seek(
&self,
meta: &TableMeta,
table_name: &str,
sel: &Select,
params: &Params,
) -> Result<Option<Vec<InputRow>>> {
let Some(where_expr) = &sel.where_clause else {
return Ok(None);
};
let hint = sel.from.as_ref().and_then(|f| f.first.index_hint.as_ref());
if matches!(hint, Some(IndexHint::NotIndexed)) {
return Ok(None);
}
let mut eqs: Vec<(usize, Value)> = Vec::new();
collect_eq_constraints(where_expr, &meta.columns, params, &mut eqs);
eqs.retain(|(_, v)| !matches!(v, Value::Null));
if eqs.is_empty() {
return Ok(None);
}
let pk: Vec<usize> = meta.storage_order[..meta.pk_len].to_vec();
let indexes = self.indexes_of(table_name)?;
if let Some(IndexHint::IndexedBy(n)) = hint {
if !indexes.iter().any(|i| i.name.eq_ignore_ascii_case(n)) {
return Err(Error::Error(alloc::format!("no such index: {n}")));
}
}
let src = self.backend.source();
for idx in &indexes {
if let Some(IndexHint::IndexedBy(n)) = hint {
if !idx.name.eq_ignore_ascii_case(n) {
continue;
}
}
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
let mut key = Vec::new();
let mut colls = Vec::new();
for (i, &c) in idx.cols.iter().enumerate() {
let Some((_, v)) = eqs.iter().find(|(col, _)| *col == c) else {
break;
};
key.push(meta.columns[c].affinity.coerce(v.clone()));
colls.push(idx.collations.get(i).copied().unwrap_or_default());
}
if key.is_empty() {
continue;
}
let records = crate::btree::index_seek_records(src, idx.root, &key, &colls)?;
let covering = self.wr_index_covers(idx, &pk, meta, sel, where_expr);
return Ok(Some(
self.wr_index_rows(meta, idx, &pk, records, covering, params)?,
));
}
Ok(None)
}
fn wr_index_covers(
&self,
idx: &IndexMeta,
pk: &[usize],
meta: &TableMeta,
sel: &Select,
where_expr: &Expr,
) -> bool {
let mut avail = idx.cols.clone();
if !idx.name.starts_with("sqlite_autoindex_") {
for &p in pk {
if !avail.contains(&p) {
avail.push(p);
}
}
}
self.seek_index_covers(sel, meta, &avail, where_expr)
}
fn wr_index_rows(
&self,
meta: &TableMeta,
idx: &IndexMeta,
pk: &[usize],
records: Vec<Vec<Value>>,
covering: bool,
params: &Params,
) -> Result<Vec<InputRow>> {
let mut out = Vec::with_capacity(records.len());
if covering {
for rec in &records {
let mut values = alloc::vec![Value::Null; meta.columns.len()];
for (i, &mc) in idx.cols.iter().enumerate() {
values[mc] = rec[i].clone();
}
for (j, &pc) in pk.iter().enumerate() {
values[pc] = rec[idx.cols.len() + j].clone();
}
out.push(InputRow {
values,
rowid: None,
});
}
} else {
let src = self.backend.source();
let pk_colls: Vec<crate::value::Collation> =
wr_storage_collations(meta)[..pk.len()].to_vec();
for rec in &records {
let pk_key: Vec<Value> = (0..pk.len())
.map(|j| rec[idx.cols.len() + j].clone())
.collect();
for storage in crate::btree::index_seek_records(src, meta.root, &pk_key, &pk_colls)?
{
let mut row = unpermute_row(meta, storage);
self.compute_generated(meta, &mut row, params)?;
out.push(InputRow {
values: row,
rowid: None,
});
}
}
}
Ok(out)
}
fn try_without_rowid_index_range(
&self,
meta: &TableMeta,
table_name: &str,
sel: &Select,
params: &Params,
) -> Result<Option<Vec<InputRow>>> {
let Some(where_expr) = &sel.where_clause else {
return Ok(None);
};
let hint = sel.from.as_ref().and_then(|f| f.first.index_hint.as_ref());
if matches!(hint, Some(IndexHint::NotIndexed)) {
return Ok(None);
}
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
if ranges.is_empty() {
return Ok(None);
}
let pk: Vec<usize> = meta.storage_order[..meta.pk_len].to_vec();
let indexes = self.indexes_of(table_name)?;
if let Some(IndexHint::IndexedBy(n)) = hint {
if !indexes.iter().any(|i| i.name.eq_ignore_ascii_case(n)) {
return Err(Error::Error(alloc::format!("no such index: {n}")));
}
}
for idx in &indexes {
if let Some(IndexHint::IndexedBy(n)) = hint {
if !idx.name.eq_ignore_ascii_case(n) {
continue;
}
}
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
let Some(&lead) = idx.cols.first() else {
continue;
};
let Some(b) = ranges.get(&lead) else {
continue;
};
let aff = meta.columns[lead].affinity;
let coll = idx.collations.first().copied().unwrap_or_default();
let lower = b.lower.as_ref().map(|(v, i)| (aff.coerce(v.clone()), *i));
let upper = b.upper.as_ref().map(|(v, i)| (aff.coerce(v.clone()), *i));
let colls = [coll];
let lower_arg = lower
.as_ref()
.map(|(v, inc)| (core::slice::from_ref(v), *inc));
let upper_arg = upper
.as_ref()
.map(|(v, inc)| (core::slice::from_ref(v), *inc));
let records = crate::btree::index_range_records(
self.backend.source(),
idx.root,
lower_arg,
upper_arg,
&colls,
)?;
let covering = self.wr_index_covers(idx, &pk, meta, sel, where_expr);
return Ok(Some(
self.wr_index_rows(meta, idx, &pk, records, covering, params)?,
));
}
Ok(None)
}
fn try_index_lookup(
&self,
meta: &TableMeta,
table_name: &str,
sel: &Select,
params: &Params,
) -> Result<Option<Vec<InputRow>>> {
let Some(where_expr) = &sel.where_clause else {
return Ok(None);
};
let hint = sel.from.as_ref().and_then(|f| f.first.index_hint.as_ref());
if matches!(hint, Some(IndexHint::NotIndexed)) {
return Ok(None);
}
let mut eqs: Vec<(usize, Value)> = Vec::new();
collect_eq_constraints(where_expr, &meta.columns, params, &mut eqs);
if eqs.is_empty() || eqs.iter().any(|(_, v)| matches!(v, Value::Null)) {
return self.partial_expr_lookup(meta, table_name, sel, where_expr, params);
}
if !matches!(hint, Some(IndexHint::IndexedBy(_))) {
if let Some(ipk) = meta.ipk {
if let Some((_, v)) = eqs.iter().find(|(c, _)| *c == ipk) {
let rid = eval::to_i64(v);
let encoding = self.backend.source().header().text_encoding;
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut out = Vec::new();
if cur.seek(rid)? {
let values = self.decode_full_row(meta, rid, &cur.payload()?, encoding)?;
out.push(InputRow {
values,
rowid: Some(rid),
});
}
return Ok(Some(out));
}
}
}
let indexes = self.indexes_of(table_name)?;
if let Some(IndexHint::IndexedBy(n)) = hint {
if !indexes.iter().any(|i| i.name.eq_ignore_ascii_case(n)) {
return Err(Error::Error(alloc::format!("no such index: {n}")));
}
}
let stats = self.stat1_map();
#[allow(clippy::type_complexity)]
let mut best: Option<(
u32,
Vec<Value>,
Vec<crate::value::Collation>,
Vec<usize>,
u64,
)> = None;
for idx in &indexes {
if let Some(IndexHint::IndexedBy(n)) = hint {
if !idx.name.eq_ignore_ascii_case(n) {
continue;
}
}
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
let mut key = Vec::new();
for &c in &idx.cols {
match eqs.iter().find(|(col, _)| *col == c) {
Some((_, v)) => key.push(meta.columns[c].affinity.coerce(v.clone())),
None => break,
}
}
if key.is_empty() {
continue;
}
let est = stats
.get(&idx.name)
.and_then(|s| s.get(key.len()).copied())
.unwrap_or(u64::MAX - key.len() as u64);
let better = match &best {
None => true,
Some((_, bk, _, _, be)) => est < *be || (est == *be && key.len() > bk.len()),
};
if better {
best = Some((idx.root, key, idx.collations.clone(), idx.cols.clone(), est));
}
}
let (root, key, full_colls, idx_cols) = match best {
Some((root, key, colls, idx_cols, _)) => (root, key, colls, idx_cols),
None => {
return self.partial_expr_lookup(meta, table_name, sel, where_expr, params);
}
};
if key.is_empty() {
return Ok(None);
}
if self.seek_index_covers(sel, meta, &idx_cols, where_expr) {
return Ok(Some(self.covering_seek_rows(meta, root, &idx_cols)?));
}
let next_pos = key.len();
if let Some(&next_col) = idx_cols.get(next_pos) {
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
if let Some(b) = ranges.get(&next_col) {
let aff = meta.columns[next_col].affinity;
let colls = full_colls[..=next_pos].to_vec();
let mut lo_key = key.clone();
let lo_inc = match b.lower.as_ref() {
Some((v, inc)) => {
lo_key.push(aff.coerce(v.clone()));
*inc
}
None => true,
};
let mut hi_key = key.clone();
let hi_inc = match b.upper.as_ref() {
Some((v, inc)) => {
hi_key.push(aff.coerce(v.clone()));
*inc
}
None => true,
};
let rowids = crate::btree::index_range_rowids(
self.backend.source(),
root,
Some((lo_key.as_slice(), lo_inc)),
Some((hi_key.as_slice(), hi_inc)),
&colls,
)?;
let encoding = self.backend.source().header().text_encoding;
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut out = Vec::new();
for rid in rowids {
if cur.seek(rid)? {
let values = self.decode_full_row(meta, rid, &cur.payload()?, encoding)?;
out.push(InputRow {
values,
rowid: Some(rid),
});
}
}
return Ok(Some(out));
}
}
self.index_seek_fetch(meta, root, &key, &full_colls[..key.len()])
}
fn index_seek_fetch(
&self,
meta: &TableMeta,
root: u32,
key: &[Value],
colls: &[crate::value::Collation],
) -> Result<Option<Vec<InputRow>>> {
let rowids = crate::btree::index_seek_rowids(self.backend.source(), root, key, colls)?;
let encoding = self.backend.source().header().text_encoding;
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut out = Vec::new();
for rid in rowids {
if cur.seek(rid)? {
let values = self.decode_full_row(meta, rid, &cur.payload()?, encoding)?;
out.push(InputRow {
values,
rowid: Some(rid),
});
}
}
Ok(Some(out))
}
fn partial_expr_lookup(
&self,
meta: &TableMeta,
table_name: &str,
sel: &Select,
where_expr: &Expr,
params: &Params,
) -> Result<Option<Vec<InputRow>>> {
let hint = sel.from.as_ref().and_then(|f| f.first.index_hint.as_ref());
for idx in self.indexes_of(table_name)? {
if let Some(IndexHint::IndexedBy(n)) = hint {
if !idx.name.eq_ignore_ascii_case(n) {
continue;
}
}
if let Some((key, colls)) = self.partial_expr_seek(&idx, where_expr, meta, params)? {
return self.index_seek_fetch(meta, idx.root, &key, &colls);
}
}
Ok(None)
}
fn partial_expr_seek(
&self,
idx: &IndexMeta,
where_expr: &Expr,
meta: &TableMeta,
params: &Params,
) -> Result<Option<(Vec<Value>, Vec<crate::value::Collation>)>> {
if idx.partial.is_none() && idx.key_exprs.is_none() {
return Ok(None);
}
let mut conjuncts = Vec::new();
and_conjuncts(where_expr, &mut conjuncts);
if let Some(pred) = &idx.partial {
if !conjuncts.iter().any(|c| expr_eq_modulo_parens(c, pred)) {
return Ok(None);
}
}
match &idx.key_exprs {
None => {
let mut key = Vec::new();
let mut colls = Vec::new();
let mut eqs: Vec<(usize, Value)> = Vec::new();
collect_eq_constraints(where_expr, &meta.columns, params, &mut eqs);
for (pos, &c) in idx.cols.iter().enumerate() {
match eqs
.iter()
.find(|(col, v)| *col == c && !matches!(v, Value::Null))
{
Some((_, v)) => {
key.push(meta.columns[c].affinity.coerce(v.clone()));
colls.push(idx.collations[pos]);
}
None => break,
}
}
if key.is_empty() {
return Ok(None);
}
Ok(Some((key, colls)))
}
Some(exprs) => {
let [key_expr] = exprs.as_slice() else {
return Ok(None);
};
for c in &conjuncts {
let Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} = unparen(c)
else {
continue;
};
let val = if expr_eq_modulo_parens(left, key_expr) {
const_value(right, params)
} else if expr_eq_modulo_parens(right, key_expr) {
const_value(left, params)
} else {
None
};
if let Some(v) = val {
if matches!(v, Value::Null) {
continue; }
let coll = idx.collations.first().copied().unwrap_or_default();
return Ok(Some((alloc::vec![v], alloc::vec![coll])));
}
}
Ok(None)
}
}
}
fn try_index_range(
&self,
meta: &TableMeta,
table_name: &str,
sel: &Select,
params: &Params,
) -> Result<Option<Vec<InputRow>>> {
let Some(where_expr) = &sel.where_clause else {
return Ok(None);
};
let hint = sel.from.as_ref().and_then(|f| f.first.index_hint.as_ref());
if matches!(hint, Some(IndexHint::NotIndexed)) {
return Ok(None);
}
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
if ranges.is_empty() {
return Ok(None);
}
if !matches!(hint, Some(IndexHint::IndexedBy(_))) {
if let Some(ipk) = meta.ipk {
if let Some(b) = ranges.get(&ipk) {
let int_bound = |o: &Option<(Value, bool)>| match o {
Some((Value::Integer(i), _)) => Some(*i),
None => None,
_ => Some(i64::MAX), };
let lo = int_bound(&b.lower);
let hi = int_bound(&b.upper);
let lo_ok =
b.lower.is_none() || matches!(b.lower, Some((Value::Integer(_), _)));
let hi_ok =
b.upper.is_none() || matches!(b.upper, Some((Value::Integer(_), _)));
if lo_ok && hi_ok {
let start = lo.unwrap_or(i64::MIN);
let stop = hi.unwrap_or(i64::MAX);
let encoding = self.backend.source().header().text_encoding;
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut out = Vec::new();
let mut ok = if start == i64::MIN {
cur.first()?
} else {
cur.seek(start)?;
cur.is_valid()
};
while ok {
let rid = cur.rowid()?;
if rid > stop {
break;
}
let values =
self.decode_full_row(meta, rid, &cur.payload()?, encoding)?;
out.push(InputRow {
values,
rowid: Some(rid),
});
ok = cur.next()?;
}
return Ok(Some(out));
}
}
}
}
let indexes = self.indexes_of(table_name)?;
if let Some(IndexHint::IndexedBy(n)) = hint {
if !indexes.iter().any(|i| i.name.eq_ignore_ascii_case(n)) {
return Err(Error::Error(alloc::format!("no such index: {n}")));
}
}
let mut chosen: Option<(u32, RangeBound, crate::value::Collation, Vec<usize>)> = None;
for idx in &indexes {
if let Some(IndexHint::IndexedBy(n)) = hint {
if !idx.name.eq_ignore_ascii_case(n) {
continue;
}
}
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
let Some(&lead) = idx.cols.first() else {
continue;
};
if let Some(b) = ranges.get(&lead) {
let coll = idx.collations.first().copied().unwrap_or_default();
let aff = meta.columns[lead].affinity;
let b = RangeBound {
lower: b.lower.as_ref().map(|(v, i)| (aff.coerce(v.clone()), *i)),
upper: b.upper.as_ref().map(|(v, i)| (aff.coerce(v.clone()), *i)),
};
chosen = Some((idx.root, b, coll, idx.cols.clone()));
break;
}
}
match chosen {
Some((root, bound, coll, idx_cols)) => {
if self.seek_index_covers(sel, meta, &idx_cols, where_expr) {
return Ok(Some(self.covering_seek_rows(meta, root, &idx_cols)?));
}
Ok(Some(self.range_seek_fetch(meta, root, &bound, coll)?))
}
None => {
for idx in &indexes {
if let Some(IndexHint::IndexedBy(n)) = hint {
if !idx.name.eq_ignore_ascii_case(n) {
continue;
}
}
if let Some((bound, coll)) =
self.partial_expr_range(idx, where_expr, meta, params)
{
return Ok(Some(self.range_seek_fetch(meta, idx.root, &bound, coll)?));
}
}
Ok(None)
}
}
}
fn range_seek_fetch(
&self,
meta: &TableMeta,
root: u32,
bound: &RangeBound,
coll: crate::value::Collation,
) -> Result<Vec<InputRow>> {
let colls = [coll];
let lower_key = bound.lower.as_ref().map(|(v, _)| core::slice::from_ref(v));
let upper_key = bound.upper.as_ref().map(|(v, _)| core::slice::from_ref(v));
let lower = lower_key.map(|k| (k, bound.lower.as_ref().unwrap().1));
let upper = upper_key.map(|k| (k, bound.upper.as_ref().unwrap().1));
let rowids =
crate::btree::index_range_rowids(self.backend.source(), root, lower, upper, &colls)?;
let encoding = self.backend.source().header().text_encoding;
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut out = Vec::new();
for rid in rowids {
if cur.seek(rid)? {
let values = self.decode_full_row(meta, rid, &cur.payload()?, encoding)?;
out.push(InputRow {
values,
rowid: Some(rid),
});
}
}
Ok(out)
}
fn in_seek_fetch(
&self,
meta: &TableMeta,
root: u32,
colls: &[crate::value::Collation],
keys: &[Vec<Value>],
) -> Result<Vec<InputRow>> {
let src = self.backend.source();
let encoding = src.header().text_encoding;
let mut rowids: Vec<i64> = Vec::new();
for key in keys {
for rid in crate::btree::index_seek_rowids(src, root, key, colls)? {
if !rowids.contains(&rid) {
rowids.push(rid);
}
}
}
let mut cur = TableCursor::new(src, meta.root);
let mut out = Vec::new();
for rid in rowids {
if cur.seek(rid)? {
let values = self.decode_full_row(meta, rid, &cur.payload()?, encoding)?;
out.push(InputRow {
values,
rowid: Some(rid),
});
}
}
Ok(out)
}
fn partial_expr_range(
&self,
idx: &IndexMeta,
where_expr: &Expr,
meta: &TableMeta,
params: &Params,
) -> Option<(RangeBound, crate::value::Collation)> {
if idx.partial.is_none() && idx.key_exprs.is_none() {
return None;
}
let mut conjuncts = Vec::new();
and_conjuncts(where_expr, &mut conjuncts);
if let Some(pred) = &idx.partial {
if !conjuncts.iter().any(|c| expr_eq_modulo_parens(c, pred)) {
return None;
}
}
let coll = idx.collations.first().copied().unwrap_or_default();
match &idx.key_exprs {
None => {
let lead = *idx.cols.first()?;
let mut ranges = alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
let b = ranges.get(&lead)?;
let aff = meta.columns[lead].affinity;
Some((
RangeBound {
lower: b.lower.as_ref().map(|(v, i)| (aff.coerce(v.clone()), *i)),
upper: b.upper.as_ref().map(|(v, i)| (aff.coerce(v.clone()), *i)),
},
coll,
))
}
Some(exprs) => {
let [key_expr] = exprs.as_slice() else {
return None;
};
let mut bound = RangeBound {
lower: None,
upper: None,
};
for c in &conjuncts {
let Expr::Binary { op, left, right } = unparen(c) else {
continue;
};
let (val, op) = if expr_eq_modulo_parens(left, key_expr) {
(const_value(right, params), *op)
} else if expr_eq_modulo_parens(right, key_expr) {
(const_value(left, params), mirror_comparison(*op))
} else {
continue;
};
let Some(v) = val else { continue };
if matches!(v, Value::Null) {
continue;
}
match op {
BinaryOp::Gt => bound.lower = Some((v, false)),
BinaryOp::GtEq => bound.lower = Some((v, true)),
BinaryOp::Lt => bound.upper = Some((v, false)),
BinaryOp::LtEq => bound.upper = Some((v, true)),
_ => {}
}
}
if bound.lower.is_none() && bound.upper.is_none() {
return None;
}
Some((bound, coll))
}
}
}
fn try_index_in(
&self,
meta: &TableMeta,
table_name: &str,
sel: &Select,
params: &Params,
) -> Result<Option<Vec<InputRow>>> {
let Some(where_expr) = &sel.where_clause else {
return Ok(None);
};
let hint = sel.from.as_ref().and_then(|f| f.first.index_hint.as_ref());
if matches!(hint, Some(IndexHint::NotIndexed)) {
return Ok(None);
}
let indexes = self.indexes_of(table_name)?;
if let Some(IndexHint::IndexedBy(n)) = hint {
if !indexes.iter().any(|i| i.name.eq_ignore_ascii_case(n)) {
return Err(Error::Error(alloc::format!("no such index: {n}")));
}
}
let by_name = |idx: &IndexMeta| match hint {
Some(IndexHint::IndexedBy(n)) => idx.name.eq_ignore_ascii_case(n),
_ => true,
};
if let Some((col, values)) = find_in_constraint(where_expr, &meta.columns, params) {
if !values.iter().any(|v| matches!(v, Value::Null)) {
let encoding = self.backend.source().header().text_encoding;
let aff = meta.columns[col].affinity;
if !matches!(hint, Some(IndexHint::IndexedBy(_))) {
if let Some(ipk) = meta.ipk {
if col == ipk {
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut out = Vec::new();
let mut seen: Vec<i64> = Vec::new();
for v in &values {
let rid = eval::to_i64(v);
if seen.contains(&rid) {
continue;
}
seen.push(rid);
if cur.seek(rid)? {
let values =
self.decode_full_row(meta, rid, &cur.payload()?, encoding)?;
out.push(InputRow {
values,
rowid: Some(rid),
});
}
}
return Ok(Some(out));
}
}
}
let keys: Vec<Vec<Value>> = values
.iter()
.map(|v| alloc::vec![aff.coerce(v.clone())])
.collect();
for idx in &indexes {
if !by_name(idx) || idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
if idx.cols.first() == Some(&col) {
if self.seek_index_covers(sel, meta, &idx.cols, where_expr) {
return Ok(Some(self.covering_seek_rows(meta, idx.root, &idx.cols)?));
}
let coll = idx.collations.first().copied().unwrap_or_default();
return Ok(Some(self.in_seek_fetch(meta, idx.root, &[coll], &keys)?));
}
}
for idx in &indexes {
if !by_name(idx) || idx.key_exprs.is_some() || idx.partial.is_none() {
continue;
}
if idx.cols.first() == Some(&col) && partial_pred_guaranteed(idx, where_expr) {
let coll = idx.collations.first().copied().unwrap_or_default();
return Ok(Some(self.in_seek_fetch(meta, idx.root, &[coll], &keys)?));
}
}
}
}
for idx in &indexes {
if !by_name(idx) {
continue;
}
let Some(exprs) = &idx.key_exprs else {
continue;
};
let [key_expr] = exprs.as_slice() else {
continue;
};
if !partial_pred_guaranteed(idx, where_expr) {
continue;
}
let Some(values) = find_expr_in_values(key_expr, where_expr, params) else {
continue;
};
if values.iter().any(|v| matches!(v, Value::Null)) {
continue;
}
let coll = idx.collations.first().copied().unwrap_or_default();
let keys: Vec<Vec<Value>> = values.iter().map(|v| alloc::vec![v.clone()]).collect();
return Ok(Some(self.in_seek_fetch(meta, idx.root, &[coll], &keys)?));
}
Ok(None)
}
fn leading_index_for(
&self,
table_name: &str,
col: usize,
hint: Option<&IndexHint>,
) -> Result<Option<(u32, crate::value::Collation)>> {
for idx in &self.indexes_of(table_name)? {
if let Some(IndexHint::IndexedBy(n)) = hint {
if !idx.name.eq_ignore_ascii_case(n) {
continue;
}
}
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
if idx.cols.first() == Some(&col) {
return Ok(Some((
idx.root,
idx.collations.first().copied().unwrap_or_default(),
)));
}
}
Ok(None)
}
fn seek_col_values(
&self,
meta: &TableMeta,
table_name: &str,
hint: Option<&IndexHint>,
col: usize,
values: &[Value],
) -> Result<Option<Vec<i64>>> {
let mut rowids: Vec<i64> = Vec::new();
if !matches!(hint, Some(IndexHint::IndexedBy(_))) && meta.ipk == Some(col) {
for v in values {
let rid = eval::to_i64(v);
if !rowids.contains(&rid) {
rowids.push(rid);
}
}
return Ok(Some(rowids));
}
let Some((root, coll)) = self.leading_index_for(table_name, col, hint)? else {
return Ok(None);
};
let aff = meta.columns[col].affinity;
let colls = [coll];
for v in values {
let key = [aff.coerce(v.clone())];
for rid in crate::btree::index_seek_rowids(self.backend.source(), root, &key, &colls)? {
if !rowids.contains(&rid) {
rowids.push(rid);
}
}
}
Ok(Some(rowids))
}
fn seek_col_range(
&self,
meta: &TableMeta,
table_name: &str,
hint: Option<&IndexHint>,
col: usize,
bound: &RangeBound,
) -> Result<Option<Vec<i64>>> {
if !matches!(hint, Some(IndexHint::IndexedBy(_))) && meta.ipk == Some(col) {
let lo_int =
bound.lower.is_none() || matches!(bound.lower, Some((Value::Integer(_), _)));
let hi_int =
bound.upper.is_none() || matches!(bound.upper, Some((Value::Integer(_), _)));
if !(lo_int && hi_int) {
return Ok(None);
}
let start = match &bound.lower {
Some((Value::Integer(i), _)) => *i,
_ => i64::MIN,
};
let stop = match &bound.upper {
Some((Value::Integer(i), _)) => *i,
_ => i64::MAX,
};
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut rowids = Vec::new();
let mut ok = if start == i64::MIN {
cur.first()?
} else {
cur.seek(start)?;
cur.is_valid()
};
while ok {
let rid = cur.rowid()?;
if rid > stop {
break;
}
rowids.push(rid);
ok = cur.next()?;
}
return Ok(Some(rowids));
}
let Some((root, coll)) = self.leading_index_for(table_name, col, hint)? else {
return Ok(None);
};
let aff = meta.columns[col].affinity;
let lo = bound
.lower
.as_ref()
.map(|(v, i)| (aff.coerce(v.clone()), *i));
let hi = bound
.upper
.as_ref()
.map(|(v, i)| (aff.coerce(v.clone()), *i));
let colls = [coll];
let lower = lo.as_ref().map(|(v, i)| (core::slice::from_ref(v), *i));
let upper = hi.as_ref().map(|(v, i)| (core::slice::from_ref(v), *i));
let rowids =
crate::btree::index_range_rowids(self.backend.source(), root, lower, upper, &colls)?;
Ok(Some(rowids))
}
fn predicate_rowids(
&self,
meta: &TableMeta,
table_name: &str,
hint: Option<&IndexHint>,
pred: &Expr,
params: &Params,
) -> Result<Option<Vec<i64>>> {
if let Some((col, vals)) = find_in_constraint(pred, &meta.columns, params) {
if vals.iter().any(|v| matches!(v, Value::Null)) {
return Ok(None);
}
return self.seek_col_values(meta, table_name, hint, col, &vals);
}
let mut eqs: Vec<(usize, Value)> = Vec::new();
collect_eq_constraints(pred, &meta.columns, params, &mut eqs);
eqs.retain(|(_, v)| !matches!(v, Value::Null));
if let Some((col, v)) = eqs.into_iter().next() {
return self.seek_col_values(meta, table_name, hint, col, &[v]);
}
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(pred, &meta.columns, params, &mut ranges);
if let Some((&col, bound)) = ranges.iter().next() {
return self.seek_col_range(meta, table_name, hint, col, bound);
}
Ok(None)
}
fn try_index_or(
&self,
meta: &TableMeta,
table_name: &str,
sel: &Select,
params: &Params,
) -> Result<Option<Vec<InputRow>>> {
let Some(where_expr) = &sel.where_clause else {
return Ok(None);
};
let hint = sel.from.as_ref().and_then(|f| f.first.index_hint.as_ref());
if matches!(hint, Some(IndexHint::NotIndexed)) {
return Ok(None);
}
let mut disjuncts: Vec<&Expr> = Vec::new();
flatten_or(where_expr, &mut disjuncts);
if disjuncts.len() < 2 {
return Ok(None);
}
let mut rowids: Vec<i64> = Vec::new();
for d in disjuncts {
match self.predicate_rowids(meta, table_name, hint, d, params)? {
Some(rs) => {
for r in rs {
if !rowids.contains(&r) {
rowids.push(r);
}
}
}
None => return Ok(None),
}
}
let encoding = self.backend.source().header().text_encoding;
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut out = Vec::new();
for rid in rowids {
if cur.seek(rid)? {
let values = self.decode_full_row(meta, rid, &cur.payload()?, encoding)?;
out.push(InputRow {
values,
rowid: Some(rid),
});
}
}
Ok(Some(out))
}
fn explain_query_plan(&self, stmt: &Statement, params: &Params) -> Result<QueryResult> {
let mut details: Vec<(i64, i64, String)> = Vec::new();
let mut next_id = 1i64;
match stmt {
Statement::Select(sel) => {
self.eqp_select(sel, 0, &mut next_id, &mut details, params)?
}
Statement::Delete(d) => {
let meta = self.table_meta(&d.table, None)?;
let detail = self.eqp_access(
&d.table,
&d.table,
&meta,
d.where_clause.as_ref(),
None,
params,
)?;
details.push((next_id, 0, detail));
}
Statement::Update(u) => {
let meta = self.table_meta(&u.table, None)?;
let detail = self.eqp_access(
&u.table,
&u.table,
&meta,
u.where_clause.as_ref(),
None,
params,
)?;
details.push((next_id, 0, detail));
}
Statement::Insert(ins) => {
if let InsertSource::Select(sel) = &ins.source {
self.eqp_select(sel, 0, &mut next_id, &mut details, params)?;
}
}
_ => return Err(Error::Unsupported("EXPLAIN QUERY PLAN for this statement")),
}
Ok(QueryResult {
columns: alloc::vec![
String::from("id"),
String::from("parent"),
String::from("notused"),
String::from("detail"),
],
rows: details
.into_iter()
.map(|(id, parent, detail)| {
alloc::vec![
Value::Integer(id),
Value::Integer(parent),
Value::Integer(0),
Value::Text(detail),
]
})
.collect(),
})
}
fn eqp_vtab_detail(
&self,
name: &str,
label: &str,
sel: &Select,
params: &Params,
) -> Result<String> {
use crate::schema::ObjectType;
let plain = || alloc::format!("SCAN {label} VIRTUAL TABLE INDEX 0:");
let cvt = self
.schema
.objects()
.iter()
.find(|o| o.obj_type == ObjectType::Table && o.name.eq_ignore_ascii_case(name))
.and_then(|o| o.sql.as_deref())
.and_then(|s| match sql::parse_one(s) {
Ok(Statement::CreateVirtualTable(cvt)) => Some(cvt),
_ => None,
});
let Some(cvt) = cvt else { return Ok(plain()) };
let Some(module) = self.vtab_registry.get(&cvt.module) else {
return Ok(plain());
};
if module.dyn_persistent() {
return Ok(plain());
}
let arg_refs: Vec<&str> = cvt.args.iter().map(String::as_str).collect();
let schema = module.dyn_connect(&arg_refs)?;
let columns: Vec<ColumnInfo> = schema
.columns
.iter()
.map(|n| ColumnInfo {
name: n.clone(),
table: label.to_string(),
affinity: eval::Affinity::Blob,
collation: crate::value::Collation::default(),
})
.collect();
let (constraints, _) = collect_vtab_constraints(sel, &columns, params);
let plan = module.dyn_best_index(&constraints)?;
Ok(alloc::format!(
"SCAN {label} VIRTUAL TABLE INDEX {}:{}",
plan.idx_num,
plan.idx_str.as_deref().unwrap_or("")
))
}
fn eqp_select(
&self,
sel: &Select,
parent: i64,
next_id: &mut i64,
out: &mut Vec<(i64, i64, String)>,
params: &Params,
) -> Result<()> {
let rewritten;
let sel = match promote_comma_join_ons(sel) {
Some(r) => {
rewritten = r;
&rewritten
}
None => sel,
};
let Some(from) = &sel.from else {
return Ok(()); };
let label = eqp_label(&from.first);
if from.joins.is_empty()
&& from.first.subquery.is_none()
&& from.first.tvf_args.is_none()
&& self.lookup_cte(&from.first.name, None).is_none()
&& self.is_virtual_table(&from.first.name)
{
let detail = self.eqp_vtab_detail(&from.first.name, &label, sel, params)?;
let id = *next_id;
*next_id += 1;
out.push((id, parent, detail));
return Ok(());
}
let meta = self.table_meta(&from.first.name, from.first.alias.as_deref())?;
if from.joins.is_empty()
&& self.eqp_or_plan(
&label,
&from.first.name,
&meta,
sel.where_clause.as_ref(),
parent,
next_id,
out,
params,
)?
{
} else {
let detail = if from.joins.is_empty() {
if let Some((name, _)) = self.count_covering_index(sel) {
alloc::format!("SCAN {label} USING COVERING INDEX {name}")
}
else if let Some(s) = self.order_index_scan(sel) {
let kind = if s.covering {
"COVERING INDEX"
} else {
"INDEX"
};
alloc::format!("SCAN {label} USING {kind} {}", s.name)
}
else if let Some((name, _, _)) = self.covering_scan(sel, &meta, params) {
alloc::format!("SCAN {label} USING COVERING INDEX {name}")
} else {
self.eqp_access(
&label,
&from.first.name,
&meta,
sel.where_clause.as_ref(),
Some(sel),
params,
)?
}
} else {
alloc::format!("SCAN {label}")
};
let id = *next_id;
*next_id += 1;
out.push((id, parent, detail));
}
if !from.joins.is_empty() {
let mut left_columns = self.resolve_join_source(&from.first, params)?.0;
for join in &from.joins {
let label = eqp_label(&join.table);
let (details, jcols): (Vec<String>, Vec<ColumnInfo>) = if let Some((
_,
inner_meta,
)) =
self.rowid_join_seek(join, &left_columns)
{
(
alloc::vec![alloc::format!(
"SEARCH {label} USING INTEGER PRIMARY KEY (rowid=?)"
)],
inner_meta.columns,
)
} else if let Some((_, inner_meta, idx)) = self.index_join_seek(join, &left_columns)
{
let col = &inner_meta.columns[idx.cols[0]].name;
(
alloc::vec![alloc::format!(
"SEARCH {label} USING INDEX {} ({col}=?)",
idx.name
)],
inner_meta.columns,
)
} else if let Some((_, inner_meta)) =
self.without_rowid_pk_join_seek(join, &left_columns)
{
let col = &inner_meta.columns[inner_meta.storage_order[0]].name;
let suffix = if matches!(join.kind, JoinKind::Left) {
" LEFT-JOIN"
} else {
""
};
(
alloc::vec![alloc::format!(
"SEARCH {label} USING PRIMARY KEY ({col}=?){suffix}"
)],
inner_meta.columns,
)
} else {
let jcols = self.resolve_join_source(&join.table, params)?.0;
let auto_col = if join.natural
|| !join.using.is_empty()
|| !matches!(join.kind, JoinKind::Inner | JoinKind::Left)
{
None
} else {
join.on.as_ref().and_then(|on| {
let mut combined = left_columns.clone();
combined.extend(jcols.iter().cloned());
join_equi_cols(on, &combined, left_columns.len())
.map(|(_, ri)| jcols[ri].name.clone())
})
};
match auto_col {
Some(col) => {
let suffix = if matches!(join.kind, JoinKind::Left) {
" LEFT-JOIN"
} else {
""
};
(
alloc::vec![
alloc::format!("BLOOM FILTER ON {label} ({col}=?)"),
alloc::format!(
"SEARCH {label} USING AUTOMATIC COVERING INDEX ({col}=?){suffix}"
),
],
jcols,
)
}
None => (alloc::vec![alloc::format!("SCAN {label}")], jcols),
}
};
for detail in details {
let id = *next_id;
*next_id += 1;
out.push((id, parent, detail));
}
let left_width = left_columns.len();
left_columns.extend(jcols);
if join.natural || !join.using.is_empty() {
let mut drop: Vec<usize> = if join.natural {
(left_width..left_columns.len())
.filter(|&rl| {
left_columns[..left_width]
.iter()
.any(|c| c.name.eq_ignore_ascii_case(&left_columns[rl].name))
})
.collect()
} else {
join.using
.iter()
.filter_map(|name| {
(left_width..left_columns.len())
.find(|&rl| left_columns[rl].name.eq_ignore_ascii_case(name))
})
.collect()
};
drop.sort_unstable();
drop.dedup();
for &d in drop.iter().rev() {
left_columns.remove(d);
}
}
}
}
if !sel.order_by.is_empty() && self.order_satisfied_by_scan(sel, params).is_none() {
let n = sel.order_by.len();
let sorted = n - self
.seek_order_prefix(sel, params)
.map_or(0, |(k, _)| k.min(n));
let detail = match sorted {
_ if sorted >= n => String::from("USE TEMP B-TREE FOR ORDER BY"),
1 => String::from("USE TEMP B-TREE FOR LAST TERM OF ORDER BY"),
_ => alloc::format!("USE TEMP B-TREE FOR LAST {sorted} TERMS OF ORDER BY"),
};
let id = *next_id;
*next_id += 1;
out.push((id, parent, detail));
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn eqp_or_plan(
&self,
label: &str,
table: &str,
meta: &TableMeta,
where_clause: Option<&Expr>,
parent: i64,
next_id: &mut i64,
out: &mut Vec<(i64, i64, String)>,
params: &Params,
) -> Result<bool> {
let Some(where_expr) = where_clause else {
return Ok(false);
};
let mut disjuncts: Vec<&Expr> = Vec::new();
flatten_or(where_expr, &mut disjuncts);
if disjuncts.len() < 2 {
return Ok(false);
}
let mut details = Vec::with_capacity(disjuncts.len());
for d in &disjuncts {
let detail = self.eqp_access(label, table, meta, Some(d), None, params)?;
if !detail.starts_with("SEARCH") {
return Ok(false);
}
details.push(detail);
}
let or_id = *next_id;
*next_id += 1;
out.push((or_id, parent, String::from("MULTI-INDEX OR")));
for (i, detail) in details.into_iter().enumerate() {
let idx_id = *next_id;
*next_id += 1;
out.push((idx_id, or_id, alloc::format!("INDEX {}", i + 1)));
let search_id = *next_id;
*next_id += 1;
out.push((search_id, idx_id, detail));
}
Ok(true)
}
fn eqp_access(
&self,
label: &str,
table: &str,
meta: &TableMeta,
where_clause: Option<&Expr>,
sel: Option<&Select>,
params: &Params,
) -> Result<String> {
let Some(where_expr) = where_clause else {
return Ok(alloc::format!("SCAN {label}"));
};
let index_kw = |idx_cols: &[usize]| -> &'static str {
match sel {
Some(s) if self.seek_index_covers(s, meta, idx_cols, where_expr) => {
"COVERING INDEX"
}
_ => "INDEX",
}
};
let mut eqs: Vec<(usize, Value)> = Vec::new();
collect_eq_constraints(where_expr, &meta.columns, params, &mut eqs);
eqs.retain(|(_, v)| !matches!(v, Value::Null));
if meta.without_rowid {
let pk = &meta.storage_order[..meta.pk_len];
let mut names = Vec::new();
for &c in pk {
if eqs.iter().any(|(col, _)| *col == c) {
names.push(alloc::format!("{}=?", meta.columns[c].name));
} else {
break;
}
}
if !names.is_empty() {
return Ok(alloc::format!(
"SEARCH {label} USING PRIMARY KEY ({})",
names.join(" AND ")
));
}
if let Some(&lead) = pk.first() {
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
if let Some(b) = ranges.get(&lead) {
let name = &meta.columns[lead].name;
let cond = match (&b.lower, &b.upper) {
(Some(_), Some(_)) => alloc::format!("{name}>? AND {name}<?"),
(Some(_), None) => alloc::format!("{name}>?"),
(None, Some(_)) => alloc::format!("{name}<?"),
(None, None) => String::new(),
};
if !cond.is_empty() {
return Ok(alloc::format!("SEARCH {label} USING PRIMARY KEY ({cond})"));
}
}
}
for idx in self.indexes_of(table)? {
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
let mut matched = Vec::new();
for &c in &idx.cols {
if eqs.iter().any(|(col, _)| *col == c) {
matched.push(c);
} else {
break;
}
}
if matched.is_empty() {
continue;
}
let mut avail = idx.cols.clone();
if !idx.name.starts_with("sqlite_autoindex_") {
for &p in pk {
if !avail.contains(&p) {
avail.push(p);
}
}
}
let kw = match sel {
Some(s) if self.seek_index_covers(s, meta, &avail, where_expr) => {
"COVERING INDEX"
}
_ => "INDEX",
};
let cond = matched
.iter()
.map(|&c| alloc::format!("{}=?", meta.columns[c].name))
.collect::<Vec<_>>()
.join(" AND ");
return Ok(alloc::format!(
"SEARCH {label} USING {kw} {} ({cond})",
idx.name
));
}
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
for idx in self.indexes_of(table)? {
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
let Some(&lead) = idx.cols.first() else {
continue;
};
let Some(b) = ranges.get(&lead) else {
continue;
};
let name = &meta.columns[lead].name;
let cond = match (&b.lower, &b.upper) {
(Some(_), Some(_)) => alloc::format!("{name}>? AND {name}<?"),
(Some(_), None) => alloc::format!("{name}>?"),
(None, Some(_)) => alloc::format!("{name}<?"),
(None, None) => continue,
};
let mut avail = idx.cols.clone();
if !idx.name.starts_with("sqlite_autoindex_") {
for &p in pk {
if !avail.contains(&p) {
avail.push(p);
}
}
}
let kw = match sel {
Some(s) if self.seek_index_covers(s, meta, &avail, where_expr) => {
"COVERING INDEX"
}
_ => "INDEX",
};
return Ok(alloc::format!(
"SEARCH {label} USING {kw} {} ({cond})",
idx.name
));
}
return Ok(alloc::format!("SCAN {label}"));
}
if let Some(ipk) = meta.ipk {
if eqs.iter().any(|(c, _)| *c == ipk) {
return Ok(alloc::format!(
"SEARCH {label} USING INTEGER PRIMARY KEY (rowid=?)"
));
}
}
let stats = self.stat1_map();
#[allow(clippy::type_complexity)]
let mut best: Option<(String, Vec<usize>, Vec<usize>, u64)> = None;
for idx in self.indexes_of(table)? {
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
let mut matched = Vec::new();
for &c in &idx.cols {
if eqs.iter().any(|(col, _)| *col == c) {
matched.push(c);
} else {
break;
}
}
if matched.is_empty() {
continue;
}
let est = stats
.get(&idx.name)
.and_then(|s| s.get(matched.len()).copied())
.unwrap_or(u64::MAX - matched.len() as u64);
let better = match &best {
None => true,
Some((_, bm, _, be)) => est < *be || (est == *be && matched.len() > bm.len()),
};
if better {
best = Some((idx.name.clone(), matched, idx.cols.clone(), est));
}
}
if let Some((idx_name, matched, idx_cols, _)) = best {
if !matched.is_empty() {
let mut conds = matched
.iter()
.map(|&c| alloc::format!("{}=?", meta.columns[c].name))
.collect::<Vec<_>>();
if let Some(&next_col) = idx_cols.get(matched.len()) {
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
if let Some(b) = ranges.get(&next_col) {
let name = &meta.columns[next_col].name;
if b.lower.is_some() {
conds.push(alloc::format!("{name}>?"));
}
if b.upper.is_some() {
conds.push(alloc::format!("{name}<?"));
}
}
}
let kw = index_kw(&idx_cols);
return Ok(alloc::format!(
"SEARCH {label} USING {kw} {idx_name} ({})",
conds.join(" AND ")
));
}
}
for idx in self.indexes_of(table)? {
if self
.partial_expr_seek(&idx, where_expr, meta, params)?
.is_some()
{
let cond = match &idx.key_exprs {
None => idx
.cols
.iter()
.take_while(|&&c| eqs.iter().any(|(col, _)| *col == c))
.map(|&c| alloc::format!("{}=?", meta.columns[c].name))
.collect::<Vec<_>>()
.join(" AND "),
Some(_) => "<expr>=?".into(),
};
return Ok(alloc::format!(
"SEARCH {label} USING INDEX {} ({cond})",
idx.name
));
}
}
let leading_index = |target: usize| -> Option<(String, Vec<usize>)> {
for obj in self.schema.indexes_on(table) {
let sql = obj.sql.as_ref()?;
let Ok(Statement::CreateIndex(ci)) = sql::parse_one(sql) else {
continue;
};
if ci.where_clause.is_some() {
continue;
}
let Ok(cols) = self.index_columns(meta, &ci) else {
continue;
};
if cols.first() == Some(&target) {
return Some((obj.name.clone(), cols));
}
}
None
};
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
if let Some(ipk) = meta.ipk {
if let Some(b) = ranges.get(&ipk) {
let lo_int = b.lower.is_none() || matches!(b.lower, Some((Value::Integer(_), _)));
let hi_int = b.upper.is_none() || matches!(b.upper, Some((Value::Integer(_), _)));
if lo_int && hi_int {
let cond = match (&b.lower, &b.upper) {
(Some(_), Some(_)) => "rowid>? AND rowid<?",
(Some(_), None) => "rowid>?",
(None, Some(_)) => "rowid<?",
(None, None) => "",
};
if !cond.is_empty() {
return Ok(alloc::format!(
"SEARCH {label} USING INTEGER PRIMARY KEY ({cond})"
));
}
}
}
}
for (&col, bound) in &ranges {
if let Some((idx_name, idx_cols)) = leading_index(col) {
let name = &meta.columns[col].name;
let cond = match (&bound.lower, &bound.upper) {
(Some(_), Some(_)) => alloc::format!("{name}>? AND {name}<?"),
(Some(_), None) => alloc::format!("{name}>?"),
(None, Some(_)) => alloc::format!("{name}<?"),
(None, None) => continue,
};
let kw = index_kw(&idx_cols);
return Ok(alloc::format!(
"SEARCH {label} USING {kw} {idx_name} ({cond})"
));
}
}
for idx in self.indexes_of(table)? {
if let Some((bound, _)) = self.partial_expr_range(&idx, where_expr, meta, params) {
let cond = |name: &str| match (&bound.lower, &bound.upper) {
(Some(_), Some(_)) => alloc::format!("{name}>? AND {name}<?"),
(Some(_), None) => alloc::format!("{name}>?"),
(None, Some(_)) => alloc::format!("{name}<?"),
(None, None) => String::new(),
};
let rendered = match &idx.key_exprs {
None => cond(&meta.columns[idx.cols[0]].name),
Some(_) => cond("<expr>"),
};
if !rendered.is_empty() {
return Ok(alloc::format!(
"SEARCH {label} USING INDEX {} ({rendered})",
idx.name
));
}
}
}
if let Some((col, _)) = find_in_constraint(where_expr, &meta.columns, params) {
if meta.ipk == Some(col) {
return Ok(alloc::format!(
"SEARCH {label} USING INTEGER PRIMARY KEY (rowid=?)"
));
}
if let Some((idx_name, idx_cols)) = leading_index(col) {
let name = &meta.columns[col].name;
let kw = index_kw(&idx_cols);
return Ok(alloc::format!(
"SEARCH {label} USING {kw} {idx_name} ({name}=?)"
));
}
for idx in self.indexes_of(table)? {
if idx.key_exprs.is_some() || idx.partial.is_none() {
continue;
}
if idx.cols.first() == Some(&col) && partial_pred_guaranteed(&idx, where_expr) {
let name = &meta.columns[col].name;
return Ok(alloc::format!(
"SEARCH {label} USING INDEX {} ({name}=?)",
idx.name
));
}
}
}
for idx in self.indexes_of(table)? {
let Some(exprs) = &idx.key_exprs else {
continue;
};
let [key_expr] = exprs.as_slice() else {
continue;
};
if partial_pred_guaranteed(&idx, where_expr)
&& find_expr_in_values(key_expr, where_expr, params).is_some()
{
return Ok(alloc::format!(
"SEARCH {label} USING INDEX {} (<expr>=?)",
idx.name
));
}
}
Ok(alloc::format!("SCAN {label}"))
}
fn indexes_of(&self, table: &str) -> Result<Vec<IndexMeta>> {
let tmeta = match self.schema.table(table) {
Some(_) => self.table_meta(table, None)?,
None => return Ok(Vec::new()),
};
let mut out = Vec::new();
for obj in self.schema.indexes_on(table) {
match &obj.sql {
Some(sql) => {
let Statement::CreateIndex(ci) = sql::parse_one(sql)? else {
continue;
};
let (cols, key_exprs, collations) = self.index_key_spec(&tmeta, &ci)?;
out.push(IndexMeta {
name: obj.name.clone(),
root: obj.rootpage,
cols,
collations,
partial: ci.where_clause.clone(),
key_exprs,
unique: ci.unique,
});
}
None => {
if let Some(n) = autoindex_number(&obj.name, table) {
if let Some(cols) = tmeta.unique.get(n - 1) {
let collations = self.col_collations(&tmeta, cols);
out.push(IndexMeta {
name: obj.name.clone(),
root: obj.rootpage,
cols: cols.clone(),
collations,
partial: None,
key_exprs: None,
unique: true,
});
}
}
}
}
}
Ok(out)
}
fn index_columns(&self, tmeta: &TableMeta, ci: &CreateIndex) -> Result<Vec<usize>> {
Ok(self.index_columns_coll(tmeta, ci)?.0)
}
fn index_columns_coll(
&self,
tmeta: &TableMeta,
ci: &CreateIndex,
) -> Result<(Vec<usize>, Vec<crate::value::Collation>)> {
let mut cols = Vec::new();
let mut colls = Vec::new();
for term in &ci.columns {
let (inner, explicit) = match &term.expr {
Expr::Collate { expr, collation } => {
(expr.as_ref(), crate::value::Collation::parse(collation))
}
e => (e, None),
};
let Expr::Column { column, .. } = inner else {
return Err(Error::Unsupported("expression indexes"));
};
let pos = tmeta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(column))
.ok_or_else(|| Error::Error(format!("no such column: {column}")))?;
cols.push(pos);
colls.push(explicit.unwrap_or(tmeta.columns[pos].collation));
}
Ok((cols, colls))
}
#[allow(clippy::type_complexity)]
fn index_key_spec(
&self,
tmeta: &TableMeta,
ci: &CreateIndex,
) -> Result<(Vec<usize>, Option<Vec<Expr>>, Vec<crate::value::Collation>)> {
let mut cols = Vec::new();
let mut exprs = Vec::new();
let mut colls = Vec::new();
let mut is_expr = false;
for term in &ci.columns {
let (inner, explicit) = match &term.expr {
Expr::Collate { expr, collation } => {
(expr.as_ref(), crate::value::Collation::parse(collation))
}
e => (e, None),
};
exprs.push(inner.clone());
match inner {
Expr::Column { column, .. } => {
let pos = tmeta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(column))
.ok_or_else(|| Error::Error(format!("no such column: {column}")))?;
cols.push(pos);
colls.push(explicit.unwrap_or(tmeta.columns[pos].collation));
}
_ => {
is_expr = true;
colls.push(explicit.unwrap_or_default());
}
}
}
if is_expr {
Ok((Vec::new(), Some(exprs), colls))
} else {
Ok((cols, None, colls))
}
}
fn index_key_bytes(
&self,
idx: &IndexMeta,
meta: &TableMeta,
values: &[Value],
rowid: i64,
params: &Params,
) -> Result<Vec<u8>> {
match &idx.key_exprs {
None => Ok(index_key(&idx.cols, values, rowid)),
Some(exprs) => {
let ctx = row_ctx(values, &meta.columns, Some(rowid), params).with_subqueries(self);
let mut key: Vec<Value> = exprs
.iter()
.map(|e| eval::eval(e, &ctx))
.collect::<Result<_>>()?;
key.push(Value::Integer(rowid));
Ok(encode_record(&key))
}
}
}
fn col_collations(&self, tmeta: &TableMeta, cols: &[usize]) -> Vec<crate::value::Collation> {
cols.iter().map(|&c| tmeta.columns[c].collation).collect()
}
fn row_in_index(
&self,
idx: &IndexMeta,
tmeta: &TableMeta,
values: &[Value],
rowid: Option<i64>,
params: &Params,
) -> Result<bool> {
match &idx.partial {
None => Ok(true),
Some(pred) => {
let ctx = row_ctx(values, &tmeta.columns, rowid, params).with_subqueries(self);
Ok(eval::truth(&eval::eval(pred, &ctx)?) == Some(true))
}
}
}
fn rebuild_indexes(&mut self, tmeta: &TableMeta, indexes: &[IndexMeta]) -> Result<()> {
if indexes.is_empty() {
return Ok(());
}
let rows = self.scan_table(tmeta)?;
let no_params = Params::default();
let mut per_index: Vec<Vec<Vec<u8>>> = Vec::with_capacity(indexes.len());
for idx in indexes {
let mut keys = Vec::new();
for (rowid, values) in &rows {
if self.row_in_index(idx, tmeta, values, Some(*rowid), &no_params)? {
keys.push(self.index_key_bytes(idx, tmeta, values, *rowid, &no_params)?);
}
}
per_index.push(keys);
}
let w = self.backend.writer()?;
for (idx, keys) in indexes.iter().zip(&per_index) {
clear_index(w, idx.root)?;
for key in keys {
insert_index(w, idx.root, key, &idx.collations)?;
}
}
Ok(())
}
fn order_limit_rowids(
&self,
meta: &TableMeta,
rowids: Vec<i64>,
order_by: &[OrderTerm],
limit: Option<&Expr>,
offset: Option<&Expr>,
params: &Params,
) -> Result<Vec<i64>> {
let mut rowids = rowids;
if !order_by.is_empty() {
let mut keyed: Vec<(i64, Vec<Value>)> = Vec::with_capacity(rowids.len());
for rid in rowids {
let row = self.read_row(meta, rid)?.unwrap_or_default();
let ctx = row_ctx(&row, &meta.columns, Some(rid), params).with_subqueries(self);
let keys = order_by
.iter()
.map(|t| eval::eval(&t.expr, &ctx))
.collect::<Result<Vec<_>>>()?;
keyed.push((rid, keys));
}
keyed.sort_by(|a, b| {
for (i, t) in order_by.iter().enumerate() {
let o = cmp_order(
&a.1[i],
&b.1[i],
t.descending,
t.nulls_first,
crate::value::Collation::Binary,
);
if o != core::cmp::Ordering::Equal {
return o;
}
}
core::cmp::Ordering::Equal
});
rowids = keyed.into_iter().map(|(r, _)| r).collect();
}
let off = match offset {
Some(e) => must_be_int(eval::eval(
e,
&EvalCtx::rowless(params).with_subqueries(self),
)?)?
.max(0) as usize,
None => 0,
};
if off > 0 {
rowids.drain(0..off.min(rowids.len()));
}
if let Some(e) = limit {
let n = eval::to_i64(&eval::eval(e, &EvalCtx::rowless(params))?);
if n >= 0 {
rowids.truncate(n as usize);
}
}
Ok(rowids)
}
fn matching_rowids(
&self,
meta: &TableMeta,
pred: Option<&Expr>,
params: &Params,
) -> Result<Vec<i64>> {
let mut out = Vec::new();
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let encoding = self.backend.source().header().text_encoding;
let mut ok = cur.first()?;
while ok {
let rowid = cur.rowid()?;
let values = self.decode_full_row(meta, rowid, &cur.payload()?, encoding)?;
let keep = match pred {
Some(p) => {
let ctx =
row_ctx(&values, &meta.columns, Some(rowid), params).with_subqueries(self);
eval::truth(&eval::eval(p, &ctx)?) == Some(true)
}
None => true,
};
if keep {
out.push(rowid);
}
ok = cur.next()?;
}
Ok(out)
}
fn next_rowid(&self, root: u32) -> Result<i64> {
let mut cur = TableCursor::new(self.backend.source(), root);
if cur.last()? {
Ok(cur.rowid()? + 1)
} else {
Ok(1)
}
}
fn run_select(&self, sel: &Select, params: &Params) -> Result<QueryResult> {
let base = self.cte_env.borrow().len();
let pushed = self.push_ctes(sel, params);
let result = pushed.and_then(|()| self.run_select_compound(sel, params));
self.cte_env.borrow_mut().truncate(base);
result
}
fn run_select_compound(&self, sel: &Select, params: &Params) -> Result<QueryResult> {
if sel.compound.is_empty() {
return self.run_core(sel, params);
}
let mut first = sel.clone();
first.compound = Vec::new();
first.order_by = Vec::new();
first.limit = None;
first.offset = None;
let mut result = self.run_core(&first, params)?;
let colls = {
let (cols, _) = self.scan_source(&first, params)?;
self.output_collations(&first, &cols, params)
};
let is_values = sel.from.is_none()
&& sel.where_clause.is_none()
&& sel.group_by.is_empty()
&& sel
.compound
.iter()
.all(|(op, c)| *op == CompoundOp::UnionAll && c.from.is_none());
for (op, operand) in &sel.compound {
let r = self.run_select_compound(operand, params)?;
if r.columns.len() != result.columns.len() {
return Err(Error::Error(if is_values {
"all VALUES must have the same number of terms".into()
} else {
"SELECTs to the left and right of the compound operator do not have the same \
number of result columns"
.into()
}));
}
result.rows = apply_compound(*op, result.rows, r.rows, &colls);
}
if sel.order_by.is_empty()
&& sel
.compound
.iter()
.any(|(op, _)| *op != CompoundOp::UnionAll)
{
result.rows.sort_by(|a, b| {
for (i, va) in a.iter().enumerate() {
let coll = colls.get(i).copied().unwrap_or_default();
let ord = crate::value::cmp_values_coll(va, &b[i], coll);
if ord != core::cmp::Ordering::Equal {
return ord;
}
}
core::cmp::Ordering::Equal
});
}
self.compound_order_limit(&mut result, sel, params, &colls)?;
Ok(result)
}
fn compound_order_limit(
&self,
result: &mut QueryResult,
sel: &Select,
params: &Params,
colls: &[crate::value::Collation],
) -> Result<()> {
if !sel.order_by.is_empty() {
check_positional_terms(&[], &sel.order_by, result.columns.len())?;
let mut keys = Vec::new();
for term in &sel.order_by {
let idx = resolve_order_index(&term.expr, &result.columns, result.columns.len())
.ok_or(Error::Unsupported(
"ORDER BY term must be an output column in a compound query",
))?;
let coll = colls.get(idx).copied().unwrap_or_default();
keys.push((idx, term.descending, term.nulls_first, coll));
}
result.rows.sort_by(|a, b| {
for (idx, desc, nf, coll) in &keys {
let ord = cmp_order(&a[*idx], &b[*idx], *desc, *nf, *coll);
if ord != core::cmp::Ordering::Equal {
return ord;
}
}
core::cmp::Ordering::Equal
});
}
let offset = match &sel.offset {
Some(e) => must_be_int(eval::eval(
e,
&EvalCtx::rowless(params).with_subqueries(self),
)?)?
.max(0) as usize,
None => 0,
};
let limit = match &sel.limit {
Some(e) => {
let n = must_be_int(eval::eval(
e,
&EvalCtx::rowless(params).with_subqueries(self),
)?)?;
if n < 0 {
None
} else {
Some(n as usize)
}
}
None => None,
};
if offset > 0 {
result.rows.drain(0..offset.min(result.rows.len()));
}
if let Some(n) = limit {
result.rows.truncate(n);
}
Ok(())
}
fn apply_windows(
&self,
sel: &Select,
columns: &mut Vec<ColumnInfo>,
rows: &mut [InputRow],
params: &Params,
) -> Result<Select> {
let wins = window::collect_window_exprs(sel);
let mut new_sel = sel.clone();
for (k, wexpr) in wins.iter().enumerate() {
let resolved = resolve_window_ref(wexpr, &sel.window_defs)?;
let values = self.compute_window(&resolved, columns, rows, params)?;
let col_name = alloc::format!("__win{k}");
columns.push(ColumnInfo {
name: col_name.clone(),
table: String::new(),
affinity: eval::Affinity::Blob,
collation: crate::value::Collation::default(),
});
for (row, v) in rows.iter_mut().zip(values) {
row.values.push(v);
}
let repl = Expr::Column {
table: None,
column: col_name,
};
window::replace_window_expr(&mut new_sel, wexpr, &repl);
}
Ok(new_sel)
}
fn compute_window(
&self,
wexpr: &Expr,
columns: &[ColumnInfo],
rows: &[InputRow],
params: &Params,
) -> Result<Vec<Value>> {
let Expr::Function {
name,
distinct,
args,
star,
filter,
over: Some(spec),
..
} = wexpr
else {
return Err(Error::Error("not a window function".into()));
};
if *distinct {
return Err(Error::Error(
"DISTINCT is not supported for window functions".into(),
));
}
let lname = name.to_ascii_lowercase();
let n = rows.len();
let mut part_keys: Vec<Vec<Value>> = Vec::with_capacity(n);
let mut ord_keys: Vec<Vec<Value>> = Vec::with_capacity(n);
let mut arg_vals: Vec<Vec<Value>> = Vec::with_capacity(n);
let mut passes: Vec<bool> = Vec::with_capacity(n);
for r in rows {
let ctx = r.ctx(columns, params).with_subqueries(self);
part_keys.push(
spec.partition_by
.iter()
.map(|e| eval::eval(e, &ctx))
.collect::<Result<_>>()?,
);
ord_keys.push(
spec.order_by
.iter()
.map(|t| eval::eval(&t.expr, &ctx))
.collect::<Result<_>>()?,
);
arg_vals.push(
args.iter()
.map(|e| eval::eval(e, &ctx))
.collect::<Result<_>>()?,
);
passes.push(match filter {
Some(pred) => eval::truth(&eval::eval(pred, &ctx)?) == Some(true),
None => true,
});
}
let descending: Vec<bool> = spec.order_by.iter().map(|t| t.descending).collect();
let mut partitions: Vec<Vec<usize>> = Vec::new();
let mut part_of: Vec<usize> = Vec::new();
for i in 0..n {
let p = partitions
.iter()
.position(|members| rows_equal(&part_keys[members[0]], &part_keys[i]));
match p {
Some(idx) => {
partitions[idx].push(i);
part_of.push(idx);
}
None => {
part_of.push(partitions.len());
partitions.push(alloc::vec![i]);
}
}
}
let mut result = alloc::vec![Value::Null; n];
for members in &partitions {
let mut ordered = members.clone();
ordered.sort_by(|&a, &b| cmp_keys(&ord_keys[a], &ord_keys[b], &descending));
self.fill_window_partition(
&lname,
*star,
&ordered,
&ord_keys,
&arg_vals,
&passes,
spec,
&mut result,
)?;
}
Ok(result)
}
#[allow(clippy::too_many_arguments)]
fn fill_window_partition(
&self,
lname: &str,
star: bool,
ordered: &[usize],
ord_keys: &[Vec<Value>],
arg_vals: &[Vec<Value>],
passes: &[bool],
spec: &WindowSpec,
result: &mut [Value],
) -> Result<()> {
let m = ordered.len();
let mut gid = alloc::vec![0usize; m];
for q in 1..m {
gid[q] = gid[q - 1]
+ usize::from(
!cmp_keys(&ord_keys[ordered[q - 1]], &ord_keys[ordered[q]], &[]).is_eq(),
);
}
let ovals: Vec<Value> = if spec.order_by.len() == 1 {
ordered
.iter()
.map(|&i| ord_keys[i].first().cloned().unwrap_or(Value::Null))
.collect()
} else {
Vec::new()
};
let desc = spec.order_by.first().map(|t| t.descending).unwrap_or(false);
let exclude = spec
.frame
.as_ref()
.map(|f| f.exclude)
.unwrap_or(FrameExclude::NoOthers);
for p in 0..m {
let idx = ordered[p];
let (fstart, fend) = frame_bounds(p, m, &gid, spec, &ovals, desc);
let fpos: Vec<usize> = (fstart..fend)
.filter(|&k| match exclude {
FrameExclude::NoOthers => true,
FrameExclude::CurrentRow => k != p,
FrameExclude::Group => gid[k] != gid[p],
FrameExclude::Ties => gid[k] != gid[p] || k == p,
})
.collect();
let val = match lname {
"row_number" => Value::Integer(p as i64 + 1),
"rank" => {
let mut r = p;
while r > 0 && cmp_keys(&ord_keys[ordered[r - 1]], &ord_keys[idx], &[]).is_eq()
{
r -= 1;
}
Value::Integer(r as i64 + 1)
}
"dense_rank" => {
let mut dr = 1i64;
for q in 1..=p {
if !cmp_keys(&ord_keys[ordered[q - 1]], &ord_keys[ordered[q]], &[]).is_eq()
{
dr += 1;
}
}
Value::Integer(dr)
}
"percent_rank" => {
let mut r = p;
while r > 0 && cmp_keys(&ord_keys[ordered[r - 1]], &ord_keys[idx], &[]).is_eq()
{
r -= 1;
}
if m > 1 {
Value::Real(r as f64 / (m - 1) as f64)
} else {
Value::Real(0.0)
}
}
"cume_dist" => {
let mut last = p;
while last + 1 < m
&& cmp_keys(&ord_keys[idx], &ord_keys[ordered[last + 1]], &[]).is_eq()
{
last += 1;
}
Value::Real((last + 1) as f64 / m as f64)
}
"ntile" => {
let buckets = arg_vals[idx].first().map(eval::to_i64).unwrap_or(1).max(1);
Value::Integer(ntile_bucket(p, m, buckets))
}
"lag" | "lead" => {
let offset = arg_vals[idx].get(1).map(eval::to_i64).unwrap_or(1);
let default = arg_vals[idx].get(2).cloned().unwrap_or(Value::Null);
let target = if lname == "lag" {
p as i64 - offset
} else {
p as i64 + offset
};
if target >= 0 && (target as usize) < m {
arg_vals[ordered[target as usize]]
.first()
.cloned()
.unwrap_or(Value::Null)
} else {
default
}
}
"first_value" => fpos
.first()
.and_then(|&k| arg_vals[ordered[k]].first().cloned())
.unwrap_or(Value::Null),
"last_value" => fpos
.last()
.and_then(|&k| arg_vals[ordered[k]].first().cloned())
.unwrap_or(Value::Null),
"nth_value" => {
let nth = arg_vals[idx].get(1).map(eval::to_i64).unwrap_or(1);
if nth >= 1 {
fpos.get((nth - 1) as usize)
.and_then(|&k| arg_vals[ordered[k]].first().cloned())
.unwrap_or(Value::Null)
} else {
Value::Null
}
}
_ => {
let frame: Vec<&Vec<Value>> = fpos
.iter()
.filter(|&&k| passes[ordered[k]])
.map(|&k| &arg_vals[ordered[k]])
.collect();
window_aggregate(lname, star, &frame)?
}
};
result[idx] = val;
}
Ok(())
}
fn order_collations(
&self,
sel: &Select,
columns: &[ColumnInfo],
params: &Params,
) -> Vec<crate::value::Collation> {
let ctx = row_ctx(&[], columns, None, params);
sel.order_by
.iter()
.map(|t| eval::key_collation(&t.expr, &ctx))
.collect()
}
fn output_collations(
&self,
sel: &Select,
columns: &[ColumnInfo],
params: &Params,
) -> Vec<crate::value::Collation> {
let ctx = row_ctx(&[], columns, None, params);
let mut out = Vec::new();
for col in &sel.columns {
match col {
ResultColumn::Expr { expr, .. } => out.push(eval::key_collation(expr, &ctx)),
ResultColumn::Wildcard => {
out.extend(columns.iter().map(|c| c.collation));
}
ResultColumn::TableWildcard(t) => out.extend(
columns
.iter()
.filter(|c| c.table.eq_ignore_ascii_case(t))
.map(|c| c.collation),
),
}
}
out
}
fn rowid_ordered_scan(&self, sel: &Select) -> Option<bool> {
let from = sel.from.as_ref()?;
if !from.joins.is_empty() {
return None;
}
let t = &from.first;
if t.subquery.is_some() || t.tvf_args.is_some() || t.schema.is_some() {
return None;
}
if sel.where_clause.is_some()
|| !sel.group_by.is_empty()
|| sel.having.is_some()
|| sel.distinct
|| sel.order_by.len() != 1
{
return None;
}
if self.has_aggregate(sel) || window::has_window(sel) {
return None;
}
let term = &sel.order_by[0];
let (tbl, col) = match &term.expr {
Expr::Column { table, column } => (table.as_deref(), column.as_str()),
_ => return None,
};
if self.lookup_cte(&t.name, None).is_some() || self.is_view(&t.name) {
return None;
}
let label = t.alias.as_deref().unwrap_or(&t.name);
if tbl.is_some_and(|tn| !tn.eq_ignore_ascii_case(label)) {
return None;
}
let meta = self.table_meta(&t.name, t.alias.as_deref()).ok()?;
if meta.without_rowid {
return None;
}
let shadowed = meta
.columns
.iter()
.any(|c| c.name.eq_ignore_ascii_case(col));
let is_rowid_alias = matches!(
col.to_ascii_lowercase().as_str(),
"rowid" | "_rowid_" | "oid"
) && !shadowed;
let is_ipk = meta
.ipk
.is_some_and(|i| meta.columns[i].name.eq_ignore_ascii_case(col));
if is_rowid_alias || is_ipk {
Some(term.descending)
} else {
None
}
}
fn order_index_scan(&self, sel: &Select) -> Option<OrderIndexScan> {
let from = sel.from.as_ref()?;
if !from.joins.is_empty() {
return None;
}
let t = &from.first;
if t.subquery.is_some() || t.tvf_args.is_some() || t.schema.is_some() {
return None;
}
if sel.where_clause.is_some()
|| !sel.group_by.is_empty()
|| sel.having.is_some()
|| sel.distinct
|| sel.order_by.is_empty()
{
return None;
}
if self.has_aggregate(sel) || window::has_window(sel) {
return None;
}
if self.lookup_cte(&t.name, None).is_some() || self.is_view(&t.name) {
return None;
}
let label = t.alias.as_deref().unwrap_or(&t.name);
let meta = self.table_meta(&t.name, t.alias.as_deref()).ok()?;
if meta.without_rowid {
return None;
}
let descending = sel.order_by[0].descending;
let mut cols: Vec<usize> = Vec::with_capacity(sel.order_by.len());
for term in &sel.order_by {
if term.descending != descending || term.nulls_first.is_some() {
return None;
}
let (tbl, col_name) = match &term.expr {
Expr::Column { table, column } => (table.as_deref(), column.as_str()),
_ => return None,
};
if tbl.is_some_and(|tn| !tn.eq_ignore_ascii_case(label)) {
return None;
}
let col = meta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(col_name))?;
cols.push(col);
}
if cols.len() == 1 && meta.ipk == Some(cols[0]) {
return None;
}
for idx in self.indexes_of(&t.name).ok()? {
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
if idx.cols.len() < cols.len() || idx.cols[..cols.len()] != cols[..] {
continue;
}
let coll_ok = cols
.iter()
.enumerate()
.all(|(i, &c)| idx.collations[i] == meta.columns[c].collation);
if !coll_ok {
continue;
}
let covering = self.index_covers_query(sel, &meta, &idx.cols);
return Some(OrderIndexScan {
name: idx.name,
root: idx.root,
colls: idx.collations,
cols: idx.cols,
descending,
covering,
});
}
None
}
fn seek_index_covers(
&self,
sel: &Select,
meta: &TableMeta,
idx_cols: &[usize],
where_expr: &Expr,
) -> bool {
if !self.index_covers_query(sel, meta, idx_cols) {
return false;
}
where_cols_covered(where_expr, meta, idx_cols)
}
fn covering_seek_rows(
&self,
meta: &TableMeta,
root: u32,
idx_cols: &[usize],
) -> Result<Vec<InputRow>> {
let src = self.backend.source();
let encoding = src.header().text_encoding;
let mut icur = IndexCursor::new(src, root);
let mut out = Vec::new();
while let Some(payload) = icur.next()? {
let rec = decode_record(&payload, encoding)?;
let rowid = match rec.get(idx_cols.len()) {
Some(Value::Integer(r)) => *r,
_ => return Err(Error::Corrupt("index record missing rowid".into())),
};
let mut values = alloc::vec![Value::Null; meta.columns.len()];
for (i, &mc) in idx_cols.iter().enumerate() {
values[mc] = rec[i].clone();
}
if let Some(ipk) = meta.ipk {
values[ipk] = Value::Integer(rowid);
}
out.push(InputRow {
values,
rowid: Some(rowid),
});
}
Ok(out)
}
fn index_covers_query(&self, sel: &Select, meta: &TableMeta, idx_cols: &[usize]) -> bool {
if meta.generated.iter().any(|g| g.is_some()) {
return false;
}
let covered = |ci: usize| idx_cols.contains(&ci) || meta.ipk == Some(ci);
let col_ok = |expr: &Expr| -> bool {
match expr {
Expr::Column { column, .. } => match meta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(column))
{
Some(ci) => covered(ci),
None => matches!(
column.to_ascii_lowercase().as_str(),
"rowid" | "_rowid_" | "oid"
),
},
_ => false,
}
};
for rc in &sel.columns {
match rc {
ResultColumn::Wildcard | ResultColumn::TableWildcard(_) => {
if !(0..meta.columns.len()).all(covered) {
return false;
}
}
ResultColumn::Expr { expr, .. } => {
if !col_ok(expr) {
return false;
}
}
}
}
sel.order_by.iter().all(|t| col_ok(&t.expr))
}
fn query_cols_covered(&self, sel: &Select, meta: &TableMeta, idx_cols: &[usize]) -> bool {
if meta.generated.iter().any(|g| g.is_some()) {
return false;
}
let covered_all =
(0..meta.columns.len()).all(|ci| idx_cols.contains(&ci) || meta.ipk == Some(ci));
for rc in &sel.columns {
match rc {
ResultColumn::Wildcard | ResultColumn::TableWildcard(_) => {
if !covered_all {
return false;
}
}
ResultColumn::Expr { expr, .. } => {
if !where_cols_covered(expr, meta, idx_cols) {
return false;
}
}
}
}
sel.group_by
.iter()
.all(|e| where_cols_covered(e, meta, idx_cols))
&& sel
.having
.as_ref()
.is_none_or(|h| where_cols_covered(h, meta, idx_cols))
&& sel
.order_by
.iter()
.all(|t| where_cols_covered(&t.expr, meta, idx_cols))
&& sel
.where_clause
.as_ref()
.is_none_or(|w| where_cols_covered(w, meta, idx_cols))
}
fn covering_scan(
&self,
sel: &Select,
meta: &TableMeta,
params: &Params,
) -> Option<(String, u32, Vec<usize>)> {
let from = sel.from.as_ref()?;
if !from.joins.is_empty() {
return None;
}
let t = &from.first;
if t.subquery.is_some() || t.tvf_args.is_some() || t.schema.is_some() {
return None;
}
if sel.where_clause.is_some() || window::has_window(sel) || meta.without_rowid {
return None;
}
if self.lookup_cte(&t.name, None).is_some() || self.is_view(&t.name) {
return None;
}
if self.order_satisfied_by_scan(sel, params).is_some() {
return None;
}
let mut covering = self.indexes_of(&t.name).ok()?.into_iter().filter(|idx| {
idx.partial.is_none()
&& idx.key_exprs.is_none()
&& self.query_cols_covered(sel, meta, &idx.cols)
});
let chosen = covering.next()?;
if covering.next().is_some() {
return None;
}
Some((chosen.name, chosen.root, chosen.cols))
}
fn count_covering_index(&self, sel: &Select) -> Option<(String, u32)> {
let from = sel.from.as_ref()?;
if !from.joins.is_empty() {
return None;
}
let t = &from.first;
if t.subquery.is_some() || t.tvf_args.is_some() || t.schema.is_some() {
return None;
}
if sel.where_clause.is_some()
|| !sel.group_by.is_empty()
|| sel.having.is_some()
|| sel.distinct
|| !sel.order_by.is_empty()
{
return None;
}
if window::has_window(sel) {
return None;
}
if sel.columns.len() != 1 {
return None;
}
let ResultColumn::Expr { expr, .. } = &sel.columns[0] else {
return None;
};
match expr {
Expr::Function {
name,
distinct: false,
star: true,
filter: None,
over: None,
..
} if name.eq_ignore_ascii_case("count") => {}
_ => return None,
}
if self.lookup_cte(&t.name, None).is_some() || self.is_view(&t.name) {
return None;
}
let meta = self.table_meta(&t.name, t.alias.as_deref()).ok()?;
if meta.without_rowid {
return None;
}
let mut chosen: Option<(String, u32)> = None;
for idx in self.indexes_of(&t.name).ok()? {
if idx.partial.is_some() || idx.key_exprs.is_some() {
continue;
}
if chosen.is_some() {
return None; }
chosen = Some((idx.name, idx.root));
}
chosen
}
fn order_satisfied_by_scan(&self, sel: &Select, params: &Params) -> Option<bool> {
if let Some(d) = self.rowid_ordered_scan(sel) {
return Some(d);
}
if let Some(s) = self.order_index_scan(sel) {
return Some(s.descending);
}
match self.seek_order_prefix(sel, params) {
Some((k, descending)) if k == sel.order_by.len() => Some(descending),
_ => None,
}
}
fn seek_order_prefix(&self, sel: &Select, params: &Params) -> Option<(usize, bool)> {
let from = sel.from.as_ref()?;
if !from.joins.is_empty() {
return None;
}
let t = &from.first;
if t.subquery.is_some()
|| t.tvf_args.is_some()
|| t.schema.is_some()
|| from.first.index_hint.is_some()
{
return None;
}
let where_expr = sel.where_clause.as_ref()?;
if sel.order_by.is_empty()
|| !sel.group_by.is_empty()
|| sel.having.is_some()
|| sel.distinct
|| self.has_aggregate(sel)
|| window::has_window(sel)
{
return None;
}
if self.lookup_cte(&t.name, None).is_some() || self.is_view(&t.name) {
return None;
}
let label = t.alias.as_deref().unwrap_or(&t.name);
let meta = self.table_meta(&t.name, t.alias.as_deref()).ok()?;
if meta.without_rowid {
return None;
}
let indexes = self.indexes_of(&t.name).ok()?;
let mut eqs: Vec<(usize, Value)> = Vec::new();
collect_eq_constraints(where_expr, &meta.columns, params, &mut eqs);
eqs.retain(|(_, v)| !matches!(v, Value::Null));
let (walk_cols, walk_colls): (&[usize], &[crate::value::Collation]) = if !eqs.is_empty() {
if meta
.ipk
.is_some_and(|ipk| eqs.iter().any(|(c, _)| *c == ipk))
{
return None;
}
let mut seekable = indexes.iter().filter(|idx| {
idx.partial.is_none()
&& idx.key_exprs.is_none()
&& idx
.cols
.first()
.is_some_and(|c| eqs.iter().any(|(col, _)| col == c))
});
let idx = seekable.next()?;
if seekable.next().is_some() {
return None;
}
let prefix = idx
.cols
.iter()
.take_while(|&&c| eqs.iter().any(|(col, _)| *col == c))
.count();
(&idx.cols[prefix..], &idx.collations[prefix..])
} else {
let mut ranges: alloc::collections::BTreeMap<usize, RangeBound> =
alloc::collections::BTreeMap::new();
collect_range_constraints(where_expr, &meta.columns, params, &mut ranges);
if ranges.is_empty() {
return None;
}
if meta.ipk.is_some_and(|ipk| ranges.contains_key(&ipk)) {
return None;
}
if indexes
.iter()
.any(|idx| idx.partial.is_some() || idx.key_exprs.is_some())
{
return None;
}
let mut seekable = indexes
.iter()
.filter(|idx| idx.cols.first().is_some_and(|c| ranges.contains_key(c)));
let idx = seekable.next()?;
if seekable.next().is_some() {
return None;
}
(&idx.cols[..], &idx.collations[..])
};
let descending = sel.order_by[0].descending;
let mut k = 0;
for term in &sel.order_by {
if k >= walk_cols.len() || term.descending != descending || term.nulls_first.is_some() {
break;
}
let (tbl, col_name) = match &term.expr {
Expr::Column { table, column } => (table.as_deref(), column.as_str()),
_ => break,
};
if tbl.is_some_and(|tn| !tn.eq_ignore_ascii_case(label)) {
break;
}
let Some(oc) = meta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(col_name))
else {
break;
};
if walk_cols[k] != oc || walk_colls[k] != meta.columns[oc].collation {
break;
}
k += 1;
}
Some((k, descending))
}
fn fts5_match_query(&self, expr: &Expr, params: &Params) -> Option<(String, String)> {
match expr {
Expr::Function { name, args, .. }
if name.eq_ignore_ascii_case("match") && args.len() == 2 =>
{
let v = eval::eval(&args[0], &eval::EvalCtx::rowless(params)).ok()?;
let operand = match &args[1] {
Expr::Column { column, .. } => column.clone(),
_ => return None,
};
Some((eval::to_text(&v), operand))
}
Expr::Binary { left, right, .. } => self
.fts5_match_query(left, params)
.or_else(|| self.fts5_match_query(right, params)),
Expr::Unary { expr, .. } | Expr::Paren(expr) => self.fts5_match_query(expr, params),
_ => None,
}
}
fn fts5_query_ctx(
&self,
sel: &Select,
columns: &[ColumnInfo],
input_rows: &[InputRow],
params: &Params,
) -> Option<Fts5QueryCtx> {
const AUX: &[&str] = &["rank", "bm25", "highlight"];
const RANK: &[&str] = &["rank", "bm25"];
if !select_mentions(sel, AUX) {
return None;
}
let from = sel.from.as_ref()?;
if !from.joins.is_empty()
|| from.first.subquery.is_some()
|| from.first.tvf_args.is_some()
|| from.first.schema.is_some()
{
return None;
}
let (module, _, _) = self.vtab_meta(&from.first.name).ok()?;
if !module.eq_ignore_ascii_case("fts5") {
return None;
}
let (query, operand) = self.fts5_match_query(sel.where_clause.as_ref()?, params)?;
let col_names: Vec<String> = columns.iter().map(|c| c.name.clone()).collect();
let scope = col_names
.iter()
.find(|n| n.eq_ignore_ascii_case(&operand))
.cloned();
let bm25 = select_mentions(sel, RANK).then(|| {
let docs: Vec<Vec<String>> = input_rows
.iter()
.map(|r| r.values.iter().map(eval::to_text).collect())
.collect();
let corpus = crate::vtab::fts5_bm25_corpus(&query, &col_names, &docs, scope.as_deref());
let index = input_rows
.iter()
.enumerate()
.filter_map(|(i, r)| Some((r.rowid?, i)))
.collect();
(corpus, index)
});
Some(Fts5QueryCtx {
col_names,
query,
scope,
bm25,
})
}
fn run_core(&self, sel: &Select, params: &Params) -> Result<QueryResult> {
let rewritten;
let sel = match promote_comma_join_ons(sel) {
Some(r) => {
rewritten = r;
&rewritten
}
None => sel,
};
if let Some((_, root)) = self.count_covering_index(sel) {
let mut cur = IndexCursor::new(self.backend.source(), root);
let mut n = 0i64;
while cur.next()?.is_some() {
n += 1;
}
let label = self.output_labels(sel, &[]).pop().unwrap_or_default();
return Ok(QueryResult {
columns: alloc::vec![label],
rows: alloc::vec![alloc::vec![Value::Integer(n)]],
});
}
let (mut columns, input_rows) = self.scan_source(sel, params)?;
let _fts5_rank_guard = Fts5RankGuard {
conn: self,
prev: core::mem::replace(
&mut *self.fts5_rank.borrow_mut(),
self.fts5_query_ctx(sel, &columns, &input_rows, params),
),
};
let alias_rewritten;
let sel = match alias_substituted(sel, &columns) {
Some(s) => {
alias_rewritten = s;
&alias_rewritten
}
None => sel,
};
let ncols = self.output_labels(sel, &columns).len();
check_positional_terms(&sel.group_by, &sel.order_by, ncols)?;
let mut rows: Vec<InputRow> = Vec::new();
for r in input_rows {
if let Some(pred) = &sel.where_clause {
let ctx = r.ctx(&columns, params).with_subqueries(self);
if eval::truth(&eval::eval(pred, &ctx)?) != Some(true) {
continue;
}
}
rows.push(r);
}
let rewritten;
let sel = if window::has_window(sel) {
if !sel.group_by.is_empty() || self.has_aggregate(sel) {
return Err(Error::Unsupported(
"window functions combined with GROUP BY / aggregates",
));
}
rewritten = self.apply_windows(sel, &mut columns, &mut rows, params)?;
&rewritten
} else {
sel
};
let aggregated = !sel.group_by.is_empty() || self.has_aggregate(sel);
if sel.having.is_some() && !aggregated {
return Err(Error::Error(
"HAVING clause on a non-aggregate query".into(),
));
}
let (out_labels, mut out) = if aggregated {
self.eval_aggregated(sel, &columns, rows, params)?
} else {
self.eval_simple(sel, &columns, rows, params)?
};
if sel.distinct {
let colls = self.output_collations(sel, &columns, params);
let mut seen: Vec<Vec<Value>> = Vec::new();
out.retain(|row| {
if seen.iter().any(|s| rows_equal_coll(s, &row.values, &colls)) {
false
} else {
seen.push(row.values.clone());
true
}
});
}
if !sel.order_by.is_empty() {
match self.order_satisfied_by_scan(sel, params) {
Some(true) => out.reverse(),
Some(false) => {}
None => {
let colls = self.order_collations(sel, &columns, params);
out.sort_by(|a, b| {
for (i, term) in sel.order_by.iter().enumerate() {
let ord = cmp_order(
&a.sort_keys[i],
&b.sort_keys[i],
term.descending,
term.nulls_first,
colls[i],
);
if ord != core::cmp::Ordering::Equal {
return ord;
}
}
core::cmp::Ordering::Equal
});
}
}
}
let offset = match &sel.offset {
Some(e) => must_be_int(eval::eval(
e,
&EvalCtx::rowless(params).with_subqueries(self),
)?)?
.max(0) as usize,
None => 0,
};
let limit = match &sel.limit {
Some(e) => {
let n = must_be_int(eval::eval(
e,
&EvalCtx::rowless(params).with_subqueries(self),
)?)?;
if n < 0 {
None
} else {
Some(n as usize)
}
}
None => None,
};
let mut final_rows: Vec<Vec<Value>> =
out.into_iter().skip(offset).map(|r| r.values).collect();
if let Some(n) = limit {
final_rows.truncate(n);
}
Ok(QueryResult {
columns: out_labels,
rows: final_rows,
})
}
fn scan_source(
&self,
sel: &Select,
params: &Params,
) -> Result<(Vec<ColumnInfo>, Vec<InputRow>)> {
let Some(from) = &sel.from else {
return Ok((
Vec::new(),
alloc::vec![InputRow {
values: Vec::new(),
rowid: None
}],
));
};
if from.joins.is_empty() && from.first.subquery.is_none() && from.first.tvf_args.is_none() {
if from.first.schema.is_none() && is_temp_schema_table(&from.first.name) {
let alias = from.first.alias.as_deref();
return match &self.temp_db {
Some(_) => self.scan_db_table(DbRef::Temp, "sqlite_master", alias),
None => Ok((
schema_table_meta(alias.unwrap_or(&from.first.name)).columns,
Vec::new(),
)),
};
}
let db = match from.first.schema.as_deref() {
Some(_) => self.resolve_db(from.first.schema.as_deref())?,
None if self.lookup_cte(&from.first.name, None).is_none()
&& !self.is_view(&from.first.name) =>
{
self.unqualified_db(&from.first.name)
}
None => DbRef::Main,
};
if db != DbRef::Main {
let alias = from.first.alias.as_deref();
if let Some(r) = self.scan_db_view(db, &from.first.name, alias, params)? {
return Ok(r);
}
return self.scan_db_table(db, &from.first.name, alias);
}
}
if from.joins.is_empty()
&& (from.first.tvf_args.is_some() || self.is_pragma_tvf(&from.first))
{
let (columns, rows) = self.tvf_rows(&from.first, params)?;
let input = rows
.into_iter()
.map(|values| InputRow {
values,
rowid: None,
})
.collect();
return Ok((columns, input));
}
if from.joins.is_empty() {
if let Some(sub) = &from.first.subquery {
let (columns, rows) =
self.run_subquery_source(sub, from.first.alias.as_deref(), params)?;
let input = rows
.into_iter()
.map(|values| InputRow {
values,
rowid: None,
})
.collect();
return Ok((columns, input));
}
}
if from.joins.is_empty() {
if let Some((columns, rows)) =
self.lookup_cte(&from.first.name, from.first.alias.as_deref())
{
return Ok((columns, rows));
}
}
if from.joins.is_empty() {
if let Some((columns, rows)) =
self.try_view(&from.first.name, from.first.alias.as_deref(), params)?
{
return Ok((columns, rows));
}
}
if from.joins.is_empty() && from.first.schema.is_none() {
if let Some((columns, rows)) = self.try_virtual_table(
&from.first.name,
from.first.alias.as_deref(),
Some((sel, params)),
)? {
return Ok((columns, rows));
}
}
if from.joins.is_empty() {
let first_meta = self.table_meta(&from.first.name, from.first.alias.as_deref())?;
if first_meta.without_rowid {
if let Some(rows) = self.try_without_rowid_pk_seek(&first_meta, sel, params)? {
return Ok((first_meta.columns, rows));
}
if let Some(rows) = self.try_without_rowid_pk_range(&first_meta, sel, params)? {
return Ok((first_meta.columns, rows));
}
if let Some(rows) =
self.try_without_rowid_index_seek(&first_meta, &from.first.name, sel, params)?
{
return Ok((first_meta.columns, rows));
}
if let Some(rows) =
self.try_without_rowid_index_range(&first_meta, &from.first.name, sel, params)?
{
return Ok((first_meta.columns, rows));
}
let input_rows = self
.scan_without_rowid(&first_meta)?
.into_iter()
.map(|values| InputRow {
values,
rowid: None,
})
.collect();
return Ok((first_meta.columns, input_rows));
}
if let Some(rows) = self.try_index_lookup(&first_meta, &from.first.name, sel, params)? {
return Ok((first_meta.columns, rows));
}
if let Some(rows) = self.try_index_range(&first_meta, &from.first.name, sel, params)? {
return Ok((first_meta.columns, rows));
}
if let Some(rows) = self.try_index_in(&first_meta, &from.first.name, sel, params)? {
return Ok((first_meta.columns, rows));
}
if let Some(rows) = self.try_index_or(&first_meta, &from.first.name, sel, params)? {
return Ok((first_meta.columns, rows));
}
if let Some(s) = self.order_index_scan(sel) {
let src = self.backend.source();
let encoding = src.header().text_encoding;
if s.covering {
let mut icur = IndexCursor::new(src, s.root);
let mut input_rows = Vec::new();
while let Some(payload) = icur.next()? {
let rec = decode_record(&payload, encoding)?;
let rowid = match rec.get(s.cols.len()) {
Some(Value::Integer(r)) => *r,
_ => return Err(Error::Corrupt("index record missing rowid".into())),
};
let mut values = alloc::vec![Value::Null; first_meta.columns.len()];
for (i, &mc) in s.cols.iter().enumerate() {
values[mc] = rec[i].clone();
}
if let Some(ipk) = first_meta.ipk {
values[ipk] = Value::Integer(rowid);
}
input_rows.push(InputRow {
values,
rowid: Some(rowid),
});
}
return Ok((first_meta.columns, input_rows));
}
let rowids = crate::btree::index_range_rowids(src, s.root, None, None, &s.colls)?;
let mut cur = TableCursor::new(src, first_meta.root);
let mut input_rows = Vec::with_capacity(rowids.len());
for rid in rowids {
if cur.seek(rid)? {
let values =
self.decode_full_row(&first_meta, rid, &cur.payload()?, encoding)?;
input_rows.push(InputRow {
values,
rowid: Some(rid),
});
}
}
return Ok((first_meta.columns, input_rows));
}
if let Some((_, root, cols)) = self.covering_scan(sel, &first_meta, params) {
return Ok((
first_meta.columns.clone(),
self.covering_seek_rows(&first_meta, root, &cols)?,
));
}
let input_rows = self
.scan_table(&first_meta)?
.into_iter()
.map(|(rowid, values)| InputRow {
values,
rowid: Some(rowid),
})
.collect();
return Ok((first_meta.columns, input_rows));
}
let (mut columns, mut rows) = self.resolve_join_source(&from.first, params)?;
for join in &from.joins {
if let Some((outer_col, inner_meta)) = self.rowid_join_seek(join, &columns) {
let (new_columns, joined) = self.exec_rowid_join_seek(
join,
&columns,
&rows,
outer_col,
&inner_meta,
params,
)?;
columns = new_columns;
rows = joined;
continue;
}
if let Some((outer_col, inner_meta, idx)) = self.index_join_seek(join, &columns) {
let (new_columns, joined) = self.exec_index_join_seek(
join,
&columns,
&rows,
outer_col,
&inner_meta,
&idx,
params,
)?;
columns = new_columns;
rows = joined;
continue;
}
if let Some((outer_col, inner_meta)) = self.without_rowid_pk_join_seek(join, &columns) {
let (new_columns, joined) = self.exec_without_rowid_pk_join_seek(
join,
&columns,
&rows,
outer_col,
&inner_meta,
params,
)?;
columns = new_columns;
rows = joined;
continue;
}
let (jcols, jrows) = self.resolve_join_source(&join.table, params)?;
let left_width = columns.len();
let pairs: Vec<(usize, usize)> = if join.natural {
jcols
.iter()
.enumerate()
.filter_map(|(rl, rc)| {
columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(&rc.name))
.map(|li| (li, rl))
})
.collect()
} else if !join.using.is_empty() {
let mut v = Vec::with_capacity(join.using.len());
for name in &join.using {
let li = columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(name));
let rl = jcols.iter().position(|c| c.name.eq_ignore_ascii_case(name));
match (li, rl) {
(Some(li), Some(rl)) => v.push((li, rl)),
_ => {
return Err(Error::Error(format!(
"cannot join using column {name} - column not present in both tables"
)))
}
}
}
v
} else {
Vec::new()
};
let mut new_columns = columns.clone();
new_columns.extend(jcols.iter().cloned());
let n_jcols = jcols.len();
let mut joined: Vec<Vec<Value>> = Vec::new();
let mut right_matched = alloc::vec![false; jrows.len()];
let equi = if pairs.is_empty() {
join.on
.as_ref()
.and_then(|on| join_equi_cols(on, &new_columns, left_width))
} else {
None
};
let hash: Option<(usize, alloc::collections::BTreeMap<JoinKey, Vec<usize>>)> = equi
.map(|(li, ri_local)| {
let mut map: alloc::collections::BTreeMap<JoinKey, Vec<usize>> =
alloc::collections::BTreeMap::new();
for (ri, right) in jrows.iter().enumerate() {
for k in join_keys_of(&right[ri_local]) {
map.entry(k).or_default().push(ri);
}
}
(li, map)
});
for left in &rows {
let mut matched = false;
let candidates: Vec<usize> = match &hash {
Some((li, map)) => {
let mut c: Vec<usize> = Vec::new();
for k in join_keys_of(&left[*li]) {
if let Some(idxs) = map.get(&k) {
c.extend_from_slice(idxs);
}
}
c.sort_unstable();
c.dedup();
c
}
None => (0..jrows.len()).collect(),
};
for ri in candidates {
let right = &jrows[ri];
let mut combined = left.clone();
combined.extend(right.iter().cloned());
let keep = if !pairs.is_empty() {
pairs.iter().all(|&(li, rl)| {
let coll = new_columns[li].collation;
eval::truth(&eval::compare_op(
BinaryOp::Eq,
&combined[li],
&combined[left_width + rl],
coll,
)) == Some(true)
})
} else {
match &join.on {
Some(on) => {
let ctx = row_ctx(&combined, &new_columns, None, params);
eval::truth(&eval::eval(on, &ctx)?) == Some(true)
}
None => true, }
};
if keep {
joined.push(combined);
matched = true;
right_matched[ri] = true;
}
}
if !matched && matches!(join.kind, JoinKind::Left | JoinKind::Full) {
let mut combined = left.clone();
combined.extend(core::iter::repeat_n(Value::Null, n_jcols));
joined.push(combined);
}
}
if matches!(join.kind, JoinKind::Right | JoinKind::Full) {
for (ri, right) in jrows.iter().enumerate() {
if !right_matched[ri] {
let mut combined = alloc::vec![Value::Null; left_width];
combined.extend(right.iter().cloned());
joined.push(combined);
}
}
}
if !pairs.is_empty() {
let mut drop: Vec<usize> = pairs.iter().map(|&(_, rl)| left_width + rl).collect();
drop.sort_unstable();
drop.dedup();
for row in &mut joined {
for &(li, rl) in &pairs {
if matches!(row[li], Value::Null) {
row[li] = row[left_width + rl].clone();
}
}
for &d in drop.iter().rev() {
row.remove(d);
}
}
for &d in drop.iter().rev() {
new_columns.remove(d);
}
}
columns = new_columns;
rows = joined;
}
let input_rows = rows
.into_iter()
.map(|values| InputRow {
values,
rowid: None, })
.collect();
Ok((columns, input_rows))
}
fn is_pragma_tvf(&self, tref: &TableRef) -> bool {
tref.tvf_args.is_none()
&& tref.subquery.is_none()
&& tref.schema.is_none()
&& tref.name.to_ascii_lowercase().starts_with("pragma_")
&& self.lookup_cte(&tref.name, None).is_none()
&& !self.is_view(&tref.name)
&& self.unqualified_db(&tref.name) == DbRef::Main
&& self.schema.table(&tref.name).is_none()
}
fn tvf_rows(
&self,
tref: &TableRef,
params: &Params,
) -> Result<(Vec<ColumnInfo>, Vec<Vec<Value>>)> {
let args = tref.tvf_args.as_deref().unwrap_or(&[]);
let lname = tref.name.to_ascii_lowercase();
let label = tref.alias.clone().unwrap_or_else(|| tref.name.clone());
let ctx = EvalCtx::rowless(params).with_subqueries(self);
let col = |name: &str, affinity| ColumnInfo {
name: String::from(name),
table: label.clone(),
affinity,
collation: crate::value::Collation::default(),
};
match lname.as_str() {
"generate_series" => {
if args.is_empty() {
return Err(Error::Error("generate_series() requires arguments".into()));
}
let nums: Vec<i64> = args
.iter()
.map(|a| eval::eval(a, &ctx).map(|v| eval::to_i64(&v)))
.collect::<Result<_>>()?;
let start = nums[0];
let stop = nums.get(1).copied().unwrap_or(start);
let step = match nums.get(2).copied().unwrap_or(1) {
0 => 1,
s => s,
};
let mut rows = Vec::new();
if step != 0 {
let mut v = start;
loop {
let in_range = if step > 0 { v <= stop } else { v >= stop };
if !in_range {
break;
}
rows.push(alloc::vec![Value::Integer(v)]);
match v.checked_add(step) {
Some(n) => v = n,
None => break, }
}
}
Ok((alloc::vec![col("value", eval::Affinity::Integer)], rows))
}
"json_each" | "json_tree" => {
let columns = alloc::vec![
col("key", eval::Affinity::Blob),
col("value", eval::Affinity::Blob),
col("type", eval::Affinity::Text),
col("atom", eval::Affinity::Blob),
col("id", eval::Affinity::Integer),
col("parent", eval::Affinity::Integer),
col("fullkey", eval::Affinity::Text),
col("path", eval::Affinity::Text),
];
let Some(doc_arg) = args.first() else {
return Err(Error::Error(format!("{lname}() requires a JSON argument")));
};
let doc = eval::eval(doc_arg, &ctx)?;
if matches!(doc, Value::Null) {
return Ok((columns, Vec::new()));
}
let Some(root) = crate::exec::json::parse(&eval::to_text(&doc)) else {
return Err(Error::Error("malformed JSON".into()));
};
let (target, root_path) = match args.get(1) {
Some(path_arg) => {
let p = eval::to_text(&eval::eval(path_arg, &ctx)?);
match crate::exec::json::navigate(&root, &p) {
Some(sub) => (sub, p),
None => return Ok((columns, Vec::new())),
}
}
None => (&root, String::from("$")),
};
let mut rows = Vec::new();
let mut next_id = 0i64;
if lname == "json_tree" {
let (parent_path, key) = split_json_path(&root_path);
json_tree_walk(
target,
key,
&root_path,
&parent_path,
None,
&mut next_id,
&mut rows,
);
} else {
json_each_children(target, &root_path, &mut next_id, &mut rows);
}
Ok((columns, rows))
}
pragma if pragma.starts_with("pragma_") => {
let p = Pragma {
name: String::from(&pragma["pragma_".len()..]),
value: args.first().cloned(),
};
let result = self.run_pragma(&p)?;
let columns = result
.columns
.iter()
.map(|n| col(n, eval::Affinity::Blob))
.collect();
Ok((columns, result.rows))
}
_ => Err(Error::Error(format!(
"no such table-valued function: {}",
tref.name
))),
}
}
fn run_subquery_source(
&self,
select: &Select,
alias: Option<&str>,
params: &Params,
) -> Result<(Vec<ColumnInfo>, Vec<Vec<Value>>)> {
let result = self.run_select(select, params)?;
let label = alias.unwrap_or("").to_string();
let columns = result
.columns
.iter()
.map(|n| ColumnInfo {
name: n.clone(),
table: label.clone(),
affinity: eval::Affinity::Blob,
collation: crate::value::Collation::default(),
})
.collect();
Ok((columns, result.rows))
}
fn rowid_join_seek(
&self,
join: &Join,
left_columns: &[ColumnInfo],
) -> Option<(usize, TableMeta)> {
if !matches!(join.kind, JoinKind::Inner | JoinKind::Left)
|| join.natural
|| !join.using.is_empty()
{
return None;
}
let on = join.on.as_ref()?;
let tref = &join.table;
if tref.subquery.is_some()
|| tref.tvf_args.is_some()
|| self.is_pragma_tvf(tref)
|| tref.schema.is_some()
|| self.lookup_cte(&tref.name, tref.alias.as_deref()).is_some()
|| self.is_view(&tref.name)
|| self.unqualified_db(&tref.name) != DbRef::Main
{
return None;
}
let meta = self.table_meta(&tref.name, tref.alias.as_deref()).ok()?;
let ipk = meta.ipk?;
let mut on = on;
while let Expr::Paren(inner) = on {
on = inner;
}
let left_width = left_columns.len();
let mut combined = left_columns.to_vec();
combined.extend(meta.columns.iter().cloned());
let (a, b) = match on {
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} => (col_index(left, &combined)?, col_index(right, &combined)?),
_ => return None,
};
let inner_ipk = left_width + ipk;
let outer = if a == inner_ipk && b < left_width {
b
} else if b == inner_ipk && a < left_width {
a
} else {
return None;
};
Some((outer, meta))
}
fn index_join_seek(
&self,
join: &Join,
left_columns: &[ColumnInfo],
) -> Option<(usize, TableMeta, IndexMeta)> {
if !matches!(join.kind, JoinKind::Inner | JoinKind::Left)
|| join.natural
|| !join.using.is_empty()
{
return None;
}
let on = join.on.as_ref()?;
let tref = &join.table;
if tref.subquery.is_some()
|| tref.tvf_args.is_some()
|| self.is_pragma_tvf(tref)
|| tref.schema.is_some()
|| self.lookup_cte(&tref.name, tref.alias.as_deref()).is_some()
|| self.is_view(&tref.name)
|| self.unqualified_db(&tref.name) != DbRef::Main
{
return None;
}
let meta = self.table_meta(&tref.name, tref.alias.as_deref()).ok()?;
if meta.without_rowid {
return None;
}
let mut on = on;
while let Expr::Paren(inner) = on {
on = inner;
}
let left_width = left_columns.len();
let mut combined = left_columns.to_vec();
combined.extend(meta.columns.iter().cloned());
let (a, b) = match on {
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} => (col_index(left, &combined)?, col_index(right, &combined)?),
_ => return None,
};
let (inner_idx, outer) = if a >= left_width && b < left_width {
(a - left_width, b)
} else if b >= left_width && a < left_width {
(b - left_width, a)
} else {
return None;
};
let indexes = self.indexes_of(&tref.name).ok()?;
let idx = indexes.into_iter().find(|i| {
i.partial.is_none() && i.key_exprs.is_none() && i.cols.first() == Some(&inner_idx)
})?;
Some((outer, meta, idx))
}
fn without_rowid_pk_join_seek(
&self,
join: &Join,
left_columns: &[ColumnInfo],
) -> Option<(usize, TableMeta)> {
if !matches!(join.kind, JoinKind::Inner | JoinKind::Left)
|| join.natural
|| !join.using.is_empty()
{
return None;
}
let on = join.on.as_ref()?;
let tref = &join.table;
if tref.subquery.is_some()
|| tref.tvf_args.is_some()
|| self.is_pragma_tvf(tref)
|| tref.schema.is_some()
|| self.lookup_cte(&tref.name, tref.alias.as_deref()).is_some()
|| self.is_view(&tref.name)
|| self.unqualified_db(&tref.name) != DbRef::Main
{
return None;
}
let meta = self.table_meta(&tref.name, tref.alias.as_deref()).ok()?;
if !meta.without_rowid || meta.pk_len == 0 {
return None;
}
let lead_pk = meta.storage_order[0];
let mut on = on;
while let Expr::Paren(inner) = on {
on = inner;
}
let left_width = left_columns.len();
let mut combined = left_columns.to_vec();
combined.extend(meta.columns.iter().cloned());
let (a, b) = match on {
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} => (col_index(left, &combined)?, col_index(right, &combined)?),
_ => return None,
};
let (inner_idx, outer) = if a >= left_width && b < left_width {
(a - left_width, b)
} else if b >= left_width && a < left_width {
(b - left_width, a)
} else {
return None;
};
if inner_idx != lead_pk {
return None;
}
Some((outer, meta))
}
fn exec_without_rowid_pk_join_seek(
&self,
join: &Join,
columns: &[ColumnInfo],
rows: &[Vec<Value>],
outer_col: usize,
inner_meta: &TableMeta,
params: &Params,
) -> Result<(Vec<ColumnInfo>, Vec<Vec<Value>>)> {
let mut new_columns = columns.to_vec();
new_columns.extend(inner_meta.columns.iter().cloned());
let n_jcols = inner_meta.columns.len();
let on = join.on.as_ref();
let is_left = matches!(join.kind, JoinKind::Left);
let lead = inner_meta.storage_order[0];
let coll = wr_storage_collations(inner_meta)[0];
let src = self.backend.source();
let mut joined: Vec<Vec<Value>> = Vec::new();
for left in rows {
let mut matched = false;
if !matches!(left[outer_col], Value::Null) {
let key = [inner_meta.columns[lead]
.affinity
.coerce(left[outer_col].clone())];
let records =
crate::btree::index_seek_records(src, inner_meta.root, &key, &[coll])?;
for storage in records {
let mut inner = unpermute_row(inner_meta, storage);
self.compute_generated(inner_meta, &mut inner, params)?;
let mut row = left.clone();
row.extend(inner);
let keep = match on {
Some(on) => {
let ctx = row_ctx(&row, &new_columns, None, params);
eval::truth(&eval::eval(on, &ctx)?) == Some(true)
}
None => true,
};
if keep {
joined.push(row);
matched = true;
}
}
}
if !matched && is_left {
let mut combined = left.clone();
combined.extend(core::iter::repeat_n(Value::Null, n_jcols));
joined.push(combined);
}
}
Ok((new_columns, joined))
}
#[allow(clippy::too_many_arguments)]
fn exec_index_join_seek(
&self,
join: &Join,
columns: &[ColumnInfo],
rows: &[Vec<Value>],
outer_col: usize,
inner_meta: &TableMeta,
idx: &IndexMeta,
params: &Params,
) -> Result<(Vec<ColumnInfo>, Vec<Vec<Value>>)> {
let encoding = self.backend.source().header().text_encoding;
let mut new_columns = columns.to_vec();
new_columns.extend(inner_meta.columns.iter().cloned());
let n_jcols = inner_meta.columns.len();
let on = join.on.as_ref();
let is_left = matches!(join.kind, JoinKind::Left);
let lead = idx.cols[0];
let coll = idx.collations[0];
let src = self.backend.source();
let mut cur = TableCursor::new(self.backend.source(), inner_meta.root);
let mut joined: Vec<Vec<Value>> = Vec::new();
for left in rows {
let mut matched = false;
if !matches!(left[outer_col], Value::Null) {
let key = [inner_meta.columns[lead]
.affinity
.coerce(left[outer_col].clone())];
let colls = [coll];
let rowids = crate::btree::index_seek_rowids(src, idx.root, &key, &colls)?;
for rid in rowids {
if cur.seek(rid)? {
let inner =
self.decode_full_row(inner_meta, rid, &cur.payload()?, encoding)?;
let mut row = left.clone();
row.extend(inner);
let keep = match on {
Some(on) => {
let ctx = row_ctx(&row, &new_columns, None, params);
eval::truth(&eval::eval(on, &ctx)?) == Some(true)
}
None => true,
};
if keep {
joined.push(row);
matched = true;
}
}
}
}
if !matched && is_left {
let mut combined = left.clone();
combined.extend(core::iter::repeat_n(Value::Null, n_jcols));
joined.push(combined);
}
}
Ok((new_columns, joined))
}
fn exec_rowid_join_seek(
&self,
join: &Join,
columns: &[ColumnInfo],
rows: &[Vec<Value>],
outer_col: usize,
inner_meta: &TableMeta,
params: &Params,
) -> Result<(Vec<ColumnInfo>, Vec<Vec<Value>>)> {
let encoding = self.backend.source().header().text_encoding;
let mut new_columns = columns.to_vec();
new_columns.extend(inner_meta.columns.iter().cloned());
let n_jcols = inner_meta.columns.len();
let on = join.on.as_ref();
let is_left = matches!(join.kind, JoinKind::Left);
let mut cur = TableCursor::new(self.backend.source(), inner_meta.root);
let mut joined: Vec<Vec<Value>> = Vec::new();
for left in rows {
let key = &left[outer_col];
let candidate = match key {
Value::Integer(i) => Some(*i),
Value::Real(_) | Value::Text(_) => match eval::to_number(key) {
Value::Integer(i) => Some(i),
Value::Real(r) if r == (r as i64) as f64 => Some(r as i64),
_ => None,
},
Value::Null | Value::Blob(_) => None,
};
let mut matched = false;
if let Some(rid) = candidate {
if cur.seek(rid)? {
let inner = self.decode_full_row(inner_meta, rid, &cur.payload()?, encoding)?;
let mut combined = left.clone();
combined.extend(inner);
let keep = match on {
Some(on) => {
let ctx = row_ctx(&combined, &new_columns, None, params);
eval::truth(&eval::eval(on, &ctx)?) == Some(true)
}
None => true,
};
if keep {
joined.push(combined);
matched = true;
}
}
}
if !matched && is_left {
let mut combined = left.clone();
combined.extend(core::iter::repeat_n(Value::Null, n_jcols));
joined.push(combined);
}
}
Ok((new_columns, joined))
}
fn resolve_join_source(
&self,
tref: &TableRef,
params: &Params,
) -> Result<(Vec<ColumnInfo>, Vec<Vec<Value>>)> {
if tref.tvf_args.is_some() || self.is_pragma_tvf(tref) {
return self.tvf_rows(tref, params);
}
if let Some(sub) = &tref.subquery {
return self.run_subquery_source(sub, tref.alias.as_deref(), params);
}
if let Some((cols, rows)) = self.lookup_cte(&tref.name, tref.alias.as_deref()) {
return Ok((cols, rows.into_iter().map(|r| r.values).collect()));
}
if let Some((cols, rows)) = self.try_view(&tref.name, tref.alias.as_deref(), params)? {
return Ok((cols, rows.into_iter().map(|r| r.values).collect()));
}
if tref.schema.is_none() {
if let Some((cols, rows)) =
self.try_virtual_table(&tref.name, tref.alias.as_deref(), None)?
{
return Ok((cols, rows.into_iter().map(|r| r.values).collect()));
}
}
let db = match tref.schema.as_deref() {
Some(_) => self.resolve_db(tref.schema.as_deref())?,
None => self.unqualified_db(&tref.name),
};
if db != DbRef::Main {
if let Some((cols, input)) =
self.scan_db_view(db, &tref.name, tref.alias.as_deref(), params)?
{
return Ok((cols, input.into_iter().map(|r| r.values).collect()));
}
let (cols, input) = self.scan_db_table(db, &tref.name, tref.alias.as_deref())?;
return Ok((cols, input.into_iter().map(|r| r.values).collect()));
}
let meta = self.table_meta(&tref.name, tref.alias.as_deref())?;
let rows = if meta.without_rowid {
self.scan_without_rowid(&meta)?
} else {
self.scan_table(&meta)?
.into_iter()
.map(|(_, v)| v)
.collect()
};
Ok((meta.columns, rows))
}
fn scan_without_rowid(&self, meta: &TableMeta) -> Result<Vec<Vec<Value>>> {
let encoding = self.backend.source().header().text_encoding;
let mut cur = IndexCursor::new(self.backend.source(), meta.root);
let params = Params::default();
let mut out = Vec::new();
while let Some(payload) = cur.next()? {
let storage = decode_record(&payload, encoding)?;
let mut row = unpermute_row(meta, storage);
self.compute_generated(meta, &mut row, ¶ms)?;
out.push(row);
}
Ok(out)
}
fn build_insert_row(
&self,
meta: &TableMeta,
target: &[usize],
row_exprs: &[Expr],
params: &Params,
) -> Result<Vec<Value>> {
let ctx = EvalCtx::rowless(params).with_subqueries(self);
let mut values: Vec<Value> = meta
.defaults
.iter()
.map(|d| match d {
Some(e) => eval::eval(e, &ctx),
None => Ok(Value::Null),
})
.collect::<Result<_>>()?;
for (i, e) in row_exprs.iter().enumerate() {
if meta.is_generated(target[i]) {
return Err(Error::Error(format!(
"cannot INSERT into generated column \"{}\"",
meta.columns[target[i]].name
)));
}
values[target[i]] = eval::eval(e, &ctx)?;
}
apply_column_affinity(meta, &mut values);
self.materialize_generated(meta, &mut values, params)?;
self.check_strict_types(meta, &values)?;
Ok(values)
}
fn exec_insert_without_rowid(
&mut self,
ins: &Insert,
meta: &TableMeta,
rows: &[Vec<Expr>],
params: &Params,
) -> Result<usize> {
let n_cols = meta.columns.len();
let target: Vec<usize> = if ins.columns.is_empty() {
(0..n_cols).collect()
} else {
ins.columns
.iter()
.map(|name| {
meta.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(name))
.ok_or_else(|| Error::Error(format!("no such column: {name}")))
})
.collect::<Result<_>>()?
};
let pk = &meta.storage_order[..meta.pk_len];
let mut affected = 0;
for row_exprs in rows {
if !ins.columns.is_empty() && row_exprs.len() != target.len() {
return Err(Error::Error("INSERT column/value count mismatch".into()));
}
let values = self.build_insert_row(meta, &target, row_exprs, params)?;
{
let r = (|| {
for &c in pk {
if matches!(values[c], Value::Null) {
return Err(Error::Constraint("NOT NULL constraint failed".into()));
}
}
check_not_null(meta, &values)?;
self.check_constraints(meta, &values, None, params)
})();
match r {
Ok(()) => {}
Err(Error::Constraint(_)) if ins.on_conflict == OnConflict::Ignore => continue,
Err(e) => return Err(e),
}
}
let existing = self.scan_without_rowid(meta)?;
let mut collide = Vec::new();
for (i, r) in existing.iter().enumerate() {
if unique_match(meta, r, &values)
|| self.wr_index_collision(&ins.table, meta, r, &values, params)?
{
collide.push(i);
}
}
if !collide.is_empty() {
match ins.on_conflict {
oc @ (OnConflict::Abort | OnConflict::Fail | OnConflict::Rollback) => {
return Err(self.conflict_error(oc, "UNIQUE constraint failed"))
}
OnConflict::Ignore => continue,
OnConflict::Replace => {
let kept: Vec<Vec<Value>> = existing
.into_iter()
.enumerate()
.filter(|(i, _)| !collide.contains(i))
.map(|(_, r)| r)
.collect();
self.rewrite_without_rowid(meta, kept.into_iter())?;
}
}
}
let record = encode_record(&permute_row(meta, &values));
let scolls = wr_storage_collations(meta);
insert_index(self.backend.writer()?, meta.root, &record, &scolls)?;
affected += 1;
}
if affected > 0 {
self.rebuild_wr_indexes(meta, &ins.table)?;
}
Ok(affected)
}
fn exec_delete_without_rowid(
&mut self,
del: &Delete,
meta: &TableMeta,
params: &Params,
) -> Result<usize> {
let all = self.scan_without_rowid(meta)?;
let mut kept = Vec::new();
let mut deleted = 0;
for row in all {
let keep = match &del.where_clause {
Some(p) => {
let ctx = row_ctx(&row, &meta.columns, None, params).with_subqueries(self);
eval::truth(&eval::eval(p, &ctx)?) != Some(true)
}
None => false,
};
if keep {
kept.push(row);
} else {
deleted += 1;
}
}
if deleted > 0 {
self.rewrite_without_rowid(meta, kept.into_iter())?;
self.rebuild_wr_indexes(meta, &del.table)?;
}
Ok(deleted)
}
fn exec_update_without_rowid(
&mut self,
upd: &Update,
meta: &TableMeta,
params: &Params,
) -> Result<usize> {
let all = self.scan_without_rowid(meta)?;
let mut out = Vec::with_capacity(all.len());
let mut affected = 0;
for mut row in all {
let matches = match &upd.where_clause {
Some(p) => {
let ctx = row_ctx(&row, &meta.columns, None, params).with_subqueries(self);
eval::truth(&eval::eval(p, &ctx)?) == Some(true)
}
None => true,
};
if matches {
let original = row.clone();
for (col, expr) in &upd.assignments {
let pos = meta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(col))
.ok_or_else(|| Error::Error(format!("no such column: {col}")))?;
if meta.is_generated(pos) {
return Err(Error::Error(format!(
"cannot UPDATE generated column \"{col}\""
)));
}
let ctx = row_ctx(&original, &meta.columns, None, params).with_subqueries(self);
row[pos] = eval::eval(expr, &ctx)?;
}
apply_column_affinity(meta, &mut row);
self.materialize_generated(meta, &mut row, params)?;
check_not_null(meta, &row)?;
self.check_strict_types(meta, &row)?;
self.check_constraints(meta, &row, None, params)?;
affected += 1;
}
out.push(row);
}
for i in 0..out.len() {
for j in (i + 1)..out.len() {
if unique_match(meta, &out[i], &out[j])
|| self.wr_index_collision(&upd.table, meta, &out[i], &out[j], params)?
{
return Err(Error::Constraint("UNIQUE constraint failed".into()));
}
}
}
if affected > 0 {
self.rewrite_without_rowid(meta, out.into_iter())?;
self.rebuild_wr_indexes(meta, &upd.table)?;
}
Ok(affected)
}
fn rewrite_without_rowid(
&mut self,
meta: &TableMeta,
rows: impl Iterator<Item = Vec<Value>>,
) -> Result<()> {
let records: Vec<Vec<u8>> = rows
.map(|r| encode_record(&permute_row(meta, &r)))
.collect();
let scolls = wr_storage_collations(meta);
let w = self.backend.writer()?;
clear_index(w, meta.root)?;
for rec in &records {
insert_index(w, meta.root, rec, &scolls)?;
}
Ok(())
}
fn rebuild_wr_indexes(&mut self, meta: &TableMeta, table: &str) -> Result<()> {
let indexes = self.indexes_of(table)?;
if indexes.is_empty() {
return Ok(());
}
let rows = self.scan_without_rowid(meta)?;
let pk_cols = meta.storage_order[..meta.pk_len].to_vec();
let pk_colls: Vec<crate::value::Collation> =
pk_cols.iter().map(|&c| meta.columns[c].collation).collect();
let mut keep: Vec<Vec<usize>> = Vec::with_capacity(indexes.len());
for idx in &indexes {
let mut ks = Vec::new();
for (i, row) in rows.iter().enumerate() {
if self.row_in_index(idx, meta, row, None, &Params::default())? {
ks.push(i);
}
}
keep.push(ks);
}
let w = self.backend.writer()?;
for (idx, ks) in indexes.iter().zip(&keep) {
let mut key_colls = idx.collations.clone();
key_colls.extend(pk_colls.iter().copied());
clear_index(w, idx.root)?;
for &i in ks {
insert_index(
w,
idx.root,
&wr_index_key(&idx.cols, &pk_cols, &rows[i]),
&key_colls,
)?;
}
}
Ok(())
}
fn table_is_empty(&self, table: &str) -> Result<bool> {
let meta = self.table_meta(table, None)?;
if meta.without_rowid {
Ok(self.scan_without_rowid(&meta)?.is_empty())
} else {
Ok(self.scan_table(&meta)?.is_empty())
}
}
fn resolve_db(&self, schema: Option<&str>) -> Result<DbRef> {
match schema {
None => Ok(DbRef::Main),
Some(s) if s.eq_ignore_ascii_case("main") => Ok(DbRef::Main),
Some(s) if s.eq_ignore_ascii_case("temp") || s.eq_ignore_ascii_case("temporary") => {
Ok(DbRef::Temp)
}
Some(s) => self
.attached
.iter()
.position(|d| d.name.eq_ignore_ascii_case(s))
.map(DbRef::Attached)
.ok_or_else(|| Error::Error(alloc::format!("unknown database {s}"))),
}
}
fn db_parts(&self, db: DbRef) -> (&Schema, &Backend) {
match db {
DbRef::Main => (&self.schema, &self.backend),
DbRef::Temp => {
let t = self.temp_db.as_ref().expect("temp db exists");
(&t.schema, &t.backend)
}
DbRef::Attached(i) => (&self.attached[i].schema, &self.attached[i].backend),
}
}
fn unqualified_db(&self, name: &str) -> DbRef {
if let Some(t) = &self.temp_db {
if t.schema.table(name).is_some() {
return DbRef::Temp;
}
}
let def = self.read_default.get();
if def != DbRef::Main {
let (schema, _) = self.db_parts(def);
if schema.table(name).is_some() {
return def;
}
}
DbRef::Main
}
fn ensure_temp(&mut self) -> Result<()> {
if self.temp_db.is_some() {
return Ok(());
}
let vfs = crate::vfs::memory::MemoryVfs::new();
let f = vfs.open("temp", OpenFlags::READ_WRITE_CREATE)?;
let mut db = WritePager::create(f, None, 4096)?;
db.commit()?;
let backend = Backend::Write(Box::new(db));
let schema = Schema::read(backend.source())?;
self.temp_db = Some(AttachedDb {
name: "temp".into(),
file: String::new(),
backend,
schema,
});
Ok(())
}
fn scan_db_table(
&self,
db: DbRef,
name: &str,
alias: Option<&str>,
) -> Result<(Vec<ColumnInfo>, Vec<InputRow>)> {
let (schema, backend) = self.db_parts(db);
let meta = self.table_meta_in(schema, name, alias)?;
let source = backend.source();
let encoding = source.header().text_encoding;
if meta.without_rowid {
let params = Params::default();
let mut rows = Vec::new();
let mut cur = IndexCursor::new(source, meta.root);
while let Some(payload) = cur.next()? {
let storage = decode_record(&payload, encoding)?;
let mut values = unpermute_row(&meta, storage);
self.compute_generated(&meta, &mut values, ¶ms)?;
rows.push(InputRow {
values,
rowid: None,
});
}
return Ok((meta.columns, rows));
}
let mut rows = Vec::new();
let mut cur = TableCursor::new(source, meta.root);
let mut ok = cur.first()?;
while ok {
let rowid = cur.rowid()?;
let values = self.decode_full_row(&meta, rowid, &cur.payload()?, encoding)?;
rows.push(InputRow {
values,
rowid: Some(rowid),
});
ok = cur.next()?;
}
Ok((meta.columns, rows))
}
fn scan_db_view(
&self,
db: DbRef,
name: &str,
alias: Option<&str>,
params: &Params,
) -> Result<Option<(Vec<ColumnInfo>, Vec<InputRow>)>> {
use crate::schema::ObjectType;
let (schema, _) = self.db_parts(db);
let obj = match schema
.objects()
.iter()
.find(|o| o.obj_type == ObjectType::View && o.name.eq_ignore_ascii_case(name))
{
Some(o) => o.clone(),
None => return Ok(None),
};
let sql = obj
.sql
.as_deref()
.ok_or_else(|| Error::Corrupt("view has no CREATE statement".into()))?;
let Statement::CreateView(cv) = sql::parse_one(sql)? else {
return Err(Error::Corrupt("schema sql is not CREATE VIEW".into()));
};
let prev = self.read_default.get();
self.read_default.set(db);
let run = self.run_select(&cv.select, params);
self.read_default.set(prev);
let result = run?;
let label = alias.unwrap_or(name).to_string();
let names = if cv.columns.is_empty() {
result.columns.clone()
} else {
cv.columns.clone()
};
let columns: Vec<ColumnInfo> = names
.into_iter()
.map(|n| ColumnInfo {
name: n,
table: label.clone(),
affinity: eval::Affinity::Blob,
collation: crate::value::Collation::default(),
})
.collect();
let rows = result
.rows
.into_iter()
.map(|values| InputRow {
values,
rowid: None,
})
.collect();
Ok(Some((columns, rows)))
}
fn scan_table(&self, meta: &TableMeta) -> Result<Vec<(i64, Vec<Value>)>> {
let encoding = self.backend.source().header().text_encoding;
let mut rows = Vec::new();
let mut cur = TableCursor::new(self.backend.source(), meta.root);
let mut ok = cur.first()?;
while ok {
let rowid = cur.rowid()?;
let values = self.decode_full_row(meta, rowid, &cur.payload()?, encoding)?;
rows.push((rowid, values));
ok = cur.next()?;
}
Ok(rows)
}
fn decode_full_row(
&self,
meta: &TableMeta,
rowid: i64,
payload: &[u8],
encoding: crate::format::TextEncoding,
) -> Result<Vec<Value>> {
let record = decode_record(payload, encoding)?;
let n = meta.columns.len();
let mut values = alloc::vec![Value::Null; n];
let p = Params::default();
let mut ri = 0usize;
for (i, def) in meta.defaults.iter().enumerate() {
if meta.is_virtual(i) {
continue;
}
if ri < record.len() {
values[i] = record[ri].clone();
} else if let Some(e) = def {
values[i] = eval::eval(e, &EvalCtx::rowless(&p))?;
}
ri += 1;
}
if let Some(ipk) = meta.ipk {
values[ipk] = Value::Integer(rowid);
}
self.compute_generated(meta, &mut values, &p)?;
Ok(values)
}
fn compute_generated(
&self,
meta: &TableMeta,
values: &mut [Value],
params: &Params,
) -> Result<()> {
if meta.generated.iter().all(|g| g.is_none()) {
return Ok(());
}
for i in 0..meta.columns.len() {
if let Some((expr, stored)) = &meta.generated[i] {
if !stored {
let ctx = row_ctx(values, &meta.columns, None, params).with_subqueries(self);
let v = eval::eval(expr, &ctx)?;
values[i] = meta.columns[i].affinity.coerce(v);
}
}
}
Ok(())
}
fn materialize_generated(
&self,
meta: &TableMeta,
values: &mut [Value],
params: &Params,
) -> Result<()> {
if meta.generated.iter().all(|g| g.is_none()) {
return Ok(());
}
for i in 0..meta.columns.len() {
if let Some((expr, _)) = &meta.generated[i] {
let ctx = row_ctx(values, &meta.columns, None, params).with_subqueries(self);
let v = eval::eval(expr, &ctx)?;
values[i] = meta.columns[i].affinity.coerce(v);
}
}
Ok(())
}
fn encode_table_record(&self, meta: &TableMeta, values: &[Value]) -> Vec<u8> {
let stored: Vec<Value> = (0..meta.columns.len())
.filter(|&i| !meta.is_virtual(i))
.map(|i| {
if Some(i) == meta.ipk {
Value::Null
} else {
values[i].clone()
}
})
.collect();
encode_record(&stored)
}
fn eval_simple(
&self,
sel: &Select,
columns: &[ColumnInfo],
rows: Vec<InputRow>,
params: &Params,
) -> Result<(Vec<String>, Vec<OutRow>)> {
let labels = self.output_labels(sel, columns);
let mut out = Vec::with_capacity(rows.len());
for r in &rows {
let ctx = r.ctx(columns, params).with_subqueries(self);
let mut values = Vec::new();
for col in &sel.columns {
project_column(col, columns, &ctx, &mut values)?;
}
let mut sort_keys = Vec::new();
for term in &sel.order_by {
match resolve_order_index(&term.expr, &labels, values.len()) {
Some(idx) => sort_keys.push(values[idx].clone()),
None => sort_keys.push(eval::eval(&term.expr, &ctx)?),
}
}
out.push(OutRow { values, sort_keys });
}
Ok((labels, out))
}
fn eval_aggregated(
&self,
sel: &Select,
columns: &[ColumnInfo],
rows: Vec<InputRow>,
params: &Params,
) -> Result<(Vec<String>, Vec<OutRow>)> {
let expanded;
let sel = if sel
.columns
.iter()
.any(|c| matches!(c, ResultColumn::Wildcard | ResultColumn::TableWildcard(_)))
{
expanded = expand_agg_wildcards(sel, columns);
&expanded
} else {
sel
};
let group_by: Vec<Expr> = sel
.group_by
.iter()
.map(|g| {
if let Expr::Literal(Literal::Integer(n)) = g {
if *n >= 1 {
if let Some(ResultColumn::Expr { expr, .. }) =
sel.columns.get((*n - 1) as usize)
{
return expr.clone();
}
}
}
g.clone()
})
.collect();
let group_colls: Vec<crate::value::Collation> = {
let cctx = row_ctx(&[], columns, None, params);
group_by
.iter()
.map(|g| eval::key_collation(g, &cctx))
.collect()
};
let mut group_keys: Vec<Vec<Value>> = Vec::new();
let mut groups: Vec<Vec<usize>> = Vec::new();
for (i, r) in rows.iter().enumerate() {
let ctx = r.ctx(columns, params).with_subqueries(self);
let mut key = Vec::new();
for g in &group_by {
key.push(eval::eval(g, &ctx)?);
}
match group_keys
.iter()
.position(|k| rows_equal_coll(k, &key, &group_colls))
{
Some(idx) => groups[idx].push(i),
None => {
group_keys.push(key);
groups.push(alloc::vec![i]);
}
}
}
if sel.group_by.is_empty() {
groups = alloc::vec![(0..rows.len()).collect()];
} else {
let mut order: Vec<usize> = (0..groups.len()).collect();
order.sort_by(|&i, &j| {
for (k, coll) in group_colls.iter().enumerate() {
let ord =
crate::value::cmp_values_coll(&group_keys[i][k], &group_keys[j][k], *coll);
if ord != core::cmp::Ordering::Equal {
return ord;
}
}
core::cmp::Ordering::Equal
});
let mut sorted = Vec::with_capacity(groups.len());
for i in order {
sorted.push(core::mem::take(&mut groups[i]));
}
groups = sorted;
}
let labels = self.output_labels(sel, columns);
let minmax = single_minmax_arg(sel);
let mut out = Vec::new();
for group in &groups {
let repr_idx = match &minmax {
Some((is_max, arg)) => {
self.argextreme_row(group, columns, &rows, arg, *is_max, params)?
}
None => group.first().copied(),
};
let repr = repr_idx.map(|i| &rows[i]);
let empty = InputRow {
values: alloc::vec![Value::Null; columns.len()],
rowid: None,
};
let repr_ctx = repr
.unwrap_or(&empty)
.ctx(columns, params)
.with_subqueries(self);
let mut values = Vec::new();
for col in &sel.columns {
let ResultColumn::Expr { expr, .. } = col else {
unreachable!("wildcards rejected above")
};
let substituted =
self.substitute_aggregates(expr, columns, &rows, group, params)?;
values.push(eval::eval(&substituted, &repr_ctx)?);
}
if let Some(having) = &sel.having {
let h = self.substitute_aggregates(having, columns, &rows, group, params)?;
let mut aug_cols = columns.to_vec();
for label in &labels {
aug_cols.push(ColumnInfo {
name: label.clone(),
table: String::new(),
affinity: eval::Affinity::Blob,
collation: crate::value::Collation::Binary,
});
}
let mut aug_vals = repr.unwrap_or(&empty).values.clone();
aug_vals.extend(values.iter().cloned());
let aug_row = InputRow {
values: aug_vals,
rowid: repr.and_then(|r| r.rowid),
};
let actx = aug_row.ctx(&aug_cols, params).with_subqueries(self);
if eval::truth(&eval::eval(&h, &actx)?) != Some(true) {
continue;
}
}
let mut sort_keys = Vec::new();
for term in &sel.order_by {
if let Some(idx) = resolve_order_index(&term.expr, &labels, values.len()) {
sort_keys.push(values[idx].clone());
} else {
let s =
self.substitute_aggregates(&term.expr, columns, &rows, group, params)?;
sort_keys.push(eval::eval(&s, &repr_ctx)?);
}
}
out.push(OutRow { values, sort_keys });
}
Ok((labels, out))
}
fn argextreme_row(
&self,
group: &[usize],
columns: &[ColumnInfo],
rows: &[InputRow],
arg: &Expr,
is_max: bool,
params: &Params,
) -> Result<Option<usize>> {
let mut best: Option<(usize, Value)> = None;
for &i in group {
let ctx = rows[i].ctx(columns, params).with_subqueries(self);
let v = eval::eval(arg, &ctx)?;
if matches!(v, Value::Null) {
continue;
}
let take = match &best {
None => true,
Some((_, bv)) => {
let ord = eval::compare(&v, bv);
if is_max {
ord == core::cmp::Ordering::Greater
} else {
ord == core::cmp::Ordering::Less
}
}
};
if take {
best = Some((i, v));
}
}
Ok(best.map(|(i, _)| i).or_else(|| group.first().copied()))
}
fn substitute_aggregates(
&self,
expr: &Expr,
columns: &[ColumnInfo],
rows: &[InputRow],
group: &[usize],
params: &Params,
) -> Result<Expr> {
Ok(match expr {
Expr::Function {
name,
distinct,
args,
star,
filter,
order_by,
over: None,
} if func::is_aggregate_call(name, args.len(), *star)
|| self.aggregates.contains_key(&name.to_ascii_lowercase()) =>
{
let filtered;
let group = match filter {
Some(pred) => {
filtered = self.filter_group(pred, columns, rows, group, params)?;
&filtered[..]
}
None => group,
};
let v = self.compute_aggregate(
name, *distinct, args, *star, order_by, columns, rows, group, params,
)?;
Expr::Literal(value_to_literal(v))
}
Expr::Function {
name,
distinct,
args,
star,
filter,
order_by,
over,
} => {
let mut new_args = Vec::with_capacity(args.len());
for a in args {
new_args.push(self.substitute_aggregates(a, columns, rows, group, params)?);
}
Expr::Function {
name: name.clone(),
distinct: *distinct,
args: new_args,
star: *star,
filter: filter.clone(),
order_by: order_by.clone(),
over: over.clone(),
}
}
Expr::Binary { op, left, right } => Expr::Binary {
op: *op,
left: Box::new(self.substitute_aggregates(left, columns, rows, group, params)?),
right: Box::new(self.substitute_aggregates(right, columns, rows, group, params)?),
},
Expr::Unary { op, expr } => Expr::Unary {
op: *op,
expr: Box::new(self.substitute_aggregates(expr, columns, rows, group, params)?),
},
Expr::Paren(e) => Expr::Paren(Box::new(
self.substitute_aggregates(e, columns, rows, group, params)?,
)),
Expr::Cast { expr, type_name } => Expr::Cast {
expr: Box::new(self.substitute_aggregates(expr, columns, rows, group, params)?),
type_name: type_name.clone(),
},
Expr::IsNull { expr, negated } => Expr::IsNull {
expr: Box::new(self.substitute_aggregates(expr, columns, rows, group, params)?),
negated: *negated,
},
Expr::Between {
expr,
low,
high,
negated,
} => Expr::Between {
expr: Box::new(self.substitute_aggregates(expr, columns, rows, group, params)?),
low: Box::new(self.substitute_aggregates(low, columns, rows, group, params)?),
high: Box::new(self.substitute_aggregates(high, columns, rows, group, params)?),
negated: *negated,
},
Expr::InList {
expr,
list,
negated,
} => {
let mut new_list = Vec::with_capacity(list.len());
for e in list {
new_list.push(self.substitute_aggregates(e, columns, rows, group, params)?);
}
Expr::InList {
expr: Box::new(self.substitute_aggregates(expr, columns, rows, group, params)?),
list: new_list,
negated: *negated,
}
}
Expr::Case {
operand,
when_then,
else_result,
} => {
let operand = match operand {
Some(o) => Some(Box::new(
self.substitute_aggregates(o, columns, rows, group, params)?,
)),
None => None,
};
let mut new_wt = Vec::with_capacity(when_then.len());
for (w, t) in when_then {
new_wt.push((
self.substitute_aggregates(w, columns, rows, group, params)?,
self.substitute_aggregates(t, columns, rows, group, params)?,
));
}
let else_result = match else_result {
Some(e) => Some(Box::new(
self.substitute_aggregates(e, columns, rows, group, params)?,
)),
None => None,
};
Expr::Case {
operand,
when_then: new_wt,
else_result,
}
}
other => other.clone(),
})
}
fn filter_group(
&self,
pred: &Expr,
columns: &[ColumnInfo],
rows: &[InputRow],
group: &[usize],
params: &Params,
) -> Result<Vec<usize>> {
let mut out = Vec::new();
for &i in group {
let ctx = rows[i].ctx(columns, params).with_subqueries(self);
if eval::truth(&eval::eval(pred, &ctx)?) == Some(true) {
out.push(i);
}
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
fn compute_aggregate(
&self,
name: &str,
distinct: bool,
args: &[Expr],
star: bool,
order_by: &[OrderTerm],
columns: &[ColumnInfo],
rows: &[InputRow],
group: &[usize],
params: &Params,
) -> Result<Value> {
let lname = name.to_ascii_lowercase();
if !star && args.is_empty() {
return Err(Error::Error(format!(
"wrong number of arguments to function {lname}()"
)));
}
if (lname == "json_group_object" || lname == "jsonb_group_object") && args.len() < 2 {
return Err(Error::Error(format!(
"wrong number of arguments to function {lname}()"
)));
}
let ordered_group;
let group = if order_by.is_empty() {
group
} else {
let mut g = group.to_vec();
let mut err = None;
g.sort_by(|&a, &b| {
for term in order_by {
let ca = rows[a].ctx(columns, params).with_subqueries(self);
let cb = rows[b].ctx(columns, params).with_subqueries(self);
let (va, vb) = match (eval::eval(&term.expr, &ca), eval::eval(&term.expr, &cb))
{
(Ok(x), Ok(y)) => (x, y),
(Err(e), _) | (_, Err(e)) => {
err.get_or_insert(e);
return core::cmp::Ordering::Equal;
}
};
let coll = eval::key_collation(&term.expr, &ca);
let ord = cmp_order(&va, &vb, term.descending, term.nulls_first, coll);
if ord != core::cmp::Ordering::Equal {
return ord;
}
}
core::cmp::Ordering::Equal
});
if let Some(e) = err {
return Err(e);
}
ordered_group = g;
&ordered_group[..]
};
if lname == "json_group_array" || lname == "jsonb_group_array" {
let mut vals = Vec::new();
for &i in group {
let ctx = rows[i].ctx(columns, params).with_subqueries(self);
vals.push(eval::eval(&args[0], &ctx)?);
}
if distinct {
dedup_values(&mut vals, crate::value::Collation::default());
}
let items: Vec<_> = vals
.iter()
.map(|v| func::arg_to_json(v, args.first()))
.collect();
let arr = json::Json::Array(items);
return Ok(if lname.starts_with("jsonb") {
Value::Blob(arr.to_jsonb())
} else {
Value::Text(arr.serialize())
});
}
if lname == "json_group_object" || lname == "jsonb_group_object" {
let mut pairs = Vec::new();
for &i in group {
let ctx = rows[i].ctx(columns, params).with_subqueries(self);
let k = eval::eval(&args[0], &ctx)?;
let v = eval::eval(&args[1], &ctx)?;
pairs.push((eval::to_text(&k), func::arg_to_json(&v, args.get(1))));
}
let obj = json::Json::Object(pairs);
return Ok(if lname.starts_with("jsonb") {
Value::Blob(obj.to_jsonb())
} else {
Value::Text(obj.serialize())
});
}
let mut vals: Vec<Value> = Vec::new();
let mut count_rows = 0usize; for &i in group {
count_rows += 1;
if star {
continue;
}
let ctx = rows[i].ctx(columns, params).with_subqueries(self);
let v = eval::eval(&args[0], &ctx)?;
if !matches!(v, Value::Null) {
vals.push(v);
}
}
if distinct {
let coll = if star || args.is_empty() {
crate::value::Collation::default()
} else {
let cctx = row_ctx(&[], columns, None, params);
eval::key_collation(&args[0], &cctx)
};
dedup_values(&mut vals, coll);
}
let arg_coll = if args.is_empty() {
crate::value::Collation::default()
} else {
let cctx = row_ctx(&[], columns, None, params);
eval::key_collation(&args[0], &cctx)
};
Ok(match lname.as_str() {
"count" => {
if star {
Value::Integer(count_rows as i64)
} else {
Value::Integer(vals.len() as i64)
}
}
"sum" => {
if vals.is_empty() {
Value::Null
} else if vals.iter().all(|v| matches!(v, Value::Integer(_))) {
let mut acc: i64 = 0;
let mut overflow = false;
for v in &vals {
if let Value::Integer(i) = v {
match acc.checked_add(*i) {
Some(s) => acc = s,
None => {
overflow = true;
break;
}
}
}
}
if overflow {
return Err(Error::Error("integer overflow".into()));
} else {
Value::Integer(acc)
}
} else {
Value::Real(vals.iter().map(eval::to_f64).sum())
}
}
"total" => Value::Real(vals.iter().map(eval::to_f64).sum()),
"avg" => {
if vals.is_empty() {
Value::Null
} else {
let sum: f64 = vals.iter().map(eval::to_f64).sum();
Value::Real(sum / vals.len() as f64)
}
}
"min" => vals
.into_iter()
.reduce(|a, b| {
if crate::value::cmp_values_coll(&b, &a, arg_coll) == core::cmp::Ordering::Less
{
b
} else {
a
}
})
.unwrap_or(Value::Null),
"max" => vals
.into_iter()
.reduce(|a, b| {
if crate::value::cmp_values_coll(&b, &a, arg_coll)
== core::cmp::Ordering::Greater
{
b
} else {
a
}
})
.unwrap_or(Value::Null),
"group_concat" | "string_agg" => {
if vals.is_empty() {
Value::Null
} else {
let sep = if args.len() >= 2 {
let ctx = EvalCtx::rowless(params);
eval::to_text(&eval::eval(&args[1], &ctx)?)
} else {
",".to_string()
};
let parts: Vec<String> = vals.iter().map(eval::to_text).collect();
Value::Text(parts.join(&sep))
}
}
_ => {
if let Some(factory) = self.aggregates.get(&lname) {
let mut acc = factory();
let mut seen: Vec<Vec<Value>> = Vec::new();
for &i in group {
let ctx = rows[i].ctx(columns, params).with_subqueries(self);
let vals: Vec<Value> = args
.iter()
.map(|a| eval::eval(a, &ctx))
.collect::<Result<_>>()?;
if distinct {
if seen.contains(&vals) {
continue;
}
seen.push(vals.clone());
}
acc.step(&vals)?;
}
return acc.finalize();
}
return Err(Error::Error(format!("no such function: {name}")));
}
})
}
fn has_aggregate(&self, sel: &Select) -> bool {
let is_agg = |name: &str, n: usize, star: bool| {
func::is_aggregate_call(name, n, star)
|| self.aggregates.contains_key(&name.to_ascii_lowercase())
};
sel.columns.iter().any(|c| match c {
ResultColumn::Expr { expr, .. } => expr_contains_agg(expr, &is_agg),
_ => false,
}) || sel
.having
.as_ref()
.is_some_and(|h| expr_contains_agg(h, &is_agg))
}
fn output_labels(&self, sel: &Select, columns: &[ColumnInfo]) -> Vec<String> {
let mut labels = Vec::new();
for col in &sel.columns {
match col {
ResultColumn::Wildcard | ResultColumn::TableWildcard(_) => {
for c in columns {
labels.push(c.name.clone());
}
}
ResultColumn::Expr {
expr,
alias,
source,
} => {
labels.push(result_column_label(expr, alias, source));
}
}
}
labels
}
fn table_meta(&self, name: &str, alias: Option<&str>) -> Result<TableMeta> {
self.table_meta_in(&self.schema, name, alias)
}
fn table_meta_in(&self, schema: &Schema, name: &str, alias: Option<&str>) -> Result<TableMeta> {
if is_main_schema_table(name) {
return Ok(schema_table_meta(alias.unwrap_or(name)));
}
let obj = schema
.table(name)
.ok_or_else(|| Error::Error(alloc::format!("no such table: {name}")))?;
let sql = obj
.sql
.as_ref()
.ok_or_else(|| Error::Corrupt("table has no CREATE statement".into()))?;
let Statement::CreateTable(ct) = sql::parse_one(sql)? else {
return Err(Error::Corrupt("schema sql is not CREATE TABLE".into()));
};
let table_label = alias.unwrap_or(name).to_string();
let columns: Vec<ColumnInfo> = ct
.columns
.iter()
.map(|c| ColumnInfo {
name: c.name.clone(),
table: table_label.clone(),
affinity: eval::Affinity::from_type(c.type_name.as_deref()),
collation: column_collation(c),
})
.collect();
let defaults: Vec<Option<Expr>> = ct
.columns
.iter()
.map(|c| {
c.constraints.iter().find_map(|k| match k {
ColumnConstraint::Default(e) => Some(e.clone()),
_ => None,
})
})
.collect();
let ipk = if ct.without_rowid {
None
} else {
find_integer_primary_key(&ct)
};
let not_null: Vec<bool> = ct
.columns
.iter()
.enumerate()
.map(|(i, c)| {
Some(i) == ipk
|| c.constraints
.iter()
.any(|k| matches!(k, ColumnConstraint::NotNull))
})
.collect();
let generated: Vec<Option<(Expr, bool)>> = ct
.columns
.iter()
.map(|c| {
c.constraints.iter().find_map(|k| match k {
ColumnConstraint::Generated { expr, stored } => Some((expr.clone(), *stored)),
_ => None,
})
})
.collect();
let mut checks: Vec<Expr> = Vec::new();
for col in &ct.columns {
for k in &col.constraints {
if let ColumnConstraint::Check(e) = k {
checks.push(e.clone());
}
}
}
for tc in &ct.constraints {
if let TableConstraint::Check(e) = tc {
checks.push(e.clone());
}
}
let unique = collect_unique_sets(&ct, ipk);
let (without_rowid, storage_order, pk_len) = if ct.without_rowid {
let pk = primary_key_positions(&ct);
if pk.is_empty() {
return Err(Error::Error(
"WITHOUT ROWID table must have a PRIMARY KEY".into(),
));
}
let mut order = pk.clone();
for (i, gen) in generated.iter().enumerate() {
let is_virtual = matches!(gen, Some((_, false)));
if !pk.contains(&i) && !is_virtual {
order.push(i);
}
}
let pk_len = pk.len();
(true, order, pk_len)
} else {
(false, Vec::new(), 0)
};
let strict_types: Option<Vec<(StrictType, String)>> = if ct.strict {
let mut v = Vec::with_capacity(columns.len());
for c in &ct.columns {
let st = strict_column_type(c.type_name.as_deref()).unwrap_or(StrictType::Any);
let decl = c.type_name.clone().unwrap_or_default();
v.push((st, decl));
}
Some(v)
} else {
None
};
let mut columns = columns;
if let Some(st) = &strict_types {
for (col, (ty, _)) in columns.iter_mut().zip(st) {
if *ty == StrictType::Any {
col.affinity = eval::Affinity::Blob; }
}
}
Ok(TableMeta {
root: obj.rootpage,
columns,
defaults,
not_null,
checks,
unique,
ipk,
generated,
without_rowid,
storage_order,
pk_len,
strict_types,
})
}
fn check_strict_types(&self, meta: &TableMeta, values: &[Value]) -> Result<()> {
let Some(stypes) = &meta.strict_types else {
return Ok(());
};
for (i, (st, decl)) in stypes.iter().enumerate() {
let v = &values[i];
let ok = matches!(
(st, v),
(_, Value::Null)
| (StrictType::Any, _)
| (StrictType::Int, Value::Integer(_))
| (StrictType::Real, Value::Real(_))
| (StrictType::Text, Value::Text(_))
| (StrictType::Blob, Value::Blob(_))
);
if !ok {
let class = match v {
Value::Integer(_) => "INT",
Value::Real(_) => "REAL",
Value::Text(_) => "TEXT",
Value::Blob(_) => "BLOB",
Value::Null => unreachable!(),
};
return Err(Error::Constraint(format!(
"cannot store {class} value in {decl} column {}.{}",
meta.columns[i].table, meta.columns[i].name
)));
}
}
Ok(())
}
fn check_constraints(
&self,
meta: &TableMeta,
values: &[Value],
rowid: Option<i64>,
params: &Params,
) -> Result<()> {
for expr in &meta.checks {
let ctx = row_ctx(values, &meta.columns, rowid, params).with_subqueries(self);
if eval::truth(&eval::eval(expr, &ctx)?) == Some(false) {
return Err(Error::Constraint("CHECK constraint failed".into()));
}
}
Ok(())
}
}
struct TableMeta {
root: u32,
columns: Vec<ColumnInfo>,
defaults: Vec<Option<Expr>>,
not_null: Vec<bool>,
checks: Vec<Expr>,
unique: Vec<Vec<usize>>,
ipk: Option<usize>,
generated: Vec<Option<(Expr, bool)>>,
without_rowid: bool,
storage_order: Vec<usize>,
pk_len: usize,
strict_types: Option<Vec<(StrictType, String)>>,
}
fn expand_agg_wildcards(sel: &Select, columns: &[ColumnInfo]) -> Select {
let col_ref = |c: &ColumnInfo| ResultColumn::Expr {
expr: Expr::Column {
table: Some(c.table.clone()),
column: c.name.clone(),
},
alias: None,
source: None,
};
let mut new_cols = Vec::new();
for col in &sel.columns {
match col {
ResultColumn::Wildcard => new_cols.extend(columns.iter().map(&col_ref)),
ResultColumn::TableWildcard(t) => new_cols.extend(
columns
.iter()
.filter(|c| c.table.eq_ignore_ascii_case(t))
.map(&col_ref),
),
other => new_cols.push(other.clone()),
}
}
let mut s = sel.clone();
s.columns = new_cols;
s
}
fn alias_substituted(sel: &Select, columns: &[ColumnInfo]) -> Option<Select> {
let mut aliases: Vec<(String, Expr)> = Vec::new();
for c in &sel.columns {
if let ResultColumn::Expr {
expr,
alias: Some(name),
..
} = c
{
if !columns
.iter()
.any(|col| col.name.eq_ignore_ascii_case(name))
&& !aliases.iter().any(|(a, _)| a.eq_ignore_ascii_case(name))
{
aliases.push((name.clone(), expr.clone()));
}
}
}
if aliases.is_empty() {
return None;
}
let mentions = |e: &Expr| -> bool {
let mut found = false;
window::visit(e, &mut |n| {
if let Expr::Column {
table: None,
column,
} = n
{
if aliases.iter().any(|(a, _)| a.eq_ignore_ascii_case(column)) {
found = true;
}
}
});
found
};
let used = sel.where_clause.as_ref().is_some_and(&mentions)
|| sel.group_by.iter().any(&mentions)
|| sel.having.as_ref().is_some_and(&mentions);
if !used {
return None;
}
let mut out = sel.clone();
let apply = |e: &mut Expr| {
for (name, repl) in &aliases {
let target = Expr::Column {
table: None,
column: name.clone(),
};
window::replace_expr(e, &target, repl);
}
};
if let Some(w) = &mut out.where_clause {
apply(w);
}
for g in &mut out.group_by {
apply(g);
}
if let Some(h) = &mut out.having {
apply(h);
}
Some(out)
}
fn value_to_literal_expr(v: Value) -> Expr {
Expr::Literal(match v {
Value::Null => Literal::Null,
Value::Integer(i) => Literal::Integer(i),
Value::Real(r) => Literal::Real(r),
Value::Text(s) => Literal::Str(s),
Value::Blob(b) => Literal::Blob(b),
})
}
fn is_main_schema_table(name: &str) -> bool {
name.eq_ignore_ascii_case("sqlite_schema") || name.eq_ignore_ascii_case("sqlite_master")
}
fn is_temp_schema_table(name: &str) -> bool {
name.eq_ignore_ascii_case("sqlite_temp_schema")
|| name.eq_ignore_ascii_case("sqlite_temp_master")
}
fn reject_schema_write(table: &str) -> Result<()> {
if is_main_schema_table(table) {
return Err(Error::Error(alloc::format!(
"table {table} may not be modified"
)));
}
Ok(())
}
fn schema_table_meta(label: &str) -> TableMeta {
let col = |n: &str, aff: eval::Affinity| ColumnInfo {
name: n.to_string(),
table: label.to_string(),
affinity: aff,
collation: crate::value::Collation::default(),
};
let columns = alloc::vec![
col("type", eval::Affinity::Text),
col("name", eval::Affinity::Text),
col("tbl_name", eval::Affinity::Text),
col("rootpage", eval::Affinity::Integer),
col("sql", eval::Affinity::Text),
];
let n = columns.len();
TableMeta {
root: crate::schema::SCHEMA_ROOT_PAGE,
columns,
defaults: alloc::vec![None; n],
not_null: alloc::vec![false; n],
checks: Vec::new(),
unique: Vec::new(),
ipk: None,
generated: alloc::vec![None; n],
without_rowid: false,
storage_order: Vec::new(),
pk_len: 0,
strict_types: None,
}
}
fn expr_has_subquery(e: &Expr) -> bool {
let mut found = false;
window::visit(e, &mut |n| {
if matches!(
n,
Expr::Subquery(_) | Expr::Exists { .. } | Expr::InSelect { .. }
) {
found = true;
}
});
found
}
fn expr_is_nondeterministic(e: &Expr) -> bool {
let mut found = false;
window::visit(e, &mut |n| {
if let Expr::Function { name, .. } = n {
if matches!(
name.to_ascii_lowercase().as_str(),
"random" | "randomblob" | "last_insert_rowid" | "changes" | "total_changes"
) {
found = true;
}
}
});
found
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum StrictType {
Int,
Real,
Text,
Blob,
Any,
}
fn strict_column_type(type_name: Option<&str>) -> Option<StrictType> {
let t = type_name?.trim();
if t.eq_ignore_ascii_case("INT") || t.eq_ignore_ascii_case("INTEGER") {
Some(StrictType::Int)
} else if t.eq_ignore_ascii_case("REAL") {
Some(StrictType::Real)
} else if t.eq_ignore_ascii_case("TEXT") {
Some(StrictType::Text)
} else if t.eq_ignore_ascii_case("BLOB") {
Some(StrictType::Blob)
} else if t.eq_ignore_ascii_case("ANY") {
Some(StrictType::Any)
} else {
None
}
}
impl TableMeta {
fn is_virtual(&self, i: usize) -> bool {
matches!(self.generated[i], Some((_, false)))
}
fn is_generated(&self, i: usize) -> bool {
self.generated[i].is_some()
}
}
struct OrderIndexScan {
name: String,
root: u32,
colls: Vec<crate::value::Collation>,
cols: Vec<usize>,
descending: bool,
covering: bool,
}
struct IndexMeta {
name: String,
root: u32,
cols: Vec<usize>,
collations: Vec<crate::value::Collation>,
partial: Option<Expr>,
key_exprs: Option<Vec<Expr>>,
unique: bool,
}
impl Connection {
fn with_outer_frame<T>(
&self,
outer: &EvalCtx,
body: impl FnOnce(&Params) -> Result<T>,
) -> Result<T> {
self.outer_scope.borrow_mut().push(OuterFrame {
columns: outer.columns.to_vec(),
row: outer.row.to_vec(),
rowid: outer.rowid,
});
let params_ptr = outer.params;
let out = body(params_ptr);
self.outer_scope.borrow_mut().pop();
out
}
}
impl eval::Subqueries for Connection {
fn last_insert_rowid(&self) -> i64 {
self.last_insert_rowid.get()
}
fn changes(&self) -> i64 {
self.changes.get()
}
fn total_changes(&self) -> i64 {
self.total_changes.get()
}
fn next_random(&self) -> i64 {
let s = self.rng_state.get().wrapping_add(0x9E37_79B9_7F4A_7C15);
self.rng_state.set(s);
let mut z = s;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
z as i64
}
fn call_udf(&self, name: &str, args: &[Value]) -> Option<Result<Value>> {
self.functions.get(name).map(|f| f(args))
}
fn fts5_bm25(&self, rowid: i64, weights: &[f64]) -> Option<f64> {
let cell = self.fts5_rank.borrow();
let (corpus, index) = cell.as_ref()?.bm25.as_ref()?;
Some(corpus.score(*index.get(&rowid)?, weights))
}
fn fts5_highlight(&self, col: usize, text: &str, open: &str, close: &str) -> Option<String> {
let cell = self.fts5_rank.borrow();
let ctx = cell.as_ref()?;
Some(crate::vtab::fts5_highlight(
&ctx.query,
&ctx.col_names,
ctx.scope.as_deref(),
col,
text,
open,
close,
))
}
fn scalar(&self, select: &Select, outer: &EvalCtx) -> Result<Value> {
self.with_outer_frame(outer, |params| {
let r = self.run_select(select, params)?;
Ok(r.rows
.first()
.and_then(|row| row.first())
.cloned()
.unwrap_or(Value::Null))
})
}
fn column(&self, select: &Select, outer: &EvalCtx) -> Result<Vec<Value>> {
self.with_outer_frame(outer, |params| {
let r = self.run_select(select, params)?;
Ok(r.rows
.into_iter()
.map(|mut row| {
if row.is_empty() {
Value::Null
} else {
row.swap_remove(0)
}
})
.collect())
})
}
fn rows(&self, select: &Select, outer: &EvalCtx) -> Result<Vec<Vec<Value>>> {
self.with_outer_frame(outer, |params| Ok(self.run_select(select, params)?.rows))
}
fn exists(&self, select: &Select, outer: &EvalCtx) -> Result<bool> {
self.with_outer_frame(outer, |params| {
Ok(!self.run_select(select, params)?.rows.is_empty())
})
}
fn resolve_outer(&self, table: Option<&str>, name: &str) -> Option<Value> {
let scope = self.outer_scope.borrow();
for frame in scope.iter().rev() {
if eval::is_rowid_alias(name) {
let qualifies = match table {
None => true,
Some(t) => frame
.columns
.iter()
.any(|c| c.table.eq_ignore_ascii_case(t)),
};
let has_real = frame.columns.iter().any(|c| {
c.name.eq_ignore_ascii_case(name)
&& table.is_none_or(|t| c.table.eq_ignore_ascii_case(t))
});
if qualifies && !has_real {
if let Some(r) = frame.rowid {
return Some(Value::Integer(r));
}
}
}
for (i, col) in frame.columns.iter().enumerate() {
let name_ok = col.name.eq_ignore_ascii_case(name);
let table_ok = table.is_none_or(|t| col.table.eq_ignore_ascii_case(t));
if name_ok && table_ok {
return Some(frame.row[i].clone());
}
}
}
None
}
}
fn is_text(v: &Value, s: &str) -> bool {
matches!(v, Value::Text(t) if t == s)
}
fn references_name(select: &Select, name: &str) -> bool {
if references_name_select(select, name) {
return true;
}
select
.compound
.iter()
.any(|(_, s)| references_name_select(s, name))
}
fn references_name_select(select: &Select, name: &str) -> bool {
let Some(from) = &select.from else {
return false;
};
if from.first.name.eq_ignore_ascii_case(name) {
return true;
}
from.joins
.iter()
.any(|j| j.table.name.eq_ignore_ascii_case(name))
}
fn cmp_keys(a: &[Value], b: &[Value], desc: &[bool]) -> core::cmp::Ordering {
use core::cmp::Ordering;
for (i, (x, y)) in a.iter().zip(b).enumerate() {
let o = cmp_order(
x,
y,
desc.get(i).copied().unwrap_or(false),
None,
crate::value::Collation::Binary,
);
if o != Ordering::Equal {
return o;
}
}
Ordering::Equal
}
pub(crate) fn cmp_order(
a: &Value,
b: &Value,
descending: bool,
nulls_first: Option<bool>,
coll: crate::value::Collation,
) -> core::cmp::Ordering {
use core::cmp::Ordering;
let a_null = matches!(a, Value::Null);
let b_null = matches!(b, Value::Null);
let nulls_first = nulls_first.unwrap_or(!descending);
match (a_null, b_null) {
(true, true) => Ordering::Equal,
(true, false) => {
if nulls_first {
Ordering::Less
} else {
Ordering::Greater
}
}
(false, true) => {
if nulls_first {
Ordering::Greater
} else {
Ordering::Less
}
}
(false, false) => {
let ord = crate::value::cmp_values_coll(a, b, coll);
if descending {
ord.reverse()
} else {
ord
}
}
}
}
fn resolve_window_ref(wexpr: &Expr, defs: &[(String, WindowSpec)]) -> Result<Expr> {
let Expr::Function {
name,
distinct,
args,
star,
filter,
order_by,
over: Some(spec),
} = wexpr
else {
return Ok(wexpr.clone());
};
let Some(base) = &spec.base_name else {
return Ok(wexpr.clone());
};
let def = defs
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(base))
.map(|(_, s)| s)
.ok_or_else(|| Error::Error(alloc::format!("no such window: {base}")))?;
let effective = WindowSpec {
partition_by: def.partition_by.clone(),
order_by: if spec.order_by.is_empty() {
def.order_by.clone()
} else {
spec.order_by.clone()
},
frame: spec.frame.clone().or_else(|| def.frame.clone()),
base_name: None,
};
Ok(Expr::Function {
name: name.clone(),
distinct: *distinct,
args: args.clone(),
star: *star,
filter: filter.clone(),
order_by: order_by.clone(),
over: Some(effective),
})
}
fn json_emit_node(
node: &crate::exec::json::Json,
key: Option<Value>,
fullkey: &str,
path: &str,
parent: Option<i64>,
next_id: &mut i64,
rows: &mut Vec<Vec<Value>>,
) -> i64 {
use crate::exec::json::Json;
let id = *next_id;
*next_id += 1;
let is_container = matches!(node, Json::Object(_) | Json::Array(_));
let value = node.to_sql();
let atom = if is_container {
Value::Null
} else {
value.clone()
};
rows.push(alloc::vec![
key.unwrap_or(Value::Null),
value,
Value::Text(String::from(node.type_name())),
atom,
Value::Integer(id),
parent.map(Value::Integer).unwrap_or(Value::Null),
Value::Text(String::from(fullkey)),
Value::Text(String::from(path)),
]);
id
}
fn json_each_children(
root: &crate::exec::json::Json,
root_path: &str,
next_id: &mut i64,
rows: &mut Vec<Vec<Value>>,
) {
use crate::exec::json::Json;
match root {
Json::Object(members) => {
for (k, v) in members {
let fullkey = alloc::format!("{root_path}.{k}");
json_emit_node(
v,
Some(Value::Text(k.clone())),
&fullkey,
root_path,
None,
next_id,
rows,
);
}
}
Json::Array(items) => {
for (i, v) in items.iter().enumerate() {
let fullkey = alloc::format!("{root_path}[{i}]");
json_emit_node(
v,
Some(Value::Integer(i as i64)),
&fullkey,
root_path,
None,
next_id,
rows,
);
}
}
scalar => {
json_emit_node(scalar, None, root_path, root_path, None, next_id, rows);
}
}
}
fn split_json_path(path: &str) -> (alloc::string::String, Option<Value>) {
if path.ends_with(']') {
if let Some(open) = path.rfind('[') {
if let Ok(i) = path[open + 1..path.len() - 1].parse::<i64>() {
return (String::from(&path[..open]), Some(Value::Integer(i)));
}
}
}
if let Some(dot) = path.rfind('.') {
let name = path[dot + 1..].trim_matches('"');
return (
String::from(&path[..dot]),
Some(Value::Text(String::from(name))),
);
}
(String::from(path), None)
}
fn json_tree_walk(
node: &crate::exec::json::Json,
key: Option<Value>,
fullkey: &str,
path: &str,
parent: Option<i64>,
next_id: &mut i64,
rows: &mut Vec<Vec<Value>>,
) {
use crate::exec::json::Json;
let id = json_emit_node(node, key, fullkey, path, parent, next_id, rows);
match node {
Json::Object(members) => {
for (k, v) in members {
let child = alloc::format!("{fullkey}.{k}");
json_tree_walk(
v,
Some(Value::Text(k.clone())),
&child,
fullkey,
Some(id),
next_id,
rows,
);
}
}
Json::Array(items) => {
for (i, v) in items.iter().enumerate() {
let child = alloc::format!("{fullkey}[{i}]");
json_tree_walk(
v,
Some(Value::Integer(i as i64)),
&child,
fullkey,
Some(id),
next_id,
rows,
);
}
}
_ => {}
}
}
fn frame_bounds(
p: usize,
m: usize,
gid: &[usize],
spec: &WindowSpec,
ovals: &[Value],
desc: bool,
) -> (usize, usize) {
let Some(frame) = &spec.frame else {
if spec.order_by.is_empty() {
return (0, m);
}
let mut e = p + 1;
while e < m && gid[e] == gid[p] {
e += 1;
}
return (0, e);
};
let (start, end) = match frame.mode {
FrameMode::Rows => (
row_bound(&frame.start, p, m, true),
row_bound(&frame.end, p, m, false),
),
FrameMode::Range
if !ovals.is_empty()
&& (matches!(
frame.start,
FrameBound::Preceding(_) | FrameBound::Following(_)
) || matches!(
frame.end,
FrameBound::Preceding(_) | FrameBound::Following(_)
)) =>
{
(
range_value_bound(&frame.start, p, m, gid, ovals, desc, true),
range_value_bound(&frame.end, p, m, gid, ovals, desc, false),
)
}
FrameMode::Range | FrameMode::Groups => (
group_bound(&frame.start, p, m, gid, true),
group_bound(&frame.end, p, m, gid, false),
),
};
let start = start.min(m);
(start, end.min(m).max(start))
}
fn range_value_bound(
b: &FrameBound,
p: usize,
m: usize,
gid: &[usize],
ovals: &[Value],
desc: bool,
is_start: bool,
) -> usize {
if matches!(
b,
FrameBound::CurrentRow | FrameBound::Preceding(_) | FrameBound::Following(_)
) && matches!(ovals[p], Value::Null)
{
return group_bound(b, p, m, gid, is_start);
}
let val = eval::to_f64(&ovals[p]);
let threshold = match b {
FrameBound::UnboundedPreceding => return 0,
FrameBound::UnboundedFollowing => return m,
FrameBound::CurrentRow => val,
FrameBound::Preceding(n) => {
if desc {
val + *n as f64
} else {
val - *n as f64
}
}
FrameBound::Following(n) => {
if desc {
val - *n as f64
} else {
val + *n as f64
}
}
};
let inside = |vk: f64, edge: f64| if desc { vk >= edge } else { vk <= edge };
if is_start {
(0..m)
.find(|&k| {
!matches!(ovals[k], Value::Null) && {
let vk = eval::to_f64(&ovals[k]);
if desc {
vk <= threshold
} else {
vk >= threshold
}
}
})
.unwrap_or(m)
} else {
let mut e = m;
for (k, ov) in ovals.iter().enumerate().take(m) {
if matches!(ov, Value::Null) {
continue;
}
if !inside(eval::to_f64(ov), threshold) {
e = k;
break;
}
}
e
}
}
fn row_bound(b: &FrameBound, p: usize, m: usize, is_start: bool) -> usize {
match (b, is_start) {
(FrameBound::UnboundedPreceding, _) => 0,
(FrameBound::UnboundedFollowing, _) => m,
(FrameBound::CurrentRow, true) => p,
(FrameBound::CurrentRow, false) => p + 1,
(FrameBound::Preceding(n), true) => p.saturating_sub(*n as usize),
(FrameBound::Preceding(n), false) => (p + 1).saturating_sub(*n as usize),
(FrameBound::Following(n), true) => (p + *n as usize).min(m),
(FrameBound::Following(n), false) => (p + 1 + *n as usize).min(m),
}
}
fn group_bound(b: &FrameBound, p: usize, m: usize, gid: &[usize], is_start: bool) -> usize {
let maxg = if m == 0 { 0 } else { gid[m - 1] as i64 };
let target = |g: i64| -> i64 { gid[p] as i64 + g };
let first_of = |g: i64| -> usize {
if g < 0 {
0
} else if g > maxg {
m
} else {
(0..m).find(|&i| gid[i] as i64 == g).unwrap_or(m)
}
};
let after_last_of = |g: i64| -> usize {
if g < 0 {
0
} else if g > maxg {
m
} else {
(0..m)
.rev()
.find(|&i| gid[i] as i64 == g)
.map_or(0, |i| i + 1)
}
};
match (b, is_start) {
(FrameBound::UnboundedPreceding, _) => 0,
(FrameBound::UnboundedFollowing, _) => m,
(FrameBound::CurrentRow, true) => first_of(target(0)),
(FrameBound::CurrentRow, false) => after_last_of(target(0)),
(FrameBound::Preceding(n), true) => first_of(target(-(*n))),
(FrameBound::Preceding(n), false) => after_last_of(target(-(*n))),
(FrameBound::Following(n), true) => first_of(target(*n)),
(FrameBound::Following(n), false) => after_last_of(target(*n)),
}
}
fn ntile_bucket(p: usize, m: usize, buckets: i64) -> i64 {
let buckets = (buckets.max(1) as usize).min(m.max(1));
let size = m / buckets;
let rem = m % buckets;
let big = rem * (size + 1);
if p < big {
(p / (size + 1)) as i64 + 1
} else {
(rem + (p - big) / size.max(1)) as i64 + 1
}
}
fn window_aggregate(lname: &str, star: bool, frame: &[&Vec<Value>]) -> Result<Value> {
let mut vals: Vec<Value> = Vec::new();
for row in frame {
if star {
continue;
}
if let Some(v) = row.first() {
if !matches!(v, Value::Null) {
vals.push(v.clone());
}
}
}
Ok(match lname {
"count" => {
if star {
Value::Integer(frame.len() as i64)
} else {
Value::Integer(vals.len() as i64)
}
}
"sum" => {
if vals.is_empty() {
Value::Null
} else if vals.iter().all(|v| matches!(v, Value::Integer(_))) {
let mut acc: i64 = 0;
let mut overflow = false;
for v in &vals {
if let Value::Integer(i) = v {
match acc.checked_add(*i) {
Some(s) => acc = s,
None => {
overflow = true;
break;
}
}
}
}
if overflow {
Value::Real(vals.iter().map(eval::to_f64).sum())
} else {
Value::Integer(acc)
}
} else {
Value::Real(vals.iter().map(eval::to_f64).sum())
}
}
"total" => Value::Real(vals.iter().map(eval::to_f64).sum()),
"avg" => {
if vals.is_empty() {
Value::Null
} else {
let sum: f64 = vals.iter().map(eval::to_f64).sum();
Value::Real(sum / vals.len() as f64)
}
}
"min" => vals
.into_iter()
.reduce(|a, b| {
if eval::compare(&b, &a) == core::cmp::Ordering::Less {
b
} else {
a
}
})
.unwrap_or(Value::Null),
"max" => vals
.into_iter()
.reduce(|a, b| {
if eval::compare(&b, &a) == core::cmp::Ordering::Greater {
b
} else {
a
}
})
.unwrap_or(Value::Null),
"group_concat" | "string_agg" => {
if vals.is_empty() {
Value::Null
} else {
let sep = frame
.first()
.and_then(|r| r.get(1))
.map(eval::to_text)
.unwrap_or_else(|| String::from(","));
let parts: Vec<String> = vals.iter().map(eval::to_text).collect();
Value::Text(parts.join(&sep))
}
}
_ => return Err(Error::Unsupported("window function")),
})
}
fn dedup_rows(rows: &mut Vec<Vec<Value>>) {
let mut seen: Vec<Vec<Value>> = Vec::new();
rows.retain(|row| {
if seen.iter().any(|s| rows_equal(s, row)) {
false
} else {
seen.push(row.clone());
true
}
});
}
fn pragma_text(e: &Expr) -> String {
match e {
Expr::Column { column, .. } => column.clone(),
Expr::Literal(Literal::Str(s)) => s.clone(),
_ => String::new(),
}
}
fn pragma_truth(e: &Expr, params: &Params) -> bool {
match e {
Expr::Column { column, .. } => {
matches!(column.to_ascii_lowercase().as_str(), "on" | "yes" | "true")
}
Expr::Literal(Literal::Str(s)) => {
matches!(s.to_ascii_lowercase().as_str(), "on" | "yes" | "true" | "1")
}
_ => eval::eval(e, &EvalCtx::rowless(params))
.map(|v| eval::to_i64(&v) != 0)
.unwrap_or(false),
}
}
fn eqp_label(t: &TableRef) -> String {
match &t.alias {
Some(a) => alloc::format!("{} AS {}", t.name, a),
None => t.name.clone(),
}
}
fn partial_pred_guaranteed(idx: &IndexMeta, where_expr: &Expr) -> bool {
match &idx.partial {
None => true,
Some(pred) => {
let mut conjuncts = Vec::new();
and_conjuncts(where_expr, &mut conjuncts);
conjuncts.iter().any(|c| expr_eq_modulo_parens(c, pred))
}
}
}
fn find_expr_in_values(key_expr: &Expr, e: &Expr, params: &Params) -> Option<Vec<Value>> {
match e {
Expr::Binary {
op: BinaryOp::And,
left,
right,
} => find_expr_in_values(key_expr, left, params)
.or_else(|| find_expr_in_values(key_expr, right, params)),
Expr::Paren(inner) => find_expr_in_values(key_expr, inner, params),
Expr::InList {
expr,
list,
negated: false,
} => {
if list.is_empty() || !expr_eq_modulo_parens(expr, key_expr) {
return None;
}
let mut vals = Vec::with_capacity(list.len());
for item in list {
vals.push(const_value(item, params)?);
}
Some(vals)
}
_ => None,
}
}
struct ExecVTabStore<'a> {
conn: &'a mut Connection,
backing: &'a str,
}
impl VTabStore for ExecVTabStore<'_> {
fn rows(&self) -> Result<Vec<(i64, Vec<Value>)>> {
let meta = self.conn.table_meta(self.backing, None)?;
self.conn.scan_table(&meta)
}
fn put(&mut self, rowid: i64, values: &[Value]) -> Result<()> {
let root = self.conn.table_meta(self.backing, None)?.root;
let payload = encode_record(values);
let w = self.conn.backend.writer()?;
crate::btree::delete_table(w, root, rowid)?;
crate::btree::insert_table(w, root, rowid, &payload)?;
Ok(())
}
fn delete(&mut self, rowid: i64) -> Result<()> {
let root = self.conn.table_meta(self.backing, None)?.root;
let w = self.conn.backend.writer()?;
crate::btree::delete_table(w, root, rowid)?;
Ok(())
}
}
fn mirror_comparison(op: BinaryOp) -> BinaryOp {
match op {
BinaryOp::Lt => BinaryOp::Gt,
BinaryOp::LtEq => BinaryOp::GtEq,
BinaryOp::Gt => BinaryOp::Lt,
BinaryOp::GtEq => BinaryOp::LtEq,
other => other,
}
}
fn where_cols_covered(e: &Expr, meta: &TableMeta, idx_cols: &[usize]) -> bool {
let covered = |ci: usize| idx_cols.contains(&ci) || meta.ipk == Some(ci);
match e {
Expr::Literal(_) | Expr::Parameter(_) => true,
Expr::Column { column, .. } => match meta
.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(column))
{
Some(ci) => covered(ci),
None => matches!(
column.to_ascii_lowercase().as_str(),
"rowid" | "_rowid_" | "oid"
),
},
Expr::Unary { expr, .. }
| Expr::IsNull { expr, .. }
| Expr::Cast { expr, .. }
| Expr::Collate { expr, .. }
| Expr::Paren(expr) => where_cols_covered(expr, meta, idx_cols),
Expr::Binary { left, right, .. } => {
where_cols_covered(left, meta, idx_cols) && where_cols_covered(right, meta, idx_cols)
}
Expr::Between {
expr, low, high, ..
} => {
where_cols_covered(expr, meta, idx_cols)
&& where_cols_covered(low, meta, idx_cols)
&& where_cols_covered(high, meta, idx_cols)
}
Expr::InList { expr, list, .. } => {
where_cols_covered(expr, meta, idx_cols)
&& list.iter().all(|x| where_cols_covered(x, meta, idx_cols))
}
Expr::RowValue(items) => items.iter().all(|x| where_cols_covered(x, meta, idx_cols)),
Expr::Function {
args, filter, over, ..
} => {
over.is_none()
&& filter.is_none()
&& args.iter().all(|x| where_cols_covered(x, meta, idx_cols))
}
Expr::Case {
operand,
when_then,
else_result,
} => {
operand
.as_deref()
.map(|o| where_cols_covered(o, meta, idx_cols))
.unwrap_or(true)
&& when_then.iter().all(|(w, t)| {
where_cols_covered(w, meta, idx_cols) && where_cols_covered(t, meta, idx_cols)
})
&& else_result
.as_deref()
.map(|x| where_cols_covered(x, meta, idx_cols))
.unwrap_or(true)
}
Expr::Subquery(_) | Expr::Exists { .. } | Expr::InSelect { .. } => false,
}
}
fn collect_vtab_constraints(
sel: &Select,
columns: &[ColumnInfo],
params: &Params,
) -> (Vec<IndexConstraint>, Vec<Value>) {
let mut constraints = Vec::new();
let mut values = Vec::new();
let Some(where_expr) = &sel.where_clause else {
return (constraints, values);
};
let mut conjuncts = Vec::new();
and_conjuncts(where_expr, &mut conjuncts);
let mut push = |col: usize, op: ConstraintOp, v: Value| {
constraints.push(IndexConstraint {
column: col,
op,
usable: true,
});
values.push(v);
};
for c in conjuncts {
match c {
Expr::Binary { op, left, right }
if matches!(
op,
BinaryOp::Eq | BinaryOp::Lt | BinaryOp::LtEq | BinaryOp::Gt | BinaryOp::GtEq
) =>
{
if let (Some(ci), Some(v)) = (col_index(left, columns), const_value(right, params))
{
if let Some(cop) = binop_to_constraint(*op) {
push(ci, cop, v);
}
} else if let (Some(ci), Some(v)) =
(col_index(right, columns), const_value(left, params))
{
if let Some(cop) = binop_to_constraint(flip_cmp(*op)) {
push(ci, cop, v);
}
}
}
Expr::Between {
expr,
low,
high,
negated: false,
} => {
if let Some(ci) = col_index(expr, columns) {
if let Some(v) = const_value(low, params) {
push(ci, ConstraintOp::Ge, v);
}
if let Some(v) = const_value(high, params) {
push(ci, ConstraintOp::Le, v);
}
}
}
_ => {}
}
}
(constraints, values)
}
fn binop_to_constraint(op: BinaryOp) -> Option<ConstraintOp> {
Some(match op {
BinaryOp::Eq => ConstraintOp::Eq,
BinaryOp::Lt => ConstraintOp::Lt,
BinaryOp::LtEq => ConstraintOp::Le,
BinaryOp::Gt => ConstraintOp::Gt,
BinaryOp::GtEq => ConstraintOp::Ge,
_ => return None,
})
}
fn order_vtab_argv(plan: &IndexPlan, values: &[Value]) -> Vec<Value> {
let mut slots: Vec<(u32, Value)> = plan
.argv_index
.iter()
.zip(values.iter())
.filter(|(pos, _)| **pos != 0)
.map(|(pos, v)| (*pos, v.clone()))
.collect();
slots.sort_by_key(|(pos, _)| *pos);
slots.into_iter().map(|(_, v)| v).collect()
}
fn collect_eq_constraints(
e: &Expr,
columns: &[ColumnInfo],
params: &Params,
out: &mut Vec<(usize, Value)>,
) {
match e {
Expr::Binary {
op: BinaryOp::And,
left,
right,
} => {
collect_eq_constraints(left, columns, params, out);
collect_eq_constraints(right, columns, params, out);
}
Expr::Paren(inner) => collect_eq_constraints(inner, columns, params, out),
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} => {
if let (Some(ci), Some(v)) = (col_index(left, columns), const_value(right, params)) {
out.push((ci, v));
} else if let (Some(ci), Some(v)) =
(col_index(right, columns), const_value(left, params))
{
out.push((ci, v));
}
}
_ => {}
}
}
fn unparen(e: &Expr) -> &Expr {
let mut cur = e;
while let Expr::Paren(inner) = cur {
cur = inner;
}
cur
}
fn expr_eq_modulo_parens(a: &Expr, b: &Expr) -> bool {
unparen(a) == unparen(b)
}
fn and_conjuncts<'e>(e: &'e Expr, out: &mut Vec<&'e Expr>) {
match unparen(e) {
Expr::Binary {
op: BinaryOp::And,
left,
right,
} => {
and_conjuncts(left, out);
and_conjuncts(right, out);
}
other => out.push(other),
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum JoinKey {
Num(u64),
Text(String),
Blob(Vec<u8>),
}
fn num_bits(f: f64) -> u64 {
(if f == 0.0 { 0.0 } else { f }).to_bits()
}
fn join_keys_of(v: &Value) -> Vec<JoinKey> {
match v {
Value::Null => Vec::new(),
Value::Integer(i) => alloc::vec![
JoinKey::Num(num_bits(*i as f64)),
JoinKey::Text(i.to_string())
],
Value::Real(r) => {
alloc::vec![
JoinKey::Num(num_bits(*r)),
JoinKey::Text(eval::format_real(*r))
]
}
Value::Text(s) => {
let mut keys = alloc::vec![JoinKey::Text(s.clone())];
match eval::to_number(&Value::Text(s.clone())) {
Value::Integer(i) => keys.push(JoinKey::Num(num_bits(i as f64))),
Value::Real(r) => keys.push(JoinKey::Num(num_bits(r))),
_ => {}
}
keys
}
Value::Blob(b) => alloc::vec![JoinKey::Blob(b.clone())],
}
}
fn promote_comma_join_ons(sel: &Select) -> Option<Select> {
let from = sel.from.as_ref()?;
let where_clause = sel.where_clause.as_ref()?;
let promotable = |j: &Join| {
j.on.is_none() && !j.natural && j.using.is_empty() && matches!(j.kind, JoinKind::Inner)
};
if !from.joins.iter().any(promotable) {
return None;
}
let mut conjuncts: Vec<&Expr> = Vec::new();
and_conjuncts(where_clause, &mut conjuncts);
let label = |t: &TableRef| t.alias.clone().unwrap_or_else(|| t.name.clone());
let mut available: Vec<String> = alloc::vec![label(&from.first)];
let mut new_joins = from.joins.clone();
let mut changed = false;
for (i, join) in from.joins.iter().enumerate() {
let jlabel = label(&join.table);
if promotable(join) {
if let Some(cond) = conjuncts
.iter()
.find_map(|c| eligible_join_equi(c, &jlabel, &available))
{
new_joins[i].on = Some(cond);
changed = true;
}
}
available.push(jlabel);
}
if !changed {
return None;
}
let mut new_sel = sel.clone();
new_sel.from = Some(FromClause {
first: from.first.clone(),
joins: new_joins,
});
Some(new_sel)
}
fn eligible_join_equi(c: &Expr, jlabel: &str, available: &[String]) -> Option<Expr> {
let mut c = c;
while let Expr::Paren(inner) = c {
c = inner;
}
let (l, r) = match c {
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} => (left.as_ref(), right.as_ref()),
_ => return None,
};
let lt = column_qualifier(l)?;
let rt = column_qualifier(r)?;
let here = |t: &str| t.eq_ignore_ascii_case(jlabel);
let earlier = |t: &str| available.iter().any(|a| a.eq_ignore_ascii_case(t));
if (here(lt) && earlier(rt)) || (here(rt) && earlier(lt)) {
Some(c.clone())
} else {
None
}
}
fn column_qualifier(e: &Expr) -> Option<&str> {
match e {
Expr::Column { table: Some(t), .. } => Some(t),
Expr::Paren(inner) => column_qualifier(inner),
_ => None,
}
}
fn join_equi_cols(on: &Expr, cols: &[ColumnInfo], left_width: usize) -> Option<(usize, usize)> {
match on {
Expr::Binary {
op: BinaryOp::And,
left,
right,
} => join_equi_cols(left, cols, left_width)
.or_else(|| join_equi_cols(right, cols, left_width)),
Expr::Paren(inner) => join_equi_cols(inner, cols, left_width),
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} => {
let a = col_index(left, cols)?;
let b = col_index(right, cols)?;
let binary = |i: usize| cols[i].collation == crate::value::Collation::Binary;
let (l, r) = if a < left_width && b >= left_width {
(a, b)
} else if b < left_width && a >= left_width {
(b, a)
} else {
return None;
};
if binary(l) && binary(r) {
Some((l, r - left_width))
} else {
None
}
}
_ => None,
}
}
fn flatten_or<'a>(e: &'a Expr, out: &mut Vec<&'a Expr>) {
match e {
Expr::Binary {
op: BinaryOp::Or,
left,
right,
} => {
flatten_or(left, out);
flatten_or(right, out);
}
Expr::Paren(inner) => flatten_or(inner, out),
other => out.push(other),
}
}
fn find_in_constraint(
e: &Expr,
columns: &[ColumnInfo],
params: &Params,
) -> Option<(usize, Vec<Value>)> {
match e {
Expr::Binary {
op: BinaryOp::And,
left,
right,
} => find_in_constraint(left, columns, params)
.or_else(|| find_in_constraint(right, columns, params)),
Expr::Paren(inner) => find_in_constraint(inner, columns, params),
Expr::InList {
expr,
list,
negated: false,
} => {
let ci = col_index(expr, columns)?;
if list.is_empty() {
return None;
}
let mut vals = Vec::with_capacity(list.len());
for item in list {
vals.push(const_value(item, params)?);
}
Some((ci, vals))
}
_ => None,
}
}
#[derive(Default, Clone)]
struct RangeBound {
lower: Option<(Value, bool)>,
upper: Option<(Value, bool)>,
}
fn apply_bound(b: &mut RangeBound, op: BinaryOp, v: Value) {
match op {
BinaryOp::Gt => b.lower = Some((v, false)),
BinaryOp::GtEq => b.lower = Some((v, true)),
BinaryOp::Lt => b.upper = Some((v, false)),
BinaryOp::LtEq => b.upper = Some((v, true)),
_ => {}
}
}
fn flip_cmp(op: BinaryOp) -> BinaryOp {
match op {
BinaryOp::Lt => BinaryOp::Gt,
BinaryOp::LtEq => BinaryOp::GtEq,
BinaryOp::Gt => BinaryOp::Lt,
BinaryOp::GtEq => BinaryOp::LtEq,
other => other,
}
}
fn collect_range_constraints(
e: &Expr,
columns: &[ColumnInfo],
params: &Params,
out: &mut alloc::collections::BTreeMap<usize, RangeBound>,
) {
match e {
Expr::Binary {
op: BinaryOp::And,
left,
right,
} => {
collect_range_constraints(left, columns, params, out);
collect_range_constraints(right, columns, params, out);
}
Expr::Paren(inner) => collect_range_constraints(inner, columns, params, out),
Expr::Binary { op, left, right }
if matches!(
op,
BinaryOp::Lt | BinaryOp::LtEq | BinaryOp::Gt | BinaryOp::GtEq
) =>
{
if let (Some(ci), Some(v)) = (col_index(left, columns), const_value(right, params)) {
apply_bound(out.entry(ci).or_default(), *op, v);
} else if let (Some(ci), Some(v)) =
(col_index(right, columns), const_value(left, params))
{
apply_bound(out.entry(ci).or_default(), flip_cmp(*op), v);
}
}
Expr::Between {
expr,
low,
high,
negated: false,
} => {
if let Some(ci) = col_index(expr, columns) {
let b = out.entry(ci).or_default();
if let Some(v) = const_value(low, params) {
apply_bound(b, BinaryOp::GtEq, v);
}
if let Some(v) = const_value(high, params) {
apply_bound(b, BinaryOp::LtEq, v);
}
}
}
_ => {}
}
}
fn col_index(e: &Expr, columns: &[ColumnInfo]) -> Option<usize> {
if let Expr::Column { table, column } = e {
columns.iter().position(|c| {
c.name.eq_ignore_ascii_case(column)
&& table
.as_deref()
.is_none_or(|t| c.table.eq_ignore_ascii_case(t))
})
} else {
None
}
}
fn const_value(e: &Expr, params: &Params) -> Option<Value> {
eval::eval(e, &EvalCtx::rowless(params)).ok()
}
fn apply_column_affinity(meta: &TableMeta, values: &mut [Value]) {
for (i, v) in values.iter_mut().enumerate() {
let taken = core::mem::replace(v, Value::Null);
*v = meta.columns[i].affinity.coerce(taken);
}
}
fn check_not_null(meta: &TableMeta, values: &[Value]) -> Result<()> {
for (i, v) in values.iter().enumerate() {
if meta.not_null[i] && matches!(v, Value::Null) {
return Err(Error::Constraint(format!(
"NOT NULL constraint failed: {}.{}",
meta.columns[i].table, meta.columns[i].name
)));
}
}
Ok(())
}
fn index_key(cols: &[usize], values: &[Value], rowid: i64) -> Vec<u8> {
let mut key: Vec<Value> = cols.iter().map(|&p| values[p].clone()).collect();
key.push(Value::Integer(rowid));
encode_record(&key)
}
fn index_stat_string(
cols: &[usize],
colls: &[crate::value::Collation],
rows: &[Vec<Value>],
) -> String {
let n = rows.len();
let mut tuples: Vec<Vec<Value>> = rows
.iter()
.map(|r| cols.iter().map(|&c| r[c].clone()).collect())
.collect();
tuples.sort_by(|a, b| stat_prefix_cmp(a, b, colls, cols.len()));
let mut s = alloc::format!("{n}");
for k in 1..=cols.len() {
let mut distinct = 1usize; for w in tuples.windows(2) {
if stat_prefix_cmp(&w[0], &w[1], colls, k) != core::cmp::Ordering::Equal {
distinct += 1;
}
}
let avg = (n + distinct / 2) / distinct;
s.push(' ');
s.push_str(&avg.to_string());
}
s
}
fn stat_prefix_cmp(
a: &[Value],
b: &[Value],
colls: &[crate::value::Collation],
len: usize,
) -> core::cmp::Ordering {
for i in 0..len {
let coll = colls.get(i).copied().unwrap_or_default();
let ord = crate::value::cmp_values_coll(&a[i], &b[i], coll);
if ord != core::cmp::Ordering::Equal {
return ord;
}
}
core::cmp::Ordering::Equal
}
fn unique_match(meta: &TableMeta, a: &[Value], b: &[Value]) -> bool {
meta.unique.iter().any(|set| {
set.iter().all(|&c| {
!matches!(a[c], Value::Null)
&& !matches!(b[c], Value::Null)
&& crate::value::cmp_values_coll(&a[c], &b[c], meta.columns[c].collation).is_eq()
})
})
}
fn wr_index_key(cols: &[usize], pk_cols: &[usize], values: &[Value]) -> Vec<u8> {
let mut key: Vec<Value> = cols.iter().map(|&p| values[p].clone()).collect();
key.extend(pk_cols.iter().map(|&p| values[p].clone()));
encode_record(&key)
}
#[derive(Clone)]
struct InputRow {
values: Vec<Value>,
rowid: Option<i64>,
}
impl InputRow {
fn ctx<'a>(&'a self, columns: &'a [ColumnInfo], params: &'a Params) -> EvalCtx<'a> {
EvalCtx {
row: &self.values,
columns,
rowid: self.rowid,
params,
anon_counter: core::cell::Cell::new(0),
subqueries: None,
}
}
}
fn row_ctx<'a>(
values: &'a [Value],
columns: &'a [ColumnInfo],
rowid: Option<i64>,
params: &'a Params,
) -> EvalCtx<'a> {
EvalCtx {
row: values,
columns,
rowid,
params,
anon_counter: core::cell::Cell::new(0),
subqueries: None,
}
}
fn journal_path(path: &str) -> String {
let mut p = String::from(path);
p.push_str("-journal");
p
}
fn wal_path(path: &str) -> String {
let mut p = String::from(path);
p.push_str("-wal");
p
}
struct OutRow {
values: Vec<Value>,
sort_keys: Vec<Value>,
}
fn returning_labels(returning: &[ResultColumn], columns: &[ColumnInfo]) -> Vec<String> {
let mut labels = Vec::new();
for col in returning {
match col {
ResultColumn::Wildcard => {
for c in columns {
labels.push(c.name.clone());
}
}
ResultColumn::TableWildcard(t) => {
for c in columns {
if c.table.eq_ignore_ascii_case(t) {
labels.push(c.name.clone());
}
}
}
ResultColumn::Expr {
expr,
alias,
source,
} => {
labels.push(result_column_label(expr, alias, source));
}
}
}
labels
}
fn project_column(
col: &ResultColumn,
columns: &[ColumnInfo],
ctx: &EvalCtx,
out: &mut Vec<Value>,
) -> Result<()> {
match col {
ResultColumn::Wildcard => {
for v in ctx.row {
out.push(v.clone());
}
}
ResultColumn::TableWildcard(table) => {
for (i, c) in columns.iter().enumerate() {
if c.table.eq_ignore_ascii_case(table) {
out.push(ctx.row[i].clone());
}
}
}
ResultColumn::Expr { expr, .. } => {
out.push(eval::eval(expr, ctx)?);
}
}
Ok(())
}
fn positional_int(expr: &Expr) -> Option<i64> {
match expr {
Expr::Literal(Literal::Integer(n)) => Some(*n),
Expr::Unary {
op: UnaryOp::Negate,
expr,
} => match expr.as_ref() {
Expr::Literal(Literal::Integer(n)) => Some(n.wrapping_neg()),
_ => None,
},
Expr::Collate { expr, .. } | Expr::Paren(expr) => positional_int(expr),
_ => None,
}
}
fn check_positional_terms(group_by: &[Expr], order_by: &[OrderTerm], ncols: usize) -> Result<()> {
for g in group_by {
if let Some(n) = positional_int(g) {
if n < 1 || (n as u64) > ncols as u64 {
return Err(Error::Error("GROUP BY term out of range".into()));
}
}
}
for t in order_by {
if let Some(n) = positional_int(&t.expr) {
if n < 1 || (n as u64) > ncols as u64 {
return Err(Error::Error("ORDER BY term out of range".into()));
}
}
}
Ok(())
}
fn must_be_int(v: Value) -> Result<i64> {
fn real_exact(r: f64) -> Result<i64> {
if r.is_finite()
&& r == crate::util::float::trunc(r)
&& r >= i64::MIN as f64
&& r < 9_223_372_036_854_775_808.0
{
Ok(r as i64)
} else {
Err(Error::Error("datatype mismatch".into()))
}
}
match v {
Value::Integer(i) => Ok(i),
Value::Real(r) => real_exact(r),
Value::Text(s) => {
let t = s.trim();
if let Ok(i) = t.parse::<i64>() {
Ok(i)
} else if let Ok(r) = t.parse::<f64>() {
real_exact(r)
} else {
Err(Error::Error("datatype mismatch".into()))
}
}
Value::Null | Value::Blob(_) => Err(Error::Error("datatype mismatch".into())),
}
}
fn resolve_order_index(expr: &Expr, labels: &[String], ncols: usize) -> Option<usize> {
match expr {
Expr::Literal(Literal::Integer(n)) => {
let idx = (*n as usize).checked_sub(1)?;
(idx < ncols).then_some(idx)
}
Expr::Column {
table: None,
column,
} => labels.iter().position(|l| l.eq_ignore_ascii_case(column)),
Expr::Collate { expr, .. } | Expr::Paren(expr) => resolve_order_index(expr, labels, ncols),
_ => None,
}
}
fn for_each_minmax(expr: &Expr, f: &mut dyn FnMut(bool, &Expr)) {
match expr {
Expr::Function {
over: Some(_),
args,
..
} => {
for a in args {
for_each_minmax(a, f);
}
}
Expr::Function {
name,
args,
star: false,
..
} => {
if args.len() == 1 {
let l = name.to_ascii_lowercase();
if l == "min" || l == "max" {
f(l == "max", &args[0]);
}
}
for a in args {
for_each_minmax(a, f);
}
}
Expr::Function { args, .. } => {
for a in args {
for_each_minmax(a, f);
}
}
Expr::Binary { left, right, .. } => {
for_each_minmax(left, f);
for_each_minmax(right, f);
}
Expr::Unary { expr, .. }
| Expr::Paren(expr)
| Expr::IsNull { expr, .. }
| Expr::Cast { expr, .. }
| Expr::Collate { expr, .. } => for_each_minmax(expr, f),
Expr::Between {
expr, low, high, ..
} => {
for_each_minmax(expr, f);
for_each_minmax(low, f);
for_each_minmax(high, f);
}
Expr::InList { expr, list, .. } => {
for_each_minmax(expr, f);
for l in list {
for_each_minmax(l, f);
}
}
Expr::Case {
operand,
when_then,
else_result,
} => {
if let Some(o) = operand {
for_each_minmax(o, f);
}
for (w, t) in when_then {
for_each_minmax(w, f);
for_each_minmax(t, f);
}
if let Some(e) = else_result {
for_each_minmax(e, f);
}
}
_ => {}
}
}
fn single_minmax_arg(sel: &Select) -> Option<(bool, Expr)> {
let mut hits: Vec<(bool, Expr)> = Vec::new();
let mut collect =
|e: &Expr| for_each_minmax(e, &mut |is_max, arg| hits.push((is_max, arg.clone())));
for col in &sel.columns {
if let ResultColumn::Expr { expr, .. } = col {
collect(expr);
}
}
if let Some(h) = &sel.having {
collect(h);
}
for term in &sel.order_by {
collect(&term.expr);
}
if hits.len() == 1 {
hits.pop()
} else {
None
}
}
struct Fts5QueryCtx {
col_names: Vec<String>,
query: String,
scope: Option<String>,
bm25: Option<(
crate::vtab::Fts5Bm25,
alloc::collections::BTreeMap<i64, usize>,
)>,
}
struct Fts5RankGuard<'a> {
conn: &'a Connection,
prev: Option<Fts5QueryCtx>,
}
impl core::ops::Drop for Fts5RankGuard<'_> {
fn drop(&mut self) {
*self.conn.fts5_rank.borrow_mut() = self.prev.take();
}
}
fn expr_mentions_any(expr: &Expr, names: &[&str]) -> bool {
let rec = |e: &Expr| expr_mentions_any(e, names);
match expr {
Expr::Column {
table: None,
column,
} => names.iter().any(|n| column.eq_ignore_ascii_case(n)),
Expr::Function { name, args, .. } => {
names.iter().any(|n| name.eq_ignore_ascii_case(n)) || args.iter().any(rec)
}
Expr::Binary { left, right, .. } => rec(left) || rec(right),
Expr::Unary { expr, .. } | Expr::Paren(expr) => rec(expr),
Expr::IsNull { expr, .. } => rec(expr),
Expr::Between {
expr, low, high, ..
} => rec(expr) || rec(low) || rec(high),
Expr::InList { expr, list, .. } => rec(expr) || list.iter().any(rec),
Expr::Case {
operand,
when_then,
else_result,
} => {
operand.as_deref().is_some_and(rec)
|| when_then.iter().any(|(w, t)| rec(w) || rec(t))
|| else_result.as_deref().is_some_and(rec)
}
Expr::Cast { expr, .. } => rec(expr),
_ => false,
}
}
fn select_mentions(sel: &Select, names: &[&str]) -> bool {
sel.columns
.iter()
.any(|c| matches!(c, ResultColumn::Expr { expr, .. } if expr_mentions_any(expr, names)))
|| sel
.order_by
.iter()
.any(|t| expr_mentions_any(&t.expr, names))
|| sel
.having
.as_ref()
.is_some_and(|h| expr_mentions_any(h, names))
}
fn expr_contains_agg(expr: &Expr, is_agg: &dyn Fn(&str, usize, bool) -> bool) -> bool {
let rec = |e: &Expr| expr_contains_agg(e, is_agg);
match expr {
Expr::Function {
over: Some(_),
args,
..
} => args.iter().any(rec),
Expr::Function {
name, args, star, ..
} => is_agg(name, args.len(), *star) || args.iter().any(rec),
Expr::Binary { left, right, .. } => rec(left) || rec(right),
Expr::Unary { expr, .. } | Expr::Paren(expr) => rec(expr),
Expr::IsNull { expr, .. } => rec(expr),
Expr::Between {
expr, low, high, ..
} => rec(expr) || rec(low) || rec(high),
Expr::InList { expr, list, .. } => rec(expr) || list.iter().any(rec),
Expr::Case {
operand,
when_then,
else_result,
} => {
operand.as_deref().is_some_and(rec)
|| when_then.iter().any(|(w, t)| rec(w) || rec(t))
|| else_result.as_deref().is_some_and(rec)
}
Expr::Cast { expr, .. } => rec(expr),
_ => false,
}
}
fn apply_compound(
op: CompoundOp,
left: Vec<Vec<Value>>,
right: Vec<Vec<Value>>,
colls: &[crate::value::Collation],
) -> Vec<Vec<Value>> {
let eq = |a: &[Value], b: &[Value]| rows_equal_coll(a, b, colls);
let dedup = |rows: Vec<Vec<Value>>| -> Vec<Vec<Value>> {
let mut seen: Vec<Vec<Value>> = Vec::new();
for r in rows {
match seen.iter().position(|s| eq(s, &r)) {
Some(i) => seen[i] = r,
None => seen.push(r),
}
}
seen
};
match op {
CompoundOp::UnionAll => {
let mut out = left;
out.extend(right);
out
}
CompoundOp::Union => {
let mut out = left;
out.extend(right);
dedup(out)
}
CompoundOp::Intersect => dedup(
left.into_iter()
.filter(|l| right.iter().any(|r| eq(l, r)))
.collect(),
),
CompoundOp::Except => dedup(
left.into_iter()
.filter(|l| !right.iter().any(|r| eq(l, r)))
.collect(),
),
}
}
fn rows_equal(a: &[Value], b: &[Value]) -> bool {
a.len() == b.len()
&& a.iter()
.zip(b)
.all(|(x, y)| eval::compare(x, y) == core::cmp::Ordering::Equal)
}
fn rows_equal_coll(a: &[Value], b: &[Value], colls: &[crate::value::Collation]) -> bool {
a.len() == b.len()
&& a.iter().zip(b).enumerate().all(|(i, (x, y))| {
let c = colls.get(i).copied().unwrap_or_default();
crate::value::cmp_values_coll(x, y, c) == core::cmp::Ordering::Equal
})
}
fn dedup_values(vals: &mut Vec<Value>, coll: crate::value::Collation) {
let mut seen: Vec<Value> = Vec::new();
vals.retain(|v| {
if seen
.iter()
.any(|s| crate::value::cmp_values_coll(s, v, coll) == core::cmp::Ordering::Equal)
{
false
} else {
seen.push(v.clone());
true
}
});
}
fn value_to_literal(v: Value) -> Literal {
match v {
Value::Null => Literal::Null,
Value::Integer(i) => Literal::Integer(i),
Value::Real(r) => Literal::Real(r),
Value::Text(s) => Literal::Str(s),
Value::Blob(b) => Literal::Blob(b),
}
}
type NamedColumns = Vec<(String, Option<String>)>;
fn table_info_columns(extended: bool) -> Vec<String> {
let mut c: Vec<String> = ["cid", "name", "type", "notnull", "dflt_value", "pk"]
.iter()
.map(|s| String::from(*s))
.collect();
if extended {
c.push(String::from("hidden"));
}
c
}
fn rename_column_ref(e: &mut Expr, table: &str, old: &str, new: &str) {
window::replace_expr(
e,
&Expr::Column {
table: None,
column: String::from(old),
},
&Expr::Column {
table: None,
column: String::from(new),
},
);
window::replace_expr(
e,
&Expr::Column {
table: Some(String::from(table)),
column: String::from(old),
},
&Expr::Column {
table: Some(String::from(table)),
column: String::from(new),
},
);
}
fn rename_table_in_select(sel: &mut Select, old: &str, new: &str) {
let shadowed = sel.ctes.iter().any(|c| c.name.eq_ignore_ascii_case(old));
for cte in &mut sel.ctes {
rename_table_in_select(&mut cte.select, old, new);
}
if let Some(from) = &mut sel.from {
rename_table_in_ref(&mut from.first, old, new, shadowed);
for j in &mut from.joins {
rename_table_in_ref(&mut j.table, old, new, shadowed);
if let Some(on) = &mut j.on {
rename_table_in_expr(on, old, new);
}
}
}
for rc in &mut sel.columns {
match rc {
ResultColumn::Expr { expr, .. } => rename_table_in_expr(expr, old, new),
ResultColumn::TableWildcard(t) if !shadowed && t.eq_ignore_ascii_case(old) => {
*t = String::from(new);
}
_ => {}
}
}
if let Some(w) = &mut sel.where_clause {
rename_table_in_expr(w, old, new);
}
for e in &mut sel.group_by {
rename_table_in_expr(e, old, new);
}
if let Some(h) = &mut sel.having {
rename_table_in_expr(h, old, new);
}
for t in &mut sel.order_by {
rename_table_in_expr(&mut t.expr, old, new);
}
for (_, ws) in &mut sel.window_defs {
rename_table_in_window(ws, old, new);
}
if let Some(e) = &mut sel.limit {
rename_table_in_expr(e, old, new);
}
if let Some(e) = &mut sel.offset {
rename_table_in_expr(e, old, new);
}
for (_, comp) in &mut sel.compound {
rename_table_in_select(comp, old, new);
}
}
fn rename_table_in_ref(tref: &mut TableRef, old: &str, new: &str, shadowed: bool) {
if let Some(sub) = &mut tref.subquery {
rename_table_in_select(sub, old, new);
} else if tref.schema.is_none() && !shadowed && tref.name.eq_ignore_ascii_case(old) {
tref.name = String::from(new);
}
}
fn rename_table_in_window(ws: &mut WindowSpec, old: &str, new: &str) {
for e in &mut ws.partition_by {
rename_table_in_expr(e, old, new);
}
for t in &mut ws.order_by {
rename_table_in_expr(&mut t.expr, old, new);
}
}
fn rename_table_in_expr(e: &mut Expr, old: &str, new: &str) {
match e {
Expr::Column { table: Some(t), .. } if t.eq_ignore_ascii_case(old) => {
*t = String::from(new)
}
Expr::Column { .. } | Expr::Literal(_) | Expr::Parameter(_) => {}
Expr::Unary { expr, .. }
| Expr::IsNull { expr, .. }
| Expr::Cast { expr, .. }
| Expr::Paren(expr)
| Expr::Collate { expr, .. } => rename_table_in_expr(expr, old, new),
Expr::Binary { left, right, .. } => {
rename_table_in_expr(left, old, new);
rename_table_in_expr(right, old, new);
}
Expr::Function {
args,
filter,
order_by,
over,
..
} => {
for a in args {
rename_table_in_expr(a, old, new);
}
if let Some(f) = filter {
rename_table_in_expr(f, old, new);
}
for t in order_by {
rename_table_in_expr(&mut t.expr, old, new);
}
if let Some(w) = over {
rename_table_in_window(w, old, new);
}
}
Expr::InList { expr, list, .. } => {
rename_table_in_expr(expr, old, new);
for a in list {
rename_table_in_expr(a, old, new);
}
}
Expr::Between {
expr, low, high, ..
} => {
rename_table_in_expr(expr, old, new);
rename_table_in_expr(low, old, new);
rename_table_in_expr(high, old, new);
}
Expr::Case {
operand,
when_then,
else_result,
} => {
if let Some(o) = operand {
rename_table_in_expr(o, old, new);
}
for (w, t) in when_then {
rename_table_in_expr(w, old, new);
rename_table_in_expr(t, old, new);
}
if let Some(el) = else_result {
rename_table_in_expr(el, old, new);
}
}
Expr::RowValue(items) => {
for i in items {
rename_table_in_expr(i, old, new);
}
}
Expr::Subquery(s) => rename_table_in_select(s, old, new),
Expr::Exists { select, .. } => rename_table_in_select(select, old, new),
Expr::InSelect { expr, select, .. } => {
rename_table_in_expr(expr, old, new);
rename_table_in_select(select, old, new);
}
}
}
fn split_sql_script(sql: &str) -> Vec<&str> {
let toks = match sql::token::tokenize(sql) {
Ok(t) => t,
Err(_) => return alloc::vec![sql.trim()],
};
let mut out = Vec::new();
let mut depth: u32 = 0;
let mut seg_start = 0usize;
let mut seen = false;
for sp in &toks {
match &sp.token {
sql::token::Token::Semicolon if depth == 0 => {
if seen {
out.push(sql[seg_start..sp.start].trim());
}
seg_start = sp.end;
seen = false;
}
sql::token::Token::Word(w) => {
match w.to_ascii_uppercase().as_str() {
"BEGIN" if seen => depth += 1,
"CASE" => depth += 1,
"END" => depth = depth.saturating_sub(1),
_ => {}
}
seen = true;
}
_ => seen = true,
}
}
if seen {
out.push(sql[seg_start..].trim());
}
out
}
fn ddl_text(sql: &str) -> &str {
match sql::token::tokenize(sql) {
Ok(toks) if !toks.is_empty() => sql[toks[0].start..].trim_end(),
_ => sql.trim(),
}
}
fn view_uses_table(view_sql: &str, name: &str) -> bool {
match sql::parse_one(view_sql) {
Ok(Statement::CreateView(cv)) => {
let mut probe = cv.select.clone();
rename_table_in_select(
&mut probe,
name,
"\u{1}\u{1}graphite_rename_probe\u{1}\u{1}",
);
*probe != *cv.select
}
_ => false,
}
}
fn rewrite_ident_tokens(sql: &str, old: &str, new: &str) -> String {
let toks = match sql::token::tokenize(sql) {
Ok(t) => t,
Err(_) => return String::from(sql),
};
let rendered = sql::print::ident(new);
let mut out = String::new();
let mut cursor = 0usize;
for (i, sp) in toks.iter().enumerate() {
let hit = matches!(
&sp.token,
sql::token::Token::Word(w) | sql::token::Token::Ident(w) if w.eq_ignore_ascii_case(old)
);
if !hit {
continue;
}
let after_dot = i > 0 && matches!(toks[i - 1].token, sql::token::Token::Dot);
let before_lparen = toks
.get(i + 1)
.is_some_and(|n| matches!(n.token, sql::token::Token::LParen));
if after_dot || before_lparen {
continue;
}
out.push_str(&sql[cursor..sp.start]);
out.push_str(&rendered);
cursor = sp.end;
}
out.push_str(&sql[cursor..]);
out
}
fn expr_label(expr: &Expr) -> String {
match expr {
Expr::Column { column, .. } => column.clone(),
Expr::Literal(Literal::Integer(i)) => i.to_string(),
Expr::Literal(Literal::Str(s)) => s.clone(),
Expr::Function { name, .. } => name.clone(),
Expr::Paren(e) => expr_label(e),
_ => "expr".to_string(),
}
}
fn result_column_label(expr: &Expr, alias: &Option<String>, source: &Option<String>) -> String {
if let Some(a) = alias {
return a.clone();
}
match expr {
Expr::Column { column, .. } => column.clone(),
_ => source.clone().unwrap_or_else(|| expr_label(expr)),
}
}
fn wr_storage_collations(meta: &TableMeta) -> Vec<crate::value::Collation> {
meta.storage_order
.iter()
.map(|&c| meta.columns[c].collation)
.collect()
}
fn column_collation(col: &ColumnDef) -> crate::value::Collation {
col.constraints
.iter()
.find_map(|c| match c {
ColumnConstraint::Collate(name) => crate::value::Collation::parse(name),
_ => None,
})
.unwrap_or_default()
}
fn collect_unique_sets(ct: &CreateTable, ipk: Option<usize>) -> Vec<Vec<usize>> {
let col_pos = |name: &str| {
ct.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(name))
};
let mut unique: Vec<Vec<usize>> = Vec::new();
for (i, c) in ct.columns.iter().enumerate() {
for k in &c.constraints {
match k {
ColumnConstraint::Unique => unique.push(alloc::vec![i]),
ColumnConstraint::PrimaryKey { .. } if Some(i) != ipk => {
unique.push(alloc::vec![i])
}
_ => {}
}
}
}
for tc in &ct.constraints {
let names = match tc {
TableConstraint::Unique(n) | TableConstraint::PrimaryKey(n) => n,
_ => continue,
};
let idxs: Option<Vec<usize>> = names.iter().map(|n| col_pos(n)).collect();
if let Some(set) = idxs {
if !(set.len() == 1 && Some(set[0]) == ipk) {
unique.push(set);
}
}
}
unique
}
fn permute_row(meta: &TableMeta, declared: &[Value]) -> Vec<Value> {
meta.storage_order
.iter()
.map(|&i| declared[i].clone())
.collect()
}
fn auto_vacuum_mode(header: &crate::format::DatabaseHeader) -> u32 {
if header.largest_root_page == 0 {
0
} else if header.incremental_vacuum == 0 {
1
} else {
2
}
}
fn strip_schema_qualifier(sql: &str, schema: &str) -> Result<String> {
use crate::sql::token::Token;
let toks = crate::sql::token::tokenize(sql)?;
for (i, t) in toks.iter().enumerate() {
if i == 0 || !matches!(t.token, Token::Dot) {
continue;
}
let lead = match &toks[i - 1].token {
Token::Word(s) | Token::Ident(s) => Some(s.as_str()),
_ => None,
};
if lead.is_some_and(|s| s.eq_ignore_ascii_case(schema)) {
let schema_start = toks[i - 1].start;
let name_start = toks.get(i + 1).map_or(sql.len(), |s| s.start);
let mut out = String::with_capacity(sql.len());
out.push_str(&sql[..schema_start]);
out.push_str(&sql[name_start..]);
return Ok(out);
}
break;
}
Ok(sql.into())
}
fn unpermute_row(meta: &TableMeta, storage: Vec<Value>) -> Vec<Value> {
let mut row = alloc::vec![Value::Null; meta.columns.len()];
for (k, &col) in meta.storage_order.iter().enumerate() {
if let Some(v) = storage.get(k) {
row[col] = v.clone();
}
}
row
}
fn primary_key_positions(ct: &CreateTable) -> Vec<usize> {
for (i, c) in ct.columns.iter().enumerate() {
if c.constraints
.iter()
.any(|k| matches!(k, ColumnConstraint::PrimaryKey { .. }))
{
return alloc::vec![i];
}
}
for tc in &ct.constraints {
if let TableConstraint::PrimaryKey(names) = tc {
let pos: Option<Vec<usize>> = names
.iter()
.map(|n| {
ct.columns
.iter()
.position(|c| c.name.eq_ignore_ascii_case(n))
})
.collect();
if let Some(pos) = pos {
return pos;
}
}
}
Vec::new()
}
fn autoindex_number(name: &str, table: &str) -> Option<usize> {
let prefix = alloc::format!("sqlite_autoindex_{table}_");
name.strip_prefix(&prefix)?.parse::<usize>().ok()
}
fn find_integer_primary_key(ct: &CreateTable) -> Option<usize> {
for (i, c) in ct.columns.iter().enumerate() {
let is_integer = c
.type_name
.as_deref()
.is_some_and(|t| t.eq_ignore_ascii_case("integer"));
let is_pk = c
.constraints
.iter()
.any(|k| matches!(k, ColumnConstraint::PrimaryKey { .. }));
if is_integer && is_pk {
return Some(i);
}
}
for tc in &ct.constraints {
if let TableConstraint::PrimaryKey(cols) = tc {
if cols.len() == 1 {
if let Some(i) = ct.columns.iter().position(|c| c.name == cols[0]) {
if ct.columns[i]
.type_name
.as_deref()
.is_some_and(|t| t.eq_ignore_ascii_case("integer"))
{
return Some(i);
}
}
}
}
}
None
}