picovolt 1.5.0 → 1.7.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 +31 -18
- package/browser.js +47 -0
- package/package.json +1 -1
- package/picovolt.d.ts +40 -0
- package/picovolt.js +1 -1
- package/picovolt_bg.js +121 -0
- package/picovolt_bg.wasm +0 -0
- package/sqlite.js +42 -7
- package/worker.js +26 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# PicoVolt (PVDB)
|
|
2
2
|
|
|
3
3
|
[](https://github.com/MiniJe/picovolt/actions/workflows/ci.yml)
|
|
4
|
-
[](CHANGELOG.md)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|

|
|
7
7
|
[](https://github.com/MiniJe/picovolt)
|
|
@@ -58,6 +58,7 @@ Linux and Windows. Changes are tracked in [CHANGELOG.md](CHANGELOG.md).
|
|
|
58
58
|
| [`engine/interp.rs`](src/engine/interp.rs) | `pv-wasm`: a from-scratch WASM interpreter (integer subset) |
|
|
59
59
|
| [`engine/query.rs`](src/engine/query.rs) | SQL front-end (CREATE/INSERT/UPDATE/DELETE/DROP, `SELECT` with projection, `AS` aliases, `DISTINCT`, aggregates, `GROUP BY`/`HAVING`, `WHERE` predicates incl. `IN`/`BETWEEN`/`IS NULL`/`LIKE`, `BEFORE`, multi-column `ORDER BY`, `LIMIT`/`OFFSET`) |
|
|
60
60
|
| [`engine/compliance.rs`](src/engine/compliance.rs) | optional, app-driven usage-policy hook (not a license requirement) |
|
|
61
|
+
| [`enterprise.rs`](src/enterprise.rs) | optional, host-owned audit events and honest capability discovery for fleet integrations |
|
|
61
62
|
| [`db.rs`](src/db.rs) | the `Database` surface that ties it together |
|
|
62
63
|
| [`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 |
|
|
63
64
|
|
|
@@ -86,6 +87,10 @@ Linux and Windows. Changes are tracked in [CHANGELOG.md](CHANGELOG.md).
|
|
|
86
87
|
each flush `fsync` the data and commit the manifest atomically (write to a temp
|
|
87
88
|
file, `fsync`, then rename). The default `Fast` mode uses the OS cache only:
|
|
88
89
|
fast and durable on a clean exit, but not power-loss-safe.
|
|
90
|
+
- **Crash-recoverable transactions.** Explicit `BEGIN`, `COMMIT`, and
|
|
91
|
+
`ROLLBACK` group filesystem or in-memory writes. Filesystem transactions keep
|
|
92
|
+
a synced rollback image and recovery marker; reopening after interruption
|
|
93
|
+
restores the last committed state before loading the workspace.
|
|
89
94
|
- **Hardened against untrusted input.** Opening a `.pvdb` or workspace, or running
|
|
90
95
|
a WASM module, validates manifest hashes (no path traversal), bounds-checks CAS
|
|
91
96
|
offsets and page chains (no out-of-bounds reads or infinite loops on a crafted
|
|
@@ -117,19 +122,18 @@ Install the first-class CLI with `cargo install picovolt`, then use `pv query`,
|
|
|
117
122
|
Rust, Python, Go, Node, and browser projects are in [`starters/`](starters/README.md); supported adapters
|
|
118
123
|
are catalogued in [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md).
|
|
119
124
|
|
|
120
|
-
SQL
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
`
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
atomic `Database::transaction(...)` closures with in-memory databases.
|
|
125
|
+
SQL supports the normal PicoVolt CRUD and schema statements plus projection,
|
|
126
|
+
filters, aggregates, grouping, time travel, ordering, and pagination. The 1.7
|
|
127
|
+
query surface adds `AS`/bare table aliases, N-table equality `INNER`/`LEFT`
|
|
128
|
+
joins, searched `CASE WHEN`, and the focused `LOWER`, `UPPER`, `TRIM`, `LENGTH`,
|
|
129
|
+
`ABS`, `COALESCE`, and `NULLIF` scalar functions. Schema-light types, literal
|
|
130
|
+
defaults, named inserts, and persisted `CHECK` constraints cover common adapter
|
|
131
|
+
DDL. See the precise syntax, examples, type behavior, and deliberate limits in
|
|
132
|
+
[`docs/SQL.md`](docs/SQL.md). Rust callers can cache `Database::prepare(...)`
|
|
133
|
+
templates; C, WebAssembly, JavaScript, Python, and Go expose the same reusable
|
|
134
|
+
prepared-statement lifecycle. Callers can use explicit transactions or atomic
|
|
135
|
+
`Database::transaction(...)` closures with both filesystem and in-memory
|
|
136
|
+
databases.
|
|
133
137
|
Durability is selectable via `Database::set_durability` (`Fast` OS-cache default,
|
|
134
138
|
or crash-safe `Sync` with fsync and an atomic manifest).
|
|
135
139
|
|
|
@@ -140,8 +144,9 @@ around 33k rows/s, linear), larger-than-RAM reads through a bounded buffer pool
|
|
|
140
144
|
lookups roughly 11,000 times faster than a scan, plus range predicates), MVCC
|
|
141
145
|
time-travel, opt-in crash-safe durability (`Durability::Sync`), and a fast
|
|
142
146
|
compile-and-publish path (CAS dedup, columnar compression, memory-mappable
|
|
143
|
-
single-file artifacts). Current limits include
|
|
144
|
-
|
|
147
|
+
single-file artifacts). Current limits include full-workspace transaction
|
|
148
|
+
backups rather than an incremental WAL, left-deep equality joins rather than a
|
|
149
|
+
general SQL planner, and no concurrent writers.
|
|
145
150
|
|
|
146
151
|
## Install and distribution
|
|
147
152
|
|
|
@@ -149,7 +154,7 @@ transactions, only basic two-table equality joins, and no concurrent writers.
|
|
|
149
154
|
|--------|-----|
|
|
150
155
|
| **Rust** (crates.io) | `cargo add picovolt` |
|
|
151
156
|
| **JavaScript / npm** (WebAssembly, browser and Node) | `npm install picovolt` |
|
|
152
|
-
| **Python** (native wheels) | `python -m pip install picovolt`
|
|
157
|
+
| **Python** (native wheels) | `python -m pip install picovolt` |
|
|
153
158
|
| **C / Go** (native, via the C ABI) | `cargo build --release --features capi`, then see [`bindings/`](bindings) |
|
|
154
159
|
| **In-memory** (native, no filesystem) | `Database::open_memory()`, export with `bake_to_bytes()` |
|
|
155
160
|
|
|
@@ -172,7 +177,8 @@ JavaScript API (`import Database from "picovolt/sqlite"`), a Python DB-API 2.0
|
|
|
172
177
|
module (`import picovolt.dbapi2 as sqlite`), and the Go `database/sql` driver
|
|
173
178
|
([`bindings/go/pvsql`](bindings/go/pvsql)). Shared limits include positional `?`
|
|
174
179
|
only and the intentionally compact SQL grammar; JavaScript and in-memory Rust
|
|
175
|
-
also expose rollback-capable transaction wrappers.
|
|
180
|
+
also expose rollback-capable transaction wrappers. Native bindings expose the
|
|
181
|
+
same transaction lifecycle through the C ABI.
|
|
176
182
|
|
|
177
183
|
## Server mode
|
|
178
184
|
|
|
@@ -197,6 +203,11 @@ result rows, and response size are bounded. TLS is not built in, so network
|
|
|
197
203
|
deployments still belong behind a TLS-terminating reverse proxy. See
|
|
198
204
|
[src/bin/server.rs](src/bin/server.rs).
|
|
199
205
|
|
|
206
|
+
The HTTP API is sessionless, so each request is an atomic statement and explicit
|
|
207
|
+
transaction-control statements are rejected. Applications needing a
|
|
208
|
+
multi-statement transaction should use an embedded language binding, where the
|
|
209
|
+
transaction belongs to one database handle.
|
|
210
|
+
|
|
200
211
|
Applications accepting SQL from users can also call `Database::query_with_limits`
|
|
201
212
|
directly and choose their own scan, result, memory, and deadline budgets.
|
|
202
213
|
|
|
@@ -212,6 +223,8 @@ native modules built on the public API. Both are documented in
|
|
|
212
223
|
|--|--|
|
|
213
224
|
| Roadmap | [ROADMAP.md](ROADMAP.md) |
|
|
214
225
|
| One-million-download plan | [docs/ROADMAP_1M_DOWNLOADS.md](docs/ROADMAP_1M_DOWNLOADS.md) |
|
|
226
|
+
| Monetization thesis | [docs/MONETIZATION.md](docs/MONETIZATION.md) |
|
|
227
|
+
| Enterprise integration foundation | [docs/ENTERPRISE.md](docs/ENTERPRISE.md) |
|
|
215
228
|
| Platform and file support | [docs/SUPPORT.md](docs/SUPPORT.md) |
|
|
216
229
|
| Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) |
|
|
217
230
|
| Code of conduct | [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) |
|
package/browser.js
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
1
|
// Durable browser helper backed by the Origin Private File System (OPFS).
|
|
2
2
|
import { Db } from "./picovolt.js";
|
|
3
3
|
|
|
4
|
+
export class PersistentStatement {
|
|
5
|
+
constructor(database, source) {
|
|
6
|
+
this.database = database;
|
|
7
|
+
this.source = source;
|
|
8
|
+
this._prepared = database.db.prepare(source);
|
|
9
|
+
this.parameterCount = this._prepared.parameterCount;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
query(params = []) {
|
|
13
|
+
if (!this._prepared) throw new Error("PicoVolt prepared statement is closed");
|
|
14
|
+
this.database._assertOpen();
|
|
15
|
+
return JSON.parse(this._prepared.execute(this.database.db, params));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
close() {
|
|
19
|
+
if (!this._prepared) return false;
|
|
20
|
+
const prepared = this._prepared;
|
|
21
|
+
this._prepared = undefined;
|
|
22
|
+
this.database._statements.delete(this);
|
|
23
|
+
prepared.free();
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
finalize() {
|
|
28
|
+
return this.close();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
4
32
|
export class PersistentDb {
|
|
5
33
|
constructor(name, db) {
|
|
6
34
|
this.name = name;
|
|
7
35
|
this.db = db;
|
|
36
|
+
this._closed = false;
|
|
37
|
+
this._statements = new Set();
|
|
8
38
|
}
|
|
9
39
|
|
|
10
40
|
static async open(name = "picovolt.pvdb") {
|
|
@@ -20,11 +50,20 @@ export class PersistentDb {
|
|
|
20
50
|
}
|
|
21
51
|
|
|
22
52
|
query(sql, params) {
|
|
53
|
+
this._assertOpen();
|
|
23
54
|
const json = params === undefined ? this.db.query(sql) : this.db.query(sql, params);
|
|
24
55
|
return JSON.parse(json);
|
|
25
56
|
}
|
|
26
57
|
|
|
58
|
+
prepare(sql) {
|
|
59
|
+
this._assertOpen();
|
|
60
|
+
const statement = new PersistentStatement(this, sql);
|
|
61
|
+
this._statements.add(statement);
|
|
62
|
+
return statement;
|
|
63
|
+
}
|
|
64
|
+
|
|
27
65
|
async save() {
|
|
66
|
+
this._assertOpen();
|
|
28
67
|
const root = await navigator.storage.getDirectory();
|
|
29
68
|
const handle = await root.getFileHandle(this.name, { create: true });
|
|
30
69
|
const writable = await handle.createWritable();
|
|
@@ -33,7 +72,15 @@ export class PersistentDb {
|
|
|
33
72
|
}
|
|
34
73
|
|
|
35
74
|
async close() {
|
|
75
|
+
if (this._closed) return;
|
|
36
76
|
await this.save();
|
|
77
|
+
for (const statement of [...this._statements]) statement.close();
|
|
78
|
+
this.db.free();
|
|
79
|
+
this._closed = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_assertOpen() {
|
|
83
|
+
if (this._closed) throw new Error("PicoVolt database is closed");
|
|
37
84
|
}
|
|
38
85
|
}
|
|
39
86
|
|
package/package.json
CHANGED
package/picovolt.d.ts
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
export class Db {
|
|
8
8
|
free(): void;
|
|
9
9
|
[Symbol.dispose](): void;
|
|
10
|
+
/**
|
|
11
|
+
* Begin an explicit multi-statement in-memory transaction.
|
|
12
|
+
*/
|
|
13
|
+
beginTransaction(): void;
|
|
14
|
+
/**
|
|
15
|
+
* Commit the active transaction.
|
|
16
|
+
*/
|
|
17
|
+
commitTransaction(): void;
|
|
10
18
|
/**
|
|
11
19
|
* The most recently committed transaction id, the upper bound for a
|
|
12
20
|
* `... BEFORE tx` time-travel query.
|
|
@@ -21,6 +29,10 @@ export class Db {
|
|
|
21
29
|
* [`export`](Db::export)). Writable, with full time-travel history intact.
|
|
22
30
|
*/
|
|
23
31
|
static fromBytes(bytes: Uint8Array): Db;
|
|
32
|
+
/**
|
|
33
|
+
* Whether an explicit transaction is active.
|
|
34
|
+
*/
|
|
35
|
+
inTransaction(): boolean;
|
|
24
36
|
/**
|
|
25
37
|
* Create a new, empty in-memory database.
|
|
26
38
|
*/
|
|
@@ -32,6 +44,12 @@ export class Db {
|
|
|
32
44
|
* as queries touch them. `totalSize` is the image's byte length.
|
|
33
45
|
*/
|
|
34
46
|
static openRemote(read: Function, total_size: number): Db;
|
|
47
|
+
/**
|
|
48
|
+
* Validate and retain a reusable SQL template. Preparation verifies the
|
|
49
|
+
* syntax and records the exact positional-parameter count without running
|
|
50
|
+
* the statement.
|
|
51
|
+
*/
|
|
52
|
+
prepare(sql: string): PreparedStatement;
|
|
35
53
|
/**
|
|
36
54
|
* Run one SQL statement, optionally binding `?` placeholders to `params` (a
|
|
37
55
|
* JS array, e.g. `db.query("SELECT * FROM t WHERE id = ?", [1])`). Returns a
|
|
@@ -40,9 +58,31 @@ export class Db {
|
|
|
40
58
|
* `{"done":true}` otherwise. Throws the error message (a string) on failure.
|
|
41
59
|
*/
|
|
42
60
|
query(sql: string, params: any): string;
|
|
61
|
+
/**
|
|
62
|
+
* Roll back the active transaction.
|
|
63
|
+
*/
|
|
64
|
+
rollbackTransaction(): void;
|
|
43
65
|
/**
|
|
44
66
|
* A JSON array of the table names in this database (for introspecting an
|
|
45
67
|
* uploaded `.pvdb` whose schema is unknown).
|
|
46
68
|
*/
|
|
47
69
|
tables(): string;
|
|
48
70
|
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A validated, reusable SQL template for the raw WebAssembly API.
|
|
74
|
+
*/
|
|
75
|
+
export class PreparedStatement {
|
|
76
|
+
private constructor();
|
|
77
|
+
free(): void;
|
|
78
|
+
[Symbol.dispose](): void;
|
|
79
|
+
/**
|
|
80
|
+
* Execute this statement against `db`, returning the same JSON string as
|
|
81
|
+
* [`Db::query`]. The statement can be reused with different parameters.
|
|
82
|
+
*/
|
|
83
|
+
execute(db: Db, params: any): string;
|
|
84
|
+
/**
|
|
85
|
+
* Number of positional `?` values required by this statement.
|
|
86
|
+
*/
|
|
87
|
+
readonly parameterCount: number;
|
|
88
|
+
}
|
package/picovolt.js
CHANGED
package/picovolt_bg.js
CHANGED
|
@@ -18,6 +18,24 @@ export class Db {
|
|
|
18
18
|
const ptr = this.__destroy_into_raw();
|
|
19
19
|
wasm.__wbg_db_free(ptr, 0);
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Begin an explicit multi-statement in-memory transaction.
|
|
23
|
+
*/
|
|
24
|
+
beginTransaction() {
|
|
25
|
+
const ret = wasm.db_beginTransaction(this.__wbg_ptr);
|
|
26
|
+
if (ret[1]) {
|
|
27
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Commit the active transaction.
|
|
32
|
+
*/
|
|
33
|
+
commitTransaction() {
|
|
34
|
+
const ret = wasm.db_commitTransaction(this.__wbg_ptr);
|
|
35
|
+
if (ret[1]) {
|
|
36
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
21
39
|
/**
|
|
22
40
|
* The most recently committed transaction id, the upper bound for a
|
|
23
41
|
* `... BEFORE tx` time-travel query.
|
|
@@ -55,6 +73,14 @@ export class Db {
|
|
|
55
73
|
}
|
|
56
74
|
return Db.__wrap(ret[0]);
|
|
57
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether an explicit transaction is active.
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
inTransaction() {
|
|
81
|
+
const ret = wasm.db_inTransaction(this.__wbg_ptr);
|
|
82
|
+
return ret !== 0;
|
|
83
|
+
}
|
|
58
84
|
/**
|
|
59
85
|
* Create a new, empty in-memory database.
|
|
60
86
|
*/
|
|
@@ -80,6 +106,22 @@ export class Db {
|
|
|
80
106
|
}
|
|
81
107
|
return Db.__wrap(ret[0]);
|
|
82
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate and retain a reusable SQL template. Preparation verifies the
|
|
111
|
+
* syntax and records the exact positional-parameter count without running
|
|
112
|
+
* the statement.
|
|
113
|
+
* @param {string} sql
|
|
114
|
+
* @returns {PreparedStatement}
|
|
115
|
+
*/
|
|
116
|
+
prepare(sql) {
|
|
117
|
+
const ptr0 = passStringToWasm0(sql, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
118
|
+
const len0 = WASM_VECTOR_LEN;
|
|
119
|
+
const ret = wasm.db_prepare(this.__wbg_ptr, ptr0, len0);
|
|
120
|
+
if (ret[2]) {
|
|
121
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
122
|
+
}
|
|
123
|
+
return PreparedStatement.__wrap(ret[0]);
|
|
124
|
+
}
|
|
83
125
|
/**
|
|
84
126
|
* Run one SQL statement, optionally binding `?` placeholders to `params` (a
|
|
85
127
|
* JS array, e.g. `db.query("SELECT * FROM t WHERE id = ?", [1])`). Returns a
|
|
@@ -110,6 +152,15 @@ export class Db {
|
|
|
110
152
|
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
|
111
153
|
}
|
|
112
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Roll back the active transaction.
|
|
157
|
+
*/
|
|
158
|
+
rollbackTransaction() {
|
|
159
|
+
const ret = wasm.db_rollbackTransaction(this.__wbg_ptr);
|
|
160
|
+
if (ret[1]) {
|
|
161
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
113
164
|
/**
|
|
114
165
|
* A JSON array of the table names in this database (for introspecting an
|
|
115
166
|
* uploaded `.pvdb` whose schema is unknown).
|
|
@@ -135,6 +186,63 @@ export class Db {
|
|
|
135
186
|
}
|
|
136
187
|
}
|
|
137
188
|
if (Symbol.dispose) Db.prototype[Symbol.dispose] = Db.prototype.free;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A validated, reusable SQL template for the raw WebAssembly API.
|
|
192
|
+
*/
|
|
193
|
+
export class PreparedStatement {
|
|
194
|
+
static __wrap(ptr) {
|
|
195
|
+
const obj = Object.create(PreparedStatement.prototype);
|
|
196
|
+
obj.__wbg_ptr = ptr;
|
|
197
|
+
PreparedStatementFinalization.register(obj, obj.__wbg_ptr, obj);
|
|
198
|
+
return obj;
|
|
199
|
+
}
|
|
200
|
+
__destroy_into_raw() {
|
|
201
|
+
const ptr = this.__wbg_ptr;
|
|
202
|
+
this.__wbg_ptr = 0;
|
|
203
|
+
PreparedStatementFinalization.unregister(this);
|
|
204
|
+
return ptr;
|
|
205
|
+
}
|
|
206
|
+
free() {
|
|
207
|
+
const ptr = this.__destroy_into_raw();
|
|
208
|
+
wasm.__wbg_preparedstatement_free(ptr, 0);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Execute this statement against `db`, returning the same JSON string as
|
|
212
|
+
* [`Db::query`]. The statement can be reused with different parameters.
|
|
213
|
+
* @param {Db} db
|
|
214
|
+
* @param {any} params
|
|
215
|
+
* @returns {string}
|
|
216
|
+
*/
|
|
217
|
+
execute(db, params) {
|
|
218
|
+
let deferred2_0;
|
|
219
|
+
let deferred2_1;
|
|
220
|
+
try {
|
|
221
|
+
_assertClass(db, Db);
|
|
222
|
+
const ret = wasm.preparedstatement_execute(this.__wbg_ptr, db.__wbg_ptr, params);
|
|
223
|
+
var ptr1 = ret[0];
|
|
224
|
+
var len1 = ret[1];
|
|
225
|
+
if (ret[3]) {
|
|
226
|
+
ptr1 = 0; len1 = 0;
|
|
227
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
228
|
+
}
|
|
229
|
+
deferred2_0 = ptr1;
|
|
230
|
+
deferred2_1 = len1;
|
|
231
|
+
return getStringFromWasm0(ptr1, len1);
|
|
232
|
+
} finally {
|
|
233
|
+
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Number of positional `?` values required by this statement.
|
|
238
|
+
* @returns {number}
|
|
239
|
+
*/
|
|
240
|
+
get parameterCount() {
|
|
241
|
+
const ret = wasm.preparedstatement_parameterCount(this.__wbg_ptr);
|
|
242
|
+
return ret >>> 0;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (Symbol.dispose) PreparedStatement.prototype[Symbol.dispose] = PreparedStatement.prototype.free;
|
|
138
246
|
export function __wbg___wbindgen_boolean_get_c9c83ebd41b34df3(arg0) {
|
|
139
247
|
const v = arg0;
|
|
140
248
|
const ret = typeof(v) === 'boolean' ? v : undefined;
|
|
@@ -205,6 +313,10 @@ export function __wbg_instanceof_Uint8Array_f935dbb0aa7cdeed(arg0) {
|
|
|
205
313
|
const ret = result;
|
|
206
314
|
return ret;
|
|
207
315
|
}
|
|
316
|
+
export function __wbg_isArray_6339f732981044bf(arg0) {
|
|
317
|
+
const ret = Array.isArray(arg0);
|
|
318
|
+
return ret;
|
|
319
|
+
}
|
|
208
320
|
export function __wbg_length_36bd29c6848c2144(arg0) {
|
|
209
321
|
const ret = arg0.length;
|
|
210
322
|
return ret;
|
|
@@ -249,6 +361,9 @@ export function __wbindgen_init_externref_table() {
|
|
|
249
361
|
const DbFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
250
362
|
? { register: () => {}, unregister: () => {} }
|
|
251
363
|
: new FinalizationRegistry(ptr => wasm.__wbg_db_free(ptr, 1));
|
|
364
|
+
const PreparedStatementFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
365
|
+
? { register: () => {}, unregister: () => {} }
|
|
366
|
+
: new FinalizationRegistry(ptr => wasm.__wbg_preparedstatement_free(ptr, 1));
|
|
252
367
|
|
|
253
368
|
function addToExternrefTable0(obj) {
|
|
254
369
|
const idx = wasm.__externref_table_alloc();
|
|
@@ -256,6 +371,12 @@ function addToExternrefTable0(obj) {
|
|
|
256
371
|
return idx;
|
|
257
372
|
}
|
|
258
373
|
|
|
374
|
+
function _assertClass(instance, klass) {
|
|
375
|
+
if (!(instance instanceof klass)) {
|
|
376
|
+
throw new Error(`expected instance of ${klass.name}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
259
380
|
function debugString(val) {
|
|
260
381
|
// primitive types
|
|
261
382
|
const type = typeof val;
|
package/picovolt_bg.wasm
CHANGED
|
Binary file
|
package/sqlite.js
CHANGED
|
@@ -30,13 +30,19 @@ function normalizeParams(args) {
|
|
|
30
30
|
|
|
31
31
|
class Statement {
|
|
32
32
|
constructor(db, sql) {
|
|
33
|
+
db._assertOpen();
|
|
33
34
|
this._db = db;
|
|
34
35
|
this.source = sql;
|
|
36
|
+
this._prepared = db._db.prepare(sql);
|
|
37
|
+
this.parameterCount = this._prepared.parameterCount;
|
|
38
|
+
db._statements.add(this);
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
_exec(args) {
|
|
42
|
+
if (!this._prepared) throw new Error("PicoVolt prepared statement is closed");
|
|
43
|
+
this._db._assertOpen();
|
|
38
44
|
const params = normalizeParams(args);
|
|
39
|
-
const json =
|
|
45
|
+
const json = this._prepared.execute(this._db._db, params);
|
|
40
46
|
return JSON.parse(json);
|
|
41
47
|
}
|
|
42
48
|
|
|
@@ -60,12 +66,27 @@ class Statement {
|
|
|
60
66
|
*iterate(...args) {
|
|
61
67
|
yield* this.all(...args);
|
|
62
68
|
}
|
|
69
|
+
|
|
70
|
+
close() {
|
|
71
|
+
if (!this._prepared) return false;
|
|
72
|
+
const prepared = this._prepared;
|
|
73
|
+
this._prepared = undefined;
|
|
74
|
+
this._db._statements.delete(this);
|
|
75
|
+
prepared.free();
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
finalize() {
|
|
80
|
+
return this.close();
|
|
81
|
+
}
|
|
63
82
|
}
|
|
64
83
|
|
|
65
84
|
class Database {
|
|
66
85
|
constructor() {
|
|
67
86
|
this._db = new Db();
|
|
68
87
|
this._inTransaction = false;
|
|
88
|
+
this._closed = false;
|
|
89
|
+
this._statements = new Set();
|
|
69
90
|
}
|
|
70
91
|
|
|
71
92
|
prepare(sql) {
|
|
@@ -74,6 +95,7 @@ class Database {
|
|
|
74
95
|
|
|
75
96
|
// Run one or more `;`-separated statements with no bound parameters.
|
|
76
97
|
exec(sql) {
|
|
98
|
+
this._assertOpen();
|
|
77
99
|
for (const stmt of sql.split(";").map((s) => s.trim()).filter(Boolean)) {
|
|
78
100
|
this._db.query(stmt);
|
|
79
101
|
}
|
|
@@ -82,11 +104,13 @@ class Database {
|
|
|
82
104
|
|
|
83
105
|
// The most recent committed transaction id (upper bound for `... BEFORE tx`).
|
|
84
106
|
get currentTx() {
|
|
107
|
+
this._assertOpen();
|
|
85
108
|
return this._db.currentTx();
|
|
86
109
|
}
|
|
87
110
|
|
|
88
111
|
// Export the database as a `.pvdb` byte image (Uint8Array).
|
|
89
112
|
serialize() {
|
|
113
|
+
this._assertOpen();
|
|
90
114
|
return this._db.export();
|
|
91
115
|
}
|
|
92
116
|
|
|
@@ -94,19 +118,23 @@ class Database {
|
|
|
94
118
|
throw new Error("picovolt: pragma is not supported");
|
|
95
119
|
}
|
|
96
120
|
|
|
97
|
-
// Wrap a synchronous callback in an
|
|
98
|
-
//
|
|
121
|
+
// Wrap a synchronous callback in an engine transaction, matching
|
|
122
|
+
// better-sqlite3's common pattern.
|
|
99
123
|
transaction(fn) {
|
|
100
124
|
if (typeof fn !== "function") throw new TypeError("transaction expects a function");
|
|
125
|
+
this._assertOpen();
|
|
101
126
|
const db = this;
|
|
102
127
|
function wrapped(...args) {
|
|
128
|
+
db._assertOpen();
|
|
103
129
|
if (db._inTransaction) return fn(...args);
|
|
104
|
-
|
|
130
|
+
db._db.beginTransaction();
|
|
105
131
|
db._inTransaction = true;
|
|
106
132
|
try {
|
|
107
|
-
|
|
133
|
+
const value = fn(...args);
|
|
134
|
+
db._db.commitTransaction();
|
|
135
|
+
return value;
|
|
108
136
|
} catch (error) {
|
|
109
|
-
db._db
|
|
137
|
+
if (db._db.inTransaction()) db._db.rollbackTransaction();
|
|
110
138
|
throw error;
|
|
111
139
|
} finally {
|
|
112
140
|
db._inTransaction = false;
|
|
@@ -119,7 +147,14 @@ class Database {
|
|
|
119
147
|
}
|
|
120
148
|
|
|
121
149
|
close() {
|
|
122
|
-
|
|
150
|
+
if (this._closed) return;
|
|
151
|
+
for (const statement of [...this._statements]) statement.close();
|
|
152
|
+
this._db.free();
|
|
153
|
+
this._closed = true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
_assertOpen() {
|
|
157
|
+
if (this._closed) throw new Error("PicoVolt database is closed");
|
|
123
158
|
}
|
|
124
159
|
}
|
|
125
160
|
|
package/worker.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { PersistentDb } from "./browser.js";
|
|
4
4
|
|
|
5
5
|
let database;
|
|
6
|
+
let nextStatementId = 1;
|
|
7
|
+
const statements = new Map();
|
|
6
8
|
|
|
7
9
|
self.addEventListener("message", async ({ data }) => {
|
|
8
10
|
const { id, method } = data ?? {};
|
|
@@ -10,6 +12,9 @@ self.addEventListener("message", async ({ data }) => {
|
|
|
10
12
|
let result;
|
|
11
13
|
switch (method) {
|
|
12
14
|
case "open":
|
|
15
|
+
if (database) await database.close();
|
|
16
|
+
database = undefined;
|
|
17
|
+
statements.clear();
|
|
13
18
|
database = await PersistentDb.open(data.name);
|
|
14
19
|
result = true;
|
|
15
20
|
break;
|
|
@@ -17,6 +22,26 @@ self.addEventListener("message", async ({ data }) => {
|
|
|
17
22
|
if (!database) throw new Error("open the database first");
|
|
18
23
|
result = database.query(data.sql, data.params);
|
|
19
24
|
break;
|
|
25
|
+
case "prepare": {
|
|
26
|
+
if (!database) throw new Error("open the database first");
|
|
27
|
+
const statement = database.prepare(data.sql);
|
|
28
|
+
const statementId = nextStatementId++;
|
|
29
|
+
statements.set(statementId, statement);
|
|
30
|
+
result = { statementId, parameterCount: statement.parameterCount };
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case "execute": {
|
|
34
|
+
const statement = statements.get(data.statementId);
|
|
35
|
+
if (!statement) throw new Error("unknown PicoVolt prepared statement");
|
|
36
|
+
result = statement.query(data.params ?? []);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "finalize": {
|
|
40
|
+
const statement = statements.get(data.statementId);
|
|
41
|
+
result = statement ? statement.close() : false;
|
|
42
|
+
statements.delete(data.statementId);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
20
45
|
case "save":
|
|
21
46
|
if (!database) throw new Error("open the database first");
|
|
22
47
|
await database.save();
|
|
@@ -25,6 +50,7 @@ self.addEventListener("message", async ({ data }) => {
|
|
|
25
50
|
case "close":
|
|
26
51
|
if (database) await database.close();
|
|
27
52
|
database = undefined;
|
|
53
|
+
statements.clear();
|
|
28
54
|
result = true;
|
|
29
55
|
break;
|
|
30
56
|
default:
|