• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

joaoh82 / rust_sqlite / 25559195914

08 May 2026 01:48PM UTC coverage: 65.094% (+0.6%) from 64.474%
25559195914

push

github

web-flow
feat(engine): prepared-statement plan cache + parameter binding (SQLR-23) (#110)

`Connection::prepare_cached` (default 16-entry per-connection LRU) +
`Statement::query_with_params` / `Statement::execute_with_params`
substitute `?` placeholders into a cached AST at execute time, skipping
the per-call sqlparser walk. `Value::Vector(Vec<f32>)` is a first-class
bind type — a bound vector substitutes into the same in-band bracket-
array shape an inline `[…]` literal would, so the HNSW probe optimizer
still recognizes it on prepared queries.

Bench harness flipped from per-call SQL formatting (`inline_params`) to
the bound + cached path; every workload's `WorkloadId.version` bumped
`v1 → v2` in lockstep so the methodology change is captured explicitly.
Old v1 envelopes stay readable; the comparison script flags cross-
version pairs.

Smoke run confirms parser-bound wins on the workloads the plan
predicted: W1 -52%, W6 -61%, W3 -44%, W11 -26%. Republishing the
official pinned-host envelope is SQLR-25.

Surfaced one pre-existing limitation: `try_hnsw_probe` is L2-only, so
W10's `vec_distance_cosine` query has been silently brute-forcing the
HNSW variant the entire time. SQLR-28 tracks widening the probe to
cosine + dot.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

307 of 358 new or added lines in 6 files covered. (85.75%)

484 existing lines in 2 files now uncovered.

8983 of 13800 relevant lines covered (65.09%)

1.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/benchmarks/src/drivers/sqlrite.rs
1
//! SQLRite driver.
2
//!
3
//! Binds against the engine's public [`sqlrite::Connection`] surface —
4
//! the same API the language SDKs use.
5
//!
6
//! ## SQLR-23 — bound + cached path
7
//!
8
//! SQLRite gained a prepared-statement plan cache + parameter binding
9
//! in SQLR-23. This driver uses both:
10
//!
11
//! - `query_one` / `query_all` route through [`sqlrite::Connection::prepare_cached`]
12
//!   so a hot SELECT pays the sqlparser walk exactly once across the
13
//!   whole bench loop (cache cap defaults to 16, plenty for any single
14
//!   workload).
15
//! - `execute_with_params` does the same for INSERT-loop hot paths.
16
//! - `Value::Vector` binds directly through `Statement::query_with_params`
17
//!   without round-tripping through a 4 KB bracket-array SQL literal —
18
//!   this is the W10/W12 unlock. The HNSW probe optimizer recognizes
19
//!   the bound vector via the same in-band shape an inline `[…]` would
20
//!   produce, so the optimizer hook still kicks in on bound queries.
21
//!
22
//! That's how a perf-conscious SQLRite user would write hot-path code
23
//! today.
24

25
use std::path::Path;
26

27
use anyhow::{Context, Result};
28

29
use crate::{Driver, Value};
30

31
pub struct SQLRiteDriver;
32

33
impl Driver for SQLRiteDriver {
34
    type Conn = sqlrite::Connection;
35

36
    fn name(&self) -> &'static str {
×
37
        "sqlrite"
×
38
    }
39

40
    fn open(&self, path: &Path) -> Result<Self::Conn> {
×
41
        sqlrite::Connection::open(path)
×
42
            .map_err(|e| anyhow::anyhow!("sqlrite open({}): {e}", path.display()))
×
43
    }
44

45
    fn execute(&self, conn: &mut Self::Conn, sql: &str) -> Result<()> {
×
46
        conn.execute(sql)
×
47
            .map_err(|e| anyhow::anyhow!("sqlrite execute: {e}\n  sql: {sql}"))?;
×
48
        Ok(())
×
49
    }
50

51
    fn execute_with_params(
×
52
        &self,
53
        conn: &mut Self::Conn,
54
        sql: &str,
55
        params: &[Value],
56
    ) -> Result<()> {
NEW
57
        let bound = to_engine_values(params);
×
NEW
58
        let mut stmt = conn
×
NEW
59
            .prepare_cached(sql)
×
NEW
60
            .map_err(|e| anyhow::anyhow!("sqlrite prepare_cached: {e}\n  sql: {sql}"))?;
×
NEW
61
        stmt.execute_with_params(&bound)
×
NEW
62
            .map_err(|e| anyhow::anyhow!("sqlrite execute_with_params: {e}\n  sql: {sql}"))?;
×
UNCOV
63
        Ok(())
×
64
    }
65

66
    fn query_one(&self, conn: &mut Self::Conn, sql: &str, params: &[Value]) -> Result<Vec<Value>> {
×
NEW
67
        let bound = to_engine_values(params);
×
68
        let stmt = conn
×
NEW
69
            .prepare_cached(sql)
×
NEW
70
            .map_err(|e| anyhow::anyhow!("sqlrite prepare_cached: {e}\n  sql: {sql}"))?;
×
71
        let mut rows = stmt
×
NEW
72
            .query_with_params(&bound)
×
NEW
73
            .map_err(|e| anyhow::anyhow!("sqlrite query_with_params: {e}\n  sql: {sql}"))?;
×
74
        let row = rows
×
75
            .next()
×
76
            .map_err(|e| anyhow::anyhow!("sqlrite row read: {e}"))?
×
77
            .context("sqlrite query_one: zero rows returned")?;
×
78
        let cols = row.columns().len();
×
79
        let mut out = Vec::with_capacity(cols);
×
80
        for i in 0..cols {
×
81
            let v: sqlrite::Value = row.get(i)?;
×
82
            out.push(from_engine_value(v));
×
83
        }
84
        if rows
×
85
            .next()
×
86
            .map_err(|e| anyhow::anyhow!("sqlrite row read: {e}"))?
×
87
            .is_some()
88
        {
89
            anyhow::bail!("sqlrite query_one: >1 rows returned");
×
90
        }
91
        Ok(out)
×
92
    }
93

94
    fn query_all(
×
95
        &self,
96
        conn: &mut Self::Conn,
97
        sql: &str,
98
        params: &[Value],
99
    ) -> Result<Vec<Vec<Value>>> {
NEW
100
        let bound = to_engine_values(params);
×
101
        let stmt = conn
×
NEW
102
            .prepare_cached(sql)
×
NEW
103
            .map_err(|e| anyhow::anyhow!("sqlrite prepare_cached: {e}\n  sql: {sql}"))?;
×
104
        let mut rows = stmt
×
NEW
105
            .query_with_params(&bound)
×
NEW
106
            .map_err(|e| anyhow::anyhow!("sqlrite query_with_params: {e}\n  sql: {sql}"))?;
×
107
        let mut out = Vec::new();
×
108
        while let Some(row) = rows
×
109
            .next()
×
110
            .map_err(|e| anyhow::anyhow!("sqlrite row read: {e}"))?
×
111
        {
112
            let cols = row.columns().len();
×
113
            let mut buf = Vec::with_capacity(cols);
×
114
            for i in 0..cols {
×
115
                let v: sqlrite::Value = row.get(i)?;
×
116
                buf.push(from_engine_value(v));
×
117
            }
118
            out.push(buf);
×
119
        }
120
        Ok(out)
×
121
    }
122
}
123

