Skip to content

Commit 2aaa981

Browse files
committed
Merge 'Initial implementation of Statement::bind_at' from Levy A.
Resolves #607. - [x] Index parameters. - [x] Named parameters. - [x] Parameter count. - [ ] More tests. - [ ] Expose to Sqlite3 API. Closes #675
2 parents b589203 + 9b8722f commit 2aaa981

13 files changed

Lines changed: 336 additions & 8 deletions

File tree

core/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::num::NonZero;
2+
13
use thiserror::Error;
24

35
#[derive(Debug, Error, miette::Diagnostic)]
@@ -41,6 +43,8 @@ pub enum LimboError {
4143
Constraint(String),
4244
#[error("Extension error: {0}")]
4345
ExtensionError(String),
46+
#[error("Unbound parameter at index {0}")]
47+
Unbound(NonZero<usize>),
4448
}
4549

4650
#[macro_export]

core/lib.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod function;
44
mod io;
55
#[cfg(feature = "json")]
66
mod json;
7+
mod parameters;
78
mod pseudo;
89
mod result;
910
mod schema;
@@ -28,6 +29,7 @@ use sqlite3_parser::ast;
2829
use sqlite3_parser::{ast::Cmd, lexer::sql::Parser};
2930
use std::cell::Cell;
3031
use std::collections::HashMap;
32+
use std::num::NonZero;
3133
use std::sync::{Arc, OnceLock, RwLock};
3234
use std::{cell::RefCell, rc::Rc};
3335
use storage::btree::btree_init_page;
@@ -43,7 +45,7 @@ use util::parse_schema_rows;
4345

4446
pub use error::LimboError;
4547
use translate::select::prepare_select_plan;
46-
pub type Result<T> = std::result::Result<T, error::LimboError>;
48+
pub type Result<T, E = error::LimboError> = std::result::Result<T, E>;
4749

4850
use crate::translate::optimizer::optimize_plan;
4951
pub use io::OpenFlags;
@@ -386,6 +388,7 @@ impl Connection {
386388
Rc::downgrade(self),
387389
syms,
388390
)?;
391+
389392
let mut state = vdbe::ProgramState::new(program.max_registers);
390393
program.step(&mut state, self.pager.clone())?;
391394
}
@@ -473,7 +476,18 @@ impl Statement {
473476
Ok(Rows::new(stmt))
474477
}
475478

476-
pub fn reset(&self) {}
479+
pub fn parameters(&self) -> &parameters::Parameters {
480+
&self.program.parameters
481+
}
482+
483+
pub fn bind_at(&mut self, index: NonZero<usize>, value: Value) {
484+
self.state.bind_at(index, value.into());
485+
}
486+
487+
pub fn reset(&mut self) {
488+
let state = vdbe::ProgramState::new(self.program.max_registers);
489+
self.state = state
490+
}
477491
}
478492

479493
pub enum StepResult<'a> {

