jsql-neo 1.2.1 → 2.0.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/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,31 +386,63 @@ 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
 
416
+ selectInto(newTableName, newSchema, query = {}) {
417
+ const rows = this._applyFilterOptimized(this._getVisibleRows(), query);
418
+ this._db.createTable(newTableName, newSchema);
419
+ const newTable = this._db._tables[newTableName];
420
+ for (const row of rows) {
421
+ newTable.insert(row);
422
+ }
423
+ return newTable;
424
+ }
425
+
426
+ textSearch(field, terms) {
427
+ const searchTerms = Array.isArray(terms) ? terms : terms.split(/\s+/);
428
+ const rows = this._getVisibleRows();
429
+ const scored = [];
430
+ for (const row of rows) {
431
+ const value = String(row[field] || '').toLowerCase();
432
+ let score = 0;
433
+ for (const term of searchTerms) {
434
+ const t = term.toLowerCase();
435
+ const idx = value.indexOf(t);
436
+ if (idx !== -1) {
437
+ score += 10 + (idx === 0 ? 5 : 0) + (value === t ? 20 : 0)
438
+ + (value.length - idx) / value.length * 5;
439
+ }
440
+ }
441
+ if (score > 0) scored.push({ ...row, _score: score });
442
+ }
443
+ return scored.sort((a, b) => b._score - a._score);
444
+ }
445
+
430
446
  // ============================================================
431
447
  // 索引
432
448
  // ============================================================
@@ -447,6 +463,34 @@ class Table {
447
463
 
448
464
  dropIndex(field) {
449
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
+ }
450
494
  }
451
495
 
452
496
  // ============================================================
@@ -468,9 +512,41 @@ class Table {
468
512
  }
469
513
 
470
514
  // ============================================================
471
- // 内部: 过滤器
515
+ // 内部: 过滤器(B-Tree 加速)
472
516
  // ============================================================