124
/// Map the bench harness's `Value` to SQLRite's engine `Value`. Both
125
/// enums carry the same logical shapes; this is just a name-mapping.
126
fn to_engine_values(params: &[Value]) -> Vec<sqlrite::Value> {
127
    params.iter().map(to_engine_value).collect()
128
}
129

130
fn to_engine_value(v: &Value) -> sqlrite::Value {
131
    match v {
132
        Value::Null => sqlrite::Value::Null,
133
        Value::Integer(i) => sqlrite::Value::Integer(*i),
134
        Value::Real(f) => sqlrite::Value::Real(*f),
135
        Value::Text(s) => sqlrite::Value::Text(s.clone()),
136
        Value::Vector(v) => sqlrite::Value::Vector(v.clone()),
137
    }
138
}
139

140
fn from_engine_value(v: sqlrite::Value) -> Value {
141
    match v {
142
        sqlrite::Value::Null => Value::Null,
143
        sqlrite::Value::Integer(i) => Value::Integer(i),
144
        sqlrite::Value::Real(f) => Value::Real(f),
145
        sqlrite::Value::Text(s) => Value::Text(s),
146
        sqlrite::Value::Vector(v) => Value::Vector(v),
147
        // Bool / JSON aren't yet a bench `Value` variant — workloads
148
        // don't surface them. If a future workload reads one back,
149
        // grow this match alongside the harness `Value` enum.
150
        other => Value::Text(format!("{other:?}")),
151
    }
152
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc