mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-08-04 18:18:03 +00:00
feat: initial implementation of Statement::bind
This commit is contained in:
parent
b589203fea
commit
08c8c655e9
12 changed files with 241 additions and 6 deletions
|
@ -1,3 +1,5 @@
|
|||
use std::num::NonZero;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, miette::Diagnostic)]
|
||||
|
@ -41,6 +43,8 @@ pub enum LimboError {
|
|||
Constraint(String),
|
||||
#[error("Extension error: {0}")]
|
||||
ExtensionError(String),
|
||||
#[error("Unbound parameter at index {0}")]
|
||||
Unbound(NonZero<usize>),
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
10
core/lib.rs
10
core/lib.rs
|
@ -386,6 +386,7 @@ impl Connection {
|
|||
Rc::downgrade(self),
|
||||
syms,
|
||||
)?;
|
||||
|
||||
let mut state = vdbe::ProgramState::new(program.max_registers);
|
||||
program.step(&mut state, self.pager.clone())?;
|
||||
}
|
||||
|
@ -473,7 +474,14 @@ impl Statement {
|
|||
Ok(Rows::new(stmt))
|
||||
}
|
||||
|
||||
pub fn reset(&self) {}
|
||||
pub fn reset(&self) {
|
||||
self.state.reset();
|
||||
}
|
||||
|
||||
pub fn bind(&mut self, value: Value) {
|
||||
self.state.bind(value.into());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub enum StepResult<'a> {
|
||||
|
|
111
core/parameters.rs
Normal file
111
core/parameters.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
use std::num::NonZero;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Parameter {
|
||||
Anonymous(NonZero<usize>),
|
||||
Indexed(NonZero<usize>),
|
||||
Named(String, NonZero<usize>),
|
||||
}
|
||||
|
||||
impl PartialEq for Parameter {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.index() == other.index()
|
||||
}
|
||||
}
|
||||
|
||||
impl Parameter {
|
||||
pub fn index(&self) -> NonZero<usize> {
|
||||
match self {
|
||||
Parameter::Anonymous(index) => *index,
|
||||
Parameter::Indexed(index) => *index,
|
||||
Parameter::Named(_, index) => *index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Parameters {
|
||||
index: NonZero<usize>,
|
||||
pub list: Vec<Parameter>,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
index: 1.try_into().unwrap(),
|
||||
list: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
let mut params = self.list.clone();
|
||||
params.dedup();
|
||||
params.len()
|
||||
}
|
||||
|
||||
pub fn name(&self, index: NonZero<usize>) -> Option<String> {
|
||||
self.list.iter().find_map(|p| match p {
|
||||
Parameter::Anonymous(i) if *i == index => Some("?".to_string()),
|
||||
Parameter::Indexed(i) if *i == index => Some(format!("?{i}")),
|
||||
Parameter::Named(name, i) if *i == index => Some(name.to_owned()),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn index(&self, name: impl AsRef<str>) -> Option<NonZero<usize>> {
|
||||
self.list
|
||||
.iter()
|
||||
.find_map(|p| match p {
|
||||
Parameter::Named(n, index) if n == name.as_ref() => Some(index),
|
||||
_ => None,
|
||||
})
|
||||
.copied()
|
||||
}
|
||||
|
||||
pub fn next_index(&mut self) -> NonZero<usize> {
|
||||
let index = self.index;
|
||||
self.index = self.index.checked_add(1).unwrap();
|
||||
index
|
||||
}
|
||||
|
||||
pub fn push(&mut self, name: impl AsRef<str>) -> NonZero<usize> {
|
||||
match name.as_ref() {
|
||||
"" => {
|
||||
let index = self.next_index();
|
||||
self.list.push(Parameter::Anonymous(index));
|
||||
log::trace!("anonymous parameter at {index}");
|
||||
index
|
||||
}
|
||||
name if name.starts_with(&['$', ':', '@', '#']) => {
|
||||
match self
|
||||
.list
|
||||
.iter()
|
||||
.find(|p| matches!(p, Parameter::Named(n, _) if name == n))
|
||||
{
|
||||
Some(t) => {
|
||||
let index = t.index();
|
||||
self.list.push(t.clone());
|
||||
log::trace!("named parameter at {index} as {name}");
|
||||
index
|
||||
}
|
||||
None => {
|
||||
let index = self.next_index();
|
||||
self.list.push(Parameter::Named(name.to_owned(), index));
|
||||
log::trace!("named parameter at {index} as {name}");
|
||||
index
|
||||
}
|
||||
}
|
||||
}
|
||||
index => {
|
||||
// SAFETY: Garanteed from parser that the index is bigger that 0.
|
||||
let index: NonZero<usize> = index.parse().unwrap();
|
||||
if index > self.index {
|
||||
self.index = index.checked_add(1).unwrap();
|
||||
}
|
||||
self.list.push(Parameter::Indexed(index));
|
||||
log::trace!("indexed parameter at {index}");
|
||||
index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1710,7 +1710,14 @@ pub fn translate_expr(
|
|||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
ast::Expr::Variable(_) => todo!(),
|
||||
ast::Expr::Variable(name) => {
|
||||
let index = program.get_parameter_index(name);
|
||||
program.emit_insn(Insn::Variable {
|
||||
index,
|
||||
dest: target_register,
|
||||
});
|
||||
Ok(target_register)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,8 +32,7 @@ use crate::vdbe::{builder::ProgramBuilder, insn::Insn, Program};
|
|||
use crate::{bail_parse_error, Connection, LimboError, Result, SymbolTable};
|
||||
use insert::translate_insert;
|
||||
use select::translate_select;
|
||||
use sqlite3_parser::ast::fmt::ToTokens;
|
||||
use sqlite3_parser::ast::{self, PragmaName};
|
||||
use sqlite3_parser::ast::{self, fmt::ToTokens, PragmaName};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Display;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
|
|
@ -269,7 +269,7 @@ pub fn bind_column_references(
|
|||
bind_column_references(expr, referenced_tables)?;
|
||||
Ok(())
|
||||
}
|
||||
ast::Expr::Variable(_) => todo!(),
|
||||
ast::Expr::Variable(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -336,6 +336,18 @@ impl std::ops::DivAssign<OwnedValue> for OwnedValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Value<'_>> for OwnedValue {
|
||||
fn from(value: Value<'_>) -> Self {
|
||||
match value {
|
||||
Value::Null => OwnedValue::Null,
|
||||
Value::Integer(i) => OwnedValue::Integer(i),
|
||||
Value::Float(f) => OwnedValue::Float(f),
|
||||
Value::Text(s) => OwnedValue::Text(LimboText::new(Rc::new(s.to_owned()))),
|
||||
Value::Blob(b) => OwnedValue::Blob(Rc::new(b.to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_value(value: &OwnedValue) -> Value<'_> {
|
||||
match value {
|
||||
OwnedValue::Null => Value::Null,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
num::NonZero,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
|
@ -29,6 +30,8 @@ pub struct ProgramBuilder {
|
|||
seekrowid_emitted_bitmask: u64,
|
||||
// map of instruction index to manual comment (used in EXPLAIN)
|
||||
comments: HashMap<InsnReference, &'static str>,
|
||||
named_parameters: HashMap<String, NonZero<usize>>,
|
||||
next_free_parameter_index: NonZero<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -51,6 +54,7 @@ impl ProgramBuilder {
|
|||
next_free_register: 1,
|
||||
next_free_label: 0,
|
||||
next_free_cursor_id: 0,
|
||||
next_free_parameter_index: 1.into(),
|
||||
insns: Vec::new(),
|
||||
next_insn_label: None,
|
||||
cursor_ref: Vec::new(),
|
||||
|
@ -58,6 +62,7 @@ impl ProgramBuilder {
|
|||
label_to_resolved_offset: HashMap::new(),
|
||||
seekrowid_emitted_bitmask: 0,
|
||||
comments: HashMap::new(),
|
||||
named_parameters: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,4 +346,27 @@ impl ProgramBuilder {
|
|||
auto_commit: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_parameter(&mut self) -> NonZero<usize> {
|
||||
let index = self.next_free_parameter_index;
|
||||
self.next_free_parameter_index.checked_add(1).unwrap();
|
||||
index
|
||||
}
|
||||
|
||||
pub fn get_parameter_index(&mut self, name: impl AsRef<str>) -> NonZero<usize> {
|
||||
let name = name.as_ref();
|
||||
|
||||
if name == "" {
|
||||
return self.next_parameter();
|
||||
}
|
||||
|
||||
match self.named_parameters.get(name) {
|
||||
Some(index) => *index,
|
||||
None => {
|
||||
let index = self.next_parameter();
|
||||
self.named_parameters.insert(name.to_owned(), index);
|
||||
index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1062,6 +1062,15 @@ pub fn insn_to_str(
|
|||
0,
|
||||
format!("r[{}]=r[{}] << r[{}]", dest, lhs, rhs),
|
||||
),
|
||||
Insn::Variable { index, dest } => (
|
||||
"Variable",
|
||||
usize::from(*index) as i32,
|
||||
*dest as i32,
|
||||
0,
|
||||
OwnedValue::build_text(Rc::new("".to_string())),
|
||||
0,
|
||||
format!("r[{}]=parameter({})", *dest, *index),
|
||||
),
|
||||
};
|
||||
format!(
|
||||
"{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}",
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::num::NonZero;
|
||||
|
||||
use super::{AggFunc, BranchOffset, CursorID, FuncCtx, PageIdx};
|
||||
use crate::types::{OwnedRecord, OwnedValue};
|
||||
use limbo_macros::Description;
|
||||
|
@ -487,18 +489,26 @@ pub enum Insn {
|
|||
db: usize,
|
||||
where_clause: String,
|
||||
},
|
||||
|
||||
// Place the result of lhs >> rhs in dest register.
|
||||
ShiftRight {
|
||||
lhs: usize,
|
||||
rhs: usize,
|
||||
dest: usize,
|
||||
},
|
||||
|
||||
// Place the result of lhs << rhs in dest register.
|
||||
ShiftLeft {
|
||||
lhs: usize,
|
||||
rhs: usize,
|
||||
dest: usize,
|
||||
},
|
||||
|
||||
/// Get parameter variable.
|
||||
Variable {
|
||||
index: NonZero<usize>,
|
||||
dest: usize,
|
||||
},
|
||||
}
|
||||
|
||||
fn cast_text_to_numerical(value: &str) -> OwnedValue {
|
||||
|
|
|
@ -55,6 +55,7 @@ use sorter::Sorter;
|
|||
use std::borrow::BorrowMut;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::num::NonZero;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
@ -200,6 +201,7 @@ pub struct ProgramState {
|
|||
ended_coroutine: HashMap<usize, bool>, // flag to indicate that a coroutine has ended (key is the yield register)
|
||||
regex_cache: RegexCache,
|
||||
interrupted: bool,
|
||||
parameters: Vec<OwnedValue>,
|
||||
}
|
||||
|
||||
impl ProgramState {
|
||||
|
@ -222,6 +224,7 @@ impl ProgramState {
|
|||
ended_coroutine: HashMap::new(),
|
||||
regex_cache: RegexCache::new(),
|
||||
interrupted: false,
|
||||
parameters: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,6 +243,18 @@ impl ProgramState {
|
|||
pub fn is_interrupted(&self) -> bool {
|
||||
self.interrupted
|
||||
}
|
||||
|
||||
pub fn bind(&mut self, value: OwnedValue) {
|
||||
self.parameters.push(value);
|
||||
}
|
||||
|
||||
pub fn get_parameter(&self, index: NonZero<usize>) -> Option<&OwnedValue> {
|
||||
self.parameters.get(usize::from(index) - 1)
|
||||
}
|
||||
|
||||
pub fn reset(&self) {
|
||||
self.parameters.clear();
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! must_be_btree_cursor {
|
||||
|
@ -2182,6 +2197,13 @@ impl Program {
|
|||
exec_shift_left(&state.registers[*lhs], &state.registers[*rhs]);
|
||||
state.pc += 1;
|
||||
}
|
||||
Insn::Variable { index, dest } => {
|
||||
state.registers[*dest] = state
|
||||
.get_parameter(*index)
|
||||
.ok_or(LimboError::Unbound(*index))?
|
||||
.clone();
|
||||
state.pc += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ impl TempDatabase {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use limbo_core::{CheckpointStatus, Connection, StepResult, Value};
|
||||
use limbo_core::{CheckpointStatus, Connection, Rows, StepResult, Value};
|
||||
use log::debug;
|
||||
|
||||
#[ignore]
|
||||
|
@ -572,4 +572,29 @@ mod tests {
|
|||
do_flush(&conn, &tmp_db)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_statement_bind() -> anyhow::Result<()> {
|
||||
let _ = env_logger::try_init();
|
||||
let tmp_db = TempDatabase::new("CREATE TABLE test (x INTEGER PRIMARY KEY);");
|
||||
let conn = tmp_db.connect_limbo();
|
||||
let mut stmt = conn.prepare("select ?")?;
|
||||
stmt.bind(Value::Text(&"hello".to_string()));
|
||||
loop {
|
||||
match stmt.step()? {
|
||||
StepResult::Row(row) => {
|
||||
if let Value::Text(s) = row.values[0] {
|
||||
assert_eq!(s, "hello")
|
||||
}
|
||||
}
|
||||
StepResult::IO => {
|
||||
tmp_db.io.run_once()?;
|
||||
}
|
||||
StepResult::Interrupt => break,
|
||||
StepResult::Done => break,
|
||||
StepResult::Busy => panic!("Database is busy"),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue