jsql-neo 2.1.2 → 2.1.3
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/lib/btree.js +46 -0
- package/lib/table.js +82 -22
- package/package.json +1 -1
package/lib/btree.js
CHANGED
|
@@ -322,6 +322,52 @@ class BTree {
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 批量加载:从已排序的 [key, rowIndex] 数组底部向上构建
|
|
328
|
+
* O(n) 时间,适合大数据量批量构建
|
|
329
|
+
*/
|
|
330
|
+
bulkLoad(sortedPairs) {
|
|
331
|
+
if (sortedPairs.length === 0) { this.clear(); return; }
|
|
332
|
+
const order = this._order;
|
|
333
|
+
// 第一层:构建叶子节点
|
|
334
|
+
const leaves = [];
|
|
335
|
+
let i = 0;
|
|
336
|
+
while (i < sortedPairs.length) {
|
|
337
|
+
const node = new BTreeNode(true);
|
|
338
|
+
const end = Math.min(i + order - 1, sortedPairs.length);
|
|
339
|
+
for (; i < end; i++) {
|
|
340
|
+
const [key, val] = sortedPairs[i];
|
|
341
|
+
node.keys.push(key);
|
|
342
|
+
node.values.push(Array.isArray(val) ? val : [val]);
|
|
343
|
+
}
|
|
344
|
+
leaves.push(node);
|
|
345
|
+
}
|
|
346
|
+
for (let i = 0; i < leaves.length - 1; i++) leaves[i].next = leaves[i + 1];
|
|
347
|
+
if (leaves.length === 1) { this._root = leaves[0]; this._size = sortedPairs.length; return; }
|
|
348
|
+
// 逐层向上构建内部节点
|
|
349
|
+
let currentLevel = leaves;
|
|
350
|
+
while (currentLevel.length > 1) {
|
|
351
|
+
const parents = [];
|
|
352
|
+
let j = 0;
|
|
353
|
+
while (j < currentLevel.length) {
|
|
354
|
+
const node = new BTreeNode(false);
|
|
355
|
+
const end = Math.min(j + order, currentLevel.length);
|
|
356
|
+
for (; j < end; j++) {
|
|
357
|
+
if (node.children.length > 0) {
|
|
358
|
+
const child = currentLevel[j];
|
|
359
|
+
node.keys.push(child.keys[0]);
|
|
360
|
+
node.values.push(child.values[0]);
|
|
361
|
+
}
|
|
362
|
+
node.children.push(currentLevel[j]);
|
|
363
|
+
}
|
|
364
|
+
parents.push(node);
|
|
365
|
+
}
|
|
366
|
+
currentLevel = parents;
|
|
367
|
+
}
|
|
368
|
+
this._root = currentLevel[0];
|
|
369
|
+
this._size = sortedPairs.length;
|
|
370
|
+
}
|
|
325
371
|
}
|
|
326
372
|
|
|
327
373
|
module.exports = BTree;
|
package/lib/table.js
CHANGED
|
@@ -26,6 +26,7 @@ class Table {
|
|
|
26
26
|
};
|
|
27
27
|
this._softDelete = false;
|
|
28
28
|
this._computedFields = {};
|
|
29
|
+
this._btreesDirty = false;
|
|
29
30
|
|
|
30
31
|
this._primaryKey = null;
|
|
31
32
|
this._autoIncrementField = null;
|
|
@@ -68,7 +69,10 @@ class Table {
|
|
|
68
69
|
this._softDelete = true;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
this._cachedSchemaFields = Object.keys(schema).filter(f => f !== '_softDelete');
|
|
73
|
+
this._cachedDateFields = Object.keys(this._dateFields);
|
|
74
|
+
this._cachedUniqueFields = this._cachedSchemaFields.filter(f => schema[f].unique);
|
|
75
|
+
this._cachedRequiredFields = this._cachedSchemaFields.filter(f => schema[f].required);
|
|
72
76
|
this._autoCreateBTrees();
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -119,35 +123,73 @@ class Table {
|
|
|
119
123
|
insertMany(items) {
|
|
120
124
|
if (items.length === 0) return [];
|
|
121
125
|
for (const hook of this._hooks.beforeInsert) {
|
|
122
|
-
|
|
126
|
+
const mapped = new Array(items.length);
|
|
127
|
+
for (let i = 0; i < items.length; i++) mapped[i] = hook({ ...items[i] }) || items[i];
|
|
128
|
+
items = mapped;
|
|
123
129
|
}
|
|
130
|
+
const N = items.length;
|
|
124
131
|
const uniqueTracker = {};
|
|
125
|
-
for (const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
for (const field of this._cachedUniqueFields) uniqueTracker[field] = new Set();
|
|
133
|
+
const dateFields = this._cachedDateFields;
|
|
134
|
+
const hasDateFields = dateFields.length > 0;
|
|
135
|
+
const autoIncField = this._autoIncrementField;
|
|
136
|
+
const defaults = this._defaults;
|
|
137
|
+
const computedFields = this._computedFields;
|
|
138
|
+
const schema = this._schema;
|
|
139
|
+
const checkConstraints = this._checkConstraints;
|
|
140
|
+
const foreignKeys = this._foreignKeys;
|
|
141
|
+
for (let i = 0; i < N; i++) {
|
|
142
|
+
let data = items[i];
|
|
143
|
+
for (const [f, dv] of Object.entries(defaults)) {
|
|
144
|
+
if (data[f] === undefined) data[f] = typeof dv === 'function' ? dv() : dv === 'CURRENT_TIMESTAMP' ? now() : dv;
|
|
145
|
+
}
|
|
146
|
+
for (const f of Object.keys(computedFields)) data[f] = computedFields[f](data);
|
|
147
|
+
if (hasDateFields) {
|
|
148
|
+
for (const f of dateFields) {
|
|
149
|
+
if (data[f] !== undefined && data[f] !== null) data[f] = validateDateType(data[f], this._dateFields[f], f);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const field of this._cachedSchemaFields) {
|
|
153
|
+
const def = schema[field];
|
|
154
|
+
if (def.required && (data[field] === undefined || data[field] === null)) throw createError('ER_BAD_NULL_ERROR', field);
|
|
155
|
+
if (def.unique && data[field] !== undefined) {
|
|
156
|
+
if (uniqueTracker[field].has(data[field])) throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
157
|
+
uniqueTracker[field].add(data[field]);
|
|
158
|
+
}
|
|
159
|
+
if (def.length && typeof data[field] === 'string' && data[field].length > def.length) throw createError('ER_DATA_TOO_LONG', field);
|
|
160
|
+
if (def.type === 'integer' || def.type === 'number') {
|
|
161
|
+
const v = def.type === 'integer' ? Number(data[field]) : data[field];
|
|
162
|
+
if (data[field] !== undefined && data[field] !== null && def.min !== undefined && v < def.min) throw createError('ER_OUT_OF_RANGE', field);
|
|
163
|
+
if (data[field] !== undefined && data[field] !== null && def.max !== undefined && v > def.max) throw createError('ER_OUT_OF_RANGE', field);
|
|
164
|
+
}
|
|
165
|
+
if (def.check && data[field] !== undefined && !def.check(data[field], data)) throw createError('ER_CHECK_CONSTRAINT', this._name + '.' + field);
|
|
166
|
+
}
|
|
167
|
+
for (const fk of foreignKeys) {
|
|
168
|
+
if (data[fk.field] !== undefined && data[fk.field] !== null) {
|
|
169
|
+
const refTable = this._resolveTable(fk.table);
|
|
170
|
+
if (refTable) {
|
|
171
|
+
const exists = refTable.findOne({ [fk.refField]: data[fk.field] });
|
|
172
|
+
if (!exists) throw createError('ER_NO_REFERENCED_ROW');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (autoIncField && data[autoIncField] === undefined) {
|
|
135
177
|
this._autoIncrement++;
|
|
136
|
-
data[
|
|
137
|
-
} else if (
|
|
138
|
-
this._autoIncrement = data[
|
|
178
|
+
data[autoIncField] = this._autoIncrement;
|
|
179
|
+
} else if (autoIncField && data[autoIncField] > this._autoIncrement) {
|
|
180
|
+
this._autoIncrement = data[autoIncField];
|
|
139
181
|
}
|
|
140
|
-
this._rows.push({
|
|
141
|
-
|
|
182
|
+
this._rows.push(Object.assign({}, data));
|
|
183
|
+
items[i] = data;
|
|
142
184
|
}
|
|
143
|
-
this.
|
|
185
|
+
this._btreesDirty = true;
|
|
144
186
|
this._dirty = true;
|
|
145
187
|
this._db._markDirty();
|
|
146
188
|
for (const hook of this._hooks.afterInsert) {
|
|
147
|
-
for (
|
|
189
|
+
for (let i = 0; i < N; i++) hook(items[i]);
|
|
148
190
|
}
|
|
149
|
-
|
|
150
|
-
return
|
|
191
|
+
this._db._emitChange('insertMany', this._name, { count: N });
|
|
192
|
+
return items;
|
|
151
193
|
}
|
|
152
194
|
|
|
153
195
|
upsert(data) {
|
|
@@ -180,7 +222,7 @@ class Table {
|
|
|
180
222
|
|
|
181
223
|
findById(id) {
|
|
182
224
|
if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
|
|
183
|
-
|
|
225
|
+
this._ensureBTrees();
|
|
184
226
|
if (this._btrees[this._primaryKey]) {
|
|
185
227
|
const indices = this._btrees[this._primaryKey].search(id);
|
|
186
228
|
if (indices.length > 0) {
|
|
@@ -517,6 +559,21 @@ class Table {
|
|
|
517
559
|
}
|
|
518
560
|
}
|
|
519
561
|
|
|
562
|
+
_ensureBTrees() {
|
|
563
|
+
if (!this._btreesDirty) return;
|
|
564
|
+
this._btreesDirty = false;
|
|
565
|
+
for (const field of Object.keys(this._btrees)) {
|
|
566
|
+
this._btrees[field].clear();
|
|
567
|
+
}
|
|
568
|
+
for (let i = 0; i < this._rows.length; i++) {
|
|
569
|
+
const row = this._rows[i];
|
|
570
|
+
for (const [field, tree] of Object.entries(this._btrees)) {
|
|
571
|
+
const v = row[field];
|
|
572
|
+
if (v !== undefined && v !== null) tree.insert(v, i);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
520
577
|
_rebuildAllBTrees() {
|
|
521
578
|
for (const field of Object.keys(this._btrees)) {
|
|
522
579
|
this._rebuildBTree(field);
|
|
@@ -546,6 +603,7 @@ class Table {
|
|
|
546
603
|
// ============================================================
|
|
547
604
|
|
|
548
605
|
_applyFilterOptimized(rows, query) {
|
|
606
|
+
this._ensureBTrees();
|
|
549
607
|
// 尝试用 B-Tree 加速
|
|
550
608
|
for (const [field, condition] of Object.entries(query)) {
|
|
551
609
|
if (field === '$or' || field === '$and' || field === '$not') continue;
|
|
@@ -843,6 +901,7 @@ class Table {
|
|
|
843
901
|
}
|
|
844
902
|
|
|
845
903
|
_addToIndexes(rowIndex, data) {
|
|
904
|
+
this._ensureBTrees();
|
|
846
905
|
for (const field of Object.keys(this._indexes)) {
|
|
847
906
|
const value = data[field];
|
|
848
907
|
if (value !== undefined) {
|
|
@@ -861,6 +920,7 @@ class Table {
|
|
|
861
920
|
}
|
|
862
921
|
|
|
863
922
|
_removeFromIndexes(rowIndex, data) {
|
|
923
|
+
this._ensureBTrees();
|
|
864
924
|
for (const field of Object.keys(this._indexes)) {
|
|
865
925
|
const value = data[field];
|
|
866
926
|
if (value !== undefined && this._indexes[field].has(value)) {
|
package/package.json
CHANGED