@eltjs/elt-sqlite 0.1.2 → 0.2.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 +2 -1
- package/dist/computed-dataset.js +18 -7
- package/dist/sqlite-deduplicating-writer.js +2 -4
- package/dist/sqlite-file-store.js +2 -5
- package/dist/sqlite-writer.js +2 -2
- package/dist/view.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -197,7 +197,8 @@ At each refresh, the computed dataset compares the inputs with the output rows.
|
|
|
197
197
|
## When something fails
|
|
198
198
|
|
|
199
199
|
- **A rule failure** gives an output row with the status `failed` and the reason in `error`. The run continues, and the refresh after the next pass tries the input again.
|
|
200
|
-
- **A dataset failure**, for example a query that SQLite refuses, rejects the run, or it stops the watch.
|
|
200
|
+
- **A dataset failure**, for example a query that SQLite refuses, rejects the run, or it stops the watch, with a `DerivedDatasetError` that names the dataset.
|
|
201
|
+
- **A table that is gone**, for example one that a person dropped by hand, is no failure. A computed dataset that reads it waits and keeps its output rows. When the copy loads the table again, the dataset asks the rule only for inputs that changed.
|
|
201
202
|
- **A copy that failed** keeps its last committed rows. The derived datasets read those rows.
|
|
202
203
|
|
|
203
204
|
## Recipes
|
package/dist/computed-dataset.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hash } from 'node:crypto';
|
|
2
2
|
import { Declaration } from '@zukhruf/sqlite-drift';
|
|
3
3
|
import { open } from '@zukhruf/sqlite-open';
|
|
4
4
|
import { transaction } from '@zukhruf/sqlite-transaction';
|
|
@@ -78,6 +78,8 @@ export class ComputedDataset extends DerivedDataset {
|
|
|
78
78
|
}
|
|
79
79
|
async refresh({ connections, signal, trigger, }) {
|
|
80
80
|
const inputs = this.#inputs(connections);
|
|
81
|
+
if (inputs === null)
|
|
82
|
+
return;
|
|
81
83
|
using database = openDestination(this.#into.path);
|
|
82
84
|
const key = this.#rule.key.name;
|
|
83
85
|
const stored = transaction(database, () => {
|
|
@@ -119,9 +121,7 @@ export class ComputedDataset extends DerivedDataset {
|
|
|
119
121
|
for (const [id, input] of inputs) {
|
|
120
122
|
if (signal?.aborted)
|
|
121
123
|
return;
|
|
122
|
-
const inputHash =
|
|
123
|
-
.update(JSON.stringify(input))
|
|
124
|
-
.digest('hex');
|
|
124
|
+
const inputHash = hash('sha256', JSON.stringify(input));
|
|
125
125
|
const rule = await this.#rule.identity(input);
|
|
126
126
|
const before = stored.get(id);
|
|
127
127
|
// A rule that does not handle the input keeps what it made before.
|
|
@@ -190,6 +190,10 @@ export class ComputedDataset extends DerivedDataset {
|
|
|
190
190
|
// Each input by key, from the rows of each relation of from that exists
|
|
191
191
|
// yet: what the copies loaded, or what a view before this dataset gives. A
|
|
192
192
|
// key read twice keeps its first input.
|
|
193
|
+
// null while a relation reads a table that is gone, such as one a person
|
|
194
|
+
// dropped by hand: its copy loads it again, and the dataset waits with what it
|
|
195
|
+
// made, as a view waits. Read as no rows, it would delete every output, and the
|
|
196
|
+
// reload would pay the rule for each again.
|
|
193
197
|
#inputs(connections) {
|
|
194
198
|
const inputs = new Map();
|
|
195
199
|
for (const target of this.#from) {
|
|
@@ -206,9 +210,16 @@ export class ComputedDataset extends DerivedDataset {
|
|
|
206
210
|
.prepare('SELECT 1 FROM sqlite_schema WHERE lower("name") = lower(?)')
|
|
207
211
|
.get(relation) === undefined)
|
|
208
212
|
continue;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
.all()
|
|
213
|
+
let rows;
|
|
214
|
+
try {
|
|
215
|
+
rows = database.prepare(`SELECT * FROM ${quote(relation)}`).all();
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (Error.isError(error) && error.message.startsWith('no such table: '))
|
|
219
|
+
return null;
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
for (const row of rows) {
|
|
212
223
|
const read = this.#rule.read(row);
|
|
213
224
|
if (read !== null && !inputs.has(read.key))
|
|
214
225
|
inputs.set(read.key, read.input);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hash } from 'node:crypto';
|
|
2
2
|
import { SQLiteColumns } from './sqlite-columns.js';
|
|
3
3
|
import { SQLiteWriter, op, seq } from './sqlite-writer.js';
|
|
4
4
|
export class SQLiteDeduplicatingWriter extends SQLiteWriter {
|
|
@@ -29,9 +29,7 @@ export class SQLiteDeduplicatingWriter extends SQLiteWriter {
|
|
|
29
29
|
// Named after the table and its key, so a changed key builds a new index
|
|
30
30
|
// and an unchanged one is never rebuilt.
|
|
31
31
|
get #dedupIndex() {
|
|
32
|
-
return `_elt_dedup_${
|
|
33
|
-
.update(JSON.stringify([this.table.name, this.keys.map((key) => key.name)]))
|
|
34
|
-
.digest('hex')}`;
|
|
32
|
+
return `_elt_dedup_${hash('sha256', JSON.stringify([this.table.name, this.keys.map((key) => key.name)]))}`;
|
|
35
33
|
}
|
|
36
34
|
// A stored table the load merges into must hold keys and cursors of the
|
|
37
35
|
// stream's types, none null, and no key twice, which its index forbids once
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hash } from 'node:crypto';
|
|
2
2
|
import { identifiers } from './sqlite-identifiers.js';
|
|
3
3
|
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
4
4
|
const tablePrefix = '_elt_files_';
|
|
@@ -26,10 +26,7 @@ export class SQLiteFileStore {
|
|
|
26
26
|
// table a_b with column c and table a with column b_c would share chunks
|
|
27
27
|
// and prune each other's.
|
|
28
28
|
static tableName(table, column) {
|
|
29
|
-
const key =
|
|
30
|
-
.update(JSON.stringify([table.location, identifiers.key(column.name)]))
|
|
31
|
-
.digest('hex')
|
|
32
|
-
.slice(0, 40);
|
|
29
|
+
const key = hash('sha256', JSON.stringify([table.location, identifiers.key(column.name)])).slice(0, 40);
|
|
33
30
|
return `${tablePrefix}${key}`;
|
|
34
31
|
}
|
|
35
32
|
#exists(location) {
|
package/dist/sqlite-writer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hash } from 'node:crypto';
|
|
2
2
|
import { Declaration } from '@zukhruf/sqlite-drift';
|
|
3
3
|
import { transaction } from '@zukhruf/sqlite-transaction';
|
|
4
4
|
import { FileContent, TargetOwnedError, Writer, describeTarget, errorMessage, reloadMode, } from '@eltjs/elt';
|
|
@@ -71,7 +71,7 @@ export class SQLiteWriter extends Writer {
|
|
|
71
71
|
database.exec(`DELETE FROM ${this.table.quotedName}`);
|
|
72
72
|
}
|
|
73
73
|
get hash() {
|
|
74
|
-
return
|
|
74
|
+
return hash('sha256', this.table.location);
|
|
75
75
|
}
|
|
76
76
|
get fields() {
|
|
77
77
|
return [
|
package/dist/view.js
CHANGED
|
@@ -33,7 +33,7 @@ export class View extends DerivedDataset {
|
|
|
33
33
|
database.prepare(this.#view.query);
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
36
|
-
const missing = error
|
|
36
|
+
const missing = Error.isError(error)
|
|
37
37
|
? /^no such table: (?:main\.)?(.+)$/.exec(error.message)?.[1]
|
|
38
38
|
: undefined;
|
|
39
39
|
const missingName = missing?.toLowerCase();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eltjs/elt-sqlite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The SQLite destination of elt: each stream is a raw table in one SQLite file, with a reader view over it and a catalog of each table, view and column.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=24"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@eltjs/elt": "^0.
|
|
42
|
+
"@eltjs/elt": "^0.2.0",
|
|
43
43
|
"@zukhruf/mutex": "^0.8.10",
|
|
44
44
|
"@zukhruf/sqlite-drift": "^0.8.10",
|
|
45
45
|
"@zukhruf/sqlite-open": "^0.8.10",
|