core/parameters.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use std::num::NonZero;
2+
3+
#[derive(Clone, Debug)]
4+
pub enum Parameter {
5+
Anonymous(NonZero<usize>),
6+
Indexed(NonZero<usize>),
7+
Named(String, NonZero<usize>),
8+
}
9+
10+
impl PartialEq for Parameter {
11+
fn eq(&self, other: &Self) -> bool {
12+
self.index() == other.index()
13+
}
14+
}
15+
16+
impl Parameter {
17+
pub fn index(&self) -> NonZero<usize> {
18+
match self {
19+
Parameter::Anonymous(index) => *index,
20+
Parameter::Indexed(index) => *index,
21+
Parameter::Named(_, index) => *index,
22+
}
23+
}
24+
}
25+
26+
#[derive(Debug)]
27+
pub struct Parameters {
28+
index: NonZero<usize>,
29+
pub list: Vec<Parameter>,
30+
}
31+
32+
impl Parameters {
33+
pub fn new() -> Self {
34+
Self {
35+
index: 1.try_into().unwrap(),
36+
list: vec![],
37+
}
38+
}
39+
40+
pub fn count(&self) -> usize {
41+
let mut params = self.list.clone();
42+
params.dedup();
43+
params.len()
44+
}
45+
46+
pub fn name(&self, index: NonZero<usize>) -> Option<String> {
47+
self.list.iter().find_map(|p| match p {
48+
Parameter::Anonymous(i) if *i == index => Some("?".to_string()),
49+
Parameter::Indexed(i) if *i == index => Some(format!("?{i}")),
50+
Parameter::Named(name, i) if *i == index => Some(name.to_owned()),
51+
_ => None,
52+
})
53+
}
54+
55+
pub fn index(&self, name: impl AsRef<str>) -> Option<NonZero<usize>> {
56+
self.list
57+
.iter()
58+
.find_map(|p| match p {
59+
Parameter::Named(n, index) if n == name.as_ref() => Some(index),
60+
_ => None,
61+
})
62+
.copied()
63+
}
64+
65+
pub fn next_index(&mut self) -> NonZero<usize> {
66+
let index = self.index;
67+
self.index = self.index.checked_add(1).unwrap();
68+
index
69+
}
70+
71+
pub fn push(&mut self, name: impl AsRef<str>) -> NonZero<usize> {
72+
match name.as_ref() {
73+
"" => {
74+
let index = self.next_index();
75+
self.list.push(Parameter::Anonymous(index));
76+
log::trace!("anonymous parameter at {index}");
77+
index
78+
}
79+
name if name.starts_with(&['$', ':', '@', '#']) => {
80+
match self
81+
.list
82+
.iter()
83+
.find(|p| matches!(p, Parameter::Named(n, _) if name == n))
84+
{
85+
Some(t) => {
86+
let index = t.index();
87+
self.list.push(t.clone());
88+
log::trace!("named parameter at {index} as {name}");
89+
index
90+
}
91+
None => {
92+
let index = self.next_index();
93+
self.list.push(Parameter::Named(name.to_owned(), index));
94+
log::trace!("named parameter at {index} as {name}");
95+
index
96+
}
97+
}
98+
}
99+
index => {
100+
// SAFETY: Garanteed from parser that the index is bigger that 0.
101+
let index: NonZero<usize> = index.parse().unwrap();
102+
if index > self.index {
103+
self.index = index.checked_add(1).unwrap();
104+
}
105+
self.list.push(Parameter::Indexed(index));
106+
log::trace!("indexed parameter at {index}");
107+
index
108+
}
109+
}
110+
}
111+
}

core/translate/expr.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1710,7 +1710,14 @@ pub fn translate_expr(
17101710
}
17111711
_ => todo!(),
17121712
},
1713-
ast::Expr::Variable(_) => todo!(),
1713+
ast::Expr::Variable(name) => {
1714+
let index = program.parameters.push(name);
1715+
program.emit_insn(Insn::Variable {
1716+
index,
1717+
dest: target_register,
1718+
});
1719+
Ok(target_register)
1720+
}
17141721
}
17151722
}
17161723

core/translate/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ use crate::vdbe::{builder::ProgramBuilder, insn::Insn, Program};
3232
use crate::{bail_parse_error, Connection, LimboError, Result, SymbolTable};
3333
use insert::translate_insert;
3434
use select::translate_select;
35-
use sqlite3_parser::ast::fmt::ToTokens;
36-
use sqlite3_parser::ast::{self, PragmaName};
35+
use sqlite3_parser::ast::{self, fmt::ToTokens, PragmaName};
3736
use std::cell::RefCell;
3837
use std::fmt::Display;
3938
use std::rc::{Rc, Weak};
@@ -49,6 +48,7 @@ pub fn translate(
4948
syms: &SymbolTable,
5049
) -> Result<Program> {
5150
let mut program = ProgramBuilder::new();
51+
5252
match stmt {
5353
ast::Stmt::AlterTable(_, _) => bail_parse_error!("ALTER TABLE not supported yet"),
5454
ast::Stmt::Analyze(_) => bail_parse_error!("ANALYZE not supported yet"),
@@ -119,6 +119,7 @@ pub fn translate(
119119
)?;
120120
}
121121
}
122+
122123
Ok(program.build(database_header, connection))
123124
}
124125

core/translate/planner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ pub fn bind_column_references(
269269
bind_column_references(expr, referenced_tables)?;
270270
Ok(())
271271
}
272-
ast::Expr::Variable(_) => todo!(),
272+
ast::Expr::Variable(_) => Ok(()),
273273
}
274274
}
275275

