jsql-neo 6.3.5 → 6.3.10
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 +78 -8239
- package/index.js +1 -1
- package/lib/cache.js +33 -27
- package/lib/client.js +11 -2
- package/lib/database.js +370 -56
- package/lib/date-types.js +51 -13
- package/lib/errors.js +4 -1
- package/lib/jsql_format.js +30 -21
- package/lib/mod.js +27 -6
- package/lib/mongo_server.js +143 -26
- package/lib/mysql_compat.js +57 -11
- package/lib/mysql_server.js +104 -4
- package/lib/native_client.js +30 -0
- package/lib/nedb_compat.js +9 -6
- package/lib/pg_server.js +4 -11
- package/lib/plugin.js +20 -2
- package/lib/prepared.js +10 -0
- package/lib/query.js +9 -2
- package/lib/redis_server.js +88 -16
- package/lib/sql.js +861 -82
- package/lib/sqlite_compat.js +2 -1
- package/lib/sqlite_worker.js +8 -2
- package/lib/table.js +68 -26
- package/lib/tui.js +1 -1
- package/lib/wasm_client.js +35 -0
- package/lib/web_ui.js +24 -8
- package/native/jsql-neo-native.node +0 -0
- package/package.json +10 -3
- package/test/alter-table.test.js +289 -0
- package/test/dml-constraints.test.js +41 -1
- package/test/identifier-keywords.test.js +140 -0
- package/test/index-ddl.test.js +203 -0
- package/test/mongo-group.test.js +105 -0
- package/test/null-semantics.test.js +126 -0
- package/test/recursive-cte.test.js +123 -0
- package/test/savepoint.test.js +388 -0
- package/test/sql-parser.test.js +30 -5
package/lib/sqlite_compat.js
CHANGED
|
@@ -81,6 +81,7 @@ class Database {
|
|
|
81
81
|
close() {
|
|
82
82
|
if (!this.open) return;
|
|
83
83
|
try {
|
|
84
|
+
// P3-12: close() 不等 worker 保存完——显式等待保存完成
|
|
84
85
|
this._sync('close', []);
|
|
85
86
|
} finally {
|
|
86
87
|
this.open = false;
|
|
@@ -285,7 +286,7 @@ class Statement {
|
|
|
285
286
|
|
|
286
287
|
run(...params) {
|
|
287
288
|
const r = this._call('run', params.length > 0 ? params : undefined);
|
|
288
|
-
return r;
|
|
289
|
+
return { changes: r && r.affectedRows, lastInsertRowid: r && r.insertId, _stmt: this };
|
|
289
290
|
}
|
|
290
291
|
|
|
291
292
|
get(...params) {
|
package/lib/sqlite_worker.js
CHANGED
|
@@ -97,7 +97,8 @@ function cleanResult(r) {
|
|
|
97
97
|
if (v instanceof Date) out[k] = v.toISOString();
|
|
98
98
|
else if (v === undefined) out[k] = null;
|
|
99
99
|
else if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
|
|
100
|
-
//
|
|
100
|
+
// P3-14: 双次 stringify 检查可能导致某些值被错误地过滤
|
|
101
|
+
// 只检查一次 stringify 是否成功,然后保留原值
|
|
101
102
|
try { JSON.stringify(v); out[k] = v; } catch (e) { out[k] = null; }
|
|
102
103
|
} else out[k] = v;
|
|
103
104
|
}
|
|
@@ -123,9 +124,14 @@ async function handleOp(op, args) {
|
|
|
123
124
|
}
|
|
124
125
|
case 'run': {
|
|
125
126
|
const r = await executeStatement(args[0], args[1]);
|
|
127
|
+
// P3-13: 非 insert 操作不应回退到上次 insertId
|
|
128
|
+
const lastId = r && r.type === 'insert' ? Number(r.insertId || 0) : 0;
|
|
129
|
+
if (r && r.type === 'insert' && r.insertId !== undefined && r.insertId !== null) {
|
|
130
|
+
_lastInsertId = Number(r.insertId);
|
|
131
|
+
}
|
|
126
132
|
result = {
|
|
127
133
|
changes: r ? (r.affectedRows || 0) : 0,
|
|
128
|
-
lastInsertRowid:
|
|
134
|
+
lastInsertRowid: lastId,
|
|
129
135
|
};
|
|
130
136
|
break;
|
|
131
137
|
}
|
package/lib/table.js
CHANGED
|
@@ -122,7 +122,7 @@ class Table {
|
|
|
122
122
|
};
|
|
123
123
|
this._softDelete = false;
|
|
124
124
|
this._computedFields = {};
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
|
|
127
127
|
this._primaryKey = null;
|
|
128
128
|
this._pkIndex = null;
|
|
@@ -283,6 +283,13 @@ class Table {
|
|
|
283
283
|
}
|
|
284
284
|
if (def.check && data[field] !== undefined && !def.check(data[field], data)) throw createError('ER_CHECK_CONSTRAINT', this._name + '.' + field);
|
|
285
285
|
}
|
|
286
|
+
// 主键唯一性检查(主键可能不在 _cachedUniqueFields 中)
|
|
287
|
+
if (this._primaryKey && data[this._primaryKey] !== undefined && data[this._primaryKey] !== null) {
|
|
288
|
+
const pkVal = data[this._primaryKey];
|
|
289
|
+
if (this._pkIndex && this._pkIndex.has(pkVal)) {
|
|
290
|
+
throw createError('ER_DUP_ENTRY', String(pkVal), this._primaryKey);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
286
293
|
for (const fk of foreignKeys) {
|
|
287
294
|
if (data[fk.field] !== undefined && data[fk.field] !== null) {
|
|
288
295
|
const refTable = this._resolveTable(fk.table);
|
|
@@ -303,7 +310,11 @@ class Table {
|
|
|
303
310
|
this._rows.push(Object.assign({}, data));
|
|
304
311
|
if (this._pkIndex && this._primaryKey) {
|
|
305
312
|
const pkVal = data[this._primaryKey];
|
|
306
|
-
if (pkVal !== undefined && pkVal !== null)
|
|
313
|
+
if (pkVal !== undefined && pkVal !== null) {
|
|
314
|
+
// P1-1: insertMany 主键重复检查
|
|
315
|
+
if (this._pkIndex.has(pkVal)) throw createError('ER_DUP_ENTRY', String(pkVal), this._primaryKey);
|
|
316
|
+
this._pkIndex.set(pkVal, rowIdx);
|
|
317
|
+
}
|
|
307
318
|
}
|
|
308
319
|
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
309
320
|
const val = data[field];
|
|
@@ -369,7 +380,6 @@ class Table {
|
|
|
369
380
|
}
|
|
370
381
|
return null;
|
|
371
382
|
}
|
|
372
|
-
this._ensureBTrees();
|
|
373
383
|
if (this._btrees[this._primaryKey]) {
|
|
374
384
|
const indices = this._btrees[this._primaryKey].search(id);
|
|
375
385
|
if (indices.length > 0) {
|
|
@@ -442,7 +452,6 @@ class Table {
|
|
|
442
452
|
if (this._pkIndex) {
|
|
443
453
|
rowIndex = this._pkIndex.get(id);
|
|
444
454
|
} else {
|
|
445
|
-
this._ensureBTrees();
|
|
446
455
|
if (this._btrees[this._primaryKey]) {
|
|
447
456
|
const indices = this._btrees[this._primaryKey].search(id);
|
|
448
457
|
rowIndex = indices.length > 0 ? indices[0] : undefined;
|
|
@@ -539,7 +548,6 @@ class Table {
|
|
|
539
548
|
if (this._pkIndex) {
|
|
540
549
|
rowIndex = this._pkIndex.get(id);
|
|
541
550
|
} else {
|
|
542
|
-
this._ensureBTrees();
|
|
543
551
|
if (this._btrees[this._primaryKey]) {
|
|
544
552
|
const indices = this._btrees[this._primaryKey].search(id);
|
|
545
553
|
rowIndex = indices.length > 0 ? indices[0] : undefined;
|
|
@@ -587,8 +595,28 @@ class Table {
|
|
|
587
595
|
}
|
|
588
596
|
|
|
589
597
|
removeSoft(query = {}) {
|
|
590
|
-
this._softDelete
|
|
591
|
-
|
|
598
|
+
if (!this._softDelete) throw createError('ER_NOT_SUPPORTED', 'Soft delete is not enabled');
|
|
599
|
+
const matched = this._applyFilterOptimized(this._getVisibleRows(), query);
|
|
600
|
+
let count = 0;
|
|
601
|
+
const now = new Date().toISOString();
|
|
602
|
+
for (const row of matched) {
|
|
603
|
+
const index = this._rows.indexOf(row);
|
|
604
|
+
if (index === -1) continue;
|
|
605
|
+
this._removeFromIndexes(index, row);
|
|
606
|
+
row._deleted = true;
|
|
607
|
+
row._deletedAt = now;
|
|
608
|
+
this._addToIndexes(index, row);
|
|
609
|
+
count++;
|
|
610
|
+
}
|
|
611
|
+
if (count > 0) {
|
|
612
|
+
this._dirty = true;
|
|
613
|
+
this._db._markDirty(this._name);
|
|
614
|
+
for (const hook of this._hooks.afterRemove) {
|
|
615
|
+
hook(query, count);
|
|
616
|
+
}
|
|
617
|
+
this._db._emitChange('remove', this._name, { query, count, soft: true });
|
|
618
|
+
}
|
|
619
|
+
return count;
|
|
592
620
|
}
|
|
593
621
|
|
|
594
622
|
_findRowIndex(id) {
|
|
@@ -628,6 +656,7 @@ class Table {
|
|
|
628
656
|
}
|
|
629
657
|
this._indexes = {};
|
|
630
658
|
this._autoIncrement = 0;
|
|
659
|
+
// P1-13: truncate 不应重置软删除模式——保留原值
|
|
631
660
|
this._dirty = true;
|
|
632
661
|
this._db._markDirty(this._name);
|
|
633
662
|
return count;
|
|
@@ -640,6 +669,10 @@ class Table {
|
|
|
640
669
|
addColumn(field, definition) {
|
|
641
670
|
if (this._schema[field]) throw createError('ER_DUP_FIELDNAME', field);
|
|
642
671
|
this._schema[field] = definition;
|
|
672
|
+
// P1-14: 更新缓存字段列表,使新列的 unique/required 约束生效
|
|
673
|
+
if (!this._cachedSchemaFields.includes(field)) this._cachedSchemaFields.push(field);
|
|
674
|
+
if (definition.unique && !this._cachedUniqueFields.includes(field)) this._cachedUniqueFields.push(field);
|
|
675
|
+
if (definition.required && !this._cachedRequiredFields.includes(field)) this._cachedRequiredFields.push(field);
|
|
643
676
|
if (definition.default !== undefined) {
|
|
644
677
|
this._defaults[field] = definition.default;
|
|
645
678
|
for (const row of this._rows) {
|
|
@@ -666,6 +699,15 @@ class Table {
|
|
|
666
699
|
this._btrees[field] = new BTree(64, true);
|
|
667
700
|
this._rebuildBTree(field);
|
|
668
701
|
}
|
|
702
|
+
// P1-8: 添加主键列时设置 _primaryKey 并重建 pkIndex
|
|
703
|
+
if (definition.primaryKey) {
|
|
704
|
+
this._primaryKey = field;
|
|
705
|
+
if (this._pkIndex) this._pkIndex.clear();
|
|
706
|
+
for (let i = 0; i < this._rows.length; i++) {
|
|
707
|
+
const val = this._rows[i][field];
|
|
708
|
+
if (val !== undefined && val !== null) this._pkIndex.set(val, i);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
669
711
|
this._dirty = true;
|
|
670
712
|
this._db._markDirty(this._name);
|
|
671
713
|
return this;
|
|
@@ -684,7 +726,11 @@ class Table {
|
|
|
684
726
|
for (const row of this._rows) {
|
|
685
727
|
delete row[field];
|
|
686
728
|
}
|
|
687
|
-
|
|
729
|
+
// P1-8: 删除主键列时清空 pkIndex
|
|
730
|
+
if (this._primaryKey === field) {
|
|
731
|
+
this._primaryKey = null;
|
|
732
|
+
if (this._pkIndex) this._pkIndex.clear();
|
|
733
|
+
}
|
|
688
734
|
if (this._autoIncrementField === field) this._autoIncrementField = null;
|
|
689
735
|
this._dirty = true;
|
|
690
736
|
this._db._markDirty(this._name);
|
|
@@ -703,6 +749,11 @@ class Table {
|
|
|
703
749
|
if (this._btrees[oldName]) {
|
|
704
750
|
this._btrees[newName] = this._btrees[oldName];
|
|
705
751
|
delete this._btrees[oldName];
|
|
752
|
+
this._btrees[newName].clear();
|
|
753
|
+
for (let i = 0; i < this._rows.length; i++) {
|
|
754
|
+
const val = this._rows[i][newName];
|
|
755
|
+
if (val !== undefined && val !== null) this._btrees[newName].insert(val, i);
|
|
756
|
+
}
|
|
706
757
|
}
|
|
707
758
|
for (const row of this._rows) {
|
|
708
759
|
row[newName] = row[oldName];
|
|
@@ -721,9 +772,13 @@ class Table {
|
|
|
721
772
|
if (definition.default !== undefined) this._defaults[field] = definition.default;
|
|
722
773
|
this._checkConstraints = this._checkConstraints.filter(c => c.field !== field);
|
|
723
774
|
if (definition.check) this._checkConstraints.push({ field, fn: definition.check });
|
|
724
|
-
if (definition.primaryKey || definition.unique) {
|
|
725
|
-
if (
|
|
726
|
-
|
|
775
|
+
if (definition.primaryKey !== undefined || definition.unique !== undefined) {
|
|
776
|
+
if (definition.primaryKey || definition.unique) {
|
|
777
|
+
if (!this._btrees[field]) this._btrees[field] = new BTree(64, true);
|
|
778
|
+
this._rebuildBTree(field);
|
|
779
|
+
} else {
|
|
780
|
+
delete this._btrees[field];
|
|
781
|
+
}
|
|
727
782
|
}
|
|
728
783
|
this._dirty = true;
|
|
729
784
|
this._db._markDirty(this._name);
|
|
@@ -817,20 +872,6 @@ class Table {
|
|
|
817
872
|
}
|
|
818
873
|
}
|
|
819
874
|
|
|
820
|
-
_ensureBTrees() {
|
|
821
|
-
if (!this._btreesDirty) return;
|
|
822
|
-
this._btreesDirty = false;
|
|
823
|
-
for (const field of Object.keys(this._btrees)) {
|
|
824
|
-
this._btrees[field].clear();
|
|
825
|
-
}
|
|
826
|
-
for (let i = 0; i < this._rows.length; i++) {
|
|
827
|
-
const row = this._rows[i];
|
|
828
|
-
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
829
|
-
const v = row[field];
|
|
830
|
-
if (v !== undefined && v !== null) tree.insert(v, i);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
875
|
|
|
835
876
|
_rebuildAllBTrees() {
|
|
836
877
|
for (const field of Object.keys(this._btrees)) {
|
|
@@ -881,7 +922,6 @@ class Table {
|
|
|
881
922
|
}
|
|
882
923
|
|
|
883
924
|
_applyFilterOptimized(rows, query) {
|
|
884
|
-
this._ensureBTrees();
|
|
885
925
|
for (const [field, condition] of Object.entries(query)) {
|
|
886
926
|
if (field === '$or' || field === '$and' || field === '$not') continue;
|
|
887
927
|
if (typeof condition !== 'object' || condition === null || Array.isArray(condition) || condition instanceof RegExp) {
|
|
@@ -940,6 +980,8 @@ class Table {
|
|
|
940
980
|
_applyFilter(rows, query) {
|
|
941
981
|
const result = [];
|
|
942
982
|
for (let i = 0; i < rows.length; i++) {
|
|
983
|
+
// 软删除行不应出现在任何查询结果中
|
|
984
|
+
if (this._softDelete && rows[i]._deleted) continue;
|
|
943
985
|
if (this._matchRow(rows[i], query)) {
|
|
944
986
|
result.push(rows[i]);
|
|
945
987
|
}
|
package/lib/tui.js
CHANGED
|
@@ -13,7 +13,7 @@ const os = require('os');
|
|
|
13
13
|
const Database = require('./database');
|
|
14
14
|
const { executeSQL } = require('./sql');
|
|
15
15
|
|
|
16
|
-
const HISTORY_FILE = path.join(os.homedir(), '.jsql-history');
|
|
16
|
+
const HISTORY_FILE = path.join(os.homedir(), '.jsql-history.' + process.pid);
|
|
17
17
|
const MAX_HISTORY = 500;
|
|
18
18
|
|
|
19
19
|
/* ---------- ANSI helpers ---------- */
|
package/lib/wasm_client.js
CHANGED
|
@@ -664,6 +664,12 @@ class JSQL {
|
|
|
664
664
|
await this._flush();
|
|
665
665
|
const seq = this._seqIdByPk(table, id);
|
|
666
666
|
if (seq === null) return null;
|
|
667
|
+
// 改主键 / UNIQUE 列前先查冲突。
|
|
668
|
+
// 此前直接调 jsql_update_by_id,导致
|
|
669
|
+
// UPDATE t SET id = 1 WHERE id = 2 (t 已有 id=1)
|
|
670
|
+
// 静默产生两行同主键 —— 主键索引只留一个,索引与实际行数组不一致。
|
|
671
|
+
// MySQL / SQLite 都会报 UNIQUE constraint failed。
|
|
672
|
+
await this._assertNoKeyConflict(table, id, data, seq);
|
|
667
673
|
const r = safeJsonParse(wasmBindings.jsql_update_by_id(table, BigInt(seq), JSON.stringify(data)));
|
|
668
674
|
if (r && r.ok === false) throw new Error(r.error || 'update failed');
|
|
669
675
|
this._emit('update', { table, id, data });
|
|
@@ -671,6 +677,35 @@ class JSQL {
|
|
|
671
677
|
return r;
|
|
672
678
|
}
|
|
673
679
|
|
|
680
|
+
/**
|
|
681
|
+
* 校验 UPDATE 后的主键 / UNIQUE 值不与其它行冲突。
|
|
682
|
+
* 逐行比对(未建索引也正确);值未变化时跳过。
|
|
683
|
+
*/
|
|
684
|
+
async _assertNoKeyConflict(table, id, data, selfSeq) {
|
|
685
|
+
const schema = this._schemas[table];
|
|
686
|
+
if (!schema) return;
|
|
687
|
+
const fields = Object.keys(schema).filter(f => schema[f] && (schema[f].primaryKey || schema[f].unique));
|
|
688
|
+
if (!fields.length) return;
|
|
689
|
+
let rows;
|
|
690
|
+
try {
|
|
691
|
+
rows = await this.find(table, {}, { limit: 1e9, offset: 0 });
|
|
692
|
+
} catch (_) {
|
|
693
|
+
return; // 读不到就不拦,交给底层报错
|
|
694
|
+
}
|
|
695
|
+
for (const f of fields) {
|
|
696
|
+
if (!Object.prototype.hasOwnProperty.call(data, f)) continue;
|
|
697
|
+
const nv = data[f];
|
|
698
|
+
const self = rows.find(r => r && String(r.id) === String(id));
|
|
699
|
+
if (self && self[f] === nv) continue; // 值没变
|
|
700
|
+
if (rows.some(r => r && r !== self && r[f] === nv)) {
|
|
701
|
+
const e = new Error(`Duplicate entry '${nv}' for key '${f}'`);
|
|
702
|
+
e.code = 'ER_DUP_ENTRY';
|
|
703
|
+
e.errno = 1062;
|
|
704
|
+
throw e;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
674
709
|
async removeById(table, id) {
|
|
675
710
|
if (!this._runHooks('beforeDelete', [table, id])) return;
|
|
676
711
|
await this._flush();
|
package/lib/web_ui.js
CHANGED
|
@@ -141,9 +141,17 @@ class WebUI {
|
|
|
141
141
|
return fs.readdirSync(this.dataDir)
|
|
142
142
|
.filter(f => f.endsWith('.json'))
|
|
143
143
|
.map(f => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
try {
|
|
145
|
+
const stat = fs.statSync(path.join(this.dataDir, f));
|
|
146
|
+
// 只读前 4KB:schema 头部包含 __schema__ 定义,尾部数据不需要
|
|
147
|
+
const fd = fs.openSync(path.join(this.dataDir, f), 'r');
|
|
148
|
+
const buf = Buffer.alloc(4096);
|
|
149
|
+
fs.readSync(fd, buf, 0, 4096, 0);
|
|
150
|
+
fs.closeSync(fd);
|
|
151
|
+
const match = buf.toString('utf8').match(/"__schema__"\s*:\s*\{([^}]*)\}/);
|
|
152
|
+
const tables = match ? (match[1].match(/"/g) || []).length / 2 : 0;
|
|
153
|
+
return { name: f.slice(0, -5), tables };
|
|
154
|
+
} catch (_) { return { name: f.slice(0, -5), tables: 0 }; }
|
|
147
155
|
});
|
|
148
156
|
}
|
|
149
157
|
|
|
@@ -188,12 +196,20 @@ class WebUI {
|
|
|
188
196
|
res.writeHead(204, h);
|
|
189
197
|
return res.end();
|
|
190
198
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (
|
|
199
|
+
// API 认证:必须设置 authToken(从环境变量或构造选项)。
|
|
200
|
+
// 仅当显式 noAuth: true 时允许无认证访问(仅本地开发用)。
|
|
201
|
+
if (url.pathname.startsWith('/api/')) {
|
|
202
|
+
if (!this.noAuth && !this.authToken) {
|
|
195
203
|
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
196
|
-
return res.end(JSON.stringify({ ok: false, error: '
|
|
204
|
+
return res.end(JSON.stringify({ ok: false, error: 'API auth not configured' }));
|
|
205
|
+
}
|
|
206
|
+
if (this.authToken) {
|
|
207
|
+
const provided = req.headers.authorization || '';
|
|
208
|
+
const token = provided.startsWith('Bearer ') ? provided.slice(7) : '';
|
|
209
|
+
if (token !== this.authToken) {
|
|
210
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
211
|
+
return res.end(JSON.stringify({ ok: false, error: 'unauthorized' }));
|
|
212
|
+
}
|
|
197
213
|
}
|
|
198
214
|
}
|
|
199
215
|
if (url.pathname === '/api/databases' && req.method === 'GET') {
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsql-neo",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.10",
|
|
4
4
|
"description": "JSQL-NEO — Rust-powered embedded database with WASM, REST API, B-Tree indexes, WAL, crash recovery",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -51,10 +51,17 @@
|
|
|
51
51
|
"test:btree": "node test/btree.test.js",
|
|
52
52
|
"test:native": "node test/native.test.js",
|
|
53
53
|
"test:regress": "node test/regress-5.1.0.js",
|
|
54
|
-
"test:all": "node test/smoke.js && node test/sql-parser.test.js && node test/readme-audit.test.js && node test/native.test.js && node test/native-multi-instance.test.js && node test/wal-recovery.test.js && node test/mysql-compat-hijack.test.js && node test/wasm.test.js && node test/coverage.js && node test/regress-5.1.0.js && node test/join.test.js && node test/btree.test.js && node test/dml-constraints.test.js",
|
|
54
|
+
"test:all": "node test/smoke.js && node test/sql-parser.test.js && node test/readme-audit.test.js && node test/native.test.js && node test/native-multi-instance.test.js && node test/wal-recovery.test.js && node test/mysql-compat-hijack.test.js && node test/wasm.test.js && node test/coverage.js && node test/regress-5.1.0.js && node test/join.test.js && node test/btree.test.js && node test/dml-constraints.test.js && node test/savepoint.test.js && node test/alter-table.test.js && node test/null-semantics.test.js && node test/identifier-keywords.test.js && node test/recursive-cte.test.js && node test/index-ddl.test.js",
|
|
55
55
|
"test:coverage": "c8 --all --include='lib/**' --exclude='lib/web_ui.js' --reporter=text --reporter=lcov node test/coverage.js",
|
|
56
56
|
"test:orms": "node examples/orms/run-all.js",
|
|
57
|
-
"test:dml": "node test/dml-constraints.test.js"
|
|
57
|
+
"test:dml": "node test/dml-constraints.test.js",
|
|
58
|
+
"test:cte": "node test/recursive-cte.test.js",
|
|
59
|
+
"test:index": "node test/index-ddl.test.js",
|
|
60
|
+
"test:mongo": "node test/mongo-group.test.js",
|
|
61
|
+
"test:savepoint": "node test/savepoint.test.js",
|
|
62
|
+
"test:alter": "node test/alter-table.test.js",
|
|
63
|
+
"test:ident": "node test/identifier-keywords.test.js",
|
|
64
|
+
"test:null": "node test/null-semantics.test.js"
|
|
58
65
|
},
|
|
59
66
|
"keywords": [
|
|
60
67
|
"database",
|