jsql-neo 5.4.2 → 5.5.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/LICENSE +202 -202
- package/README.md +8097 -7971
- package/bin/jsql +320 -320
- package/bin/jsql-server +90 -90
- package/bin/jsql-server-control.js +177 -177
- package/index.d.ts +385 -357
- package/index.js +124 -118
- package/lib/ast.js +214 -0
- package/lib/btree.js +413 -413
- package/lib/cache.js +328 -328
- package/lib/client.js +125 -125
- package/lib/database.js +1744 -1744
- package/lib/date-types.js +182 -182
- package/lib/errors.js +89 -89
- package/lib/jsql_format.js +497 -497
- package/lib/migrate.js +254 -254
- package/lib/mod.js +292 -292
- package/lib/mongo_server.js +607 -607
- package/lib/multiserver.js +140 -140
- package/lib/mysql_compat.js +413 -413
- package/lib/mysql_server.js +1237 -1237
- package/lib/native_client.js +732 -732
- package/lib/nedb_compat.js +506 -506
- package/lib/pg_server.js +864 -864
- package/lib/plugin.js +34 -34
- package/lib/query.js +815 -815
- package/lib/redis_server.js +571 -571
- package/lib/sql.js +3460 -2925
- package/lib/sqlite_compat.js +323 -323
- package/lib/sqlite_worker.js +264 -264
- package/lib/table.js +1304 -1304
- package/lib/tui.js +502 -502
- package/lib/wasm.js +1 -1
- package/lib/wasm_client.js +718 -718
- package/lib/web_ui.js +257 -257
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.lock +455 -455
- package/nativesrc/jsql-neo-core/Cargo.toml +24 -24
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +455 -455
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +168 -168
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +62 -62
- package/nativesrc/jsql-neo-core/src/engine/table.rs +700 -700
- package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -85
- package/nativesrc/jsql-neo-core/src/types.rs +93 -93
- package/nativesrc/jsql-neo-native/Cargo.lock +606 -606
- package/nativesrc/jsql-neo-native/Cargo.toml +16 -16
- package/nativesrc/jsql-neo-native/build.rs +3 -3
- package/nativesrc/jsql-neo-native/package.json +7 -7
- package/nativesrc/jsql-neo-native/src/lib.rs +269 -269
- package/nativesrc/jsql-neo-wasm/Cargo.lock +465 -465
- package/nativesrc/jsql-neo-wasm/Cargo.toml +15 -15
- package/nativesrc/jsql-neo-wasm/src/lib.rs +447 -447
- package/package.json +87 -86
- package/postinstall.js +158 -158
- package/test/btree.test.js +128 -128
- package/test/coverage.js +187 -187
- package/test/join.test.js +110 -110
- package/test/native.test.js +158 -158
- package/test/regress-5.1.0.js +271 -271
- package/test/smoke.js +58 -58
- package/test/sql-parser.test.js +273 -0
- package/test/wasm.test.js +110 -110
- package/wasm/browser.d.ts +89 -89
- package/wasm/browser.mjs +569 -569
- package/wasm/browser_bg.mjs +597 -597
- package/wasm/jsql_neo_wasm.d.ts +38 -38
- package/wasm/jsql_neo_wasm.js +520 -520
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
- package/wasm/package.json +11 -11
package/lib/table.js
CHANGED
|
@@ -1,1305 +1,1305 @@
|
|
|
1
|
-
// © Vexify 2026 All Rights Reserved.
|
|
2
|
-
/**
|
|
3
|
-
* JSQL Table — 表操作引擎 v2.0
|
|
4
|
-
* B-Tree 自动索引、日期类型、错误码、外键 CASCADE、长度/精度校验
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const Query = require('./query');
|
|
8
|
-
const BTree = require('./btree');
|
|
9
|
-
const { validateDateType, now } = require('./date-types');
|
|
10
|
-
const { createError } = require('./errors');
|
|
11
|
-
|
|
12
|
-
function isSafeRegex(pattern) {
|
|
13
|
-
if (typeof pattern !== 'string') return false;
|
|
14
|
-
if (pattern.length > 256) return false;
|
|
15
|
-
const groups = [];
|
|
16
|
-
let i = 0;
|
|
17
|
-
const n = pattern.length;
|
|
18
|
-
while (i < n) {
|
|
19
|
-
const c = pattern[i];
|
|
20
|
-
if (c === '\\') { i += 2; continue; }
|
|
21
|
-
if (c === '[') {
|
|
22
|
-
while (i < n && pattern[i] !== ']') i++;
|
|
23
|
-
i++;
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
if (c === '(') {
|
|
27
|
-
const nxt = pattern[i + 1];
|
|
28
|
-
if (nxt === '?') {
|
|
29
|
-
const t = pattern[i + 2];
|
|
30
|
-
if (t === ':') {
|
|
31
|
-
groups.push({ hasQuant: false, hasAlt: false });
|
|
32
|
-
i += 3;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (t === '=' || t === '!') {
|
|
36
|
-
let depth = 1, j = i + 3;
|
|
37
|
-
for (; j < n && depth > 0; j++) {
|
|
38
|
-
if (pattern[j] === '\\') { j++; continue; }
|
|
39
|
-
if (pattern[j] === '(') depth++;
|
|
40
|
-
else if (pattern[j] === ')') depth--;
|
|
41
|
-
}
|
|
42
|
-
i = j;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (t === '<') {
|
|
46
|
-
const u = pattern[i + 3];
|
|
47
|
-
if (u === '=' || u === '!') {
|
|
48
|
-
let depth = 1, j = i + 4;
|
|
49
|
-
for (; j < n && depth > 0; j++) {
|
|
50
|
-
if (pattern[j] === '\\') { j++; continue; }
|
|
51
|
-
if (pattern[j] === '(') depth++;
|
|
52
|
-
else if (pattern[j] === ')') depth--;
|
|
53
|
-
}
|
|
54
|
-
i = j;
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const gt = pattern.indexOf('>', i + 3);
|
|
58
|
-
if (gt === -1) return false;
|
|
59
|
-
groups.push({ hasQuant: false, hasAlt: false });
|
|
60
|
-
i = gt + 1;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
groups.push({ hasQuant: false, hasAlt: false });
|
|
66
|
-
i++;
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (c === ')') {
|
|
70
|
-
const g = groups.pop();
|
|
71
|
-
if (!g) return false;
|
|
72
|
-
let qi = i + 1;
|
|
73
|
-
let quant = null;
|
|
74
|
-
if (pattern[qi] === '+' || pattern[qi] === '*' || pattern[qi] === '?') {
|
|
75
|
-
quant = pattern[qi];
|
|
76
|
-
} else if (pattern[qi] === '{') {
|
|
77
|
-
const close = pattern.indexOf('}', qi);
|
|
78
|
-
if (close === -1) return false;
|
|
79
|
-
quant = pattern.slice(qi, close + 1);
|
|
80
|
-
qi = close + 1;
|
|
81
|
-
}
|
|
82
|
-
if (quant && (g.hasQuant || g.hasAlt)) return false;
|
|
83
|
-
i = qi;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (c === '+' || c === '*' || c === '?') {
|
|
87
|
-
if (groups.length > 0) groups[groups.length - 1].hasQuant = true;
|
|
88
|
-
i++;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (c === '{' && /[0-9]/.test(pattern[i + 1] || '')) {
|
|
92
|
-
const close = pattern.indexOf('}', i);
|
|
93
|
-
if (close === -1) return false;
|
|
94
|
-
if (groups.length > 0) groups[groups.length - 1].hasQuant = true;
|
|
95
|
-
i = close + 1;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (c === '|') {
|
|
99
|
-
if (groups.length > 0) groups[groups.length - 1].hasAlt = true;
|
|
100
|
-
i++;
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
i++;
|
|
104
|
-
}
|
|
105
|
-
return groups.length === 0;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
class Table {
|
|
109
|
-
constructor(name, schema, db) {
|
|
110
|
-
this._name = name;
|
|
111
|
-
this._schema = schema;
|
|
112
|
-
this._db = db;
|
|
113
|
-
this._rows = [];
|
|
114
|
-
this._indexes = {}; // 旧版 hash 索引(兼容)
|
|
115
|
-
this._btrees = {}; // B-Tree 索引 { fieldName: BTree }
|
|
116
|
-
this._autoIncrement = 0;
|
|
117
|
-
this._dirty = false;
|
|
118
|
-
this._hooks = {
|
|
119
|
-
beforeInsert: [], afterInsert: [],
|
|
120
|
-
beforeUpdate: [], afterUpdate: [],
|
|
121
|
-
beforeRemove: [], afterRemove: []
|
|
122
|
-
};
|
|
123
|
-
this._softDelete = false;
|
|
124
|
-
this._computedFields = {};
|
|
125
|
-
this._btreesDirty = false;
|
|
126
|
-
|
|
127
|
-
this._primaryKey = null;
|
|
128
|
-
this._pkIndex = null;
|
|
129
|
-
this._autoIncrementField = null;
|
|
130
|
-
this._foreignKeys = [];
|
|
131
|
-
this._checkConstraints = [];
|
|
132
|
-
this._defaults = {};
|
|
133
|
-
this._dateFields = {}; // { fieldName: 'date'|'datetime'|'timestamp' }
|
|
134
|
-
|
|
135
|
-
for (const [field, def] of Object.entries(schema)) {
|
|
136
|
-
const isPk = !!(def.primaryKey || def.primary === true);
|
|
137
|
-
if (isPk) this._primaryKey = field;
|
|
138
|
-
if (def.autoIncrement) {
|
|
139
|
-
this._autoIncrementField = field;
|
|
140
|
-
if (isPk) this._primaryKey = field;
|
|
141
|
-
}
|
|
142
|
-
if (def.default !== undefined) {
|
|
143
|
-
this._defaults[field] = def.default;
|
|
144
|
-
}
|
|
145
|
-
if (def.check) {
|
|
146
|
-
this._checkConstraints.push({ field, fn: def.check });
|
|
147
|
-
}
|
|
148
|
-
if (def.foreignKey) {
|
|
149
|
-
this._foreignKeys.push({
|
|
150
|
-
field,
|
|
151
|
-
table: def.foreignKey.table,
|
|
152
|
-
refField: def.foreignKey.field || 'id',
|
|
153
|
-
onDelete: def.foreignKey.onDelete || 'restrict',
|
|
154
|
-
onUpdate: def.foreignKey.onUpdate || 'restrict'
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
if (def.computed) {
|
|
158
|
-
this._computedFields[field] = def.computed;
|
|
159
|
-
}
|
|
160
|
-
// 日期类型
|
|
161
|
-
if (['date', 'datetime', 'timestamp', 'time'].includes(def.type)) {
|
|
162
|
-
this._dateFields[field] = def.type;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (schema._softDelete) {
|
|
167
|
-
this._softDelete = true;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
this._pkIndex = this._primaryKey ? new Map() : null;
|
|
171
|
-
this._cachedSchemaFields = Object.keys(schema).filter(f => f !== '_softDelete');
|
|
172
|
-
this._cachedDateFields = Object.keys(this._dateFields);
|
|
173
|
-
this._cachedUniqueFields = this._cachedSchemaFields.filter(f => schema[f].unique);
|
|
174
|
-
this._cachedRequiredFields = this._cachedSchemaFields.filter(f => schema[f].required);
|
|
175
|
-
this._autoCreateBTrees();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
_autoCreateBTrees() {
|
|
179
|
-
for (const [field, def] of Object.entries(this._schema)) {
|
|
180
|
-
if (field === '_softDelete') continue;
|
|
181
|
-
if (def.primaryKey || def.primary === true || def.unique) {
|
|
182
|
-
this._btrees[field] = new BTree(64, true);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ============================================================
|
|
188
|
-
// 插入
|
|
189
|
-
// ============================================================
|
|
190
|
-
|
|
191
|
-
insert(data) {
|
|
192
|
-
for (const hook of this._hooks.beforeInsert) {
|
|
193
|
-
data = hook({ ...data }) || data;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
data = this._applyDefaults(data);
|
|
197
|
-
data = this._applyComputed(data);
|
|
198
|
-
data = this._validateDataTypes(data);
|
|
199
|
-
this._validateConstraints(data, -1);
|
|
200
|
-
|
|
201
|
-
if (this._autoIncrementField && data[this._autoIncrementField] === undefined) {
|
|
202
|
-
this._autoIncrement++;
|
|
203
|
-
data[this._autoIncrementField] = this._autoIncrement;
|
|
204
|
-
} else if (this._autoIncrementField && data[this._autoIncrementField] > this._autoIncrement) {
|
|
205
|
-
this._autoIncrement = data[this._autoIncrementField];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const rowIndex = this._rows.length;
|
|
209
|
-
this._rows.push({ ...data });
|
|
210
|
-
this._addToIndexes(rowIndex, data);
|
|
211
|
-
this._dirty = true;
|
|
212
|
-
this._db._markDirty(this._name);
|
|
213
|
-
|
|
214
|
-
for (const hook of this._hooks.afterInsert) {
|
|
215
|
-
hook(data);
|
|
216
|
-
}
|
|
217
|
-
this._db._emitChange('insert', this._name, data);
|
|
218
|
-
|
|
219
|
-
return data;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
insertMany(items) {
|
|
223
|
-
if (items.length === 0) return [];
|
|
224
|
-
for (const hook of this._hooks.beforeInsert) {
|
|
225
|
-
const mapped = new Array(items.length);
|
|
226
|
-
for (let i = 0; i < items.length; i++) mapped[i] = hook({ ...items[i] }) || items[i];
|
|
227
|
-
items = mapped;
|
|
228
|
-
}
|
|
229
|
-
const N = items.length;
|
|
230
|
-
const uniqueTracker = {};
|
|
231
|
-
for (const field of this._cachedUniqueFields) {
|
|
232
|
-
uniqueTracker[field] = new Set();
|
|
233
|
-
for (const row of this._rows) {
|
|
234
|
-
if (row[field] !== undefined && row[field] !== null) uniqueTracker[field].add(row[field]);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
const dateFields = this._cachedDateFields;
|
|
238
|
-
const hasDateFields = dateFields.length > 0;
|
|
239
|
-
const autoIncField = this._autoIncrementField;
|
|
240
|
-
const defaults = this._defaults;
|
|
241
|
-
const computedFields = this._computedFields;
|
|
242
|
-
const schema = this._schema;
|
|
243
|
-
const checkConstraints = this._checkConstraints;
|
|
244
|
-
const foreignKeys = this._foreignKeys;
|
|
245
|
-
// 批量预分配自增 ID 范围:先扫描显式提供的最大值,一次性推进计数器,
|
|
246
|
-
// 再在循环内用本地序号递增,避免批量内重复读取/写入同一计数器字段。
|
|
247
|
-
const baseAutoInc = this._autoIncrement;
|
|
248
|
-
if (autoIncField) {
|
|
249
|
-
let maxExplicit = 0;
|
|
250
|
-
for (const it of items) {
|
|
251
|
-
const v = it[autoIncField];
|
|
252
|
-
if (v !== undefined && v !== null) {
|
|
253
|
-
const n = Number(v);
|
|
254
|
-
if (!isNaN(n) && n > maxExplicit) maxExplicit = n;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (maxExplicit > baseAutoInc) {
|
|
258
|
-
this._autoIncrement = maxExplicit;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
let autoIncSeq = 0;
|
|
262
|
-
for (let i = 0; i < N; i++) {
|
|
263
|
-
let data = items[i];
|
|
264
|
-
for (const [f, dv] of Object.entries(defaults)) {
|
|
265
|
-
if (data[f] === undefined) data[f] = typeof dv === 'function' ? dv() : dv === 'CURRENT_TIMESTAMP' ? now() : dv;
|
|
266
|
-
}
|
|
267
|
-
for (const f of Object.keys(computedFields)) data[f] = computedFields[f](data);
|
|
268
|
-
if (hasDateFields) {
|
|
269
|
-
for (const f of dateFields) {
|
|
270
|
-
if (data[f] !== undefined && data[f] !== null) data[f] = validateDateType(data[f], this._dateFields[f], f);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
for (const field of this._cachedSchemaFields) {
|
|
274
|
-
const def = schema[field];
|
|
275
|
-
if (def.required && (data[field] === undefined || data[field] === null)
|
|
276
|
-
&& !(def.autoIncrement && data[field] === undefined)) throw createError('ER_BAD_NULL_ERROR', field);
|
|
277
|
-
if (def.unique && data[field] !== undefined) {
|
|
278
|
-
if (uniqueTracker[field].has(data[field])) throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
279
|
-
uniqueTracker[field].add(data[field]);
|
|
280
|
-
}
|
|
281
|
-
if (def.length && typeof data[field] === 'string' && data[field].length > def.length) throw createError('ER_DATA_TOO_LONG', field);
|
|
282
|
-
if (def.type === 'integer' || def.type === 'number') {
|
|
283
|
-
const v = def.type === 'integer' ? Number(data[field]) : data[field];
|
|
284
|
-
if (data[field] !== undefined && data[field] !== null && def.min !== undefined && v < def.min) throw createError('ER_OUT_OF_RANGE', field);
|
|
285
|
-
if (data[field] !== undefined && data[field] !== null && def.max !== undefined && v > def.max) throw createError('ER_OUT_OF_RANGE', field);
|
|
286
|
-
}
|
|
287
|
-
if (def.check && data[field] !== undefined && !def.check(data[field], data)) throw createError('ER_CHECK_CONSTRAINT', this._name + '.' + field);
|
|
288
|
-
}
|
|
289
|
-
for (const fk of foreignKeys) {
|
|
290
|
-
if (data[fk.field] !== undefined && data[fk.field] !== null) {
|
|
291
|
-
const refTable = this._resolveTable(fk.table);
|
|
292
|
-
if (refTable) {
|
|
293
|
-
const exists = refTable.findOne({ [fk.refField]: data[fk.field] });
|
|
294
|
-
if (!exists) throw createError('ER_NO_REFERENCED_ROW');
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (autoIncField && data[autoIncField] === undefined) {
|
|
299
|
-
autoIncSeq++;
|
|
300
|
-
data[autoIncField] = baseAutoInc + autoIncSeq;
|
|
301
|
-
this._autoIncrement = data[autoIncField];
|
|
302
|
-
} else if (autoIncField && data[autoIncField] > this._autoIncrement) {
|
|
303
|
-
this._autoIncrement = data[autoIncField];
|
|
304
|
-
}
|
|
305
|
-
const rowIdx = this._rows.length;
|
|
306
|
-
this._rows.push(Object.assign({}, data));
|
|
307
|
-
if (this._pkIndex && this._primaryKey) {
|
|
308
|
-
const pkVal = data[this._primaryKey];
|
|
309
|
-
if (pkVal !== undefined && pkVal !== null) this._pkIndex.set(pkVal, rowIdx);
|
|
310
|
-
}
|
|
311
|
-
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
312
|
-
const val = data[field];
|
|
313
|
-
if (val !== undefined && val !== null) tree.insert(val, rowIdx);
|
|
314
|
-
}
|
|
315
|
-
items[i] = data;
|
|
316
|
-
}
|
|
317
|
-
this._dirty = true;
|
|
318
|
-
this._db._markDirty(this._name);
|
|
319
|
-
for (const hook of this._hooks.afterInsert) {
|
|
320
|
-
for (let i = 0; i < N; i++) hook(items[i]);
|
|
321
|
-
}
|
|
322
|
-
this._db._emitChange('insertMany', this._name, { count: N });
|
|
323
|
-
return items;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
upsert(data) {
|
|
327
|
-
if (this._primaryKey && data[this._primaryKey] !== undefined) {
|
|
328
|
-
const existing = this.findById(data[this._primaryKey]);
|
|
329
|
-
if (existing) {
|
|
330
|
-
this.updateById(data[this._primaryKey], data);
|
|
331
|
-
return this.findById(data[this._primaryKey]);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return this.insert(data);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// ============================================================
|
|
338
|
-
// 查询(支持 B-Tree 加速)
|
|
339
|
-
// ============================================================
|
|
340
|
-
|
|
341
|
-
find(query = {}) {
|
|
342
|
-
let rows = this._getVisibleRows();
|
|
343
|
-
if (Object.keys(query).length > 0) {
|
|
344
|
-
rows = this._applyFilterOptimized(rows, query);
|
|
345
|
-
}
|
|
346
|
-
return rows.map(r => ({ ...r }));
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
findOne(query = {}) {
|
|
350
|
-
const rows = this.find(query);
|
|
351
|
-
return rows.length > 0 ? rows[0] : null;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
findById(id) {
|
|
355
|
-
if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
|
|
356
|
-
// Hash 索引 O(1)
|
|
357
|
-
if (this._pkIndex) {
|
|
358
|
-
const rowIndex = this._pkIndex.get(id);
|
|
359
|
-
if (rowIndex !== undefined) {
|
|
360
|
-
const row = this._rows[rowIndex];
|
|
361
|
-
if (row && !row._deleted) return { ...row };
|
|
362
|
-
}
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
this._ensureBTrees();
|
|
366
|
-
if (this._btrees[this._primaryKey]) {
|
|
367
|
-
const indices = this._btrees[this._primaryKey].search(id);
|
|
368
|
-
if (indices.length > 0) {
|
|
369
|
-
const row = this._rows[indices[0]];
|
|
370
|
-
if (row && !row._deleted) return { ...row };
|
|
371
|
-
}
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
|
-
return this.findOne({ [this._primaryKey]: id });
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
findAll() {
|
|
378
|
-
return this._getVisibleRows().map(r => ({ ...r }));
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
count(query = {}) {
|
|
382
|
-
if (Object.keys(query).length === 0) return this._getVisibleRows().length;
|
|
383
|
-
return this._applyFilterOptimized(this._getVisibleRows(), query).length;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
where(conditions) {
|
|
387
|
-
return new Query(this, conditions);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// ============================================================
|
|
391
|
-
// 更新
|
|
392
|
-
// ============================================================
|
|
393
|
-
|
|
394
|
-
update(query, updates) {
|
|
395
|
-
for (const hook of this._hooks.beforeUpdate) {
|
|
396
|
-
updates = hook(query, { ...updates }) || updates;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
updates = this._validateDataTypes(updates, true);
|
|
400
|
-
const matched = this._applyFilterOptimized(this._getVisibleRows(), query);
|
|
401
|
-
let count = 0;
|
|
402
|
-
|
|
403
|
-
for (const row of matched) {
|
|
404
|
-
const index = this._rows.indexOf(row);
|
|
405
|
-
if (index === -1) continue;
|
|
406
|
-
|
|
407
|
-
const oldData = { ...this._rows[index] };
|
|
408
|
-
const merged = { ...this._rows[index], ...updates };
|
|
409
|
-
this._validateConstraints(merged, index);
|
|
410
|
-
|
|
411
|
-
this._removeFromIndexes(index, oldData);
|
|
412
|
-
Object.assign(this._rows[index], updates);
|
|
413
|
-
this._addToIndexes(index, this._rows[index]);
|
|
414
|
-
count++;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (count > 0) {
|
|
418
|
-
this._dirty = true;
|
|
419
|
-
this._db._markDirty(this._name);
|
|
420
|
-
for (const hook of this._hooks.afterUpdate) {
|
|
421
|
-
hook(query, updates, count);
|
|
422
|
-
}
|
|
423
|
-
this._db._emitChange('update', this._name, { query, updates, count });
|
|
424
|
-
}
|
|
425
|
-
return count;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
updateById(id, updates) {
|
|
429
|
-
if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
|
|
430
|
-
for (const hook of this._hooks.beforeUpdate) {
|
|
431
|
-
updates = hook({ [this._primaryKey]: id }, { ...updates }) || updates;
|
|
432
|
-
}
|
|
433
|
-
updates = this._validateDataTypes(updates, true);
|
|
434
|
-
let rowIndex;
|
|
435
|
-
if (this._pkIndex) {
|
|
436
|
-
rowIndex = this._pkIndex.get(id);
|
|
437
|
-
} else {
|
|
438
|
-
this._ensureBTrees();
|
|
439
|
-
if (this._btrees[this._primaryKey]) {
|
|
440
|
-
const indices = this._btrees[this._primaryKey].search(id);
|
|
441
|
-
rowIndex = indices.length > 0 ? indices[0] : undefined;
|
|
442
|
-
} else {
|
|
443
|
-
rowIndex = this._rows.findIndex(r => r[this._primaryKey] === id);
|
|
444
|
-
if (rowIndex === -1) rowIndex = undefined;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
if (rowIndex === undefined) return 0;
|
|
448
|
-
const row = this._rows[rowIndex];
|
|
449
|
-
if (row && row._deleted) return 0;
|
|
450
|
-
const oldData = { ...row };
|
|
451
|
-
const merged = { ...row, ...updates };
|
|
452
|
-
this._validateConstraints(merged, rowIndex);
|
|
453
|
-
this._removeFromIndexes(rowIndex, oldData);
|
|
454
|
-
Object.assign(row, updates);
|
|
455
|
-
this._addToIndexes(rowIndex, row);
|
|
456
|
-
this._dirty = true;
|
|
457
|
-
this._db._markDirty(this._name);
|
|
458
|
-
for (const hook of this._hooks.afterUpdate) {
|
|
459
|
-
hook({ [this._primaryKey]: id }, updates, 1);
|
|
460
|
-
}
|
|
461
|
-
this._db._emitChange('update', this._name, { query: { [this._primaryKey]: id }, updates, count: 1 });
|
|
462
|
-
return 1;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// ============================================================
|
|
466
|
-
// 删除 & 外键 CASCADE
|
|
467
|
-
// ============================================================
|
|
468
|
-
|
|
469
|
-
remove(query = {}) {
|
|
470
|
-
for (const hook of this._hooks.beforeRemove) {
|
|
471
|
-
if (hook(query) === false) return 0;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (Object.keys(query).length === 0) {
|
|
475
|
-
return this._truncate();
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const toRemove = this._applyFilterOptimized(this._getVisibleRows(), query);
|
|
479
|
-
let count = 0;
|
|
480
|
-
const removeIndices = [];
|
|
481
|
-
|
|
482
|
-
for (const row of toRemove) {
|
|
483
|
-
const index = this._rows.indexOf(row);
|
|
484
|
-
if (index === -1) continue;
|
|
485
|
-
this._cascadeForeignKeys(row);
|
|
486
|
-
this._removeFromIndexes(index, row);
|
|
487
|
-
removeIndices.push(index);
|
|
488
|
-
count++;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (count > 0) {
|
|
492
|
-
removeIndices.sort((a, b) => b - a);
|
|
493
|
-
for (const idx of removeIndices) {
|
|
494
|
-
const lastIdx = this._rows.length - 1;
|
|
495
|
-
if (idx < lastIdx) {
|
|
496
|
-
const lastRow = this._rows[lastIdx];
|
|
497
|
-
this._rows[idx] = lastRow;
|
|
498
|
-
if (this._pkIndex && this._primaryKey) {
|
|
499
|
-
this._pkIndex.set(lastRow[this._primaryKey], idx);
|
|
500
|
-
}
|
|
501
|
-
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
502
|
-
const val = lastRow[field];
|
|
503
|
-
if (val !== undefined && val !== null) {
|
|
504
|
-
tree.remove(val, lastIdx);
|
|
505
|
-
tree.insert(val, idx);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
for (const field of Object.keys(this._indexes)) {
|
|
509
|
-
const val = lastRow[field];
|
|
510
|
-
if (val !== undefined && this._indexes[field].has(val)) {
|
|
511
|
-
const positions = this._indexes[field].get(val);
|
|
512
|
-
const pi = positions.indexOf(lastIdx);
|
|
513
|
-
if (pi !== -1) positions[pi] = idx;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
this._rows.pop();
|
|
518
|
-
}
|
|
519
|
-
this._dirty = true;
|
|
520
|
-
this._db._markDirty(this._name);
|
|
521
|
-
for (const hook of this._hooks.afterRemove) {
|
|
522
|
-
hook(query, count);
|
|
523
|
-
}
|
|
524
|
-
this._db._emitChange('remove', this._name, { query, count });
|
|
525
|
-
}
|
|
526
|
-
return count;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
removeById(id) {
|
|
530
|
-
if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
|
|
531
|
-
let rowIndex;
|
|
532
|
-
if (this._pkIndex) {
|
|
533
|
-
rowIndex = this._pkIndex.get(id);
|
|
534
|
-
} else {
|
|
535
|
-
this._ensureBTrees();
|
|
536
|
-
if (this._btrees[this._primaryKey]) {
|
|
537
|
-
const indices = this._btrees[this._primaryKey].search(id);
|
|
538
|
-
rowIndex = indices.length > 0 ? indices[0] : undefined;
|
|
539
|
-
} else {
|
|
540
|
-
rowIndex = this._rows.findIndex(r => r[this._primaryKey] === id);
|
|
541
|
-
if (rowIndex === -1) rowIndex = undefined;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (rowIndex === undefined) return 0;
|
|
545
|
-
const row = this._rows[rowIndex];
|
|
546
|
-
if (!row || row._deleted) return 0;
|
|
547
|
-
this._cascadeForeignKeys(row);
|
|
548
|
-
this._removeFromIndexes(rowIndex, row);
|
|
549
|
-
const lastIdx = this._rows.length - 1;
|
|
550
|
-
if (rowIndex < lastIdx) {
|
|
551
|
-
const lastRow = this._rows[lastIdx];
|
|
552
|
-
this._rows[rowIndex] = lastRow;
|
|
553
|
-
if (this._pkIndex && this._primaryKey) {
|
|
554
|
-
this._pkIndex.set(lastRow[this._primaryKey], rowIndex);
|
|
555
|
-
}
|
|
556
|
-
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
557
|
-
const val = lastRow[field];
|
|
558
|
-
if (val !== undefined && val !== null) {
|
|
559
|
-
tree.remove(val, lastIdx);
|
|
560
|
-
tree.insert(val, rowIndex);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
for (const field of Object.keys(this._indexes)) {
|
|
564
|
-
const val = lastRow[field];
|
|
565
|
-
if (val !== undefined && this._indexes[field].has(val)) {
|
|
566
|
-
const positions = this._indexes[field].get(val);
|
|
567
|
-
const pi = positions.indexOf(lastIdx);
|
|
568
|
-
if (pi !== -1) positions[pi] = rowIndex;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
this._rows.pop();
|
|
573
|
-
this._dirty = true;
|
|
574
|
-
this._db._markDirty(this._name);
|
|
575
|
-
for (const hook of this._hooks.afterRemove) {
|
|
576
|
-
hook({ [this._primaryKey]: id }, 1);
|
|
577
|
-
}
|
|
578
|
-
this._db._emitChange('remove', this._name, { query: { [this._primaryKey]: id }, count: 1 });
|
|
579
|
-
return 1;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
removeSoft(query = {}) {
|
|
583
|
-
this._softDelete = true;
|
|
584
|
-
return this.remove(query);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
_findRowIndex(id) {
|
|
588
|
-
if (this._pkIndex && this._primaryKey) {
|
|
589
|
-
const idx = this._pkIndex.get(id);
|
|
590
|
-
if (idx !== undefined) return idx;
|
|
591
|
-
}
|
|
592
|
-
for (let i = 0; i < this._rows.length; i++) {
|
|
593
|
-
if (this._rows[i][this._primaryKey] === id) return i;
|
|
594
|
-
}
|
|
595
|
-
return -1;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
restore(query = {}) {
|
|
599
|
-
if (!this._softDelete) throw createError('ER_NOT_SUPPORTED', 'Soft delete is not enabled');
|
|
600
|
-
const matched = this._applyFilter(this._rows, { ...query, _deleted: true });
|
|
601
|
-
for (const row of matched) {
|
|
602
|
-
const index = this._rows.indexOf(row);
|
|
603
|
-
if (index !== -1) {
|
|
604
|
-
delete this._rows[index]._deleted;
|
|
605
|
-
delete this._rows[index]._deletedAt;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
return matched.length;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
truncate() {
|
|
612
|
-
return this._truncate();
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
_truncate() {
|
|
616
|
-
const count = this._rows.length;
|
|
617
|
-
this._rows = [];
|
|
618
|
-
if (this._pkIndex) this._pkIndex.clear();
|
|
619
|
-
for (const key of Object.keys(this._btrees)) {
|
|
620
|
-
this._btrees[key].clear();
|
|
621
|
-
}
|
|
622
|
-
this._indexes = {};
|
|
623
|
-
this._autoIncrement = 0;
|
|
624
|
-
this._dirty = true;
|
|
625
|
-
this._db._markDirty(this._name);
|
|
626
|
-
return count;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// ============================================================
|
|
630
|
-
// ALTER TABLE
|
|
631
|
-
// ============================================================
|
|
632
|
-
|
|
633
|
-
addColumn(field, definition) {
|
|
634
|
-
if (this._schema[field]) throw createError('ER_DUP_FIELDNAME', field);
|
|
635
|
-
this._schema[field] = definition;
|
|
636
|
-
if (definition.default !== undefined) {
|
|
637
|
-
this._defaults[field] = definition.default;
|
|
638
|
-
for (const row of this._rows) {
|
|
639
|
-
if (row[field] === undefined) {
|
|
640
|
-
row[field] = typeof definition.default === 'function' ? definition.default() : definition.default;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
if (definition.check) this._checkConstraints.push({ field, fn: definition.check });
|
|
645
|
-
if (definition.foreignKey) {
|
|
646
|
-
this._foreignKeys.push({
|
|
647
|
-
field,
|
|
648
|
-
table: definition.foreignKey.table,
|
|
649
|
-
refField: definition.foreignKey.field || 'id',
|
|
650
|
-
onDelete: definition.foreignKey.onDelete || 'restrict',
|
|
651
|
-
onUpdate: definition.foreignKey.onUpdate || 'restrict'
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
if (definition.computed) this._computedFields[field] = definition.computed;
|
|
655
|
-
if (['date', 'datetime', 'timestamp', 'time'].includes(definition.type)) {
|
|
656
|
-
this._dateFields[field] = definition.type;
|
|
657
|
-
}
|
|
658
|
-
if (definition.primaryKey || definition.unique) {
|
|
659
|
-
this._btrees[field] = new BTree(64, true);
|
|
660
|
-
this._rebuildBTree(field);
|
|
661
|
-
}
|
|
662
|
-
this._dirty = true;
|
|
663
|
-
this._db._markDirty(this._name);
|
|
664
|
-
return this;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
dropColumn(field) {
|
|
668
|
-
if (!this._schema[field]) throw createError('ER_CANT_DROP_FIELD', field);
|
|
669
|
-
delete this._schema[field];
|
|
670
|
-
delete this._defaults[field];
|
|
671
|
-
delete this._btrees[field];
|
|
672
|
-
delete this._indexes[field];
|
|
673
|
-
this._checkConstraints = this._checkConstraints.filter(c => c.field !== field);
|
|
674
|
-
this._foreignKeys = this._foreignKeys.filter(fk => fk.field !== field);
|
|
675
|
-
delete this._computedFields[field];
|
|
676
|
-
delete this._dateFields[field];
|
|
677
|
-
for (const row of this._rows) {
|
|
678
|
-
delete row[field];
|
|
679
|
-
}
|
|
680
|
-
if (this._primaryKey === field) this._primaryKey = null;
|
|
681
|
-
if (this._autoIncrementField === field) this._autoIncrementField = null;
|
|
682
|
-
this._dirty = true;
|
|
683
|
-
this._db._markDirty(this._name);
|
|
684
|
-
return this;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
renameColumn(oldName, newName) {
|
|
688
|
-
if (!this._schema[oldName]) throw createError('ER_BAD_FIELD_ERROR', oldName, this._name);
|
|
689
|
-
if (this._schema[newName]) throw createError('ER_DUP_FIELDNAME', newName);
|
|
690
|
-
this._schema[newName] = this._schema[oldName];
|
|
691
|
-
delete this._schema[oldName];
|
|
692
|
-
if (this._defaults[oldName] !== undefined) {
|
|
693
|
-
this._defaults[newName] = this._defaults[oldName];
|
|
694
|
-
delete this._defaults[oldName];
|
|
695
|
-
}
|
|
696
|
-
if (this._btrees[oldName]) {
|
|
697
|
-
this._btrees[newName] = this._btrees[oldName];
|
|
698
|
-
delete this._btrees[oldName];
|
|
699
|
-
}
|
|
700
|
-
for (const row of this._rows) {
|
|
701
|
-
row[newName] = row[oldName];
|
|
702
|
-
delete row[oldName];
|
|
703
|
-
}
|
|
704
|
-
if (this._primaryKey === oldName) this._primaryKey = newName;
|
|
705
|
-
if (this._autoIncrementField === oldName) this._autoIncrementField = newName;
|
|
706
|
-
this._dirty = true;
|
|
707
|
-
this._db._markDirty(this._name);
|
|
708
|
-
return this;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
modifyColumn(field, definition) {
|
|
712
|
-
if (!this._schema[field]) throw createError('ER_BAD_FIELD_ERROR', field, this._name);
|
|
713
|
-
this._schema[field] = { ...this._schema[field], ...definition };
|
|
714
|
-
if (definition.default !== undefined) this._defaults[field] = definition.default;
|
|
715
|
-
this._checkConstraints = this._checkConstraints.filter(c => c.field !== field);
|
|
716
|
-
if (definition.check) this._checkConstraints.push({ field, fn: definition.check });
|
|
717
|
-
if (definition.primaryKey || definition.unique) {
|
|
718
|
-
if (!this._btrees[field]) this._btrees[field] = new BTree(64, true);
|
|
719
|
-
this._rebuildBTree(field);
|
|
720
|
-
}
|
|
721
|
-
this._dirty = true;
|
|
722
|
-
this._db._markDirty(this._name);
|
|
723
|
-
return this;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
describe() {
|
|
727
|
-
return {
|
|
728
|
-
name: this._name,
|
|
729
|
-
columns: { ...this._schema },
|
|
730
|
-
primaryKey: this._primaryKey,
|
|
731
|
-
rowCount: this._getVisibleRows().length,
|
|
732
|
-
btreeIndexes: Object.keys(this._btrees),
|
|
733
|
-
hashIndexes: Object.keys(this._indexes),
|
|
734
|
-
softDelete: this._softDelete,
|
|
735
|
-
foreignKeys: this._foreignKeys.map(fk => ({ field: fk.field, table: fk.table }))
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
selectInto(newTableName, newSchema, query = {}) {
|
|
740
|
-
const rows = this._applyFilterOptimized(this._getVisibleRows(), query);
|
|
741
|
-
this._db.createTable(newTableName, newSchema);
|
|
742
|
-
const newTable = this._db._tables[newTableName];
|
|
743
|
-
for (const row of rows) {
|
|
744
|
-
newTable.insert(row);
|
|
745
|
-
}
|
|
746
|
-
return newTable;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
textSearch(field, terms) {
|
|
750
|
-
const searchTerms = Array.isArray(terms) ? terms : terms.split(/\s+/);
|
|
751
|
-
const rows = this._getVisibleRows();
|
|
752
|
-
const scored = [];
|
|
753
|
-
for (const row of rows) {
|
|
754
|
-
const value = String(row[field] || '').toLowerCase();
|
|
755
|
-
let score = 0;
|
|
756
|
-
for (const term of searchTerms) {
|
|
757
|
-
const t = term.toLowerCase();
|
|
758
|
-
const idx = value.indexOf(t);
|
|
759
|
-
if (idx !== -1) {
|
|
760
|
-
score += 10 + (idx === 0 ? 5 : 0) + (value === t ? 20 : 0)
|
|
761
|
-
+ (value.length - idx) / value.length * 5;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
if (score > 0) scored.push({ ...row, _score: score });
|
|
765
|
-
}
|
|
766
|
-
return scored.sort((a, b) => b._score - a._score);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// ============================================================
|
|
770
|
-
// 索引
|
|
771
|
-
// ============================================================
|
|
772
|
-
|
|
773
|
-
createIndex(field) {
|
|
774
|
-
this._indexes[field] = new Map();
|
|
775
|
-
for (let i = 0; i < this._rows.length; i++) {
|
|
776
|
-
const value = this._rows[i][field];
|
|
777
|
-
if (value !== undefined) {
|
|
778
|
-
if (!this._indexes[field].has(value)) {
|
|
779
|
-
this._indexes[field].set(value, []);
|
|
780
|
-
}
|
|
781
|
-
this._indexes[field].get(value).push(i);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
return this._indexes[field];
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
dropIndex(field) {
|
|
788
|
-
delete this._indexes[field];
|
|
789
|
-
delete this._btrees[field];
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* 创建 B-Tree 索引(O(log n) 查询)
|
|
794
|
-
*/
|
|
795
|
-
createBTreeIndex(field, unique = false) {
|
|
796
|
-
this._btrees[field] = new BTree(64, unique);
|
|
797
|
-
this._rebuildBTree(field);
|
|
798
|
-
return this._btrees[field];
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
_rebuildBTree(field) {
|
|
802
|
-
if (this._btrees[field]) {
|
|
803
|
-
this._btrees[field].clear();
|
|
804
|
-
for (let i = 0; i < this._rows.length; i++) {
|
|
805
|
-
const val = this._rows[i][field];
|
|
806
|
-
if (val !== undefined && val !== null) {
|
|
807
|
-
this._btrees[field].insert(val, i);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
_ensureBTrees() {
|
|
814
|
-
if (!this._btreesDirty) return;
|
|
815
|
-
this._btreesDirty = false;
|
|
816
|
-
for (const field of Object.keys(this._btrees)) {
|
|
817
|
-
this._btrees[field].clear();
|
|
818
|
-
}
|
|
819
|
-
for (let i = 0; i < this._rows.length; i++) {
|
|
820
|
-
const row = this._rows[i];
|
|
821
|
-
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
822
|
-
const v = row[field];
|
|
823
|
-
if (v !== undefined && v !== null) tree.insert(v, i);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
_rebuildAllBTrees() {
|
|
829
|
-
for (const field of Object.keys(this._btrees)) {
|
|
830
|
-
this._rebuildBTree(field);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// ============================================================
|
|
835
|
-
// 事件钩子
|
|
836
|
-
// ============================================================
|
|
837
|
-
|
|
838
|
-
on(event, callback) {
|
|
839
|
-
if (this._hooks[event]) {
|
|
840
|
-
this._hooks[event].push(callback);
|
|
841
|
-
}
|
|
842
|
-
return this;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
off(event, callback) {
|
|
846
|
-
if (this._hooks[event]) {
|
|
847
|
-
this._hooks[event] = this._hooks[event].filter(cb => cb !== callback);
|
|
848
|
-
}
|
|
849
|
-
return this;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
// ============================================================
|
|
853
|
-
// 内部: 过滤器(B-Tree 加速)
|
|
854
|
-
// ============================================================
|
|
855
|
-
|
|
856
|
-
_applyFilterOptimized(rows, query) {
|
|
857
|
-
this._ensureBTrees();
|
|
858
|
-
// 尝试用 B-Tree 加速
|
|
859
|
-
for (const [field, condition] of Object.entries(query)) {
|
|
860
|
-
if (field === '$or' || field === '$and' || field === '$not') continue;
|
|
861
|
-
// 等值查询用 B-Tree
|
|
862
|
-
if (typeof condition !== 'object' || condition === null || Array.isArray(condition) || condition instanceof RegExp) {
|
|
863
|
-
if (this._btrees[field]) {
|
|
864
|
-
const indices = this._btrees[field].search(condition);
|
|
865
|
-
if (indices.length > 0) {
|
|
866
|
-
const subset = indices.map(i => this._rows[i]).filter(r => r);
|
|
867
|
-
return this._applyFilter(subset, query);
|
|
868
|
-
}
|
|
869
|
-
return [];
|
|
870
|
-
}
|
|
871
|
-
if (this._indexes[field] && this._indexes[field].has(condition)) {
|
|
872
|
-
const indices = this._indexes[field].get(condition);
|
|
873
|
-
const subset = indices.map(i => this._rows[i]).filter(r => r);
|
|
874
|
-
return this._applyFilter(subset, query);
|
|
875
|
-
}
|
|
876
|
-
if (this._indexes[field]) return [];
|
|
877
|
-
}
|
|
878
|
-
// 范围查询用 B-Tree
|
|
879
|
-
if (condition && typeof condition === 'object') {
|
|
880
|
-
if (condition.$gt !== undefined && condition.$lt !== undefined && this._btrees[field]) {
|
|
881
|
-
const indices = this._btrees[field].range(condition.$gt, condition.$lt);
|
|
882
|
-
const subset = indices.map(i => this._rows[i]).filter(r => r);
|
|
883
|
-
return this._applyFilter(subset, query);
|
|
884
|
-
}
|
|
885
|
-
if (condition.$gte !== undefined && condition.$lte !== undefined && this._btrees[field]) {
|
|
886
|
-
const indices = this._btrees[field].range(condition.$gte, condition.$lte);
|
|
887
|
-
const subset = indices.map(i => this._rows[i]).filter(r => r);
|
|
888
|
-
return this._applyFilter(subset, query);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
return this._applyFilter(rows, query);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
_applyFilter(rows, query) {
|
|
896
|
-
const result = [];
|
|
897
|
-
for (let i = 0; i < rows.length; i++) {
|
|
898
|
-
if (this._matchRow(rows[i], query)) {
|
|
899
|
-
result.push(rows[i]);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
return result;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
_matchRow(row, query) {
|
|
906
|
-
for (const [field, condition] of Object.entries(query)) {
|
|
907
|
-
if (field === '$or') {
|
|
908
|
-
if (!Array.isArray(condition) || !condition.some(sub => this._matchRow(row, sub))) return false;
|
|
909
|
-
continue;
|
|
910
|
-
}
|
|
911
|
-
if (field === '$and') {
|
|
912
|
-
if (!Array.isArray(condition) || !condition.every(sub => this._matchRow(row, sub))) return false;
|
|
913
|
-
continue;
|
|
914
|
-
}
|
|
915
|
-
if (field === '$not') {
|
|
916
|
-
if (this._matchRow(row, condition)) return false;
|
|
917
|
-
continue;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
const value = this._resolvePath(row, field);
|
|
921
|
-
|
|
922
|
-
if (condition !== null && typeof condition === 'object' && !Array.isArray(condition) && !(condition instanceof RegExp)) {
|
|
923
|
-
const keys = Object.keys(condition);
|
|
924
|
-
const isOperator = keys.every(k => k.startsWith('$'));
|
|
925
|
-
if (isOperator && keys.length > 0) {
|
|
926
|
-
for (const [op, target] of Object.entries(condition)) {
|
|
927
|
-
if (!this._matchOperator(value, op, target)) return false;
|
|
928
|
-
}
|
|
929
|
-
} else {
|
|
930
|
-
if (value !== null && typeof value === 'object') {
|
|
931
|
-
if (!this._matchRow(value, condition)) return false;
|
|
932
|
-
} else {
|
|
933
|
-
return false;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
} else {
|
|
937
|
-
if (value !== condition) return false;
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
return true;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
_matchOperator(value, op, target) {
|
|
944
|
-
switch (op) {
|
|
945
|
-
case '$eq': return value === target;
|
|
946
|
-
case '$ne': return value !== target;
|
|
947
|
-
case '$gt': return value > target;
|
|
948
|
-
case '$gte': return value >= target;
|
|
949
|
-
case '$lt': return value < target;
|
|
950
|
-
case '$lte': return value <= target;
|
|
951
|
-
case '$in': return Array.isArray(target) && target.includes(value);
|
|
952
|
-
case '$nin': return Array.isArray(target) && !target.includes(value);
|
|
953
|
-
case '$like':
|
|
954
|
-
if (typeof value !== 'string' || typeof target !== 'string') return false;
|
|
955
|
-
{
|
|
956
|
-
const escaped = target
|
|
957
|
-
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
958
|
-
.replace(/%/g, '.*')
|
|
959
|
-
.replace(/_/g, '.');
|
|
960
|
-
return new RegExp('^' + escaped + '$', 'i').test(value);
|
|
961
|
-
}
|
|
962
|
-
case '$regex':
|
|
963
|
-
if (target instanceof RegExp) return target.test(String(value));
|
|
964
|
-
if (!isSafeRegex(target)) throw createError('ER_BAD_REGEX', String(target));
|
|
965
|
-
return new RegExp(target).test(String(value));
|
|
966
|
-
case '$exists':
|
|
967
|
-
return target ? value !== undefined : value === undefined;
|
|
968
|
-
case '$between':
|
|
969
|
-
return Array.isArray(target) && target.length === 2 && value >= target[0] && value <= target[1];
|
|
970
|
-
case '$and':
|
|
971
|
-
return Array.isArray(target) && target.every(sub => this._matchRow({ _val: value }, { _val: sub }));
|
|
972
|
-
case '$or':
|
|
973
|
-
return Array.isArray(target) && target.some(sub => this._matchRow({ _val: value }, { _val: sub }));
|
|
974
|
-
case '$not':
|
|
975
|
-
return !this._matchRow({ _val: value }, { _val: target });
|
|
976
|
-
case '$contains':
|
|
977
|
-
return Array.isArray(value) && value.includes(target);
|
|
978
|
-
case '$type':
|
|
979
|
-
return typeof value === target;
|
|
980
|
-
case '$size':
|
|
981
|
-
return Array.isArray(value) && value.length === target;
|
|
982
|
-
case '$elemMatch':
|
|
983
|
-
return Array.isArray(value) && value.some(el =>
|
|
984
|
-
typeof el === 'object' ? this._matchRow(el, target) : el === target);
|
|
985
|
-
case '$isNull':
|
|
986
|
-
return target ? value === null || value === undefined : value !== null && value !== undefined;
|
|
987
|
-
case '$startsWith':
|
|
988
|
-
return typeof value === 'string' && value.startsWith(target);
|
|
989
|
-
case '$endsWith':
|
|
990
|
-
return typeof value === 'string' && value.endsWith(target);
|
|
991
|
-
case '$search':
|
|
992
|
-
if (typeof value !== 'string') return false;
|
|
993
|
-
const terms = Array.isArray(target) ? target : [target];
|
|
994
|
-
return terms.every(t => value.toLowerCase().includes(t.toLowerCase()));
|
|
995
|
-
case '$textSearch':
|
|
996
|
-
if (typeof value !== 'string') return false;
|
|
997
|
-
const sTerms = Array.isArray(target) ? target : target.split(/\s+/);
|
|
998
|
-
return sTerms.some(t => value.toLowerCase().includes(t.toLowerCase()));
|
|
999
|
-
case '$inSub':
|
|
1000
|
-
if (!target || !target.table) return false;
|
|
1001
|
-
const subRows = target.table._applyFilter(target.table._rows, target.query || {});
|
|
1002
|
-
return subRows.some(r => r[target.field] === value);
|
|
1003
|
-
case '$notInSub':
|
|
1004
|
-
if (!target || !target.table) return false;
|
|
1005
|
-
const subRows2 = target.table._applyFilter(target.table._rows, target.query || {});
|
|
1006
|
-
return !subRows2.some(r => r[target.field] === value);
|
|
1007
|
-
case '$expr':
|
|
1008
|
-
if (typeof target === 'function') return target(value);
|
|
1009
|
-
return false;
|
|
1010
|
-
default:
|
|
1011
|
-
return false;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
_resolvePath(row, field) {
|
|
1016
|
-
if (field.includes('.') && !field.startsWith('$')) {
|
|
1017
|
-
const parts = field.split('.');
|
|
1018
|
-
let value = row;
|
|
1019
|
-
for (const part of parts) {
|
|
1020
|
-
if (value === null || value === undefined) return undefined;
|
|
1021
|
-
value = value[part];
|
|
1022
|
-
}
|
|
1023
|
-
return value;
|
|
1024
|
-
}
|
|
1025
|
-
return row[field];
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// ============================================================
|
|
1029
|
-
// 内部: 约束校验
|
|
1030
|
-
// ============================================================
|
|
1031
|
-
|
|
1032
|
-
_validateConstraints(data, excludeIndex, uniqueTracker) {
|
|
1033
|
-
for (const [field, def] of Object.entries(this._schema)) {
|
|
1034
|
-
if (field === '_softDelete') continue;
|
|
1035
|
-
|
|
1036
|
-
// NOT NULL
|
|
1037
|
-
if (def.required && (data[field] === undefined || data[field] === null)
|
|
1038
|
-
&& !(def.autoIncrement && data[field] === undefined)) {
|
|
1039
|
-
throw createError('ER_BAD_NULL_ERROR', field);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// PRIMARY KEY 唯一性
|
|
1043
|
-
if ((def.primaryKey || def.primary === true) && data[field] !== undefined && data[field] !== null) {
|
|
1044
|
-
let dup = false;
|
|
1045
|
-
if (uniqueTracker && uniqueTracker[field]) {
|
|
1046
|
-
dup = uniqueTracker[field].has(data[field]);
|
|
1047
|
-
} else if (this._pkIndex) {
|
|
1048
|
-
const idx = this._pkIndex.get(data[field]);
|
|
1049
|
-
dup = idx !== undefined && idx !== null && idx !== excludeIndex;
|
|
1050
|
-
} else {
|
|
1051
|
-
const idx = this._rows.findIndex(r => r[field] === data[field]);
|
|
1052
|
-
dup = idx !== -1 && idx !== excludeIndex;
|
|
1053
|
-
}
|
|
1054
|
-
if (dup) {
|
|
1055
|
-
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// UNIQUE(B-Tree 优化 O(log n))
|
|
1060
|
-
if (def.unique && data[field] !== undefined) {
|
|
1061
|
-
if (uniqueTracker && uniqueTracker[field]) {
|
|
1062
|
-
if (uniqueTracker[field].has(data[field])) {
|
|
1063
|
-
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
1064
|
-
}
|
|
1065
|
-
uniqueTracker[field].add(data[field]);
|
|
1066
|
-
} else if (this._btrees[field]) {
|
|
1067
|
-
const found = this._btrees[field].search(data[field]);
|
|
1068
|
-
if (found.length > 0) {
|
|
1069
|
-
for (const idx of found) {
|
|
1070
|
-
if (idx !== excludeIndex && this._rows[idx] && this._rows[idx][field] === data[field]) {
|
|
1071
|
-
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
} else {
|
|
1076
|
-
for (let i = 0; i < this._rows.length; i++) {
|
|
1077
|
-
if (i === excludeIndex) continue;
|
|
1078
|
-
if (this._rows[i][field] === data[field]) {
|
|
1079
|
-
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// CHECK
|
|
1086
|
-
for (const { field: cf, fn } of this._checkConstraints) {
|
|
1087
|
-
if (cf === field && data[field] !== undefined && !fn(data[field], data)) {
|
|
1088
|
-
throw createError('ER_CHECK_CONSTRAINT', this._name + '.' + field);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
// 长度限制
|
|
1093
|
-
if (def.length && typeof data[field] === 'string' && data[field].length > def.length) {
|
|
1094
|
-
throw createError('ER_DATA_TOO_LONG', field);
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
// 数值范围
|
|
1098
|
-
if (def.type === 'integer' && data[field] !== undefined && data[field] !== null) {
|
|
1099
|
-
const v = Number(data[field]);
|
|
1100
|
-
if (def.min !== undefined && v < def.min) throw createError('ER_OUT_OF_RANGE', field);
|
|
1101
|
-
if (def.max !== undefined && v > def.max) throw createError('ER_OUT_OF_RANGE', field);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
// FOREIGN KEY 校验
|
|
1106
|
-
for (const fk of this._foreignKeys) {
|
|
1107
|
-
if (data[fk.field] !== undefined && data[fk.field] !== null) {
|
|
1108
|
-
const refTable = this._resolveTable(fk.table);
|
|
1109
|
-
if (refTable) {
|
|
1110
|
-
const exists = refTable.findOne({ [fk.refField]: data[fk.field] });
|
|
1111
|
-
if (!exists) {
|
|
1112
|
-
throw createError('ER_NO_REFERENCED_ROW');
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
_resolveTable(tableRef) {
|
|
1120
|
-
if (typeof tableRef === 'string') {
|
|
1121
|
-
return this._db._tables[tableRef] || this._db._attached[tableRef]?._tables[tableRef];
|
|
1122
|
-
}
|
|
1123
|
-
return tableRef;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
/**
|
|
1127
|
-
* 级联外键操作:当删除当前表的行时,查找所有引用此表的子表
|
|
1128
|
-
* 并执行 CASCADE 或 SET NULL
|
|
1129
|
-
*/
|
|
1130
|
-
_cascadeForeignKeys(row) {
|
|
1131
|
-
for (const [, table] of Object.entries(this._db._tables)) {
|
|
1132
|
-
if (table._name === this._name) continue;
|
|
1133
|
-
for (const fk of table._foreignKeys) {
|
|
1134
|
-
const refTable = table._resolveTable(fk.table);
|
|
1135
|
-
if (!refTable || refTable._name !== this._name) continue;
|
|
1136
|
-
|
|
1137
|
-
// 检查子表行是否引用当前行
|
|
1138
|
-
if (fk.onDelete === 'cascade') {
|
|
1139
|
-
table.remove({ [fk.field]: row[fk.refField] });
|
|
1140
|
-
} else if (fk.onDelete === 'set null') {
|
|
1141
|
-
table.update({ [fk.field]: row[fk.refField] }, { [fk.field]: null });
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
_validateDataTypes(data, partial = false) {
|
|
1148
|
-
const result = { ...data };
|
|
1149
|
-
for (const [field, type] of Object.entries(this._dateFields)) {
|
|
1150
|
-
if (partial && data[field] === undefined) continue;
|
|
1151
|
-
if (data[field] !== undefined && data[field] !== null) {
|
|
1152
|
-
result[field] = validateDateType(data[field], type, field);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
for (const [field, def] of Object.entries(this._schema)) {
|
|
1156
|
-
if (field === '_softDelete') continue;
|
|
1157
|
-
if (partial && data[field] === undefined) continue;
|
|
1158
|
-
const v = data[field];
|
|
1159
|
-
if (v === undefined || v === null) continue;
|
|
1160
|
-
const t = String(def.type || '').toLowerCase();
|
|
1161
|
-
if (t === 'integer' || t === 'int' || t === 'bigint') {
|
|
1162
|
-
if (typeof v === 'boolean') throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1163
|
-
if (typeof v === 'number') {
|
|
1164
|
-
if (Number.isInteger(v)) result[field] = v;
|
|
1165
|
-
else throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1166
|
-
} else if (typeof v === 'string' && v.trim() !== '' && Number.isInteger(Number(v))) {
|
|
1167
|
-
result[field] = Number(v);
|
|
1168
|
-
} else {
|
|
1169
|
-
throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1170
|
-
}
|
|
1171
|
-
} else if (t === 'float' || t === 'double' || t === 'number' || t === 'real') {
|
|
1172
|
-
if (typeof v === 'boolean') throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1173
|
-
if (typeof v === 'number') result[field] = v;
|
|
1174
|
-
else if (typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v))) result[field] = Number(v);
|
|
1175
|
-
else throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1176
|
-
} else if (t === 'boolean' || t === 'bool') {
|
|
1177
|
-
if (typeof v === 'boolean') result[field] = v;
|
|
1178
|
-
else if (v === 0 || v === 1) result[field] = !!v;
|
|
1179
|
-
else if (typeof v === 'string' && ['true', 'false', '1', '0'].includes(v.toLowerCase())) {
|
|
1180
|
-
result[field] = v.toLowerCase() === 'true' || v === '1';
|
|
1181
|
-
} else {
|
|
1182
|
-
throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1183
|
-
}
|
|
1184
|
-
} else if (t === 'string' || t === 'text' || t === 'varchar' || t === 'char') {
|
|
1185
|
-
if (typeof v === 'string') result[field] = v;
|
|
1186
|
-
else if (typeof v === 'number' || typeof v === 'boolean' || v instanceof Date) result[field] = String(v);
|
|
1187
|
-
else if (Buffer.isBuffer(v)) result[field] = v.toString('utf8');
|
|
1188
|
-
else throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
return result;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// ============================================================
|
|
1195
|
-
// 内部: 辅助
|
|
1196
|
-
// ============================================================
|
|
1197
|
-
|
|
1198
|
-
_applyDefaults(data) {
|
|
1199
|
-
const result = { ...data };
|
|
1200
|
-
for (const [field, defaultValue] of Object.entries(this._defaults)) {
|
|
1201
|
-
if (result[field] === undefined) {
|
|
1202
|
-
result[field] = typeof defaultValue === 'function'
|
|
1203
|
-
? defaultValue()
|
|
1204
|
-
: (defaultValue === 'CURRENT_TIMESTAMP' ? now() : defaultValue);
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
return result;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
_applyComputed(data) {
|
|
1211
|
-
for (const [field, fn] of Object.entries(this._computedFields)) {
|
|
1212
|
-
data[field] = fn(data);
|
|
1213
|
-
}
|
|
1214
|
-
return data;
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
_getVisibleRows() {
|
|
1218
|
-
if (this._softDelete) return this._rows.filter(r => !r._deleted);
|
|
1219
|
-
return this._rows;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
_addToIndexes(rowIndex, data) {
|
|
1223
|
-
if (this._pkIndex && this._primaryKey) {
|
|
1224
|
-
const pkVal = data[this._primaryKey];
|
|
1225
|
-
if (pkVal !== undefined && pkVal !== null) this._pkIndex.set(pkVal, rowIndex);
|
|
1226
|
-
}
|
|
1227
|
-
for (const field of Object.keys(this._indexes)) {
|
|
1228
|
-
const value = data[field];
|
|
1229
|
-
if (value !== undefined) {
|
|
1230
|
-
if (!this._indexes[field].has(value)) {
|
|
1231
|
-
this._indexes[field].set(value, []);
|
|
1232
|
-
}
|
|
1233
|
-
this._indexes[field].get(value).push(rowIndex);
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
1237
|
-
const value = data[field];
|
|
1238
|
-
if (value !== undefined && value !== null) {
|
|
1239
|
-
tree.insert(value, rowIndex);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
_removeFromIndexes(rowIndex, data) {
|
|
1245
|
-
if (this._pkIndex && this._primaryKey) {
|
|
1246
|
-
this._pkIndex.delete(data[this._primaryKey]);
|
|
1247
|
-
}
|
|
1248
|
-
for (const field of Object.keys(this._indexes)) {
|
|
1249
|
-
const value = data[field];
|
|
1250
|
-
if (value !== undefined && this._indexes[field].has(value)) {
|
|
1251
|
-
const positions = this._indexes[field].get(value).filter(p => p !== rowIndex);
|
|
1252
|
-
if (positions.length === 0) {
|
|
1253
|
-
this._indexes[field].delete(value);
|
|
1254
|
-
} else {
|
|
1255
|
-
this._indexes[field].set(value, positions);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
1260
|
-
const value = data[field];
|
|
1261
|
-
if (value !== undefined && value !== null) {
|
|
1262
|
-
tree.remove(value, rowIndex);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
_rebuildPKIndex() {
|
|
1268
|
-
if (!this._pkIndex || !this._primaryKey) return;
|
|
1269
|
-
this._pkIndex.clear();
|
|
1270
|
-
for (let i = 0; i < this._rows.length; i++) {
|
|
1271
|
-
const pkVal = this._rows[i][this._primaryKey];
|
|
1272
|
-
if (pkVal !== undefined && pkVal !== null) this._pkIndex.set(pkVal, i);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
_adjustBTreeAfterRemove(removedIndex) {
|
|
1277
|
-
this._rebuildPKIndex();
|
|
1278
|
-
for (const field of Object.keys(this._btrees)) {
|
|
1279
|
-
this._rebuildBTree(field);
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
_loadRows(rows) {
|
|
1284
|
-
this._rows = rows;
|
|
1285
|
-
if (this._autoIncrementField) {
|
|
1286
|
-
for (const row of rows) {
|
|
1287
|
-
const val = row[this._autoIncrementField];
|
|
1288
|
-
if (typeof val === 'number' && val > this._autoIncrement) {
|
|
1289
|
-
this._autoIncrement = val;
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
this._rebuildPKIndex();
|
|
1294
|
-
this._rebuildAllBTrees();
|
|
1295
|
-
for (const field of Object.keys(this._indexes)) {
|
|
1296
|
-
this.createIndex(field);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
toJSON() {
|
|
1301
|
-
return this._rows;
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1
|
+
// © Vexify 2026 All Rights Reserved.
|
|
2
|
+
/**
|
|
3
|
+
* JSQL Table — 表操作引擎 v2.0
|
|
4
|
+
* B-Tree 自动索引、日期类型、错误码、外键 CASCADE、长度/精度校验
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const Query = require('./query');
|
|
8
|
+
const BTree = require('./btree');
|
|
9
|
+
const { validateDateType, now } = require('./date-types');
|
|
10
|
+
const { createError } = require('./errors');
|
|
11
|
+
|
|
12
|
+
function isSafeRegex(pattern) {
|
|
13
|
+
if (typeof pattern !== 'string') return false;
|
|
14
|
+
if (pattern.length > 256) return false;
|
|
15
|
+
const groups = [];
|
|
16
|
+
let i = 0;
|
|
17
|
+
const n = pattern.length;
|
|
18
|
+
while (i < n) {
|
|
19
|
+
const c = pattern[i];
|
|
20
|
+
if (c === '\\') { i += 2; continue; }
|
|
21
|
+
if (c === '[') {
|
|
22
|
+
while (i < n && pattern[i] !== ']') i++;
|
|
23
|
+
i++;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (c === '(') {
|
|
27
|
+
const nxt = pattern[i + 1];
|
|
28
|
+
if (nxt === '?') {
|
|
29
|
+
const t = pattern[i + 2];
|
|
30
|
+
if (t === ':') {
|
|
31
|
+
groups.push({ hasQuant: false, hasAlt: false });
|
|
32
|
+
i += 3;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (t === '=' || t === '!') {
|
|
36
|
+
let depth = 1, j = i + 3;
|
|
37
|
+
for (; j < n && depth > 0; j++) {
|
|
38
|
+
if (pattern[j] === '\\') { j++; continue; }
|
|
39
|
+
if (pattern[j] === '(') depth++;
|
|
40
|
+
else if (pattern[j] === ')') depth--;
|
|
41
|
+
}
|
|
42
|
+
i = j;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (t === '<') {
|
|
46
|
+
const u = pattern[i + 3];
|
|
47
|
+
if (u === '=' || u === '!') {
|
|
48
|
+
let depth = 1, j = i + 4;
|
|
49
|
+
for (; j < n && depth > 0; j++) {
|
|
50
|
+
if (pattern[j] === '\\') { j++; continue; }
|
|
51
|
+
if (pattern[j] === '(') depth++;
|
|
52
|
+
else if (pattern[j] === ')') depth--;
|
|
53
|
+
}
|
|
54
|
+
i = j;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const gt = pattern.indexOf('>', i + 3);
|
|
58
|
+
if (gt === -1) return false;
|
|
59
|
+
groups.push({ hasQuant: false, hasAlt: false });
|
|
60
|
+
i = gt + 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
groups.push({ hasQuant: false, hasAlt: false });
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (c === ')') {
|
|
70
|
+
const g = groups.pop();
|
|
71
|
+
if (!g) return false;
|
|
72
|
+
let qi = i + 1;
|
|
73
|
+
let quant = null;
|
|
74
|
+
if (pattern[qi] === '+' || pattern[qi] === '*' || pattern[qi] === '?') {
|
|
75
|
+
quant = pattern[qi];
|
|
76
|
+
} else if (pattern[qi] === '{') {
|
|
77
|
+
const close = pattern.indexOf('}', qi);
|
|
78
|
+
if (close === -1) return false;
|
|
79
|
+
quant = pattern.slice(qi, close + 1);
|
|
80
|
+
qi = close + 1;
|
|
81
|
+
}
|
|
82
|
+
if (quant && (g.hasQuant || g.hasAlt)) return false;
|
|
83
|
+
i = qi;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (c === '+' || c === '*' || c === '?') {
|
|
87
|
+
if (groups.length > 0) groups[groups.length - 1].hasQuant = true;
|
|
88
|
+
i++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (c === '{' && /[0-9]/.test(pattern[i + 1] || '')) {
|
|
92
|
+
const close = pattern.indexOf('}', i);
|
|
93
|
+
if (close === -1) return false;
|
|
94
|
+
if (groups.length > 0) groups[groups.length - 1].hasQuant = true;
|
|
95
|
+
i = close + 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (c === '|') {
|
|
99
|
+
if (groups.length > 0) groups[groups.length - 1].hasAlt = true;
|
|
100
|
+
i++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
i++;
|
|
104
|
+
}
|
|
105
|
+
return groups.length === 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class Table {
|
|
109
|
+
constructor(name, schema, db) {
|
|
110
|
+
this._name = name;
|
|
111
|
+
this._schema = schema;
|
|
112
|
+
this._db = db;
|
|
113
|
+
this._rows = [];
|
|
114
|
+
this._indexes = {}; // 旧版 hash 索引(兼容)
|
|
115
|
+
this._btrees = {}; // B-Tree 索引 { fieldName: BTree }
|
|
116
|
+
this._autoIncrement = 0;
|
|
117
|
+
this._dirty = false;
|
|
118
|
+
this._hooks = {
|
|
119
|
+
beforeInsert: [], afterInsert: [],
|
|
120
|
+
beforeUpdate: [], afterUpdate: [],
|
|
121
|
+
beforeRemove: [], afterRemove: []
|
|
122
|
+
};
|
|
123
|
+
this._softDelete = false;
|
|
124
|
+
this._computedFields = {};
|
|
125
|
+
this._btreesDirty = false;
|
|
126
|
+
|
|
127
|
+
this._primaryKey = null;
|
|
128
|
+
this._pkIndex = null;
|
|
129
|
+
this._autoIncrementField = null;
|
|
130
|
+
this._foreignKeys = [];
|
|
131
|
+
this._checkConstraints = [];
|
|
132
|
+
this._defaults = {};
|
|
133
|
+
this._dateFields = {}; // { fieldName: 'date'|'datetime'|'timestamp' }
|
|
134
|
+
|
|
135
|
+
for (const [field, def] of Object.entries(schema)) {
|
|
136
|
+
const isPk = !!(def.primaryKey || def.primary === true);
|
|
137
|
+
if (isPk) this._primaryKey = field;
|
|
138
|
+
if (def.autoIncrement) {
|
|
139
|
+
this._autoIncrementField = field;
|
|
140
|
+
if (isPk) this._primaryKey = field;
|
|
141
|
+
}
|
|
142
|
+
if (def.default !== undefined) {
|
|
143
|
+
this._defaults[field] = def.default;
|
|
144
|
+
}
|
|
145
|
+
if (def.check) {
|
|
146
|
+
this._checkConstraints.push({ field, fn: def.check });
|
|
147
|
+
}
|
|
148
|
+
if (def.foreignKey) {
|
|
149
|
+
this._foreignKeys.push({
|
|
150
|
+
field,
|
|
151
|
+
table: def.foreignKey.table,
|
|
152
|
+
refField: def.foreignKey.field || 'id',
|
|
153
|
+
onDelete: def.foreignKey.onDelete || 'restrict',
|
|
154
|
+
onUpdate: def.foreignKey.onUpdate || 'restrict'
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (def.computed) {
|
|
158
|
+
this._computedFields[field] = def.computed;
|
|
159
|
+
}
|
|
160
|
+
// 日期类型
|
|
161
|
+
if (['date', 'datetime', 'timestamp', 'time'].includes(def.type)) {
|
|
162
|
+
this._dateFields[field] = def.type;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (schema._softDelete) {
|
|
167
|
+
this._softDelete = true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this._pkIndex = this._primaryKey ? new Map() : null;
|
|
171
|
+
this._cachedSchemaFields = Object.keys(schema).filter(f => f !== '_softDelete');
|
|
172
|
+
this._cachedDateFields = Object.keys(this._dateFields);
|
|
173
|
+
this._cachedUniqueFields = this._cachedSchemaFields.filter(f => schema[f].unique);
|
|
174
|
+
this._cachedRequiredFields = this._cachedSchemaFields.filter(f => schema[f].required);
|
|
175
|
+
this._autoCreateBTrees();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
_autoCreateBTrees() {
|
|
179
|
+
for (const [field, def] of Object.entries(this._schema)) {
|
|
180
|
+
if (field === '_softDelete') continue;
|
|
181
|
+
if (def.primaryKey || def.primary === true || def.unique) {
|
|
182
|
+
this._btrees[field] = new BTree(64, true);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ============================================================
|
|
188
|
+
// 插入
|
|
189
|
+
// ============================================================
|
|
190
|
+
|
|
191
|
+
insert(data) {
|
|
192
|
+
for (const hook of this._hooks.beforeInsert) {
|
|
193
|
+
data = hook({ ...data }) || data;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
data = this._applyDefaults(data);
|
|
197
|
+
data = this._applyComputed(data);
|
|
198
|
+
data = this._validateDataTypes(data);
|
|
199
|
+
this._validateConstraints(data, -1);
|
|
200
|
+
|
|
201
|
+
if (this._autoIncrementField && data[this._autoIncrementField] === undefined) {
|
|
202
|
+
this._autoIncrement++;
|
|
203
|
+
data[this._autoIncrementField] = this._autoIncrement;
|
|
204
|
+
} else if (this._autoIncrementField && data[this._autoIncrementField] > this._autoIncrement) {
|
|
205
|
+
this._autoIncrement = data[this._autoIncrementField];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const rowIndex = this._rows.length;
|
|
209
|
+
this._rows.push({ ...data });
|
|
210
|
+
this._addToIndexes(rowIndex, data);
|
|
211
|
+
this._dirty = true;
|
|
212
|
+
this._db._markDirty(this._name);
|
|
213
|
+
|
|
214
|
+
for (const hook of this._hooks.afterInsert) {
|
|
215
|
+
hook(data);
|
|
216
|
+
}
|
|
217
|
+
this._db._emitChange('insert', this._name, data);
|
|
218
|
+
|
|
219
|
+
return data;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
insertMany(items) {
|
|
223
|
+
if (items.length === 0) return [];
|
|
224
|
+
for (const hook of this._hooks.beforeInsert) {
|
|
225
|
+
const mapped = new Array(items.length);
|
|
226
|
+
for (let i = 0; i < items.length; i++) mapped[i] = hook({ ...items[i] }) || items[i];
|
|
227
|
+
items = mapped;
|
|
228
|
+
}
|
|
229
|
+
const N = items.length;
|
|
230
|
+
const uniqueTracker = {};
|
|
231
|
+
for (const field of this._cachedUniqueFields) {
|
|
232
|
+
uniqueTracker[field] = new Set();
|
|
233
|
+
for (const row of this._rows) {
|
|
234
|
+
if (row[field] !== undefined && row[field] !== null) uniqueTracker[field].add(row[field]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const dateFields = this._cachedDateFields;
|
|
238
|
+
const hasDateFields = dateFields.length > 0;
|
|
239
|
+
const autoIncField = this._autoIncrementField;
|
|
240
|
+
const defaults = this._defaults;
|
|
241
|
+
const computedFields = this._computedFields;
|
|
242
|
+
const schema = this._schema;
|
|
243
|
+
const checkConstraints = this._checkConstraints;
|
|
244
|
+
const foreignKeys = this._foreignKeys;
|
|
245
|
+
// 批量预分配自增 ID 范围:先扫描显式提供的最大值,一次性推进计数器,
|
|
246
|
+
// 再在循环内用本地序号递增,避免批量内重复读取/写入同一计数器字段。
|
|
247
|
+
const baseAutoInc = this._autoIncrement;
|
|
248
|
+
if (autoIncField) {
|
|
249
|
+
let maxExplicit = 0;
|
|
250
|
+
for (const it of items) {
|
|
251
|
+
const v = it[autoIncField];
|
|
252
|
+
if (v !== undefined && v !== null) {
|
|
253
|
+
const n = Number(v);
|
|
254
|
+
if (!isNaN(n) && n > maxExplicit) maxExplicit = n;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (maxExplicit > baseAutoInc) {
|
|
258
|
+
this._autoIncrement = maxExplicit;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
let autoIncSeq = 0;
|
|
262
|
+
for (let i = 0; i < N; i++) {
|
|
263
|
+
let data = items[i];
|
|
264
|
+
for (const [f, dv] of Object.entries(defaults)) {
|
|
265
|
+
if (data[f] === undefined) data[f] = typeof dv === 'function' ? dv() : dv === 'CURRENT_TIMESTAMP' ? now() : dv;
|
|
266
|
+
}
|
|
267
|
+
for (const f of Object.keys(computedFields)) data[f] = computedFields[f](data);
|
|
268
|
+
if (hasDateFields) {
|
|
269
|
+
for (const f of dateFields) {
|
|
270
|
+
if (data[f] !== undefined && data[f] !== null) data[f] = validateDateType(data[f], this._dateFields[f], f);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
for (const field of this._cachedSchemaFields) {
|
|
274
|
+
const def = schema[field];
|
|
275
|
+
if (def.required && (data[field] === undefined || data[field] === null)
|
|
276
|
+
&& !(def.autoIncrement && data[field] === undefined)) throw createError('ER_BAD_NULL_ERROR', field);
|
|
277
|
+
if (def.unique && data[field] !== undefined) {
|
|
278
|
+
if (uniqueTracker[field].has(data[field])) throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
279
|
+
uniqueTracker[field].add(data[field]);
|
|
280
|
+
}
|
|
281
|
+
if (def.length && typeof data[field] === 'string' && data[field].length > def.length) throw createError('ER_DATA_TOO_LONG', field);
|
|
282
|
+
if (def.type === 'integer' || def.type === 'number') {
|
|
283
|
+
const v = def.type === 'integer' ? Number(data[field]) : data[field];
|
|
284
|
+
if (data[field] !== undefined && data[field] !== null && def.min !== undefined && v < def.min) throw createError('ER_OUT_OF_RANGE', field);
|
|
285
|
+
if (data[field] !== undefined && data[field] !== null && def.max !== undefined && v > def.max) throw createError('ER_OUT_OF_RANGE', field);
|
|
286
|
+
}
|
|
287
|
+
if (def.check && data[field] !== undefined && !def.check(data[field], data)) throw createError('ER_CHECK_CONSTRAINT', this._name + '.' + field);
|
|
288
|
+
}
|
|
289
|
+
for (const fk of foreignKeys) {
|
|
290
|
+
if (data[fk.field] !== undefined && data[fk.field] !== null) {
|
|
291
|
+
const refTable = this._resolveTable(fk.table);
|
|
292
|
+
if (refTable) {
|
|
293
|
+
const exists = refTable.findOne({ [fk.refField]: data[fk.field] });
|
|
294
|
+
if (!exists) throw createError('ER_NO_REFERENCED_ROW');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (autoIncField && data[autoIncField] === undefined) {
|
|
299
|
+
autoIncSeq++;
|
|
300
|
+
data[autoIncField] = baseAutoInc + autoIncSeq;
|
|
301
|
+
this._autoIncrement = data[autoIncField];
|
|
302
|
+
} else if (autoIncField && data[autoIncField] > this._autoIncrement) {
|
|
303
|
+
this._autoIncrement = data[autoIncField];
|
|
304
|
+
}
|
|
305
|
+
const rowIdx = this._rows.length;
|
|
306
|
+
this._rows.push(Object.assign({}, data));
|
|
307
|
+
if (this._pkIndex && this._primaryKey) {
|
|
308
|
+
const pkVal = data[this._primaryKey];
|
|
309
|
+
if (pkVal !== undefined && pkVal !== null) this._pkIndex.set(pkVal, rowIdx);
|
|
310
|
+
}
|
|
311
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
312
|
+
const val = data[field];
|
|
313
|
+
if (val !== undefined && val !== null) tree.insert(val, rowIdx);
|
|
314
|
+
}
|
|
315
|
+
items[i] = data;
|
|
316
|
+
}
|
|
317
|
+
this._dirty = true;
|
|
318
|
+
this._db._markDirty(this._name);
|
|
319
|
+
for (const hook of this._hooks.afterInsert) {
|
|
320
|
+
for (let i = 0; i < N; i++) hook(items[i]);
|
|
321
|
+
}
|
|
322
|
+
this._db._emitChange('insertMany', this._name, { count: N });
|
|
323
|
+
return items;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
upsert(data) {
|
|
327
|
+
if (this._primaryKey && data[this._primaryKey] !== undefined) {
|
|
328
|
+
const existing = this.findById(data[this._primaryKey]);
|
|
329
|
+
if (existing) {
|
|
330
|
+
this.updateById(data[this._primaryKey], data);
|
|
331
|
+
return this.findById(data[this._primaryKey]);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return this.insert(data);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============================================================
|
|
338
|
+
// 查询(支持 B-Tree 加速)
|
|
339
|
+
// ============================================================
|
|
340
|
+
|
|
341
|
+
find(query = {}) {
|
|
342
|
+
let rows = this._getVisibleRows();
|
|
343
|
+
if (Object.keys(query).length > 0) {
|
|
344
|
+
rows = this._applyFilterOptimized(rows, query);
|
|
345
|
+
}
|
|
346
|
+
return rows.map(r => ({ ...r }));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
findOne(query = {}) {
|
|
350
|
+
const rows = this.find(query);
|
|
351
|
+
return rows.length > 0 ? rows[0] : null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
findById(id) {
|
|
355
|
+
if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
|
|
356
|
+
// Hash 索引 O(1)
|
|
357
|
+
if (this._pkIndex) {
|
|
358
|
+
const rowIndex = this._pkIndex.get(id);
|
|
359
|
+
if (rowIndex !== undefined) {
|
|
360
|
+
const row = this._rows[rowIndex];
|
|
361
|
+
if (row && !row._deleted) return { ...row };
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
this._ensureBTrees();
|
|
366
|
+
if (this._btrees[this._primaryKey]) {
|
|
367
|
+
const indices = this._btrees[this._primaryKey].search(id);
|
|
368
|
+
if (indices.length > 0) {
|
|
369
|
+
const row = this._rows[indices[0]];
|
|
370
|
+
if (row && !row._deleted) return { ...row };
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return this.findOne({ [this._primaryKey]: id });
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
findAll() {
|
|
378
|
+
return this._getVisibleRows().map(r => ({ ...r }));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
count(query = {}) {
|
|
382
|
+
if (Object.keys(query).length === 0) return this._getVisibleRows().length;
|
|
383
|
+
return this._applyFilterOptimized(this._getVisibleRows(), query).length;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
where(conditions) {
|
|
387
|
+
return new Query(this, conditions);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ============================================================
|
|
391
|
+
// 更新
|
|
392
|
+
// ============================================================
|
|
393
|
+
|
|
394
|
+
update(query, updates) {
|
|
395
|
+
for (const hook of this._hooks.beforeUpdate) {
|
|
396
|
+
updates = hook(query, { ...updates }) || updates;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
updates = this._validateDataTypes(updates, true);
|
|
400
|
+
const matched = this._applyFilterOptimized(this._getVisibleRows(), query);
|
|
401
|
+
let count = 0;
|
|
402
|
+
|
|
403
|
+
for (const row of matched) {
|
|
404
|
+
const index = this._rows.indexOf(row);
|
|
405
|
+
if (index === -1) continue;
|
|
406
|
+
|
|
407
|
+
const oldData = { ...this._rows[index] };
|
|
408
|
+
const merged = { ...this._rows[index], ...updates };
|
|
409
|
+
this._validateConstraints(merged, index);
|
|
410
|
+
|
|
411
|
+
this._removeFromIndexes(index, oldData);
|
|
412
|
+
Object.assign(this._rows[index], updates);
|
|
413
|
+
this._addToIndexes(index, this._rows[index]);
|
|
414
|
+
count++;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (count > 0) {
|
|
418
|
+
this._dirty = true;
|
|
419
|
+
this._db._markDirty(this._name);
|
|
420
|
+
for (const hook of this._hooks.afterUpdate) {
|
|
421
|
+
hook(query, updates, count);
|
|
422
|
+
}
|
|
423
|
+
this._db._emitChange('update', this._name, { query, updates, count });
|
|
424
|
+
}
|
|
425
|
+
return count;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
updateById(id, updates) {
|
|
429
|
+
if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
|
|
430
|
+
for (const hook of this._hooks.beforeUpdate) {
|
|
431
|
+
updates = hook({ [this._primaryKey]: id }, { ...updates }) || updates;
|
|
432
|
+
}
|
|
433
|
+
updates = this._validateDataTypes(updates, true);
|
|
434
|
+
let rowIndex;
|
|
435
|
+
if (this._pkIndex) {
|
|
436
|
+
rowIndex = this._pkIndex.get(id);
|
|
437
|
+
} else {
|
|
438
|
+
this._ensureBTrees();
|
|
439
|
+
if (this._btrees[this._primaryKey]) {
|
|
440
|
+
const indices = this._btrees[this._primaryKey].search(id);
|
|
441
|
+
rowIndex = indices.length > 0 ? indices[0] : undefined;
|
|
442
|
+
} else {
|
|
443
|
+
rowIndex = this._rows.findIndex(r => r[this._primaryKey] === id);
|
|
444
|
+
if (rowIndex === -1) rowIndex = undefined;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (rowIndex === undefined) return 0;
|
|
448
|
+
const row = this._rows[rowIndex];
|
|
449
|
+
if (row && row._deleted) return 0;
|
|
450
|
+
const oldData = { ...row };
|
|
451
|
+
const merged = { ...row, ...updates };
|
|
452
|
+
this._validateConstraints(merged, rowIndex);
|
|
453
|
+
this._removeFromIndexes(rowIndex, oldData);
|
|
454
|
+
Object.assign(row, updates);
|
|
455
|
+
this._addToIndexes(rowIndex, row);
|
|
456
|
+
this._dirty = true;
|
|
457
|
+
this._db._markDirty(this._name);
|
|
458
|
+
for (const hook of this._hooks.afterUpdate) {
|
|
459
|
+
hook({ [this._primaryKey]: id }, updates, 1);
|
|
460
|
+
}
|
|
461
|
+
this._db._emitChange('update', this._name, { query: { [this._primaryKey]: id }, updates, count: 1 });
|
|
462
|
+
return 1;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ============================================================
|
|
466
|
+
// 删除 & 外键 CASCADE
|
|
467
|
+
// ============================================================
|
|
468
|
+
|
|
469
|
+
remove(query = {}) {
|
|
470
|
+
for (const hook of this._hooks.beforeRemove) {
|
|
471
|
+
if (hook(query) === false) return 0;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (Object.keys(query).length === 0) {
|
|
475
|
+
return this._truncate();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const toRemove = this._applyFilterOptimized(this._getVisibleRows(), query);
|
|
479
|
+
let count = 0;
|
|
480
|
+
const removeIndices = [];
|
|
481
|
+
|
|
482
|
+
for (const row of toRemove) {
|
|
483
|
+
const index = this._rows.indexOf(row);
|
|
484
|
+
if (index === -1) continue;
|
|
485
|
+
this._cascadeForeignKeys(row);
|
|
486
|
+
this._removeFromIndexes(index, row);
|
|
487
|
+
removeIndices.push(index);
|
|
488
|
+
count++;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (count > 0) {
|
|
492
|
+
removeIndices.sort((a, b) => b - a);
|
|
493
|
+
for (const idx of removeIndices) {
|
|
494
|
+
const lastIdx = this._rows.length - 1;
|
|
495
|
+
if (idx < lastIdx) {
|
|
496
|
+
const lastRow = this._rows[lastIdx];
|
|
497
|
+
this._rows[idx] = lastRow;
|
|
498
|
+
if (this._pkIndex && this._primaryKey) {
|
|
499
|
+
this._pkIndex.set(lastRow[this._primaryKey], idx);
|
|
500
|
+
}
|
|
501
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
502
|
+
const val = lastRow[field];
|
|
503
|
+
if (val !== undefined && val !== null) {
|
|
504
|
+
tree.remove(val, lastIdx);
|
|
505
|
+
tree.insert(val, idx);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
for (const field of Object.keys(this._indexes)) {
|
|
509
|
+
const val = lastRow[field];
|
|
510
|
+
if (val !== undefined && this._indexes[field].has(val)) {
|
|
511
|
+
const positions = this._indexes[field].get(val);
|
|
512
|
+
const pi = positions.indexOf(lastIdx);
|
|
513
|
+
if (pi !== -1) positions[pi] = idx;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
this._rows.pop();
|
|
518
|
+
}
|
|
519
|
+
this._dirty = true;
|
|
520
|
+
this._db._markDirty(this._name);
|
|
521
|
+
for (const hook of this._hooks.afterRemove) {
|
|
522
|
+
hook(query, count);
|
|
523
|
+
}
|
|
524
|
+
this._db._emitChange('remove', this._name, { query, count });
|
|
525
|
+
}
|
|
526
|
+
return count;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
removeById(id) {
|
|
530
|
+
if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
|
|
531
|
+
let rowIndex;
|
|
532
|
+
if (this._pkIndex) {
|
|
533
|
+
rowIndex = this._pkIndex.get(id);
|
|
534
|
+
} else {
|
|
535
|
+
this._ensureBTrees();
|
|
536
|
+
if (this._btrees[this._primaryKey]) {
|
|
537
|
+
const indices = this._btrees[this._primaryKey].search(id);
|
|
538
|
+
rowIndex = indices.length > 0 ? indices[0] : undefined;
|
|
539
|
+
} else {
|
|
540
|
+
rowIndex = this._rows.findIndex(r => r[this._primaryKey] === id);
|
|
541
|
+
if (rowIndex === -1) rowIndex = undefined;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (rowIndex === undefined) return 0;
|
|
545
|
+
const row = this._rows[rowIndex];
|
|
546
|
+
if (!row || row._deleted) return 0;
|
|
547
|
+
this._cascadeForeignKeys(row);
|
|
548
|
+
this._removeFromIndexes(rowIndex, row);
|
|
549
|
+
const lastIdx = this._rows.length - 1;
|
|
550
|
+
if (rowIndex < lastIdx) {
|
|
551
|
+
const lastRow = this._rows[lastIdx];
|
|
552
|
+
this._rows[rowIndex] = lastRow;
|
|
553
|
+
if (this._pkIndex && this._primaryKey) {
|
|
554
|
+
this._pkIndex.set(lastRow[this._primaryKey], rowIndex);
|
|
555
|
+
}
|
|
556
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
557
|
+
const val = lastRow[field];
|
|
558
|
+
if (val !== undefined && val !== null) {
|
|
559
|
+
tree.remove(val, lastIdx);
|
|
560
|
+
tree.insert(val, rowIndex);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
for (const field of Object.keys(this._indexes)) {
|
|
564
|
+
const val = lastRow[field];
|
|
565
|
+
if (val !== undefined && this._indexes[field].has(val)) {
|
|
566
|
+
const positions = this._indexes[field].get(val);
|
|
567
|
+
const pi = positions.indexOf(lastIdx);
|
|
568
|
+
if (pi !== -1) positions[pi] = rowIndex;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
this._rows.pop();
|
|
573
|
+
this._dirty = true;
|
|
574
|
+
this._db._markDirty(this._name);
|
|
575
|
+
for (const hook of this._hooks.afterRemove) {
|
|
576
|
+
hook({ [this._primaryKey]: id }, 1);
|
|
577
|
+
}
|
|
578
|
+
this._db._emitChange('remove', this._name, { query: { [this._primaryKey]: id }, count: 1 });
|
|
579
|
+
return 1;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
removeSoft(query = {}) {
|
|
583
|
+
this._softDelete = true;
|
|
584
|
+
return this.remove(query);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
_findRowIndex(id) {
|
|
588
|
+
if (this._pkIndex && this._primaryKey) {
|
|
589
|
+
const idx = this._pkIndex.get(id);
|
|
590
|
+
if (idx !== undefined) return idx;
|
|
591
|
+
}
|
|
592
|
+
for (let i = 0; i < this._rows.length; i++) {
|
|
593
|
+
if (this._rows[i][this._primaryKey] === id) return i;
|
|
594
|
+
}
|
|
595
|
+
return -1;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
restore(query = {}) {
|
|
599
|
+
if (!this._softDelete) throw createError('ER_NOT_SUPPORTED', 'Soft delete is not enabled');
|
|
600
|
+
const matched = this._applyFilter(this._rows, { ...query, _deleted: true });
|
|
601
|
+
for (const row of matched) {
|
|
602
|
+
const index = this._rows.indexOf(row);
|
|
603
|
+
if (index !== -1) {
|
|
604
|
+
delete this._rows[index]._deleted;
|
|
605
|
+
delete this._rows[index]._deletedAt;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return matched.length;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
truncate() {
|
|
612
|
+
return this._truncate();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
_truncate() {
|
|
616
|
+
const count = this._rows.length;
|
|
617
|
+
this._rows = [];
|
|
618
|
+
if (this._pkIndex) this._pkIndex.clear();
|
|
619
|
+
for (const key of Object.keys(this._btrees)) {
|
|
620
|
+
this._btrees[key].clear();
|
|
621
|
+
}
|
|
622
|
+
this._indexes = {};
|
|
623
|
+
this._autoIncrement = 0;
|
|
624
|
+
this._dirty = true;
|
|
625
|
+
this._db._markDirty(this._name);
|
|
626
|
+
return count;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ============================================================
|
|
630
|
+
// ALTER TABLE
|
|
631
|
+
// ============================================================
|
|
632
|
+
|
|
633
|
+
addColumn(field, definition) {
|
|
634
|
+
if (this._schema[field]) throw createError('ER_DUP_FIELDNAME', field);
|
|
635
|
+
this._schema[field] = definition;
|
|
636
|
+
if (definition.default !== undefined) {
|
|
637
|
+
this._defaults[field] = definition.default;
|
|
638
|
+
for (const row of this._rows) {
|
|
639
|
+
if (row[field] === undefined) {
|
|
640
|
+
row[field] = typeof definition.default === 'function' ? definition.default() : definition.default;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (definition.check) this._checkConstraints.push({ field, fn: definition.check });
|
|
645
|
+
if (definition.foreignKey) {
|
|
646
|
+
this._foreignKeys.push({
|
|
647
|
+
field,
|
|
648
|
+
table: definition.foreignKey.table,
|
|
649
|
+
refField: definition.foreignKey.field || 'id',
|
|
650
|
+
onDelete: definition.foreignKey.onDelete || 'restrict',
|
|
651
|
+
onUpdate: definition.foreignKey.onUpdate || 'restrict'
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
if (definition.computed) this._computedFields[field] = definition.computed;
|
|
655
|
+
if (['date', 'datetime', 'timestamp', 'time'].includes(definition.type)) {
|
|
656
|
+
this._dateFields[field] = definition.type;
|
|
657
|
+
}
|
|
658
|
+
if (definition.primaryKey || definition.unique) {
|
|
659
|
+
this._btrees[field] = new BTree(64, true);
|
|
660
|
+
this._rebuildBTree(field);
|
|
661
|
+
}
|
|
662
|
+
this._dirty = true;
|
|
663
|
+
this._db._markDirty(this._name);
|
|
664
|
+
return this;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
dropColumn(field) {
|
|
668
|
+
if (!this._schema[field]) throw createError('ER_CANT_DROP_FIELD', field);
|
|
669
|
+
delete this._schema[field];
|
|
670
|
+
delete this._defaults[field];
|
|
671
|
+
delete this._btrees[field];
|
|
672
|
+
delete this._indexes[field];
|
|
673
|
+
this._checkConstraints = this._checkConstraints.filter(c => c.field !== field);
|
|
674
|
+
this._foreignKeys = this._foreignKeys.filter(fk => fk.field !== field);
|
|
675
|
+
delete this._computedFields[field];
|
|
676
|
+
delete this._dateFields[field];
|
|
677
|
+
for (const row of this._rows) {
|
|
678
|
+
delete row[field];
|
|
679
|
+
}
|
|
680
|
+
if (this._primaryKey === field) this._primaryKey = null;
|
|
681
|
+
if (this._autoIncrementField === field) this._autoIncrementField = null;
|
|
682
|
+
this._dirty = true;
|
|
683
|
+
this._db._markDirty(this._name);
|
|
684
|
+
return this;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
renameColumn(oldName, newName) {
|
|
688
|
+
if (!this._schema[oldName]) throw createError('ER_BAD_FIELD_ERROR', oldName, this._name);
|
|
689
|
+
if (this._schema[newName]) throw createError('ER_DUP_FIELDNAME', newName);
|
|
690
|
+
this._schema[newName] = this._schema[oldName];
|
|
691
|
+
delete this._schema[oldName];
|
|
692
|
+
if (this._defaults[oldName] !== undefined) {
|
|
693
|
+
this._defaults[newName] = this._defaults[oldName];
|
|
694
|
+
delete this._defaults[oldName];
|
|
695
|
+
}
|
|
696
|
+
if (this._btrees[oldName]) {
|
|
697
|
+
this._btrees[newName] = this._btrees[oldName];
|
|
698
|
+
delete this._btrees[oldName];
|
|
699
|
+
}
|
|
700
|
+
for (const row of this._rows) {
|
|
701
|
+
row[newName] = row[oldName];
|
|
702
|
+
delete row[oldName];
|
|
703
|
+
}
|
|
704
|
+
if (this._primaryKey === oldName) this._primaryKey = newName;
|
|
705
|
+
if (this._autoIncrementField === oldName) this._autoIncrementField = newName;
|
|
706
|
+
this._dirty = true;
|
|
707
|
+
this._db._markDirty(this._name);
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
modifyColumn(field, definition) {
|
|
712
|
+
if (!this._schema[field]) throw createError('ER_BAD_FIELD_ERROR', field, this._name);
|
|
713
|
+
this._schema[field] = { ...this._schema[field], ...definition };
|
|
714
|
+
if (definition.default !== undefined) this._defaults[field] = definition.default;
|
|
715
|
+
this._checkConstraints = this._checkConstraints.filter(c => c.field !== field);
|
|
716
|
+
if (definition.check) this._checkConstraints.push({ field, fn: definition.check });
|
|
717
|
+
if (definition.primaryKey || definition.unique) {
|
|
718
|
+
if (!this._btrees[field]) this._btrees[field] = new BTree(64, true);
|
|
719
|
+
this._rebuildBTree(field);
|
|
720
|
+
}
|
|
721
|
+
this._dirty = true;
|
|
722
|
+
this._db._markDirty(this._name);
|
|
723
|
+
return this;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
describe() {
|
|
727
|
+
return {
|
|
728
|
+
name: this._name,
|
|
729
|
+
columns: { ...this._schema },
|
|
730
|
+
primaryKey: this._primaryKey,
|
|
731
|
+
rowCount: this._getVisibleRows().length,
|
|
732
|
+
btreeIndexes: Object.keys(this._btrees),
|
|
733
|
+
hashIndexes: Object.keys(this._indexes),
|
|
734
|
+
softDelete: this._softDelete,
|
|
735
|
+
foreignKeys: this._foreignKeys.map(fk => ({ field: fk.field, table: fk.table }))
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
selectInto(newTableName, newSchema, query = {}) {
|
|
740
|
+
const rows = this._applyFilterOptimized(this._getVisibleRows(), query);
|
|
741
|
+
this._db.createTable(newTableName, newSchema);
|
|
742
|
+
const newTable = this._db._tables[newTableName];
|
|
743
|
+
for (const row of rows) {
|
|
744
|
+
newTable.insert(row);
|
|
745
|
+
}
|
|
746
|
+
return newTable;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
textSearch(field, terms) {
|
|
750
|
+
const searchTerms = Array.isArray(terms) ? terms : terms.split(/\s+/);
|
|
751
|
+
const rows = this._getVisibleRows();
|
|
752
|
+
const scored = [];
|
|
753
|
+
for (const row of rows) {
|
|
754
|
+
const value = String(row[field] || '').toLowerCase();
|
|
755
|
+
let score = 0;
|
|
756
|
+
for (const term of searchTerms) {
|
|
757
|
+
const t = term.toLowerCase();
|
|
758
|
+
const idx = value.indexOf(t);
|
|
759
|
+
if (idx !== -1) {
|
|
760
|
+
score += 10 + (idx === 0 ? 5 : 0) + (value === t ? 20 : 0)
|
|
761
|
+
+ (value.length - idx) / value.length * 5;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (score > 0) scored.push({ ...row, _score: score });
|
|
765
|
+
}
|
|
766
|
+
return scored.sort((a, b) => b._score - a._score);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// ============================================================
|
|
770
|
+
// 索引
|
|
771
|
+
// ============================================================
|
|
772
|
+
|
|
773
|
+
createIndex(field) {
|
|
774
|
+
this._indexes[field] = new Map();
|
|
775
|
+
for (let i = 0; i < this._rows.length; i++) {
|
|
776
|
+
const value = this._rows[i][field];
|
|
777
|
+
if (value !== undefined) {
|
|
778
|
+
if (!this._indexes[field].has(value)) {
|
|
779
|
+
this._indexes[field].set(value, []);
|
|
780
|
+
}
|
|
781
|
+
this._indexes[field].get(value).push(i);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return this._indexes[field];
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
dropIndex(field) {
|
|
788
|
+
delete this._indexes[field];
|
|
789
|
+
delete this._btrees[field];
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* 创建 B-Tree 索引(O(log n) 查询)
|
|
794
|
+
*/
|
|
795
|
+
createBTreeIndex(field, unique = false) {
|
|
796
|
+
this._btrees[field] = new BTree(64, unique);
|
|
797
|
+
this._rebuildBTree(field);
|
|
798
|
+
return this._btrees[field];
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
_rebuildBTree(field) {
|
|
802
|
+
if (this._btrees[field]) {
|
|
803
|
+
this._btrees[field].clear();
|
|
804
|
+
for (let i = 0; i < this._rows.length; i++) {
|
|
805
|
+
const val = this._rows[i][field];
|
|
806
|
+
if (val !== undefined && val !== null) {
|
|
807
|
+
this._btrees[field].insert(val, i);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
_ensureBTrees() {
|
|
814
|
+
if (!this._btreesDirty) return;
|
|
815
|
+
this._btreesDirty = false;
|
|
816
|
+
for (const field of Object.keys(this._btrees)) {
|
|
817
|
+
this._btrees[field].clear();
|
|
818
|
+
}
|
|
819
|
+
for (let i = 0; i < this._rows.length; i++) {
|
|
820
|
+
const row = this._rows[i];
|
|
821
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
822
|
+
const v = row[field];
|
|
823
|
+
if (v !== undefined && v !== null) tree.insert(v, i);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
_rebuildAllBTrees() {
|
|
829
|
+
for (const field of Object.keys(this._btrees)) {
|
|
830
|
+
this._rebuildBTree(field);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// ============================================================
|
|
835
|
+
// 事件钩子
|
|
836
|
+
// ============================================================
|
|
837
|
+
|
|
838
|
+
on(event, callback) {
|
|
839
|
+
if (this._hooks[event]) {
|
|
840
|
+
this._hooks[event].push(callback);
|
|
841
|
+
}
|
|
842
|
+
return this;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
off(event, callback) {
|
|
846
|
+
if (this._hooks[event]) {
|
|
847
|
+
this._hooks[event] = this._hooks[event].filter(cb => cb !== callback);
|
|
848
|
+
}
|
|
849
|
+
return this;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// ============================================================
|
|
853
|
+
// 内部: 过滤器(B-Tree 加速)
|
|
854
|
+
// ============================================================
|
|
855
|
+
|
|
856
|
+
_applyFilterOptimized(rows, query) {
|
|
857
|
+
this._ensureBTrees();
|
|
858
|
+
// 尝试用 B-Tree 加速
|
|
859
|
+
for (const [field, condition] of Object.entries(query)) {
|
|
860
|
+
if (field === '$or' || field === '$and' || field === '$not') continue;
|
|
861
|
+
// 等值查询用 B-Tree
|
|
862
|
+
if (typeof condition !== 'object' || condition === null || Array.isArray(condition) || condition instanceof RegExp) {
|
|
863
|
+
if (this._btrees[field]) {
|
|
864
|
+
const indices = this._btrees[field].search(condition);
|
|
865
|
+
if (indices.length > 0) {
|
|
866
|
+
const subset = indices.map(i => this._rows[i]).filter(r => r);
|
|
867
|
+
return this._applyFilter(subset, query);
|
|
868
|
+
}
|
|
869
|
+
return [];
|
|
870
|
+
}
|
|
871
|
+
if (this._indexes[field] && this._indexes[field].has(condition)) {
|
|
872
|
+
const indices = this._indexes[field].get(condition);
|
|
873
|
+
const subset = indices.map(i => this._rows[i]).filter(r => r);
|
|
874
|
+
return this._applyFilter(subset, query);
|
|
875
|
+
}
|
|
876
|
+
if (this._indexes[field]) return [];
|
|
877
|
+
}
|
|
878
|
+
// 范围查询用 B-Tree
|
|
879
|
+
if (condition && typeof condition === 'object') {
|
|
880
|
+
if (condition.$gt !== undefined && condition.$lt !== undefined && this._btrees[field]) {
|
|
881
|
+
const indices = this._btrees[field].range(condition.$gt, condition.$lt);
|
|
882
|
+
const subset = indices.map(i => this._rows[i]).filter(r => r);
|
|
883
|
+
return this._applyFilter(subset, query);
|
|
884
|
+
}
|
|
885
|
+
if (condition.$gte !== undefined && condition.$lte !== undefined && this._btrees[field]) {
|
|
886
|
+
const indices = this._btrees[field].range(condition.$gte, condition.$lte);
|
|
887
|
+
const subset = indices.map(i => this._rows[i]).filter(r => r);
|
|
888
|
+
return this._applyFilter(subset, query);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return this._applyFilter(rows, query);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
_applyFilter(rows, query) {
|
|
896
|
+
const result = [];
|
|
897
|
+
for (let i = 0; i < rows.length; i++) {
|
|
898
|
+
if (this._matchRow(rows[i], query)) {
|
|
899
|
+
result.push(rows[i]);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return result;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
_matchRow(row, query) {
|
|
906
|
+
for (const [field, condition] of Object.entries(query)) {
|
|
907
|
+
if (field === '$or') {
|
|
908
|
+
if (!Array.isArray(condition) || !condition.some(sub => this._matchRow(row, sub))) return false;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (field === '$and') {
|
|
912
|
+
if (!Array.isArray(condition) || !condition.every(sub => this._matchRow(row, sub))) return false;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (field === '$not') {
|
|
916
|
+
if (this._matchRow(row, condition)) return false;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const value = this._resolvePath(row, field);
|
|
921
|
+
|
|
922
|
+
if (condition !== null && typeof condition === 'object' && !Array.isArray(condition) && !(condition instanceof RegExp)) {
|
|
923
|
+
const keys = Object.keys(condition);
|
|
924
|
+
const isOperator = keys.every(k => k.startsWith('$'));
|
|
925
|
+
if (isOperator && keys.length > 0) {
|
|
926
|
+
for (const [op, target] of Object.entries(condition)) {
|
|
927
|
+
if (!this._matchOperator(value, op, target)) return false;
|
|
928
|
+
}
|
|
929
|
+
} else {
|
|
930
|
+
if (value !== null && typeof value === 'object') {
|
|
931
|
+
if (!this._matchRow(value, condition)) return false;
|
|
932
|
+
} else {
|
|
933
|
+
return false;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
} else {
|
|
937
|
+
if (value !== condition) return false;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return true;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
_matchOperator(value, op, target) {
|
|
944
|
+
switch (op) {
|
|
945
|
+
case '$eq': return value === target;
|
|
946
|
+
case '$ne': return value !== target;
|
|
947
|
+
case '$gt': return value > target;
|
|
948
|
+
case '$gte': return value >= target;
|
|
949
|
+
case '$lt': return value < target;
|
|
950
|
+
case '$lte': return value <= target;
|
|
951
|
+
case '$in': return Array.isArray(target) && target.includes(value);
|
|
952
|
+
case '$nin': return Array.isArray(target) && !target.includes(value);
|
|
953
|
+
case '$like':
|
|
954
|
+
if (typeof value !== 'string' || typeof target !== 'string') return false;
|
|
955
|
+
{
|
|
956
|
+
const escaped = target
|
|
957
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
958
|
+
.replace(/%/g, '.*')
|
|
959
|
+
.replace(/_/g, '.');
|
|
960
|
+
return new RegExp('^' + escaped + '$', 'i').test(value);
|
|
961
|
+
}
|
|
962
|
+
case '$regex':
|
|
963
|
+
if (target instanceof RegExp) return target.test(String(value));
|
|
964
|
+
if (!isSafeRegex(target)) throw createError('ER_BAD_REGEX', String(target));
|
|
965
|
+
return new RegExp(target).test(String(value));
|
|
966
|
+
case '$exists':
|
|
967
|
+
return target ? value !== undefined : value === undefined;
|
|
968
|
+
case '$between':
|
|
969
|
+
return Array.isArray(target) && target.length === 2 && value >= target[0] && value <= target[1];
|
|
970
|
+
case '$and':
|
|
971
|
+
return Array.isArray(target) && target.every(sub => this._matchRow({ _val: value }, { _val: sub }));
|
|
972
|
+
case '$or':
|
|
973
|
+
return Array.isArray(target) && target.some(sub => this._matchRow({ _val: value }, { _val: sub }));
|
|
974
|
+
case '$not':
|
|
975
|
+
return !this._matchRow({ _val: value }, { _val: target });
|
|
976
|
+
case '$contains':
|
|
977
|
+
return Array.isArray(value) && value.includes(target);
|
|
978
|
+
case '$type':
|
|
979
|
+
return typeof value === target;
|
|
980
|
+
case '$size':
|
|
981
|
+
return Array.isArray(value) && value.length === target;
|
|
982
|
+
case '$elemMatch':
|
|
983
|
+
return Array.isArray(value) && value.some(el =>
|
|
984
|
+
typeof el === 'object' ? this._matchRow(el, target) : el === target);
|
|
985
|
+
case '$isNull':
|
|
986
|
+
return target ? value === null || value === undefined : value !== null && value !== undefined;
|
|
987
|
+
case '$startsWith':
|
|
988
|
+
return typeof value === 'string' && value.startsWith(target);
|
|
989
|
+
case '$endsWith':
|
|
990
|
+
return typeof value === 'string' && value.endsWith(target);
|
|
991
|
+
case '$search':
|
|
992
|
+
if (typeof value !== 'string') return false;
|
|
993
|
+
const terms = Array.isArray(target) ? target : [target];
|
|
994
|
+
return terms.every(t => value.toLowerCase().includes(t.toLowerCase()));
|
|
995
|
+
case '$textSearch':
|
|
996
|
+
if (typeof value !== 'string') return false;
|
|
997
|
+
const sTerms = Array.isArray(target) ? target : target.split(/\s+/);
|
|
998
|
+
return sTerms.some(t => value.toLowerCase().includes(t.toLowerCase()));
|
|
999
|
+
case '$inSub':
|
|
1000
|
+
if (!target || !target.table) return false;
|
|
1001
|
+
const subRows = target.table._applyFilter(target.table._rows, target.query || {});
|
|
1002
|
+
return subRows.some(r => r[target.field] === value);
|
|
1003
|
+
case '$notInSub':
|
|
1004
|
+
if (!target || !target.table) return false;
|
|
1005
|
+
const subRows2 = target.table._applyFilter(target.table._rows, target.query || {});
|
|
1006
|
+
return !subRows2.some(r => r[target.field] === value);
|
|
1007
|
+
case '$expr':
|
|
1008
|
+
if (typeof target === 'function') return target(value);
|
|
1009
|
+
return false;
|
|
1010
|
+
default:
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
_resolvePath(row, field) {
|
|
1016
|
+
if (field.includes('.') && !field.startsWith('$')) {
|
|
1017
|
+
const parts = field.split('.');
|
|
1018
|
+
let value = row;
|
|
1019
|
+
for (const part of parts) {
|
|
1020
|
+
if (value === null || value === undefined) return undefined;
|
|
1021
|
+
value = value[part];
|
|
1022
|
+
}
|
|
1023
|
+
return value;
|
|
1024
|
+
}
|
|
1025
|
+
return row[field];
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// ============================================================
|
|
1029
|
+
// 内部: 约束校验
|
|
1030
|
+
// ============================================================
|
|
1031
|
+
|
|
1032
|
+
_validateConstraints(data, excludeIndex, uniqueTracker) {
|
|
1033
|
+
for (const [field, def] of Object.entries(this._schema)) {
|
|
1034
|
+
if (field === '_softDelete') continue;
|
|
1035
|
+
|
|
1036
|
+
// NOT NULL
|
|
1037
|
+
if (def.required && (data[field] === undefined || data[field] === null)
|
|
1038
|
+
&& !(def.autoIncrement && data[field] === undefined)) {
|
|
1039
|
+
throw createError('ER_BAD_NULL_ERROR', field);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// PRIMARY KEY 唯一性
|
|
1043
|
+
if ((def.primaryKey || def.primary === true) && data[field] !== undefined && data[field] !== null) {
|
|
1044
|
+
let dup = false;
|
|
1045
|
+
if (uniqueTracker && uniqueTracker[field]) {
|
|
1046
|
+
dup = uniqueTracker[field].has(data[field]);
|
|
1047
|
+
} else if (this._pkIndex) {
|
|
1048
|
+
const idx = this._pkIndex.get(data[field]);
|
|
1049
|
+
dup = idx !== undefined && idx !== null && idx !== excludeIndex;
|
|
1050
|
+
} else {
|
|
1051
|
+
const idx = this._rows.findIndex(r => r[field] === data[field]);
|
|
1052
|
+
dup = idx !== -1 && idx !== excludeIndex;
|
|
1053
|
+
}
|
|
1054
|
+
if (dup) {
|
|
1055
|
+
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// UNIQUE(B-Tree 优化 O(log n))
|
|
1060
|
+
if (def.unique && data[field] !== undefined) {
|
|
1061
|
+
if (uniqueTracker && uniqueTracker[field]) {
|
|
1062
|
+
if (uniqueTracker[field].has(data[field])) {
|
|
1063
|
+
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
1064
|
+
}
|
|
1065
|
+
uniqueTracker[field].add(data[field]);
|
|
1066
|
+
} else if (this._btrees[field]) {
|
|
1067
|
+
const found = this._btrees[field].search(data[field]);
|
|
1068
|
+
if (found.length > 0) {
|
|
1069
|
+
for (const idx of found) {
|
|
1070
|
+
if (idx !== excludeIndex && this._rows[idx] && this._rows[idx][field] === data[field]) {
|
|
1071
|
+
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
for (let i = 0; i < this._rows.length; i++) {
|
|
1077
|
+
if (i === excludeIndex) continue;
|
|
1078
|
+
if (this._rows[i][field] === data[field]) {
|
|
1079
|
+
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// CHECK
|
|
1086
|
+
for (const { field: cf, fn } of this._checkConstraints) {
|
|
1087
|
+
if (cf === field && data[field] !== undefined && !fn(data[field], data)) {
|
|
1088
|
+
throw createError('ER_CHECK_CONSTRAINT', this._name + '.' + field);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// 长度限制
|
|
1093
|
+
if (def.length && typeof data[field] === 'string' && data[field].length > def.length) {
|
|
1094
|
+
throw createError('ER_DATA_TOO_LONG', field);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// 数值范围
|
|
1098
|
+
if (def.type === 'integer' && data[field] !== undefined && data[field] !== null) {
|
|
1099
|
+
const v = Number(data[field]);
|
|
1100
|
+
if (def.min !== undefined && v < def.min) throw createError('ER_OUT_OF_RANGE', field);
|
|
1101
|
+
if (def.max !== undefined && v > def.max) throw createError('ER_OUT_OF_RANGE', field);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// FOREIGN KEY 校验
|
|
1106
|
+
for (const fk of this._foreignKeys) {
|
|
1107
|
+
if (data[fk.field] !== undefined && data[fk.field] !== null) {
|
|
1108
|
+
const refTable = this._resolveTable(fk.table);
|
|
1109
|
+
if (refTable) {
|
|
1110
|
+
const exists = refTable.findOne({ [fk.refField]: data[fk.field] });
|
|
1111
|
+
if (!exists) {
|
|
1112
|
+
throw createError('ER_NO_REFERENCED_ROW');
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
_resolveTable(tableRef) {
|
|
1120
|
+
if (typeof tableRef === 'string') {
|
|
1121
|
+
return this._db._tables[tableRef] || this._db._attached[tableRef]?._tables[tableRef];
|
|
1122
|
+
}
|
|
1123
|
+
return tableRef;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* 级联外键操作:当删除当前表的行时,查找所有引用此表的子表
|
|
1128
|
+
* 并执行 CASCADE 或 SET NULL
|
|
1129
|
+
*/
|
|
1130
|
+
_cascadeForeignKeys(row) {
|
|
1131
|
+
for (const [, table] of Object.entries(this._db._tables)) {
|
|
1132
|
+
if (table._name === this._name) continue;
|
|
1133
|
+
for (const fk of table._foreignKeys) {
|
|
1134
|
+
const refTable = table._resolveTable(fk.table);
|
|
1135
|
+
if (!refTable || refTable._name !== this._name) continue;
|
|
1136
|
+
|
|
1137
|
+
// 检查子表行是否引用当前行
|
|
1138
|
+
if (fk.onDelete === 'cascade') {
|
|
1139
|
+
table.remove({ [fk.field]: row[fk.refField] });
|
|
1140
|
+
} else if (fk.onDelete === 'set null') {
|
|
1141
|
+
table.update({ [fk.field]: row[fk.refField] }, { [fk.field]: null });
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
_validateDataTypes(data, partial = false) {
|
|
1148
|
+
const result = { ...data };
|
|
1149
|
+
for (const [field, type] of Object.entries(this._dateFields)) {
|
|
1150
|
+
if (partial && data[field] === undefined) continue;
|
|
1151
|
+
if (data[field] !== undefined && data[field] !== null) {
|
|
1152
|
+
result[field] = validateDateType(data[field], type, field);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
for (const [field, def] of Object.entries(this._schema)) {
|
|
1156
|
+
if (field === '_softDelete') continue;
|
|
1157
|
+
if (partial && data[field] === undefined) continue;
|
|
1158
|
+
const v = data[field];
|
|
1159
|
+
if (v === undefined || v === null) continue;
|
|
1160
|
+
const t = String(def.type || '').toLowerCase();
|
|
1161
|
+
if (t === 'integer' || t === 'int' || t === 'bigint') {
|
|
1162
|
+
if (typeof v === 'boolean') throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1163
|
+
if (typeof v === 'number') {
|
|
1164
|
+
if (Number.isInteger(v)) result[field] = v;
|
|
1165
|
+
else throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1166
|
+
} else if (typeof v === 'string' && v.trim() !== '' && Number.isInteger(Number(v))) {
|
|
1167
|
+
result[field] = Number(v);
|
|
1168
|
+
} else {
|
|
1169
|
+
throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1170
|
+
}
|
|
1171
|
+
} else if (t === 'float' || t === 'double' || t === 'number' || t === 'real') {
|
|
1172
|
+
if (typeof v === 'boolean') throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1173
|
+
if (typeof v === 'number') result[field] = v;
|
|
1174
|
+
else if (typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v))) result[field] = Number(v);
|
|
1175
|
+
else throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1176
|
+
} else if (t === 'boolean' || t === 'bool') {
|
|
1177
|
+
if (typeof v === 'boolean') result[field] = v;
|
|
1178
|
+
else if (v === 0 || v === 1) result[field] = !!v;
|
|
1179
|
+
else if (typeof v === 'string' && ['true', 'false', '1', '0'].includes(v.toLowerCase())) {
|
|
1180
|
+
result[field] = v.toLowerCase() === 'true' || v === '1';
|
|
1181
|
+
} else {
|
|
1182
|
+
throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1183
|
+
}
|
|
1184
|
+
} else if (t === 'string' || t === 'text' || t === 'varchar' || t === 'char') {
|
|
1185
|
+
if (typeof v === 'string') result[field] = v;
|
|
1186
|
+
else if (typeof v === 'number' || typeof v === 'boolean' || v instanceof Date) result[field] = String(v);
|
|
1187
|
+
else if (Buffer.isBuffer(v)) result[field] = v.toString('utf8');
|
|
1188
|
+
else throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return result;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// ============================================================
|
|
1195
|
+
// 内部: 辅助
|
|
1196
|
+
// ============================================================
|
|
1197
|
+
|
|
1198
|
+
_applyDefaults(data) {
|
|
1199
|
+
const result = { ...data };
|
|
1200
|
+
for (const [field, defaultValue] of Object.entries(this._defaults)) {
|
|
1201
|
+
if (result[field] === undefined) {
|
|
1202
|
+
result[field] = typeof defaultValue === 'function'
|
|
1203
|
+
? defaultValue()
|
|
1204
|
+
: (defaultValue === 'CURRENT_TIMESTAMP' ? now() : defaultValue);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return result;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
_applyComputed(data) {
|
|
1211
|
+
for (const [field, fn] of Object.entries(this._computedFields)) {
|
|
1212
|
+
data[field] = fn(data);
|
|
1213
|
+
}
|
|
1214
|
+
return data;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
_getVisibleRows() {
|
|
1218
|
+
if (this._softDelete) return this._rows.filter(r => !r._deleted);
|
|
1219
|
+
return this._rows;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
_addToIndexes(rowIndex, data) {
|
|
1223
|
+
if (this._pkIndex && this._primaryKey) {
|
|
1224
|
+
const pkVal = data[this._primaryKey];
|
|
1225
|
+
if (pkVal !== undefined && pkVal !== null) this._pkIndex.set(pkVal, rowIndex);
|
|
1226
|
+
}
|
|
1227
|
+
for (const field of Object.keys(this._indexes)) {
|
|
1228
|
+
const value = data[field];
|
|
1229
|
+
if (value !== undefined) {
|
|
1230
|
+
if (!this._indexes[field].has(value)) {
|
|
1231
|
+
this._indexes[field].set(value, []);
|
|
1232
|
+
}
|
|
1233
|
+
this._indexes[field].get(value).push(rowIndex);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
1237
|
+
const value = data[field];
|
|
1238
|
+
if (value !== undefined && value !== null) {
|
|
1239
|
+
tree.insert(value, rowIndex);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
_removeFromIndexes(rowIndex, data) {
|
|
1245
|
+
if (this._pkIndex && this._primaryKey) {
|
|
1246
|
+
this._pkIndex.delete(data[this._primaryKey]);
|
|
1247
|
+
}
|
|
1248
|
+
for (const field of Object.keys(this._indexes)) {
|
|
1249
|
+
const value = data[field];
|
|
1250
|
+
if (value !== undefined && this._indexes[field].has(value)) {
|
|
1251
|
+
const positions = this._indexes[field].get(value).filter(p => p !== rowIndex);
|
|
1252
|
+
if (positions.length === 0) {
|
|
1253
|
+
this._indexes[field].delete(value);
|
|
1254
|
+
} else {
|
|
1255
|
+
this._indexes[field].set(value, positions);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
1260
|
+
const value = data[field];
|
|
1261
|
+
if (value !== undefined && value !== null) {
|
|
1262
|
+
tree.remove(value, rowIndex);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
_rebuildPKIndex() {
|
|
1268
|
+
if (!this._pkIndex || !this._primaryKey) return;
|
|
1269
|
+
this._pkIndex.clear();
|
|
1270
|
+
for (let i = 0; i < this._rows.length; i++) {
|
|
1271
|
+
const pkVal = this._rows[i][this._primaryKey];
|
|
1272
|
+
if (pkVal !== undefined && pkVal !== null) this._pkIndex.set(pkVal, i);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
_adjustBTreeAfterRemove(removedIndex) {
|
|
1277
|
+
this._rebuildPKIndex();
|
|
1278
|
+
for (const field of Object.keys(this._btrees)) {
|
|
1279
|
+
this._rebuildBTree(field);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
_loadRows(rows) {
|
|
1284
|
+
this._rows = rows;
|
|
1285
|
+
if (this._autoIncrementField) {
|
|
1286
|
+
for (const row of rows) {
|
|
1287
|
+
const val = row[this._autoIncrementField];
|
|
1288
|
+
if (typeof val === 'number' && val > this._autoIncrement) {
|
|
1289
|
+
this._autoIncrement = val;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
this._rebuildPKIndex();
|
|
1294
|
+
this._rebuildAllBTrees();
|
|
1295
|
+
for (const field of Object.keys(this._indexes)) {
|
|
1296
|
+
this.createIndex(field);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
toJSON() {
|
|
1301
|
+
return this._rows;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
1305
|
module.exports = Table;
|