jsql-neo 2.1.1 → 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 +92 -23
- 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;
|
|
@@ -715,13 +773,22 @@ class Table {
|
|
|
715
773
|
throw createError('ER_BAD_NULL_ERROR', field);
|
|
716
774
|
}
|
|
717
775
|
|
|
718
|
-
// UNIQUE
|
|
776
|
+
// UNIQUE(B-Tree 优化 O(log n))
|
|
719
777
|
if (def.unique && data[field] !== undefined) {
|
|
720
778
|
if (uniqueTracker && uniqueTracker[field]) {
|
|
721
779
|
if (uniqueTracker[field].has(data[field])) {
|
|
722
780
|
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
723
781
|
}
|
|
724
782
|
uniqueTracker[field].add(data[field]);
|
|
783
|
+
} else if (this._btrees[field]) {
|
|
784
|
+
const found = this._btrees[field].search(data[field]);
|
|
785
|
+
if (found.length > 0) {
|
|
786
|
+
for (const idx of found) {
|
|
787
|
+
if (idx !== excludeIndex && this._rows[idx] && this._rows[idx][field] === data[field]) {
|
|
788
|
+
throw createError('ER_DUP_ENTRY', String(data[field]), field);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
725
792
|
} else {
|
|
726
793
|
for (let i = 0; i < this._rows.length; i++) {
|
|
727
794
|
if (i === excludeIndex) continue;
|
|
@@ -834,6 +901,7 @@ class Table {
|
|
|
834
901
|
}
|
|
835
902
|
|
|
836
903
|
_addToIndexes(rowIndex, data) {
|
|
904
|
+
this._ensureBTrees();
|
|
837
905
|
for (const field of Object.keys(this._indexes)) {
|
|
838
906
|
const value = data[field];
|
|
839
907
|
if (value !== undefined) {
|
|
@@ -852,6 +920,7 @@ class Table {
|
|
|
852
920
|
}
|
|
853
921
|
|
|
854
922
|
_removeFromIndexes(rowIndex, data) {
|
|
923
|
+
this._ensureBTrees();
|
|
855
924
|
for (const field of Object.keys(this._indexes)) {
|
|
856
925
|
const value = data[field];
|
|
857
926
|
if (value !== undefined && this._indexes[field].has(value)) {
|
package/package.json
CHANGED