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

joaoh82 / rust_sqlite / 25455403967

06 May 2026 07:05PM UTC coverage: 64.728% (-0.6%) from 65.306%
25455403967

push

github

web-flow
feat(bench): harness scaffolding + W1 read-by-PK (SQLR-16, sub-phase 9.1) (#102)

First executable code for the SQLRite-vs-SQLite benchmark suite designed
in #101. Stands up a `benchmarks/` workspace member with a small
`Driver` trait, two engine implementations (SQLRite via `Connection`,
SQLite via `rusqlite`-bundled), one workload end-to-end (W1 read-by-PK),
a criterion entry point, and an aggregator that collapses
`target/criterion/**/estimates.json` into a single results envelope
under `benchmarks/results/`. Excluded from CI alongside `sqlrite-desktop`
+ the SDK cdylibs; runs locally with `make bench`.

Plan-doc:
  - Q1-Q8 resolved on 2026-05-06 and converted to a "Decisions
    (was: open questions)" section, mirroring the post-resolution
    shape of `docs/phase-7-plan.md`. Status moved to
    "approved 2026-05-06 -- 9.1 in flight".
  - Cleaned up the muddled "Not added to the default exclude list"
    sentence in the Repository layout section.

Harness:
  - `Driver` trait is small enough to express every planned workload:
    open / execute / execute_with_params / query_one / query_all.
    `&self` so drivers are cheap to clone into criterion closures.
  - SQLRite driver inlines `?` params via SQL formatting (SQLite-side
    quoting / arity checks unit-tested) -- the engine has no parameter
    binding yet; that lands as a post-9.6 follow-up.
  - SQLite driver runs the Q3-tuned profile at open time
    (WAL + synchronous=NORMAL + 64 MB cache + temp_store=MEMORY) and
    `prepare_cached` on every hot-path SELECT so we measure execution
    cost, not per-iter parse cost.
  - Per-run `TempDir` -- no page-cache / WAL bleed across iterations.
  - Correctness gate: every workload runs `correctness_check` against
    sample keys before timing, so a divergent-result "speedup" can't
    silently land.
  - Q8 versioning baked in: workloads carry a `WorkloadId { id, name,
    version }` and the JSON envelope tags every sample with `W{n}.v{v}`.

Output:
  ... (continued)

0 of 119 new or added lines in 3 files covered. (0.0%)

8704 of 13447 relevant lines covered (64.73%)

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. SQLRite has no parameter
5
//! binding yet (see `connection.rs:145` — "parameter binding and
6
//! prepared-plan caching are future work"), so the driver formats
7
//! `[Value]` into the SQL string at call time. That's an honest cost
8
//! to include in the comparison: a SQLRite user calling a hot SELECT
9
//! today pays the same per-call parse + format overhead.
10
//!
11
//! Once SQLRite gains parameter binding (post-9.6 follow-up), this
12
//! driver will switch to the bound path and a workload `v` bump will
13
//! capture the methodology change.
14

15
use std::path::Path;
16

17
use anyhow::{Context, Result};
18

19
use crate::{Driver, Value};
20

21
pub struct SQLRiteDriver;
22

23
impl Driver for SQLRiteDriver {
24
    type Conn = sqlrite::Connection;
25

NEW
26
    fn name(&self) -> &'static str {
×
NEW
27
        "sqlrite"
×
28
    }
29

NEW
30
    fn open(&self, path: &Path) -> Result<Self::Conn> {
×
NEW
31
        sqlrite::Connection::open(path)
×
NEW
32
            .map_err(|e| anyhow::anyhow!("sqlrite open({}): {e}", path.display()))
×
33
    }
34

NEW
35
    fn execute(&self, conn: &mut Self::Conn, sql: &str) -> Result<()> {
×
NEW
36
        conn.execute(sql)
×
NEW
37
            .map_err(|e| anyhow::anyhow!("sqlrite execute: {e}\n  sql: {sql}"))?;
×
NEW
38
        Ok(())
×
39
    }
40

NEW
41
    fn execute_with_params(
×
42
        &self,
43
        conn: &mut Self::Conn,
44
        sql: &str,
45
        params: &[Value],
46
    ) -> Result<()> {
NEW
47
        let inlined = inline_params(sql, params)?;
×
NEW
48
        conn.execute(&inlined)
×
NEW
49
            .map_err(|e| anyhow::anyhow!("sqlrite execute_with_params: {e}\n  sql: {inlined}"))?;
×
NEW
50
        Ok(())
×
51
    }
52

NEW
53
    fn query_one(&self, conn: &mut Self::Conn, sql: &str, params: &[Value]) -> Result<Vec<Value>> {
×
NEW
54
        let inlined = inline_params(sql, params)?;
×
NEW
55
        let stmt = conn
×
NEW
56
            .prepare(&inlined)
×
NEW
57
            .map_err(|e| anyhow::anyhow!("sqlrite prepare: {e}\n  sql: {inlined}"))?;
×
NEW
58
        let mut rows = stmt
×
NEW
59
            .query()
×
NEW
60
            .map_err(|e| anyhow::anyhow!("sqlrite query: {e}\n  sql: {inlined}"))?;
×
NEW
61
        let row = rows
×
NEW
62
            .next()
×
NEW
63
            .map_err(|e| anyhow::anyhow!("sqlrite row read: {e}"))?
×
NEW
64
            .context("sqlrite query_one: zero rows returned")?;
×
NEW
65
        let cols = row.columns().len();
×
NEW
66
        let mut out = Vec::with_capacity(cols);
×
NEW
67
        for i in 0..cols {
×
NEW
68
            let v: sqlrite::Value = row.get(i)?;
×
NEW
69
            out.push(from_engine_value(v));
×
70
        }
NEW
71
        if rows
×
NEW
72
            .next()
×
NEW
73
            .map_err(|e| anyhow::anyhow!("sqlrite row read: {e}"))?
×
74
            .is_some()
75
        {
NEW
76
            anyhow::bail!("sqlrite query_one: >1 rows returned");
×
77
        }
NEW
78
        Ok(out)
×
79
    }
80

NEW
81
    fn query_all(
×
82
        &self,
83
        conn: &mut Self::Conn,
84
        sql: &str,
85
        params: &[Value],
86
    ) -> Result<Vec<Vec<Value>>> {
NEW
87
        let inlined = inline_params(sql, params)?;
×
NEW
88
        let stmt = conn
×
NEW
89
            .prepare(&inlined)
×
NEW
90
            .map_err(|e| anyhow::anyhow!("sqlrite prepare: {e}\n  sql: {inlined}"))?;
×
NEW
91
        let mut rows = stmt
×
NEW
92
            .query()
×
NEW
93
            .map_err(|e| anyhow::anyhow!("sqlrite query: {e}\n  sql: {inlined}"))?;
×
NEW
94
        let mut out = Vec::new();
×
NEW
95
        while let Some(row) = rows
×
NEW
96
            .next()
×
NEW
97
            .map_err(|e| anyhow::anyhow!("sqlrite row read: {e}"))?
×
98
        {
NEW
99
            let cols = row.columns().len();
×
NEW
100
            let mut buf = Vec::with_capacity(cols);
×
NEW
101
            for i in 0..cols {
×
NEW
102
                let v: sqlrite::Value = row.get(i)?;
×
NEW
103
                buf.push(from_engine_value(v));
×
104
            }
NEW
105
            out.push(buf);
×
106
        }
NEW
107
        Ok(out)
×
108
    }
109
}
110