473
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
+
474
550
  _applyFilter(rows, query) {
475
551
  const result = [];
476
552
  for (let i = 0; i < rows.length; i++) {
@@ -483,7 +559,6 @@ class Table {
483
559
 
484
560
  _matchRow(row, query) {
485
561
  for (const [field, condition] of Object.entries(query)) {
486
- // 顶层 $or / $and / $not
487
562
  if (field === '$or') {
488
563
  if (!Array.isArray(condition) || !condition.some(sub => this._matchRow(row, sub))) return false;
489
564
  continue;
@@ -497,21 +572,16 @@ class Table {
497
572
  continue;
498
573
  }
499
574
 
500
- // JSON 路径: 'profile.city' 或 'data.profile.city'
501
575
  const value = this._resolvePath(row, field);
502
576
 
503
577
  if (condition !== null && typeof condition === 'object' && !Array.isArray(condition) && !(condition instanceof RegExp)) {
504
- // 检查是否是操作符
505
578
  const keys = Object.keys(condition);
506
579
  const isOperator = keys.every(k => k.startsWith('$'));
507
580
  if (isOperator && keys.length > 0) {
508
581
  for (const [op, target] of Object.entries(condition)) {
509
- if (!this._matchOperator(value, op, target)) {
510
- return false;
511
- }
582
+ if (!this._matchOperator(value, op, target)) return false;
512
583
  }
513
584
  } else {
514
- // 普通对象:递归匹配子文档
515
585
  if (value !== null && typeof value === 'object') {
516
586
  if (!this._matchRow(value, condition)) return false;
517
587
  } else {
@@ -537,15 +607,13 @@ class Table {
537
607
  case '$nin': return Array.isArray(target) && !target.includes(value);
538
608
  case '$like':
539
609
  if (typeof value !== 'string' || typeof target !== 'string') return false;
540
- const likeRegex = target.replace(/%/g, '.*').replace(/_/g, '.');
541
- return new RegExp('^' + likeRegex + '$', 'i').test(value);
610
+ return new RegExp('^' + target.replace(/%/g, '.*').replace(/_/g, '.') + '$', 'i').test(value);
542
611
  case '$regex':
543
612
  return new RegExp(target).test(String(value));
544
613
  case '$exists':
545
614
  return target ? value !== undefined : value === undefined;
546
615
  case '$between':
547
- return Array.isArray(target) && target.length === 2
548
- && value >= target[0] && value <= target[1];
616
+ return Array.isArray(target) && target.length === 2 && value >= target[0] && value <= target[1];
549
617
  case '$and':
550
618
  return Array.isArray(target) && target.every(sub => this._matchRow({ _val: value }, { _val: sub }));
551
619
  case '$or':
@@ -560,8 +628,7 @@ class Table {
560
628
  return Array.isArray(value) && value.length === target;
561
629
  case '$elemMatch':
562
630
  return Array.isArray(value) && value.some(el =>
563
- typeof el === 'object' ? this._matchRow(el, target) : el === target
564
- );
631
+ typeof el === 'object' ? this._matchRow(el, target) : el === target);
565
632
  case '$isNull':
566
633
  return target ? value === null || value === undefined : value !== null && value !== undefined;
567
634
  case '$startsWith':
@@ -569,18 +636,29 @@ class Table {
569
636
  case '$endsWith':
570
637
  return typeof value === 'string' && value.endsWith(target);
571
638
  case '$search':
572
- // 全文搜索(简单实现)
573
639
  if (typeof value !== 'string') return false;
574
640
  const terms = Array.isArray(target) ? target : [target];
575
641
  return terms.every(t => value.toLowerCase().includes(t.toLowerCase()));
642
+ case '$textSearch':
643
+ if (typeof value !== 'string') return false;
644
+ const sTerms = Array.isArray(target) ? target : target.split(/\s+/);
645
+ return sTerms.some(t => value.toLowerCase().includes(t.toLowerCase()));
646
+ case '$inSub':
647
+ if (!target || !target.table) return false;
648
+ const subRows = target.table._applyFilter(target.table._rows, target.query || {});
649
+ return subRows.some(r => r[target.field] === value);
650
+ case '$notInSub':
651
+ if (!target || !target.table) return false;
652
+ const subRows2 = target.table._applyFilter(target.table._rows, target.query || {});
653
+ return !subRows2.some(r => r[target.field] === value);
654
+ case '$expr':
655
+ if (typeof target === 'function') return target(value);
656
+ return false;
576
657
  default:
577
658
  return false;
578
659
  }
579
660
  }
580
661
 
581
- /**
582
- * 解析 JSON 路径,如 'profile.city' -> row.profile.city
583
- */
584
662
  _resolvePath(row, field) {
585
663
  if (field.includes('.') && !field.startsWith('$')) {
586
664
  const parts = field.split('.');
@@ -594,6 +672,102 @@ class Table {
594
672
  return row[field];
595
673
  }
596
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
+
597
771
  // ============================================================
598
772
  // 内部: 辅助
599
773
  // ============================================================
@@ -602,7 +776,9 @@ class Table {
602
776
  const result = { ...data };
603
777
  for (const [field, defaultValue] of Object.entries(this._defaults)) {
604
778
  if (result[field] === undefined) {
605
- result[field] = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
779
+ result[field] = typeof defaultValue === 'function'
780
+ ? defaultValue()
781
+ : (defaultValue === 'CURRENT_TIMESTAMP' ? now() : defaultValue);
606
782
  }
607
783
  }
608
784
  return result;
@@ -616,13 +792,11 @@ class Table {
616
792
  }
617
793
 
618
794
  _getVisibleRows() {
619
- if (this._softDelete) {
620
- return this._rows.filter(r => !r._deleted);
621
- }
795
+ if (this._softDelete) return this._rows.filter(r => !r._deleted);
622
796
  return this._rows;
623
797
  }
624
798
 
625
- _updateIndexes(rowIndex, data) {
799
+ _addToIndexes(rowIndex, data) {
626
800
  for (const field of Object.keys(this._indexes)) {
627
801
  const value = data[field];
628
802
  if (value !== undefined) {
@@ -632,6 +806,12 @@ class Table {
632
806
  this._indexes[field].get(value).push(rowIndex);
633
807
  }
634
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
+ }
635
815
  }
636
816
 
637
817
  _removeFromIndexes(rowIndex, data) {
@@ -646,21 +826,18 @@ class Table {
646
826
  }
647
827
  }
648
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
+ }
649
835
  }
650
836
 
651
- _adjustIndexesAfterRemove(removedIndex) {
652
- for (const idxName of Object.keys(this._indexes)) {
653
- const idx = this._indexes[idxName];
654
- for (const [key, positions] of idx) {
655
- const newPositions = positions
656
- .filter(p => p !== removedIndex)
657
- .map(p => p > removedIndex ? p - 1 : p);
658
- if (newPositions.length === 0) {
659
- idx.delete(key);
660
- } else {
661
- idx.set(key, newPositions);
662
- }
663
- }
837
+ _adjustBTreeAfterRemove(removedIndex) {
838
+ // 重建受影响的 B-Tree
839
+ for (const field of Object.keys(this._btrees)) {
840
+ this._rebuildBTree(field);
664
841
  }
665
842
  }
666
843
 
@@ -674,6 +851,7 @@ class Table {
674
851
  }
675
852
  }
676
853
  }
854
+ this._rebuildAllBTrees();
677
855
  for (const field of Object.keys(this._indexes)) {
678
856
  this.createIndex(field);
679
857
  }