sqlrite 0.1.13__tar.gz → 0.1.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlrite-0.1.13 → sqlrite-0.1.15}/Cargo.lock +5 -5
- {sqlrite-0.1.13 → sqlrite-0.1.15}/Cargo.toml +1 -1
- {sqlrite-0.1.13 → sqlrite-0.1.15}/PKG-INFO +1 -1
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/package.json +1 -1
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/phase-7-plan.md +3 -3
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/roadmap.md +1 -1
- {sqlrite-0.1.13 → sqlrite-0.1.15}/pyproject.toml +1 -1
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/python/Cargo.toml +1 -1
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/db/table.rs +77 -1
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/executor.rs +360 -29
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/hnsw.rs +44 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/mod.rs +169 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/cell.rs +15 -0
- sqlrite-0.1.15/src/sql/pager/hnsw_cell.rs +258 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/mod.rs +453 -2
- {sqlrite-0.1.13 → sqlrite-0.1.15}/.github/workflows/ci.yml +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/.github/workflows/release-pr.yml +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/.github/workflows/release.yml +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/.github/workflows/rust.yml +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/.gitignore +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/CODE_OF_CONDUCT.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/LICENSE +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/MAINTAINERS +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/Makefile +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/README.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/index.html +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/package-lock.json +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/src/App.svelte +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/src/app.css +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/src/main.ts +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/src/vite-env.d.ts +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/svelte.config.js +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/tsconfig.json +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/desktop/vite.config.ts +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/_index.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/architecture.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/design-decisions.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/desktop.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/embedding.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/file-format.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/getting-started.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/pager.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/release-plan.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/release-secrets.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/smoke-test.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/sql-engine.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/storage-model.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/supported-sql.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/docs/usage.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/README.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/c/Makefile +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/c/hello.c +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/go/go.mod +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/go/hello.go +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/nodejs/hello.mjs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/python/hello.py +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/rust/quickstart.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/wasm/Makefile +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/examples/wasm/index.html +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/images/SQLRite - Desktop.png +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/images/SQLRite Data Structures.png +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/images/SQLRite Simple SQL Execution High Level Diagram.png +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/images/SQLRite Simple SQL INSERT Execution High Level Diagram (Insert Row).png +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/images/SQLRite Simple SQL INSERT Execution High Level Diagram.png +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/images/SQLRite_logo.png +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/images/architecture.png +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/rust-toolchain.toml +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/samples/AST.delete.example +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/samples/AST.insert.exemple +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/samples/AST.select.example +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/samples/AST.update.example +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/samples/CREATE TABLE sqlrite_schema.sql +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/samples/CREATE_TABLE with duplicate.sql +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/samples/CREATE_TABLE.sql +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/samples/INSERT.sql +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/scripts/bump-version.sh +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/go/README.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/go/conn.go +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/go/go.mod +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/go/rows.go +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/go/sqlrite.go +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/go/sqlrite_test.go +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/go/stmt.go +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/python/README.md +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/python/src/lib.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/sdk/python/tests/test_sqlrite.py +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/connection.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/error.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/lib.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/main.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/meta_command/mod.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/repl/mod.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/db/database.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/db/mod.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/db/secondary_index.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/file.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/header.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/index_cell.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/interior_page.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/overflow.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/page.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/pager.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/table_page.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/varint.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/pager/wal.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/parser/create.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/parser/insert.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/parser/mod.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/parser/select.rs +0 -0
- {sqlrite-0.1.13 → sqlrite-0.1.15}/src/sql/tokenizer.rs +0 -0
|
@@ -3736,7 +3736,7 @@ dependencies = [
|
|
|
3736
3736
|
|
|
3737
3737
|
[[package]]
|
|
3738
3738
|
name = "sqlrite-desktop"
|
|
3739
|
-
version = "0.1.
|
|
3739
|
+
version = "0.1.15"
|
|
3740
3740
|
dependencies = [
|
|
3741
3741
|
"serde",
|
|
3742
3742
|
"serde_json",
|
|
@@ -3748,7 +3748,7 @@ dependencies = [
|
|
|
3748
3748
|
|
|
3749
3749
|
[[package]]
|
|
3750
3750
|
name = "sqlrite-engine"
|
|
3751
|
-
version = "0.1.
|
|
3751
|
+
version = "0.1.15"
|
|
3752
3752
|
dependencies = [
|
|
3753
3753
|
"clap",
|
|
3754
3754
|
"env_logger",
|
|
@@ -3763,7 +3763,7 @@ dependencies = [
|
|
|
3763
3763
|
|
|
3764
3764
|
[[package]]
|
|
3765
3765
|
name = "sqlrite-ffi"
|
|
3766
|
-
version = "0.1.
|
|
3766
|
+
version = "0.1.15"
|
|
3767
3767
|
dependencies = [
|
|
3768
3768
|
"cbindgen",
|
|
3769
3769
|
"sqlrite-engine",
|
|
@@ -3771,7 +3771,7 @@ dependencies = [
|
|
|
3771
3771
|
|
|
3772
3772
|
[[package]]
|
|
3773
3773
|
name = "sqlrite-nodejs"
|
|
3774
|
-
version = "0.1.
|
|
3774
|
+
version = "0.1.15"
|
|
3775
3775
|
dependencies = [
|
|
3776
3776
|
"napi",
|
|
3777
3777
|
"napi-build",
|
|
@@ -3781,7 +3781,7 @@ dependencies = [
|
|
|
3781
3781
|
|
|
3782
3782
|
[[package]]
|
|
3783
3783
|
name = "sqlrite-python"
|
|
3784
|
-
version = "0.1.
|
|
3784
|
+
version = "0.1.15"
|
|
3785
3785
|
dependencies = [
|
|
3786
3786
|
"pyo3",
|
|
3787
3787
|
"sqlrite-engine",
|
|
@@ -27,7 +27,7 @@ resolver = "3"
|
|
|
27
27
|
# `package =` key so the import name stays `sqlrite` internally:
|
|
28
28
|
# sqlrite = { package = "sqlrite-engine", path = "…" }
|
|
29
29
|
name = "sqlrite-engine"
|
|
30
|
-
version = "0.1.
|
|
30
|
+
version = "0.1.15"
|
|
31
31
|
authors = ["Joao Henrique Machado Silva <joaoh82@gmail.com>"]
|
|
32
32
|
edition = "2024"
|
|
33
33
|
rust-version = "1.85"
|
|
@@ -160,9 +160,9 @@ SELECT id, title FROM docs ORDER BY embedding <-> [0.1, ...] LIMIT 10;
|
|
|
160
160
|
|
|
161
161
|
> **Scope correction (2026-04-27, post-7c):** Re-scoping during implementation showed 7d works out to ~1300 LOC across three logical chunks, more than the original ~700-900 estimate and too much for one reviewable PR. Splitting into three:
|
|
162
162
|
>
|
|
163
|
-
> -
|
|
164
|
-
> -
|
|
165
|
-
> -
|
|
163
|
+
> - **✅ 7d.1 — Pure HNSW algorithm** *(~700 LOC, shipped in v0.1.13).* `src/sql/hnsw.rs` standalone module: insert + search + layer assignment + beam search per layer + L2/cosine/dot distance dispatch. No SQL integration yet — vectors are passed in via a `get_vec` closure so the algorithm doesn't depend on table types. Tests verify recall@k ≥ 0.95 vs brute-force on randomly-generated vector sets; deterministic via a fixed RNG seed.
|
|
164
|
+
> - **✅ 7d.2 — SQL integration** *(~500 LOC).* `CREATE INDEX … USING hnsw (col)` parser + engine, INSERT wiring (also calls `hnsw.insert()` incrementally), query optimizer hook (recognizes `ORDER BY vec_distance_l2(col, literal) LIMIT k` and probes the HNSW instead of full-scanning). HNSW lives in memory only at this point; the **CREATE INDEX SQL persists in `sqlrite_master` and reopen rebuilds the graph from current rows** — partial persistence ahead of 7d.3. DELETE/UPDATE on HNSW-indexed tables refused with helpful error pointing at 7d.3.
|
|
165
|
+
> - **✅ 7d.3 — Persistence** *(~600 LOC).* New `KIND_HNSW` cell tag and `HnswNodeCell` encoding (varint node_id + per-layer neighbor lists). Each HNSW index gets its own page tree parallel to secondary indexes. Open path loads cells directly into `HnswIndex::from_persisted_nodes` — no algorithm runs, exact bit-for-bit reproduction. Also unblocks DELETE / UPDATE on HNSW-indexed tables: those mark the index `needs_rebuild`, save rebuilds from current rows before staging. ~2× the original 300-LOC estimate because the cell encoding + tests + rebuild path together added more than expected.
|
|
166
166
|
>
|
|
167
167
|
> Each 7d.x ships as its own PR + release wave. The user-facing value lands at 7d.2; 7d.3 closes the persistence loop. 7d.1 is foundational but ships a tested algorithmic primitive on its own — useful as documentation of the engine's "from scratch" theme.
|
|
168
168
|
|
|
@@ -473,7 +473,7 @@ Approved sub-phases (Q1–Q10 resolved):
|
|
|
473
473
|
- **✅ 7a — `VECTOR(N)` column type** *(v0.1.10)* — dense fixed-dimension f32 storage via the existing cell encoding; format bumped to v4. Bracket-array literal syntax `[0.1, 0.2, …]` (Q7).
|
|
474
474
|
- **✅ 7b — Distance functions** *(v0.1.11)* — `vec_distance_l2/cosine/dot`, plus the ORDER BY-expressions parser change so KNN queries work end-to-end. Operators (`<->` `<=>` `<#>`) deferred to **7b.1** — sqlparser doesn't parse them natively, contradicting Q6's "tiny parser change" assumption.
|
|
475
475
|
- **✅ 7c — Brute-force KNN executor optimization** — bounded `BinaryHeap` of size k for `ORDER BY <expr> LIMIT k`. ~1.8× faster than full-sort at N=10k for cheap keys; bigger gains on expensive keys like `vec_distance_l2`.
|
|
476
|
-
-
|
|
476
|
+
- **✅ 7d — HNSW ANN index** — three PRs: 7d.1 (algorithm w/ recall@10 ≥ 0.95), 7d.2 (SQL integration + query optimizer), 7d.3 (persistence + DELETE/UPDATE rebuild). `CREATE INDEX … USING hnsw (col)`; fixed defaults `M=16, ef_construction=200, ef_search=50` (Q2). New `KIND_HNSW` cell tag.
|
|
477
477
|
- **7e — JSON column type + path queries** — `JSON` data type stored as bincoded `serde_json::Value` (Q3); `json_extract` / `json_array_length` / `json_object_keys` / `json_type`.
|
|
478
478
|
- **7f — ~~Full-text search with BM25~~** — **deferred to Phase 8** (Q1).
|
|
479
479
|
- **7g — `ask()` API across the product surface** — natural-language → SQL via Anthropic API (Q4), Anthropic-first then OpenAI + Ollama follow-ups. Foundational 7g.1 introduces a new `sqlrite-ask` crate (Q10 — separate crate, not a feature flag). Thin per-product adapters in 7g.2-7g.8 cover REPL, desktop, Python, Node.js, Go, WASM (JS-callback shape per Q9), and the MCP `ask` tool.
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sqlrite"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.15"
|
|
8
8
|
description = "Python bindings for SQLRite — a small, embeddable SQLite clone written in Rust."
|
|
9
9
|
authors = [{ name = "Joao Henrique Machado Silva", email = "joaoh82@gmail.com" }]
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
use crate::error::{Result, SQLRiteError};
|
|
2
2
|
use crate::sql::db::secondary_index::{IndexOrigin, SecondaryIndex};
|
|
3
|
+
use crate::sql::hnsw::HnswIndex;
|
|
3
4
|
use crate::sql::parser::create::CreateQuery;
|
|
4
5
|
use std::collections::{BTreeMap, HashMap};
|
|
5
6
|
use std::fmt;
|
|
@@ -118,12 +119,37 @@ pub struct Table {
|
|
|
118
119
|
/// add more. Looking up an index: iterate by column name, or by index
|
|
119
120
|
/// name via `Table::index_by_name`.
|
|
120
121
|
pub secondary_indexes: Vec<SecondaryIndex>,
|
|
122
|
+
/// HNSW indexes on VECTOR columns (Phase 7d.2). Maintained in lockstep
|
|
123
|
+
/// with row storage on INSERT (incremental); rebuilt on open from the
|
|
124
|
+
/// persisted CREATE INDEX SQL. The graph itself is NOT yet persisted —
|
|
125
|
+
/// see Phase 7d.3 for cell-encoded graph storage.
|
|
126
|
+
pub hnsw_indexes: Vec<HnswIndexEntry>,
|
|
121
127
|
/// ROWID of most recent insert.
|
|
122
128
|
pub last_rowid: i64,
|
|
123
129
|
/// PRIMARY KEY column name, or "-1" if the table has no PRIMARY KEY.
|
|
124
130
|
pub primary_key: String,
|
|
125
131
|
}
|
|
126
132
|
|
|
133
|
+
/// One HNSW index attached to a table. Phase 7d.2 only supports L2
|
|
134
|
+
/// distance; cosine and dot are 7d.x follow-ups (would require either
|
|
135
|
+
/// distinct USING methods like `hnsw_cosine` or a `WITH (metric = …)`
|
|
136
|
+
/// clause — see `docs/phase-7-plan.md` for the deferred decision).
|
|
137
|
+
#[derive(Debug, Clone)]
|
|
138
|
+
pub struct HnswIndexEntry {
|
|
139
|
+
/// User-supplied name from `CREATE INDEX <name> …`. Unique across
|
|
140
|
+
/// both `secondary_indexes` and `hnsw_indexes` on a given table.
|
|
141
|
+
pub name: String,
|
|
142
|
+
/// The VECTOR column this index covers.
|
|
143
|
+
pub column_name: String,
|
|
144
|
+
/// The graph itself.
|
|
145
|
+
pub index: HnswIndex,
|
|
146
|
+
/// Phase 7d.3 — true iff a DELETE or UPDATE-on-vector-col has
|
|
147
|
+
/// invalidated the graph since the last rebuild. INSERT maintains
|
|
148
|
+
/// the graph incrementally and leaves this false. The next save
|
|
149
|
+
/// rebuilds dirty indexes from current rows before serializing.
|
|
150
|
+
pub needs_rebuild: bool,
|
|
151
|
+
}
|
|
152
|
+
|
|
127
153
|
impl Table {
|
|
128
154
|
pub fn new(create_query: CreateQuery) -> Self {
|
|
129
155
|
let table_name = create_query.table_name;
|
|
@@ -194,6 +220,11 @@ impl Table {
|
|
|
194
220
|
columns: table_cols,
|
|
195
221
|
rows: table_rows,
|
|
196
222
|
secondary_indexes,
|
|
223
|
+
// HNSW indexes only land via explicit CREATE INDEX … USING hnsw
|
|
224
|
+
// statements (Phase 7d.2); never auto-created at CREATE TABLE
|
|
225
|
+
// time, because there's no UNIQUE-style constraint that
|
|
226
|
+
// implies a vector index.
|
|
227
|
+
hnsw_indexes: Vec::new(),
|
|
197
228
|
last_rowid: 0,
|
|
198
229
|
primary_key,
|
|
199
230
|
}
|
|
@@ -217,6 +248,10 @@ impl Table {
|
|
|
217
248
|
columns: self.columns.clone(),
|
|
218
249
|
rows: Arc::new(Mutex::new(cloned_rows)),
|
|
219
250
|
secondary_indexes: self.secondary_indexes.clone(),
|
|
251
|
+
// HnswIndexEntry derives Clone, so the snapshot owns its own
|
|
252
|
+
// graph copy. Phase 4f's snapshot-rollback semantics require
|
|
253
|
+
// the snapshot to be fully decoupled from live state.
|
|
254
|
+
hnsw_indexes: self.hnsw_indexes.clone(),
|
|
220
255
|
last_rowid: self.last_rowid,
|
|
221
256
|
primary_key: self.primary_key.clone(),
|
|
222
257
|
}
|
|
@@ -813,16 +848,57 @@ impl Table {
|
|
|
813
848
|
|
|
814
849
|
// Step 2: maintain the secondary index (if any). insert() is a
|
|
815
850
|
// no-op for Value::Null and cheap for other value kinds.
|
|
816
|
-
if let Some(v) = typed_value {
|
|
851
|
+
if let Some(v) = typed_value.clone() {
|
|
817
852
|
if let Some(idx) = self.index_for_column_mut(key) {
|
|
818
853
|
idx.insert(&v, next_rowid)?;
|
|
819
854
|
}
|
|
820
855
|
}
|
|
856
|
+
|
|
857
|
+
// Step 3 (Phase 7d.2): maintain any HNSW indexes on this column.
|
|
858
|
+
// The HNSW algorithm needs access to other rows' vectors when
|
|
859
|
+
// wiring up neighbor edges, so build a get_vec closure that
|
|
860
|
+
// pulls from the table's row storage (which we *just* updated
|
|
861
|
+
// with the new value).
|
|
862
|
+
if let Some(Value::Vector(new_vec)) = typed_value {
|
|
863
|
+
self.maintain_hnsw_on_insert(key, next_rowid, &new_vec);
|
|
864
|
+
}
|
|
821
865
|
}
|
|
822
866
|
self.last_rowid = next_rowid;
|
|
823
867
|
Ok(())
|
|
824
868
|
}
|
|
825
869
|
|
|
870
|
+
/// After a row insert, push the new (rowid, vector) into every HNSW
|
|
871
|
+
/// index whose column matches `column`. Split out of `insert_row` so
|
|
872
|
+
/// the borrowing dance — we need both `&self.rows` (read other
|
|
873
|
+
/// vectors) and `&mut self.hnsw_indexes` (insert into the graph) —
|
|
874
|
+
/// stays localized.
|
|
875
|
+
fn maintain_hnsw_on_insert(&mut self, column: &str, rowid: i64, new_vec: &[f32]) {
|
|
876
|
+
// Snapshot the current vector storage so the get_vec closure
|
|
877
|
+
// doesn't fight with `&mut self.hnsw_indexes`. For a typical
|
|
878
|
+
// HNSW insert we touch ef_construction × log(N) other vectors,
|
|
879
|
+
// so the snapshot cost is small relative to the graph wiring.
|
|
880
|
+
let mut vec_snapshot: HashMap<i64, Vec<f32>> = HashMap::new();
|
|
881
|
+
{
|
|
882
|
+
let row_data = self.rows.lock().expect("rows mutex poisoned");
|
|
883
|
+
if let Some(Row::Vector(map)) = row_data.get(column) {
|
|
884
|
+
for (id, v) in map.iter() {
|
|
885
|
+
vec_snapshot.insert(*id, v.clone());
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
// The new row was just written into row storage — make sure the
|
|
890
|
+
// snapshot reflects it (it should, but defensive).
|
|
891
|
+
vec_snapshot.insert(rowid, new_vec.to_vec());
|
|
892
|
+
|
|
893
|
+
for entry in &mut self.hnsw_indexes {
|
|
894
|
+
if entry.column_name == column {
|
|
895
|
+
entry.index.insert(rowid, new_vec, |id| {
|
|
896
|
+
vec_snapshot.get(&id).cloned().unwrap_or_default()
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
826
902
|
/// Print the table schema to standard output in a pretty formatted way.
|
|
827
903
|
///
|
|
828
904
|
/// # Example
|