core/types.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,18 @@ impl std::ops::DivAssign<OwnedValue> for OwnedValue {
336336
}
337337
}
338338

339+
impl From<Value<'_>> for OwnedValue {
340+
fn from(value: Value<'_>) -> Self {
341+
match value {
342+
Value::Null => OwnedValue::Null,
343+
Value::Integer(i) => OwnedValue::Integer(i),
344+
Value::Float(f) => OwnedValue::Float(f),
345+
Value::Text(s) => OwnedValue::Text(LimboText::new(Rc::new(s.to_owned()))),
346+
Value::Blob(b) => OwnedValue::Blob(Rc::new(b.to_owned())),
347+
}
348+
}
349+
}
350+
339351
pub fn to_value(value: &OwnedValue) -> Value<'_> {
340352
match value {
341353
OwnedValue::Null => Value::Null,

core/vdbe/builder.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use std::{
55
};
66

77
use crate::{
8+
parameters::Parameters,
89
schema::{BTreeTable, Index, PseudoTable},
910
storage::sqlite3_ondisk::DatabaseHeader,
1011
Connection,
1112
};
1213

1314
use super::{BranchOffset, CursorID, Insn, InsnReference, Program};
14-
1515
#[allow(dead_code)]
1616
pub struct ProgramBuilder {
1717
next_free_register: usize,
@@ -29,6 +29,7 @@ pub struct ProgramBuilder {
2929
seekrowid_emitted_bitmask: u64,
3030
// map of instruction index to manual comment (used in EXPLAIN)
3131
comments: HashMap<InsnReference, &'static str>,
32+
pub parameters: Parameters,
3233
}
3334

3435
#[derive(Debug, Clone)]
@@ -58,6 +59,7 @@ impl ProgramBuilder {
5859
label_to_resolved_offset: HashMap::new(),
5960
seekrowid_emitted_bitmask: 0,
6061
comments: HashMap::new(),
62+
parameters: Parameters::new(),
6163
}
6264
}
6365

@@ -331,6 +333,7 @@ impl ProgramBuilder {
331333
self.constant_insns.is_empty(),
332334
"constant_insns is not empty when build() is called, did you forget to call emit_constant_insns()?"
333335
);
336+
self.parameters.list.dedup();
334337
Program {
335338
max_registers: self.next_free_register,
336339
insns: self.insns,
@@ -339,6 +342,7 @@ impl ProgramBuilder {
339342
comments: self.comments,
340343
connection,
341344
auto_commit: true,
345+
parameters: self.parameters,
342346
}
343347
}
344348
}

core/vdbe/explain.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,15 @@ pub fn insn_to_str(
10621062
0,
10631063
format!("r[{}]=r[{}] << r[{}]", dest, lhs, rhs),
10641064
),
1065+
Insn::Variable { index, dest } => (
1066+
"Variable",
1067+
usize::from(*index) as i32,
1068+
*dest as i32,
1069+
0,
1070+
OwnedValue::build_text(Rc::new("".to_string())),
1071+
0,
1072+
format!("r[{}]=parameter({})", *dest, *index),
1073+
),
10651074
};
10661075
format!(
10671076
"{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}",

core/vdbe/insn.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::num::NonZero;
2+
13
use super::{AggFunc, BranchOffset, CursorID, FuncCtx, PageIdx};
24
use crate::types::{OwnedRecord, OwnedValue};
35
use limbo_macros::Description;
@@ -487,18 +489,26 @@ pub enum Insn {
487489
db: usize,
488490
where_clause: String,
489491
},
492+
490493
// Place the result of lhs >> rhs in dest register.
491494
ShiftRight {
492495
lhs: usize,
493496
rhs: usize,
494497
dest: usize,
495498
},
499+
496500
// Place the result of lhs << rhs in dest register.
497501
ShiftLeft {
498502
lhs: usize,
499503
rhs: usize,
500504
dest: usize,
501505
},
506+
507+
/// Get parameter variable.
508+
Variable {
509+
index: NonZero<usize>,
510+
dest: usize,
511+
},
502512
}
503513

504514
fn cast_text_to_numerical(value: &str) -> OwnedValue {

0 commit comments

Comments
 (0)