deepbase-sqlite 3.8.2 → 3.8.6
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/package.json +2 -2
- package/src/SqliteDriver.js +29 -26
- package/test/test-multiprocess.js +17 -0
- package/test/test.js +14 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepbase-sqlite",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.6",
|
|
4
4
|
"description": "⚡ DeepBase SQLite - SQLite database driver",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.cjs",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"better-sqlite3": "^11.8.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"deepbase": "^3.8.
|
|
20
|
+
"deepbase": "^3.8.6"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"mocha": "^10.8.2"
|
package/src/SqliteDriver.js
CHANGED
|
@@ -72,17 +72,19 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
72
72
|
`);
|
|
73
73
|
this.delStmt = this.db.prepare('DELETE FROM deepbase WHERE key = ?');
|
|
74
74
|
this.getAllStmt = this.db.prepare('SELECT key, value FROM deepbase ORDER BY seq, key');
|
|
75
|
-
this.
|
|
76
|
-
|
|
75
|
+
this.getChildrenStmt = this.db.prepare(
|
|
76
|
+
'SELECT key, value FROM deepbase WHERE key >= ? AND key < ? ORDER BY seq, key',
|
|
77
77
|
);
|
|
78
78
|
this.getFirstChildKeyStmt = this.db.prepare(
|
|
79
|
-
|
|
79
|
+
'SELECT key FROM deepbase WHERE key >= ? AND key < ? ORDER BY seq, key LIMIT 1',
|
|
80
80
|
);
|
|
81
81
|
this.getLastChildKeyStmt = this.db.prepare(
|
|
82
|
-
|
|
82
|
+
'SELECT key FROM deepbase WHERE key >= ? AND key < ? ORDER BY seq DESC, key DESC LIMIT 1',
|
|
83
83
|
);
|
|
84
|
-
this.
|
|
85
|
-
this.
|
|
84
|
+
this.getFirstKeyStmt = this.db.prepare('SELECT key FROM deepbase ORDER BY seq, key LIMIT 1');
|
|
85
|
+
this.getLastKeyStmt = this.db.prepare('SELECT key FROM deepbase ORDER BY seq DESC, key DESC LIMIT 1');
|
|
86
|
+
this.delChildrenStmt = this.db.prepare('DELETE FROM deepbase WHERE key >= ? AND key < ?');
|
|
87
|
+
this.hasChildrenStmt = this.db.prepare('SELECT 1 FROM deepbase WHERE key >= ? AND key < ? LIMIT 1');
|
|
86
88
|
|
|
87
89
|
const setTxn = this.db.transaction((key, jsonValue, keys) => {
|
|
88
90
|
this._expandParentObjects(keys);
|
|
@@ -90,10 +92,10 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
90
92
|
});
|
|
91
93
|
this._setTxn = (...args) => setTxn.immediate(...args);
|
|
92
94
|
|
|
93
|
-
const delTxn = this.db.transaction((key,
|
|
95
|
+
const delTxn = this.db.transaction((key, childLowerBound, childUpperBound, keys) => {
|
|
94
96
|
this._expandParentObjects(keys);
|
|
95
97
|
this.delStmt.run(key);
|
|
96
|
-
this.delChildrenStmt.run(
|
|
98
|
+
this.delChildrenStmt.run(childLowerBound, childUpperBound);
|
|
97
99
|
});
|
|
98
100
|
this._delTxn = (...args) => delTxn.immediate(...args);
|
|
99
101
|
|
|
@@ -209,17 +211,17 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
209
211
|
|
|
210
212
|
const key = this._pathToKey(args);
|
|
211
213
|
const row = this.getStmt.get(key);
|
|
212
|
-
const
|
|
214
|
+
const childRange = this._childRange(key);
|
|
213
215
|
|
|
214
216
|
if (row) {
|
|
215
|
-
if (this.hasChildrenStmt.get(
|
|
216
|
-
return this._buildObjectFromChildren(key,
|
|
217
|
+
if (this.hasChildrenStmt.get(...childRange)) {
|
|
218
|
+
return this._buildObjectFromChildren(key, childRange);
|
|
217
219
|
}
|
|
218
220
|
return JSON.parse(row.value);
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
if (this.hasChildrenStmt.get(
|
|
222
|
-
return this._buildObjectFromChildren(key,
|
|
223
|
+
if (this.hasChildrenStmt.get(...childRange)) {
|
|
224
|
+
return this._buildObjectFromChildren(key, childRange);
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
return this._getFromParent(args);
|
|
@@ -249,7 +251,7 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
_replaceRow(key, jsonValue) {
|
|
252
|
-
this.delChildrenStmt.run(this.
|
|
254
|
+
this.delChildrenStmt.run(...this._childRange(key));
|
|
253
255
|
this.setStmt.run(key, jsonValue);
|
|
254
256
|
}
|
|
255
257
|
|
|
@@ -261,9 +263,9 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
const key = this._pathToKey(keys);
|
|
264
|
-
const
|
|
266
|
+
const childRange = this._childRange(key);
|
|
265
267
|
return this._runWrite(() => {
|
|
266
|
-
this._delTxn(key,
|
|
268
|
+
this._delTxn(key, ...childRange, keys);
|
|
267
269
|
});
|
|
268
270
|
}
|
|
269
271
|
|
|
@@ -322,8 +324,10 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
322
324
|
}
|
|
323
325
|
|
|
324
326
|
_findBoundaryNestedKey(path, fromEnd) {
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
+
const row = path.length === 0
|
|
328
|
+
? (fromEnd ? this.getLastKeyStmt : this.getFirstKeyStmt).get()
|
|
329
|
+
: (fromEnd ? this.getLastChildKeyStmt : this.getFirstChildKeyStmt)
|
|
330
|
+
.get(...this._childRange(this._pathToKey(path)));
|
|
327
331
|
if (!row) return undefined;
|
|
328
332
|
|
|
329
333
|
const fullPath = this._keyToPath(row.key);
|
|
@@ -343,8 +347,8 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
343
347
|
return result;
|
|
344
348
|
}
|
|
345
349
|
|
|
346
|
-
_buildObjectFromChildren(parentKey,
|
|
347
|
-
const rows = this.
|
|
350
|
+
_buildObjectFromChildren(parentKey, childRange) {
|
|
351
|
+
const rows = this.getChildrenStmt.all(...childRange);
|
|
348
352
|
const result = {};
|
|
349
353
|
const parentPathLen = parentKey ? this._keyToPath(parentKey).length : 0;
|
|
350
354
|
|
|
@@ -368,12 +372,11 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
368
372
|
);
|
|
369
373
|
}
|
|
370
374
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return this._escapeLikePattern(key) + '.%';
|
|
375
|
+
_childRange(key) {
|
|
376
|
+
// Stored descendants start with `${key}.`. Since '/' is the next ASCII
|
|
377
|
+
// character after '.', this half-open range matches exactly that prefix
|
|
378
|
+
// while allowing SQLite to seek through the primary-key index.
|
|
379
|
+
return [`${key}.`, `${key}/`];
|
|
377
380
|
}
|
|
378
381
|
|
|
379
382
|
_setNestedValue(obj, path, value) {
|
|
@@ -92,6 +92,23 @@ describe('SqliteDriver multi-process safety', function () {
|
|
|
92
92
|
await driver.disconnect();
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
+
it('uses the primary-key index to find descendant rows', async function () {
|
|
96
|
+
const driver = new SqliteDriver({
|
|
97
|
+
name: 'descendant-query-plan',
|
|
98
|
+
path: testDataPath,
|
|
99
|
+
});
|
|
100
|
+
await driver.connect();
|
|
101
|
+
|
|
102
|
+
const plan = driver.db
|
|
103
|
+
.prepare(`EXPLAIN QUERY PLAN ${driver.delChildrenStmt.source}`)
|
|
104
|
+
.all('parent.', 'parent/');
|
|
105
|
+
const details = plan.map(step => step.detail).join('\n');
|
|
106
|
+
|
|
107
|
+
assert.match(details, /SEARCH deepbase USING PRIMARY KEY/);
|
|
108
|
+
assert.doesNotMatch(details, /SCAN deepbase/);
|
|
109
|
+
await driver.disconnect();
|
|
110
|
+
});
|
|
111
|
+
|
|
95
112
|
it('adds a missing seq column without rewriting legacy rows', async function () {
|
|
96
113
|
const name = 'legacy-no-seq';
|
|
97
114
|
const fileName = path.join(testDataPath, `${name}.db`);
|
package/test/test.js
CHANGED
|
@@ -554,7 +554,7 @@ for (const pragma of PRAGMA_MODES) {
|
|
|
554
554
|
});
|
|
555
555
|
});
|
|
556
556
|
|
|
557
|
-
describe('
|
|
557
|
+
describe('Special Key Isolation', function () {
|
|
558
558
|
it('should not cross-match keys with underscores', async function () {
|
|
559
559
|
await db.set('chat_1', 'msg', 'hello');
|
|
560
560
|
await db.set('chatX1', 'msg', 'world');
|
|
@@ -585,6 +585,19 @@ for (const pragma of PRAGMA_MODES) {
|
|
|
585
585
|
assert.deepStrictEqual(await db.get('progress', '100%'), { done: true });
|
|
586
586
|
assert.deepStrictEqual(await db.get('progress', 'abc'), { done: false });
|
|
587
587
|
});
|
|
588
|
+
|
|
589
|
+
it('should isolate range boundaries for slashes and backslashes', async function () {
|
|
590
|
+
await db.set('route/path', 'child', 1);
|
|
591
|
+
await db.set('route/path0', 'child', 2);
|
|
592
|
+
await db.set('route\\path', 'child', 3);
|
|
593
|
+
|
|
594
|
+
await db.set('route/path', { replaced: true });
|
|
595
|
+
await db.del('route\\path');
|
|
596
|
+
|
|
597
|
+
assert.deepStrictEqual(await db.get('route/path'), { replaced: true });
|
|
598
|
+
assert.deepStrictEqual(await db.get('route/path0'), { child: 2 });
|
|
599
|
+
assert.strictEqual(await db.get('route\\path'), null);
|
|
600
|
+
});
|
|
588
601
|
});
|
|
589
602
|
|
|
590
603
|
describe('Storybot-like Structure', function () {
|