111
/// Inline `?`-positional placeholders with literal values. Replaces the
112
/// first `?` with `params[0]`, the second with `params[1]`, etc. Errors
113
/// if the count doesn't match. Strings are SQL-escaped.
114
fn inline_params(sql: &str, params: &[Value]) -> Result<String> {
115
    let mut out = String::with_capacity(sql.len() + params.len() * 16);
116
    let mut iter = params.iter();
117
    let mut in_string = false;
118
    for ch in sql.chars() {
119
        if ch == '\'' {
120
            in_string = !in_string;
121
            out.push(ch);
122
            continue;
123
        }
124
        if ch == '?' && !in_string {
125
            let p = iter
126
                .next()
127
                .context("inline_params: more `?` placeholders than params")?;
128
            push_literal(&mut out, p);
129
        } else {
130
            out.push(ch);
131
        }
132
    }
133
    if iter.next().is_some() {
134
        anyhow::bail!("inline_params: more params than `?` placeholders");
135
    }
136
    Ok(out)
137
}
138

139
fn push_literal(out: &mut String, v: &Value) {
140
    match v {
141
        Value::Null => out.push_str("NULL"),
142
        Value::Integer(i) => out.push_str(&i.to_string()),
143
        Value::Real(f) => out.push_str(&format!("{f}")),
144
        Value::Text(s) => {
145
            out.push('\'');
146
            for ch in s.chars() {
147
                if ch == '\'' {
148
                    out.push('\'');
149
                }
150
                out.push(ch);
151
            }
152
            out.push('\'');
153
        }
154
    }
155
}
156

157
fn from_engine_value(v: sqlrite::Value) -> Value {
158
    match v {
159
        sqlrite::Value::Null => Value::Null,
160
        sqlrite::Value::Integer(i) => Value::Integer(i),
161
        sqlrite::Value::Real(f) => Value::Real(f),
162
        sqlrite::Value::Text(s) => Value::Text(s),
163
        // Bench inputs don't include booleans / vectors / JSON yet —
164
        // when a workload starts using them, this match grows.
165
        other => Value::Text(format!("{other:?}")),
166
    }
167
}
168

169
#[cfg(test)]
170
mod tests {
171
    use super::*;
172

173
    #[test]
174
    fn inline_params_replaces_in_order() {
175
        let s = inline_params(
176
            "SELECT * FROM t WHERE a = ? AND b = ? AND c = ?",
177
            &[Value::Integer(1), Value::Text("x".into()), Value::Null],
178
        )
179
        .unwrap();
180
        assert_eq!(s, "SELECT * FROM t WHERE a = 1 AND b = 'x' AND c = NULL");
181
    }
182

183
    #[test]
184
    fn inline_params_preserves_question_marks_in_strings() {
185
        let s =
186
            inline_params("SELECT 'what?', * FROM t WHERE a = ?", &[Value::Integer(7)]).unwrap();
187
        assert_eq!(s, "SELECT 'what?', * FROM t WHERE a = 7");
188
    }
189

190
    #[test]
191
    fn inline_params_escapes_quotes() {
192
        let s = inline_params(
193
            "SELECT * FROM t WHERE name = ?",
194
            &[Value::Text("O'Hara".into())],
195
        )
196
        .unwrap();
197
        assert_eq!(s, "SELECT * FROM t WHERE name = 'O''Hara'");
198
    }
199

200
    #[test]
201
    fn inline_params_arity_mismatch_errors() {
202
        assert!(inline_params("SELECT ?", &[]).is_err());
203
        assert!(inline_params("SELECT 1", &[Value::Integer(1)]).is_err());
204
    }
205
}
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