picovolt 1.8.1 → 2.0.0
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.
- package/README.md +74 -26
- package/browser.js +5 -0
- package/package.json +1 -1
- package/picovolt.d.ts +4 -0
- package/picovolt_bg.js +26 -0
- package/picovolt_bg.wasm +0 -0
- package/sqlite.js +5 -0
- package/worker.js +4 -0
package/README.md
CHANGED
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
[](https://github.com/MiniJe/picovolt/actions/workflows/ci.yml)
|
|
4
4
|
[](https://crates.io/crates/picovolt)
|
|
5
5
|
[](LICENSE)
|
|
6
|
-

|
|
7
7
|
[](https://github.com/MiniJe/picovolt)
|
|
8
8
|
|
|
9
|
-
PicoVolt is an embedded database engine written in Rust.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
PicoVolt is an embedded database engine written in Rust. **2.0** provides
|
|
10
|
+
concurrent snapshot readers, bounded writer scheduling, and an incremental
|
|
11
|
+
durable commit log. Independent security review and external application trials
|
|
12
|
+
have not been completed. See the [2.0 release ledger](docs/RELEASE_2_0.md)
|
|
13
|
+
for qualification evidence and publication status.
|
|
14
|
+
|
|
15
|
+
Start with the [2.0 guide for every maintained interface](docs/QUICKSTART_2_0.md)
|
|
16
|
+
for atomic batches, persistence choices, log diagnostics and error recovery.
|
|
17
|
+
The [standalone review prompt](docs/INDEPENDENT_REVIEW_PROMPT.md) defines an
|
|
18
|
+
independent assessment and external trials deferred beyond the 2.0 release.
|
|
13
19
|
|
|
14
20
|
If PicoVolt is useful to you, consider starring the repository on GitHub. It is
|
|
15
21
|
the simplest way to help others discover the project.
|
|
@@ -22,12 +28,13 @@ Virtualization Layer Engine (VLE) that shifts between two on-disk shapes:
|
|
|
22
28
|
- **Production mode:** a single contiguous, memory-mappable `.pvdb` file produced
|
|
23
29
|
by `pv_bake()`.
|
|
24
30
|
|
|
25
|
-
New records use a slotted row layout for O(1) appends.
|
|
26
|
-
|
|
31
|
+
New records use a slotted row layout for O(1) appends. Applications can run
|
|
32
|
+
bounded maintenance steps that transpose immutable non-tail pages into an
|
|
33
|
+
MVCC-preserving columnar layout with packed decimal encoding.
|
|
27
34
|
|
|
28
35
|
## Status
|
|
29
36
|
|
|
30
|
-
The
|
|
37
|
+
The engine is exercised by Rust unit and integration suites plus doctests
|
|
31
38
|
and maintained-binding integration tests. CI also enforces formatting and
|
|
32
39
|
warning-free Clippy builds on Linux and Windows. Shipped changes are tracked in
|
|
33
40
|
[CHANGELOG.md](CHANGELOG.md), and the remaining work toward 2.0 is tracked in
|
|
@@ -54,6 +61,7 @@ warning-free Clippy builds on Linux and Windows. Shipped changes are tracked in
|
|
|
54
61
|
| [`engine/compliance.rs`](src/engine/compliance.rs) | optional, app-driven usage-policy hook (not a license requirement) |
|
|
55
62
|
| [`enterprise.rs`](src/enterprise.rs) | optional, host-owned audit events and honest capability discovery for fleet integrations |
|
|
56
63
|
| [`db.rs`](src/db.rs) | the `Database` surface that ties it together |
|
|
64
|
+
| [`upgrade.rs`](src/upgrade.rs) | out-of-place format migration with backup and deep verification |
|
|
57
65
|
| [`ffi.rs`](src/ffi.rs) | C ABI (the `capi` feature): a panic-safe, C-callable surface wrapping the engine for Go, Python, and C bindings |
|
|
58
66
|
|
|
59
67
|
### Engineering notes
|
|
@@ -69,22 +77,27 @@ warning-free Clippy builds on Linux and Windows. Shipped changes are tracked in
|
|
|
69
77
|
differential test checks `pv-wasm` against `wasmi` to keep it honest. Floats,
|
|
70
78
|
tables, globals, imports, SIMD, and `br_table` are out of scope for `pv-wasm`
|
|
71
79
|
and are rejected rather than mis-run.
|
|
72
|
-
- **Page-backed engine.** Tables are append-only chains of row pages
|
|
73
|
-
linking to the next. Inserts append to
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
- **Page-backed engine.** Tables are append-only chains of hot row pages and
|
|
81
|
+
optional packed cold pages, each header linking to the next. Inserts append to
|
|
82
|
+
a row tail. Commit cost also includes catalog/index maintenance, retained-log
|
|
83
|
+
accounting and the selected durability protocol; it is not uniformly O(1).
|
|
84
|
+
Logged 2.0 workspaces persist index definitions to reduce catalog rewrites.
|
|
85
|
+
Reads stream through a
|
|
86
|
+
bounded buffer pool ([`storage/cache.rs`](src/storage/cache.rs)), so datasets
|
|
87
|
+
need not fit in RAM, and opt-in ordered indexes
|
|
88
|
+
([`storage/index.rs`](src/storage/index.rs)) turn `WHERE col = value` into a
|
|
89
|
+
point lookup and range comparisons such as
|
|
79
90
|
`WHERE col > v` into an ordered scan rather than a full scan.
|
|
80
91
|
- **Selectable durability.** `Database::set_durability(Durability::Sync)` makes
|
|
81
92
|
each flush `fsync` the data and commit the manifest atomically (write to a temp
|
|
82
93
|
file, `fsync`, then rename). The default `Fast` mode uses the OS cache only:
|
|
83
94
|
fast and durable on a clean exit, but not power-loss-safe.
|
|
84
|
-
- **
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
- **Concurrent transactions.** Native `SharedDatabase` exposes independent
|
|
96
|
+
snapshot readers and bounded FIFO writers. Logged workspaces sync original
|
|
97
|
+
pages before overwriting them and publish an ordered physical change stream.
|
|
98
|
+
Reopening rolls back incomplete writes. Format 6 prevents old binaries from
|
|
99
|
+
bypassing recovery. Existing 1.x images remain readable and migratable.
|
|
100
|
+
See [the concurrency contract](docs/CONCURRENCY.md) for limits and costs.
|
|
88
101
|
- **Hardened against untrusted input.** Opening a `.pvdb` or workspace, or running
|
|
89
102
|
a WASM module, validates manifest hashes (no path traversal), bounds-checks CAS
|
|
90
103
|
offsets and page chains (no out-of-bounds reads or infinite loops on a crafted
|
|
@@ -112,7 +125,8 @@ cargo run --release --example bench # evaluation harness across modes and wor
|
|
|
112
125
|
```
|
|
113
126
|
|
|
114
127
|
Install the full CLI with `cargo install picovolt --features data-tools`, then use `pv query`,
|
|
115
|
-
`pv inspect`, `pv history`, `pv diff`, `pv
|
|
128
|
+
`pv inspect`, `pv history`, `pv diff`, `pv migrate`, `pv compact`, `pv import`,
|
|
129
|
+
`pv export`, and `pv bake`.
|
|
116
130
|
Parquet/SQLite conversion, query explanations, inspection, resumable baking,
|
|
117
131
|
and dataset signing are documented in [Data tools](docs/DATA_TOOLS.md).
|
|
118
132
|
|
|
@@ -146,16 +160,51 @@ databases.
|
|
|
146
160
|
Durability is selectable via `Database::set_durability` (`Fast` OS-cache default,
|
|
147
161
|
or crash-safe `Sync` with fsync and an atomic manifest).
|
|
148
162
|
|
|
163
|
+
Native Rust applications can begin adopting the 2.0 concurrency surface through
|
|
164
|
+
`SharedDatabase`. It is a cloneable, bounded worker-thread coordinator with
|
|
165
|
+
explicit read and write transaction handles, FIFO admission, cooperative
|
|
166
|
+
cancellation, and rollback before failed or abandoned writes release the queue.
|
|
167
|
+
The first slice serializes execution and preserves format v5; use clones of one
|
|
168
|
+
coordinator rather than independently opening the same development workspace.
|
|
169
|
+
See [Shared database concurrency](docs/CONCURRENCY.md) for the contract and
|
|
170
|
+
current limits.
|
|
171
|
+
|
|
149
172
|
Measured results and the methodology are in [BENCHMARKS.md](BENCHMARKS.md). In
|
|
150
173
|
short, PicoVolt is a page-backed engine with O(1) filesystem appends (autocommit
|
|
151
174
|
around 33k rows/s, linear), larger-than-RAM reads through a bounded buffer pool (a
|
|
152
175
|
667-page dataset serves from a 16-page pool), ordered secondary indexes (point
|
|
153
176
|
lookups roughly 6,100 times faster than a scan, plus range predicates), MVCC
|
|
154
177
|
time-travel, opt-in crash-safe durability (`Durability::Sync`), and a fast
|
|
155
|
-
compile-and-publish path (CAS dedup, columnar compression,
|
|
156
|
-
single-file artifacts). Current limits include full-workspace
|
|
157
|
-
backups rather than an incremental WAL,
|
|
158
|
-
general SQL planner, and no
|
|
178
|
+
compile-and-publish path (CAS dedup, cooperative columnar compression,
|
|
179
|
+
memory-mappable single-file artifacts). Current limits include full-workspace
|
|
180
|
+
transaction backups rather than an incremental WAL, adaptive index access within
|
|
181
|
+
a left-deep equality-join plan rather than a general SQL planner, and no
|
|
182
|
+
concurrent writers.
|
|
183
|
+
|
|
184
|
+
## Maintenance and migration
|
|
185
|
+
|
|
186
|
+
Cold-page maintenance is explicit in 1.x so the host retains ownership of
|
|
187
|
+
scheduling and threads:
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
pv compact ./data.pv --max-pages 64
|
|
191
|
+
pv inspect ./data.pv --json
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`Database::compact_step(max_pages)` preserves record addresses, indexes, and
|
|
195
|
+
complete MVCC history; it never compacts the mutable tail and leaves a page in
|
|
196
|
+
row form when transposition would not save space. Each pass uses the
|
|
197
|
+
crash-recoverable transaction protocol: allow bounded journal space for logged
|
|
198
|
+
workspaces, or a full rollback image for unlogged workspaces. Baked-image migration is
|
|
199
|
+
out-of-place and deeply verified before publication:
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
pv migrate old.pvdb new.pvdb --dry-run
|
|
203
|
+
pv migrate old.pvdb new.pvdb --backup old.exact-backup.pvdb
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The source is never modified and existing destinations are never overwritten.
|
|
207
|
+
See [Migration and compaction](docs/MIGRATION.md).
|
|
159
208
|
|
|
160
209
|
## Install and distribution
|
|
161
210
|
|
|
@@ -164,7 +213,7 @@ general SQL planner, and no concurrent writers.
|
|
|
164
213
|
| **Rust** (crates.io) | `cargo add picovolt` |
|
|
165
214
|
| **JavaScript / npm** (WebAssembly, browser and Node) | `npm install picovolt` |
|
|
166
215
|
| **Python** (native wheels) | `python -m pip install picovolt` |
|
|
167
|
-
| **Go** (`database/sql` and direct API) | `go get github.com/MiniJe/picovolt/bindings/go@
|
|
216
|
+
| **Go** (`database/sql` and direct API) | `go get github.com/MiniJe/picovolt/bindings/go/v2@v2.0.0`, then provide the matching native C ABI library described in [`bindings/go/`](bindings/go) |
|
|
168
217
|
| **C** | Download the matching `picovolt-capi-*` bundle from the [latest release](https://github.com/MiniJe/picovolt/releases/latest), or run `cargo build --release --features capi` |
|
|
169
218
|
| **In-memory** (native, no filesystem) | `Database::open_memory()`, export with `bake_to_bytes()` |
|
|
170
219
|
|
|
@@ -233,7 +282,6 @@ native modules built on the public API. Both are documented in
|
|
|
233
282
|
| | |
|
|
234
283
|
|--|--|
|
|
235
284
|
| Roadmap | [ROADMAP.md](ROADMAP.md) |
|
|
236
|
-
| One-million-download plan | [docs/ROADMAP_1M_DOWNLOADS.md](docs/ROADMAP_1M_DOWNLOADS.md) |
|
|
237
285
|
| Monetization thesis | [docs/MONETIZATION.md](docs/MONETIZATION.md) |
|
|
238
286
|
| Enterprise integration foundation | [docs/ENTERPRISE.md](docs/ENTERPRISE.md) |
|
|
239
287
|
| Platform and file support | [docs/SUPPORT.md](docs/SUPPORT.md) |
|
package/browser.js
CHANGED
|
@@ -62,6 +62,11 @@ export class PersistentDb {
|
|
|
62
62
|
return statement;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
executeMany(sql, rows) {
|
|
66
|
+
this._assertOpen();
|
|
67
|
+
return JSON.parse(this.db.executeMany(sql, rows)).mutated;
|
|
68
|
+
}
|
|
69
|
+
|
|
65
70
|
async save() {
|
|
66
71
|
this._assertOpen();
|
|
67
72
|
const root = await navigator.storage.getDirectory();
|
package/package.json
CHANGED
package/picovolt.d.ts
CHANGED
|
@@ -20,6 +20,10 @@ export class Db {
|
|
|
20
20
|
* `... BEFORE tx` time-travel query.
|
|
21
21
|
*/
|
|
22
22
|
currentTx(): number;
|
|
23
|
+
/**
|
|
24
|
+
* Atomically execute an INSERT/UPDATE/DELETE for an array of parameter arrays.
|
|
25
|
+
*/
|
|
26
|
+
executeMany(sql: string, rows: any): string;
|
|
23
27
|
/**
|
|
24
28
|
* Export the whole database as a `.pvdb` byte image (a `Uint8Array` in JS).
|
|
25
29
|
*/
|
package/picovolt_bg.js
CHANGED
|
@@ -45,6 +45,32 @@ export class Db {
|
|
|
45
45
|
const ret = wasm.db_currentTx(this.__wbg_ptr);
|
|
46
46
|
return ret >>> 0;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Atomically execute an INSERT/UPDATE/DELETE for an array of parameter arrays.
|
|
50
|
+
* @param {string} sql
|
|
51
|
+
* @param {any} rows
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
executeMany(sql, rows) {
|
|
55
|
+
let deferred3_0;
|
|
56
|
+
let deferred3_1;
|
|
57
|
+
try {
|
|
58
|
+
const ptr0 = passStringToWasm0(sql, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
59
|
+
const len0 = WASM_VECTOR_LEN;
|
|
60
|
+
const ret = wasm.db_executeMany(this.__wbg_ptr, ptr0, len0, rows);
|
|
61
|
+
var ptr2 = ret[0];
|
|
62
|
+
var len2 = ret[1];
|
|
63
|
+
if (ret[3]) {
|
|
64
|
+
ptr2 = 0; len2 = 0;
|
|
65
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
66
|
+
}
|
|
67
|
+
deferred3_0 = ptr2;
|
|
68
|
+
deferred3_1 = len2;
|
|
69
|
+
return getStringFromWasm0(ptr2, len2);
|
|
70
|
+
} finally {
|
|
71
|
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
48
74
|
/**
|
|
49
75
|
* Export the whole database as a `.pvdb` byte image (a `Uint8Array` in JS).
|
|
50
76
|
* @returns {Uint8Array}
|
package/picovolt_bg.wasm
CHANGED
|
Binary file
|
package/sqlite.js
CHANGED
|
@@ -94,6 +94,11 @@ class Database {
|
|
|
94
94
|
return new Statement(this, sql);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
executeMany(sql, rows) {
|
|
98
|
+
this._assertOpen();
|
|
99
|
+
return JSON.parse(this._db.executeMany(sql, rows)).mutated;
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
// Run one or more `;`-separated statements with no bound parameters.
|
|
98
103
|
exec(sql) {
|
|
99
104
|
this._assertOpen();
|
package/worker.js
CHANGED
|
@@ -30,6 +30,10 @@ self.addEventListener("message", async ({ data }) => {
|
|
|
30
30
|
result = { statementId, parameterCount: statement.parameterCount };
|
|
31
31
|
break;
|
|
32
32
|
}
|
|
33
|
+
case "executeMany":
|
|
34
|
+
if (!database) throw new Error("open the database first");
|
|
35
|
+
result = database.executeMany(data.sql, data.rows);
|
|
36
|
+
break;
|
|
33
37
|
case "execute": {
|
|
34
38
|
const statement = statements.get(data.statementId);
|
|
35
39
|
if (!statement) throw new Error("unknown PicoVolt prepared statement");
|