jsql-neo 1.3.0 → 2.0.1

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/table.js CHANGED
@@ -1,10 +1,13 @@
1
1
  // © Vexify 2026 All Rights Reserved.
2
2
  /**
3
- * JSQL Table — 表操作引擎
4
- * 支持 DEFAULT、CHECK、外键、UPSERT、TRUNCATE、ALTER TABLE、计算字段、JSON 路径、软删除
3
+ * JSQL Table — 表操作引擎 v2.0
4
+ * B-Tree 自动索引、日期类型、错误码、外键 CASCADE、长度/精度校验
5
5
  */
6
6
 
7
7
  const Query = require('./query');
8
+ const BTree = require('./btree');
9
+ const { validateDateType, now } = require('./date-types');
10
+ const { createError } = require('./errors');
8
11
 
9
12
  class Table {
10
13
  constructor(name, schema, db) {
@@ -12,7 +15,8 @@ class Table {
12
15
  this._schema = schema;
13
16
  this._db = db;
14
17
  this._rows = [];
15
- this._indexes = {};
18
+ this._indexes = {}; // 旧版 hash 索引(兼容)
19
+ this._btrees = {}; // B-Tree 索引 { fieldName: BTree }
16
20
  this._autoIncrement = 0;
17
21
  this._dirty = false;
18
22
  this._hooks = {
@@ -20,15 +24,15 @@ class Table {
20
24
  beforeUpdate: [], afterUpdate: [],
21
25
  beforeRemove: [], afterRemove: []
22
26
  };
23
- this._softDelete = false; // 是否启用软删除
24
- this._computedFields = {}; // 计算字段
27
+ this._softDelete = false;
28
+ this._computedFields = {};
25
29
 
26
- // 解析列定义
27
30
  this._primaryKey = null;
28
31
  this._autoIncrementField = null;
29
32
  this._foreignKeys = [];
30
33
  this._checkConstraints = [];
31
34
  this._defaults = {};
35
+ this._dateFields = {}; // { fieldName: 'date'|'datetime'|'timestamp' }
32
36
 
33
37
  for (const [field, def] of Object.entries(schema)) {
34
38
  if (def.primaryKey) this._primaryKey = field;
@@ -46,17 +50,35 @@ class Table {
46
50
  this._foreignKeys.push({
47
51
  field,
48
52
  table: def.foreignKey.table,
49
- onDelete: def.foreignKey.onDelete || 'restrict'
53
+ refField: def.foreignKey.field || 'id',
54
+ onDelete: def.foreignKey.onDelete || 'restrict',
55
+ onUpdate: def.foreignKey.onUpdate || 'restrict'
50
56
  });
51
57
  }
52
58
  if (def.computed) {
53
59
  this._computedFields[field] = def.computed;
54
60
  }
61
+ // 日期类型
62
+ if (['date', 'datetime', 'timestamp', 'time'].includes(def.type)) {
63
+ this._dateFields[field] = def.type;
64
+ }
55
65
  }
56
66
 
57
67
  if (schema._softDelete) {
58
68
  this._softDelete = true;
59
69
  }
70
+
71
+ // 自动为 primaryKey 和 unique 字段建 B-Tree 索引
72
+ this._autoCreateBTrees();
73
+ }
74
+
75
+ _autoCreateBTrees() {
76
+ for (const [field, def] of Object.entries(this._schema)) {
77
+ if (field === '_softDelete') continue;
78
+ if (def.primaryKey || def.unique) {
79
+ this._btrees[field] = new BTree(64, true);
80
+ }
81
+ }
60
82
  }
61
83
 
62
84
  // ============================================================
@@ -68,39 +90,11 @@ class Table {
68
90
  data = hook({ ...data }) || data;
69
91
  }
70
92
 
71
- // 填充默认值
72
93
  data = this._applyDefaults(data);
73
-
74
- // 计算字段
75
94
  data = this._applyComputed(data);
95
+ data = this._validateDataTypes(data);
96
+ this._validateConstraints(data, -1);
76
97
 
77
- // 校验必填
78
- for (const [field, def] of Object.entries(this._schema)) {
79
- if (field === '_softDelete') continue;
80
- if (def.required && (data[field] === undefined || data[field] === null)) {
81
- throw new Error(`Field '${field}' is required in table '${this._name}'`);
82
- }
83
- }
84
-
85
- // CHECK 约束
86
- for (const { field, fn } of this._checkConstraints) {
87
- if (data[field] !== undefined && !fn(data[field], data)) {
88
- throw new Error(`CHECK constraint failed on '${this._name}.${field}'`);
89
- }
90
- }
91
-
92
- // 唯一约束
93
- for (const [field, def] of Object.entries(this._schema)) {
94
- if (def.unique && data[field] !== undefined) {
95
- for (const row of this._rows) {
96
- if (row[field] === data[field]) {
97
- throw new Error(`Duplicate value for unique field '${field}': ${data[field]}`);
98
- }
99
- }
100
- }
101
- }
102
-
103
- // 自增
104
98
  if (this._autoIncrementField && data[this._autoIncrementField] === undefined) {
105
99
  this._autoIncrement++;
106
100
  data[this._autoIncrementField] = this._autoIncrement;
@@ -110,14 +104,13 @@ class Table {
110
104
 
111
105
  const rowIndex = this._rows.length;
112
106
  this._rows.push({ ...data });
113
- this._updateIndexes(rowIndex, data);
107
+ this._addToIndexes(rowIndex, data);
114
108
  this._dirty = true;
115
109
  this._db._markDirty();
116
110
 
117
111
  for (const hook of this._hooks.afterInsert) {
118
112
  hook(data);
119
113
  }
120
-
121
114
  this._db._emitChange('insert', this._name, data);
122
115
 
123
116
  return data;
@@ -127,10 +120,6 @@ class Table {
127
120
  return items.map(item => this.insert(item));
128
121
  }
129
122
 
130
- /**
131
- * UPSERT — 有则更新,无则插入
132
- * 按主键或唯一字段判断
133
- */
134
123
  upsert(data) {
135
124
  if (this._primaryKey && data[this._primaryKey] !== undefined) {
136
125
  const existing = this.findById(data[this._primaryKey]);
@@ -143,13 +132,13 @@ class Table {
143
132
  }
144
133
 
145
134
  // ============================================================
146
- // 查询
135
+ // 查询(支持 B-Tree 加速)
147
136
  // ============================================================
148
137
 
149
138
  find(query = {}) {
150
139
  let rows = this._getVisibleRows();
151
140
  if (Object.keys(query).length > 0) {
152
- rows = this._applyFilter(rows, query);
141
+ rows = this._applyFilterOptimized(rows, query);
153
142
  }
154
143
  return rows.map(r => ({ ...r }));
155
144
  }
@@ -160,7 +149,16 @@ class Table {
160
149
  }
161
150
 
162
151
  findById(id) {
163
- if (!this._primaryKey) throw new Error(`Table '${this._name}' has no primary key`);
152
+ if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
153
+ // B-Tree 加速
154
+ if (this._btrees[this._primaryKey]) {
155
+ const indices = this._btrees[this._primaryKey].search(id);
156
+ if (indices.length > 0) {
157
+ const row = this._rows[indices[0]];
158
+ if (row && !row._deleted) return { ...row };
159
+ }
160
+ return null;
161
+ }
164
162
  return this.findOne({ [this._primaryKey]: id });
165
163
  }
166
164
 
@@ -170,7 +168,7 @@ class Table {
170
168
 
171
169
  count(query = {}) {
172
170
  if (Object.keys(query).length === 0) return this._getVisibleRows().length;
173
- return this._applyFilter(this._getVisibleRows(), query).length;
171
+ return this._applyFilterOptimized(this._getVisibleRows(), query).length;
174
172
  }
175
173
 
176
174
  where(conditions) {
@@ -186,7 +184,8 @@ class Table {
186
184
  updates = hook(query, { ...updates }) || updates;
187
185
  }
188
186
 
189
- const matched = this._applyFilter(this._getVisibleRows(), query);
187
+ updates = this._validateDataTypes(updates, true);
188
+ const matched = this._applyFilterOptimized(this._getVisibleRows(), query);
190
189
  let count = 0;
191
190
 
192
191
  for (const row of matched) {
@@ -194,29 +193,12 @@ class Table {
194
193
  if (index === -1) continue;
195
194
 
196
195
  const oldData = { ...this._rows[index] };
197
-
198
- // 唯一约束
199
- for (const [field, def] of Object.entries(this._schema)) {
200
- if (def.unique && updates[field] !== undefined) {
201
- for (let i = 0; i < this._rows.length; i++) {
202
- if (i !== index && this._rows[i][field] === updates[field]) {
203
- throw new Error(`Duplicate value for unique field '${field}': ${updates[field]}`);
204
- }
205
- }
206
- }
207
- }
208
-
209
- // CHECK 约束
210
196
  const merged = { ...this._rows[index], ...updates };
211
- for (const { field, fn } of this._checkConstraints) {
212
- if (merged[field] !== undefined && !fn(merged[field], merged)) {
213
- throw new Error(`CHECK constraint failed on '${this._name}.${field}'`);
214
- }
215
- }
197
+ this._validateConstraints(merged, index);
216
198
 
217
- Object.assign(this._rows[index], updates);
218
199
  this._removeFromIndexes(index, oldData);
219
- this._updateIndexes(index, this._rows[index]);
200
+ Object.assign(this._rows[index], updates);
201
+ this._addToIndexes(index, this._rows[index]);
220
202
  count++;
221
203
  }
222
204
 
@@ -232,12 +214,12 @@ class Table {
232
214
  }
233
215
 
234
216
  updateById(id, updates) {
235
- if (!this._primaryKey) throw new Error(`Table '${this._name}' has no primary key`);
217
+ if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
236
218
  return this.update({ [this._primaryKey]: id }, updates);
237
219
  }
238
220
 
239
221
  // ============================================================
240
- // 删除
222
+ // 删除 & 外键 CASCADE
241
223
  // ============================================================
242
224
 
243
225
  remove(query = {}) {
@@ -249,22 +231,15 @@ class Table {
249
231
  return this._truncate();
250
232
  }
251
233
 
252
- const toRemove = this._applyFilter(this._getVisibleRows(), query);
234
+ const toRemove = this._applyFilterOptimized(this._getVisibleRows(), query);
253
235
  let count = 0;
254
236
 
255
237
  for (const row of toRemove) {
256
238
  const index = this._rows.indexOf(row);
257
239
  if (index === -1) continue;
258
240
 
259
- // 外键级联删除
260
- for (const fk of this._foreignKeys) {
261
- if (fk.onDelete === 'cascade') {
262
- const refTable = this._db._tables[fk.table];
263
- if (refTable) {
264
- refTable.remove({ [fk.field]: row[this._primaryKey] });
265
- }
266
- }
267
- }
241
+ // 外键 CASCADE / SET NULL:查找所有引用此表的子表
242
+ this._cascadeForeignKeys(row);
268
243
 
269
244
  if (this._softDelete) {
270
245
  this._rows[index]._deleted = true;
@@ -272,7 +247,7 @@ class Table {
272
247
  } else {
273
248
  this._removeFromIndexes(index, row);
274
249
  this._rows.splice(index, 1);
275
- this._adjustIndexesAfterRemove(index);
250
+ this._adjustBTreeAfterRemove(index);
276
251
  }
277
252
  count++;
278
253
  }
@@ -289,24 +264,17 @@ class Table {
289
264
  }
290
265
 
291
266
  removeById(id) {
292
- if (!this._primaryKey) throw new Error(`Table '${this._name}' has no primary key`);
267
+ if (!this._primaryKey) throw createError('ER_PARSE_ERROR', 'Table has no primary key');
293
268
  return this.remove({ [this._primaryKey]: id });
294
269
  }
295
270
 
296
- /**
297
- * 软删除 — 标记为已删除,可恢复
298
- */
299
271
  removeSoft(query = {}) {
300
272
  this._softDelete = true;
301
- const result = this.remove(query);
302
- return result;
273
+ return this.remove(query);
303
274
  }
304
275
 
305
- /**
306
- * 恢复软删除的行
307
- */
308
276
  restore(query = {}) {
309
- if (!this._softDelete) throw new Error('Soft delete is not enabled');
277
+ if (!this._softDelete) throw createError('ER_NOT_SUPPORTED', 'Soft delete is not enabled');
310
278
  const matched = this._applyFilter(this._rows, { ...query, _deleted: true });
311
279
  for (const row of matched) {
312
280
  const index = this._rows.indexOf(row);
@@ -318,9 +286,6 @@ class Table {
318
286
  return matched.length;
319
287
  }
320
288
 
321
- /**
322
- * 清空表(保留结构)
323
- */
324
289
  truncate() {
325
290
  return this._truncate();
326
291
  }
@@ -328,6 +293,9 @@ class Table {
328
293
  _truncate() {
329
294
  const count = this._rows.length;
330
295
  this._rows = [];
296
+ for (const key of Object.keys(this._btrees)) {
297
+ this._btrees[key].clear();
298
+ }
331
299
  this._indexes = {};
332
300
  this._autoIncrement = 0;
333
301
  this._dirty = true;
@@ -340,11 +308,10 @@ class Table {
340
308
  // ============================================================
341
309
 
342
310
  addColumn(field, definition) {
343
- if (this._schema[field]) throw new Error(`Column '${field}' already exists`);
311
+ if (this._schema[field]) throw createError('ER_DUP_FIELDNAME', field);
344
312
  this._schema[field] = definition;
345
313
  if (definition.default !== undefined) {
346
314
  this._defaults[field] = definition.default;
347
- // 填充现有行的默认值
348
315
  for (const row of this._rows) {
349
316
  if (row[field] === undefined) {
350
317
  row[field] = typeof definition.default === 'function' ? definition.default() : definition.default;
@@ -356,40 +323,57 @@ class Table {
356
323
  this._foreignKeys.push({
357
324
  field,
358
325
  table: definition.foreignKey.table,
359
- onDelete: definition.foreignKey.onDelete || 'restrict'
326
+ refField: definition.foreignKey.field || 'id',
327
+ onDelete: definition.foreignKey.onDelete || 'restrict',
328
+ onUpdate: definition.foreignKey.onUpdate || 'restrict'
360
329
  });
361
330
  }
362
331
  if (definition.computed) this._computedFields[field] = definition.computed;
332
+ if (['date', 'datetime', 'timestamp', 'time'].includes(definition.type)) {
333
+ this._dateFields[field] = definition.type;
334
+ }
335
+ if (definition.primaryKey || definition.unique) {
336
+ this._btrees[field] = new BTree(64, true);
337
+ this._rebuildBTree(field);
338
+ }
363
339
  this._dirty = true;
364
340
  this._db._markDirty();
365
341
  return this;
366
342
  }
367
343
 
368
344
  dropColumn(field) {
369
- if (!this._schema[field]) throw new Error(`Column '${field}' does not exist`);
345
+ if (!this._schema[field]) throw createError('ER_CANT_DROP_FIELD', field);
370
346
  delete this._schema[field];
371
347
  delete this._defaults[field];
348
+ delete this._btrees[field];
349
+ delete this._indexes[field];
372
350
  this._checkConstraints = this._checkConstraints.filter(c => c.field !== field);
373
351
  this._foreignKeys = this._foreignKeys.filter(fk => fk.field !== field);
374
352
  delete this._computedFields[field];
375
- // 移除所有行中的该字段
353
+ delete this._dateFields[field];
376
354
  for (const row of this._rows) {
377
355
  delete row[field];
378
356
  }
357
+ if (this._primaryKey === field) this._primaryKey = null;
358
+ if (this._autoIncrementField === field) this._autoIncrementField = null;
379
359
  this._dirty = true;
380
360
  this._db._markDirty();
381
361
  return this;
382
362
  }
383
363
 
384
364
  renameColumn(oldName, newName) {
385
- if (!this._schema[oldName]) throw new Error(`Column '${oldName}' does not exist`);
386
- if (this._schema[newName]) throw new Error(`Column '${newName}' already exists`);
365
+ if (!this._schema[oldName]) throw createError('ER_BAD_FIELD_ERROR', oldName, this._name);
366
+ if (this._schema[newName]) throw createError('ER_DUP_FIELDNAME', newName);
387
367
  this._schema[newName] = this._schema[oldName];
388
368
  delete this._schema[oldName];
389
369
  if (this._defaults[oldName] !== undefined) {
390
370
  this._defaults[newName] = this._defaults[oldName];
391
371
  delete this._defaults[oldName];
392
372
  }
373
+ if (this._btrees[oldName]) {
374
+ this._btrees[newName] = this._btrees[oldName];
375
+ delete this._btrees[oldName];
376
+ }
393
377
  for (const row of this._rows) {
394
378
  row[newName] = row[oldName];
395
379
  delete row[oldName];
@@ -402,40 +386,35 @@ class Table {
402
386
  }
403
387
 
404
388
  modifyColumn(field, definition) {
405
- if (!this._schema[field]) throw new Error(`Column '${field}' does not exist`);
389
+ if (!this._schema[field]) throw createError('ER_BAD_FIELD_ERROR', field, this._name);
406
390
  this._schema[field] = { ...this._schema[field], ...definition };
407
391
  if (definition.default !== undefined) this._defaults[field] = definition.default;
408
392
  this._checkConstraints = this._checkConstraints.filter(c => c.field !== field);
409
393
  if (definition.check) this._checkConstraints.push({ field, fn: definition.check });
394
+ if (definition.primaryKey || definition.unique) {
395
+ if (!this._btrees[field]) this._btrees[field] = new BTree(64, true);
396
+ this._rebuildBTree(field);
397
+ }
410
398
  this._dirty = true;
411
399
  this._db._markDirty();
412
400
  return this;
413
401
  }
414
402
 
415
- /**
416
- * 获取表结构
417
- */
418
403
  describe() {
419
404
  return {
420
405
  name: this._name,
421
406
  columns: { ...this._schema },
422
407
  primaryKey: this._primaryKey,
423
408
  rowCount: this._getVisibleRows().length,
424
- indexCount: Object.keys(this._indexes).length,
409
+ btreeIndexes: Object.keys(this._btrees),
410
+ hashIndexes: Object.keys(this._indexes),
425
411
  softDelete: this._softDelete,
426
412
  foreignKeys: this._foreignKeys.map(fk => ({ field: fk.field, table: fk.table }))
427
413
  };
428
414
  }
429
415
 
430
- // ============================================================
431
- // SELECT INTO
432
- // ============================================================
433
-
434
- /**
435
- * 从查询结果创建新表
436
- */
437
416
  selectInto(newTableName, newSchema, query = {}) {
438
- const rows = this._applyFilter(this._getVisibleRows(), query);
417
+ const rows = this._applyFilterOptimized(this._getVisibleRows(), query);
439
418
  this._db.createTable(newTableName, newSchema);
440
419
  const newTable = this._db._tables[newTableName];
441
420
  for (const row of rows) {
@@ -444,21 +423,10 @@ class Table {
444
423
  return newTable;
445
424
  }
446
425
 
447
- // ============================================================
448
- // 全文搜索评分
449
- // ============================================================
450
-
451
- /**
452
- * 全文搜索并返回带相关性评分的排序结果
453
- * @param {string} field - 搜索字段
454
- * @param {string|string[]} terms - 搜索词
455
- * @returns {Array} 按 _score 降序排列的结果
456
- */
457
426
  textSearch(field, terms) {
458
427
  const searchTerms = Array.isArray(terms) ? terms : terms.split(/\s+/);
459
428
  const rows = this._getVisibleRows();
460
429
  const scored = [];
461
-
462
430
  for (const row of rows) {
463
431
  const value = String(row[field] || '').toLowerCase();
464
432
  let score = 0;
@@ -466,17 +434,12 @@ class Table {
466
434
  const t = term.toLowerCase();
467
435
  const idx = value.indexOf(t);
468
436
  if (idx !== -1) {
469
- score += 10; // 基础命中
470
- if (idx === 0) score += 5; // 开头匹配加分
471
- if (value === t) score += 20; // 完全匹配加更多
472
- score += (value.length - idx) / value.length * 5; // 靠前加权
437
+ score += 10 + (idx === 0 ? 5 : 0) + (value === t ? 20 : 0)
438
+ + (value.length - idx) / value.length * 5;
473
439
  }
474
440
  }
475
- if (score > 0) {
476
- scored.push({ ...row, _score: score });
477
- }
441
+ if (score > 0) scored.push({ ...row, _score: score });
478
442
  }
479
-
480
443
  return scored.sort((a, b) => b._score - a._score);
481
444
  }
482
445
 
@@ -500,6 +463,34 @@ class Table {
500
463
 
501
464
  dropIndex(field) {
502
465
  delete this._indexes[field];
466
+ delete this._btrees[field];
467
+ }
468
+
469
+ /**
470
+ * 创建 B-Tree 索引(O(log n) 查询)
471
+ */
472
+ createBTreeIndex(field, unique = false) {
473
+ this._btrees[field] = new BTree(64, unique);
474
+ this._rebuildBTree(field);
475
+ return this._btrees[field];
476
+ }
477
+
478
+ _rebuildBTree(field) {
479
+ if (this._btrees[field]) {
480
+ this._btrees[field].clear();
481
+ for (let i = 0; i < this._rows.length; i++) {
482
+ const val = this._rows[i][field];
483
+ if (val !== undefined && val !== null) {
484
+ this._btrees[field].insert(val, i);
485
+ }
486
+ }
487
+ }
488
+ }
489
+
490
+ _rebuildAllBTrees() {
491
+ for (const field of Object.keys(this._btrees)) {
492
+ this._rebuildBTree(field);
493
+ }
503
494
  }
504
495
 
505
496
  // ============================================================
@@ -521,9 +512,41 @@ class Table {
521
512
  }
522
513
 
523
514
  // ============================================================
524
- // 内部: 过滤器
515
+ // 内部: 过滤器(B-Tree 加速)
525
516
  // ============================================================
526
517
 
518
+ _applyFilterOptimized(rows, query) {
519
+ // 尝试用 B-Tree 加速
520
+ for (const [field, condition] of Object.entries(query)) {
521
+ if (field === '$or' || field === '$and' || field === '$not') continue;
522
+ // 等值查询用 B-Tree
523
+ if (typeof condition !== 'object' || condition === null || Array.isArray(condition) || condition instanceof RegExp) {
524
+ if (this._btrees[field]) {
525
+ const indices = this._btrees[field].search(condition);
526
+ if (indices.length > 0) {
527
+ const subset = indices.map(i => this._rows[i]).filter(r => r);
528
+ return this._applyFilter(subset, query);
529
+ }
530
+ return [];
531
+ }
532
+ }
533
+ // 范围查询用 B-Tree
534
+ if (condition && typeof condition === 'object') {
535
+ if (condition.$gt !== undefined && condition.$lt !== undefined && this._btrees[field]) {
536
+ const indices = this._btrees[field].range(condition.$gt, condition.$lt);
537
+ const subset = indices.map(i => this._rows[i]).filter(r => r);
538
+ return this._applyFilter(subset, query);
539
+ }
540
+ if (condition.$gte !== undefined && condition.$lte !== undefined && this._btrees[field]) {
541
+ const indices = this._btrees[field].range(condition.$gte, condition.$lte);
542
+ const subset = indices.map(i => this._rows[i]).filter(r => r);
543
+ return this._applyFilter(subset, query);
544
+ }
545
+ }
546
+ }
547
+ return this._applyFilter(rows, query);
548
+ }
549
+
527
550
  _applyFilter(rows, query) {
528
551
  const result = [];
529
552
  for (let i = 0; i < rows.length; i++) {
@@ -536,7 +559,6 @@ class Table {
536
559
 
537
560
  _matchRow(row, query) {
538
561
  for (const [field, condition] of Object.entries(query)) {
539
- // 顶层 $or / $and / $not
540
562
  if (field === '$or') {
541
563
  if (!Array.isArray(condition) || !condition.some(sub => this._matchRow(row, sub))) return false;
542
564
  continue;
@@ -550,21 +572,16 @@ class Table {
550
572
  continue;
551
573
  }
552
574
 
553
- // JSON 路径: 'profile.city' 或 'data.profile.city'
554
575
  const value = this._resolvePath(row, field);
555
576
 
556
577
  if (condition !== null && typeof condition === 'object' && !Array.isArray(condition) && !(condition instanceof RegExp)) {
557
- // 检查是否是操作符
558
578
  const keys = Object.keys(condition);
559
579
  const isOperator = keys.every(k => k.startsWith('$'));
560
580
  if (isOperator && keys.length > 0) {
561
581
  for (const [op, target] of Object.entries(condition)) {
562
- if (!this._matchOperator(value, op, target)) {
563
- return false;
564
- }
582
+ if (!this._matchOperator(value, op, target)) return false;
565
583
  }
566
584
  } else {
567
- // 普通对象:递归匹配子文档
568
585
  if (value !== null && typeof value === 'object') {
569
586
  if (!this._matchRow(value, condition)) return false;
570
587
  } else {
@@ -590,15 +607,13 @@ class Table {
590
607
  case '$nin': return Array.isArray(target) && !target.includes(value);
591
608
  case '$like':
592
609
  if (typeof value !== 'string' || typeof target !== 'string') return false;
593
- const likeRegex = target.replace(/%/g, '.*').replace(/_/g, '.');
594
- return new RegExp('^' + likeRegex + '$', 'i').test(value);
610
+ return new RegExp('^' + target.replace(/%/g, '.*').replace(/_/g, '.') + '$', 'i').test(value);
595
611
  case '$regex':
596
612
  return new RegExp(target).test(String(value));
597
613
  case '$exists':
598
614
  return target ? value !== undefined : value === undefined;
599
615
  case '$between':
600
- return Array.isArray(target) && target.length === 2
601
- && value >= target[0] && value <= target[1];
616
+ return Array.isArray(target) && target.length === 2 && value >= target[0] && value <= target[1];
602
617
  case '$and':
603
618
  return Array.isArray(target) && target.every(sub => this._matchRow({ _val: value }, { _val: sub }));
604
619
  case '$or':
@@ -613,8 +628,7 @@ class Table {
613
628
  return Array.isArray(value) && value.length === target;
614
629
  case '$elemMatch':
615
630
  return Array.isArray(value) && value.some(el =>
616
- typeof el === 'object' ? this._matchRow(el, target) : el === target
617
- );
631
+ typeof el === 'object' ? this._matchRow(el, target) : el === target);
618
632
  case '$isNull':
619
633
  return target ? value === null || value === undefined : value !== null && value !== undefined;
620
634
  case '$startsWith':
@@ -622,18 +636,14 @@ class Table {
622
636
  case '$endsWith':
623
637
  return typeof value === 'string' && value.endsWith(target);
624
638
  case '$search':
625
- // 全文搜索(简单实现)
626
639
  if (typeof value !== 'string') return false;
627
640
  const terms = Array.isArray(target) ? target : [target];
628
641
  return terms.every(t => value.toLowerCase().includes(t.toLowerCase()));
629
642
  case '$textSearch':
630
- // 高级全文搜索,带相关性评分(返回 true 即可,评分在 _textScore 中)
631
643
  if (typeof value !== 'string') return false;
632
- const searchTerms = Array.isArray(target) ? target : target.split(/\s+/);
633
- const lower = value.toLowerCase();
634
- return searchTerms.some(t => lower.includes(t.toLowerCase()));
644
+ const sTerms = Array.isArray(target) ? target : target.split(/\s+/);
645
+ return sTerms.some(t => value.toLowerCase().includes(t.toLowerCase()));
635
646
  case '$inSub':
636
- // 子查询:$inSub: { table: otherTable, field: 'col', query: {} }
637
647
  if (!target || !target.table) return false;
638
648
  const subRows = target.table._applyFilter(target.table._rows, target.query || {});
639
649
  return subRows.some(r => r[target.field] === value);
@@ -642,7 +652,6 @@ class Table {
642
652
  const subRows2 = target.table._applyFilter(target.table._rows, target.query || {});
643
653
  return !subRows2.some(r => r[target.field] === value);
644
654
  case '$expr':
645
- // 表达式:$expr: (value, row) => boolean
646
655
  if (typeof target === 'function') return target(value);
647
656
  return false;
648
657
  default:
@@ -650,9 +659,6 @@ class Table {
650
659
  }
651
660
  }
652
661
 
653
- /**
654
- * 解析 JSON 路径,如 'profile.city' -> row.profile.city
655
- */
656
662
  _resolvePath(row, field) {
657
663
  if (field.includes('.') && !field.startsWith('$')) {
658
664
  const parts = field.split('.');
@@ -666,6 +672,102 @@ class Table {
666
672
  return row[field];
667
673
  }
668
674
 
675
+ // ============================================================
676
+ // 内部: 约束校验
677
+ // ============================================================
678
+
679
+ _validateConstraints(data, excludeIndex) {
680
+ for (const [field, def] of Object.entries(this._schema)) {
681
+ if (field === '_softDelete') continue;
682
+
683
+ // NOT NULL
684
+ if (def.required && (data[field] === undefined || data[field] === null)) {
685
+ throw createError('ER_BAD_NULL_ERROR', field);
686
+ }
687
+
688
+ // UNIQUE
689
+ if (def.unique && data[field] !== undefined) {
690
+ for (let i = 0; i < this._rows.length; i++) {
691
+ if (i === excludeIndex) continue;
692
+ if (this._rows[i][field] === data[field]) {
693
+ throw createError('ER_DUP_ENTRY', String(data[field]), field);
694
+ }
695
+ }
696
+ }
697
+
698
+ // CHECK
699
+ for (const { field: cf, fn } of this._checkConstraints) {
700
+ if (cf === field && data[field] !== undefined && !fn(data[field], data)) {
701
+ throw createError('ER_CHECK_CONSTRAINT', this._name + '.' + field);
702
+ }
703
+ }
704
+
705
+ // 长度限制
706
+ if (def.length && typeof data[field] === 'string' && data[field].length > def.length) {
707
+ throw createError('ER_DATA_TOO_LONG', field);
708
+ }
709
+
710
+ // 数值范围
711
+ if (def.type === 'integer' && data[field] !== undefined && data[field] !== null) {
712
+ const v = Number(data[field]);
713
+ if (def.min !== undefined && v < def.min) throw createError('ER_OUT_OF_RANGE', field);
714
+ if (def.max !== undefined && v > def.max) throw createError('ER_OUT_OF_RANGE', field);
715
+ }
716
+ }
717
+
718
+ // FOREIGN KEY 校验
719
+ for (const fk of this._foreignKeys) {
720
+ if (data[fk.field] !== undefined && data[fk.field] !== null) {
721
+ const refTable = this._resolveTable(fk.table);
722
+ if (refTable) {
723
+ const exists = refTable.findOne({ [fk.refField]: data[fk.field] });
724
+ if (!exists) {
725
+ throw createError('ER_NO_REFERENCED_ROW');
726
+ }
727
+ }
728
+ }
729
+ }
730
+ }
731
+
732
+ _resolveTable(tableRef) {
733
+ if (typeof tableRef === 'string') {
734
+ return this._db._tables[tableRef] || this._db._attached[tableRef]?._tables[tableRef];
735
+ }
736
+ return tableRef;
737
+ }
738
+
739
+ /**
740
+ * 级联外键操作:当删除当前表的行时,查找所有引用此表的子表
741
+ * 并执行 CASCADE 或 SET NULL
742
+ */
743
+ _cascadeForeignKeys(row) {
744
+ for (const [, table] of Object.entries(this._db._tables)) {
745
+ if (table._name === this._name) continue;
746
+ for (const fk of table._foreignKeys) {
747
+ const refTable = table._resolveTable(fk.table);
748
+ if (!refTable || refTable._name !== this._name) continue;
749
+
750
+ // 检查子表行是否引用当前行
751
+ if (fk.onDelete === 'cascade') {
752
+ table.remove({ [fk.field]: row[fk.refField] });
753
+ } else if (fk.onDelete === 'set null') {
754
+ table.update({ [fk.field]: row[fk.refField] }, { [fk.field]: null });
755
+ }
756
+ }
757
+ }
758
+ }
759
+
760
+ _validateDataTypes(data, partial = false) {
761
+ const result = { ...data };
762
+ for (const [field, type] of Object.entries(this._dateFields)) {
763
+ if (partial && data[field] === undefined) continue;
764
+ if (data[field] !== undefined && data[field] !== null) {
765
+ result[field] = validateDateType(data[field], type, field);
766
+ }
767
+ }
768
+ return result;
769
+ }
770
+
669
771
  // ============================================================
670
772
  // 内部: 辅助
671
773
  // ============================================================
@@ -674,7 +776,9 @@ class Table {
674
776
  const result = { ...data };
675
777
  for (const [field, defaultValue] of Object.entries(this._defaults)) {
676
778
  if (result[field] === undefined) {
677
- result[field] = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
779
+ result[field] = typeof defaultValue === 'function'
780
+ ? defaultValue()
781
+ : (defaultValue === 'CURRENT_TIMESTAMP' ? now() : defaultValue);
678
782
  }
679
783
  }
680
784
  return result;
@@ -688,13 +792,11 @@ class Table {
688
792
  }
689
793
 
690
794
  _getVisibleRows() {
691
- if (this._softDelete) {
692
- return this._rows.filter(r => !r._deleted);
693
- }
795
+ if (this._softDelete) return this._rows.filter(r => !r._deleted);
694
796
  return this._rows;
695
797
  }
696
798
 
697
- _updateIndexes(rowIndex, data) {
799
+ _addToIndexes(rowIndex, data) {
698
800
  for (const field of Object.keys(this._indexes)) {
699
801
  const value = data[field];
700
802
  if (value !== undefined) {
@@ -704,6 +806,12 @@ class Table {
704
806
  this._indexes[field].get(value).push(rowIndex);
705
807
  }
706
808
  }
809
+ for (const [field, tree] of Object.entries(this._btrees)) {
810
+ const value = data[field];
811
+ if (value !== undefined && value !== null) {
812
+ tree.insert(value, rowIndex);
813
+ }
814
+ }
707
815
  }
708
816
 
709
817
  _removeFromIndexes(rowIndex, data) {
@@ -718,21 +826,18 @@ class Table {
718
826
  }
719
827
  }
720
828
  }
829
+ for (const [field, tree] of Object.entries(this._btrees)) {
830
+ const value = data[field];
831
+ if (value !== undefined && value !== null) {
832
+ tree.remove(value, rowIndex);
833
+ }
834
+ }
721
835
  }
722
836
 
723
- _adjustIndexesAfterRemove(removedIndex) {
724
- for (const idxName of Object.keys(this._indexes)) {
725
- const idx = this._indexes[idxName];
726
- for (const [key, positions] of idx) {
727
- const newPositions = positions
728
- .filter(p => p !== removedIndex)
729
- .map(p => p > removedIndex ? p - 1 : p);
730
- if (newPositions.length === 0) {
731
- idx.delete(key);
732
- } else {
733
- idx.set(key, newPositions);
734
- }
735
- }
837
+ _adjustBTreeAfterRemove(removedIndex) {
838
+ // 重建受影响的 B-Tree
839
+ for (const field of Object.keys(this._btrees)) {
840
+ this._rebuildBTree(field);
736
841
  }
737
842
  }
738
843
 
@@ -746,6 +851,7 @@ class Table {
746
851
  }
747
852
  }
748
853
  }
854
+ this._rebuildAllBTrees();
749
855
  for (const field of Object.keys(this._indexes)) {
750
856
  this.createIndex(field);
751
857
  }