jsql-neo 1.1.0 → 1.2.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,36 +1,61 @@
1
1
  // © Vexify 2026 All Rights Reserved.
2
2
  /**
3
3
  * JSQL Table — 表操作引擎
4
- * 每张表是一个独立的数据集合,支持 CRUD、查询、索引
4
+ * 支持 DEFAULT、CHECK、外键、UPSERT、TRUNCATE、ALTER TABLE、计算字段、JSON 路径、软删除
5
5
  */
6
6
 
7
+ const Query = require('./query');
8
+
7
9
  class Table {
8
10
  constructor(name, schema, db) {
9
11
  this._name = name;
10
12
  this._schema = schema;
11
13
  this._db = db;
12
- this._rows = []; // 数据行
13
- this._indexes = {}; // 字段名 -> Map<value, [rowIndex, ...]>
14
- this._autoIncrement = 0; // 自增计数器
15
- this._dirty = false; // 是否有未保存的修改
16
- this._hooks = { // 事件钩子
17
- beforeInsert: [],
18
- afterInsert: [],
19
- beforeUpdate: [],
20
- afterUpdate: [],
21
- beforeRemove: [],
22
- afterRemove: []
14
+ this._rows = [];
15
+ this._indexes = {};
16
+ this._autoIncrement = 0;
17
+ this._dirty = false;
18
+ this._hooks = {
19
+ beforeInsert: [], afterInsert: [],
20
+ beforeUpdate: [], afterUpdate: [],
21
+ beforeRemove: [], afterRemove: []
23
22
  };
23
+ this._softDelete = false; // 是否启用软删除
24
+ this._computedFields = {}; // 计算字段
24
25
 
25
- // 解析主键和自增字段
26
+ // 解析列定义
26
27
  this._primaryKey = null;
27
28
  this._autoIncrementField = null;
29
+ this._foreignKeys = [];
30
+ this._checkConstraints = [];
31
+ this._defaults = {};
32
+
28
33
  for (const [field, def] of Object.entries(schema)) {
29
34
  if (def.primaryKey) this._primaryKey = field;
30
35
  if (def.autoIncrement) {
31
36
  this._autoIncrementField = field;
32
37
  if (def.primaryKey) this._primaryKey = field;
33
38
  }
39
+ if (def.default !== undefined) {
40
+ this._defaults[field] = def.default;
41
+ }
42
+ if (def.check) {
43
+ this._checkConstraints.push({ field, fn: def.check });
44
+ }
45
+ if (def.foreignKey) {
46
+ this._foreignKeys.push({
47
+ field,
48
+ table: def.foreignKey.table,
49
+ onDelete: def.foreignKey.onDelete || 'restrict'
50
+ });
51
+ }
52
+ if (def.computed) {
53
+ this._computedFields[field] = def.computed;
54
+ }
55
+ }
56
+
57
+ if (schema._softDelete) {
58
+ this._softDelete = true;
34
59
  }
35
60
  }
36
61
 
@@ -39,19 +64,32 @@ class Table {
39
64
  // ============================================================
40
65
 
41
66
  insert(data) {
42
- // 触发 beforeInsert 钩子
43
67
  for (const hook of this._hooks.beforeInsert) {
44
68
  data = hook({ ...data }) || data;
45
69
  }
46
70
 
47
- // 校验必填字段
71
+ // 填充默认值
72
+ data = this._applyDefaults(data);
73
+
74
+ // 计算字段
75
+ data = this._applyComputed(data);
76
+
77
+ // 校验必填
48
78
  for (const [field, def] of Object.entries(this._schema)) {
79
+ if (field === '_softDelete') continue;
49
80
  if (def.required && (data[field] === undefined || data[field] === null)) {
50
81
  throw new Error(`Field '${field}' is required in table '${this._name}'`);
51
82
  }
52
83
  }
53
84
 
54
- // 唯一约束检查
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
+ // 唯一约束
55
93
  for (const [field, def] of Object.entries(this._schema)) {
56
94
  if (def.unique && data[field] !== undefined) {
57
95
  for (const row of this._rows) {
@@ -63,25 +101,19 @@ class Table {
63
101
  }
64
102
 
65
103
  // 自增
66
- if (this._autoIncrementField) {
67
- if (data[this._autoIncrementField] === undefined) {
68
- this._autoIncrement++;
69
- data[this._autoIncrementField] = this._autoIncrement;
70
- } else if (data[this._autoIncrementField] > this._autoIncrement) {
71
- this._autoIncrement = data[this._autoIncrementField];
72
- }
104
+ if (this._autoIncrementField && data[this._autoIncrementField] === undefined) {
105
+ this._autoIncrement++;
106
+ data[this._autoIncrementField] = this._autoIncrement;
107
+ } else if (this._autoIncrementField && data[this._autoIncrementField] > this._autoIncrement) {
108
+ this._autoIncrement = data[this._autoIncrementField];
73
109
  }
74
110
 
75
111
  const rowIndex = this._rows.length;
76
112
  this._rows.push({ ...data });
77
-
78
- // 更新索引
79
113
  this._updateIndexes(rowIndex, data);
80
-
81
114
  this._dirty = true;
82
115
  this._db._markDirty();
83
116
 
84
- // 触发 afterInsert 钩子
85
117
  for (const hook of this._hooks.afterInsert) {
86
118
  hook(data);
87
119
  }
@@ -93,62 +125,52 @@ class Table {
93
125
  return items.map(item => this.insert(item));
94
126
  }
95
127
 
128
+ /**
129
+ * UPSERT — 有则更新,无则插入
130
+ * 按主键或唯一字段判断
131
+ */
132
+ upsert(data) {
133
+ if (this._primaryKey && data[this._primaryKey] !== undefined) {
134
+ const existing = this.findById(data[this._primaryKey]);
135
+ if (existing) {
136
+ this.updateById(data[this._primaryKey], data);
137
+ return this.findById(data[this._primaryKey]);
138
+ }
139
+ }
140
+ return this.insert(data);
141
+ }
142
+
96
143
  // ============================================================
97
144
  // 查询
98
145
  // ============================================================
99
146
 
100
- /**
101
- * 查找所有匹配的行
102
- */
103
147
  find(query = {}) {
104
- let rows = this._rows;
105
-
148
+ let rows = this._getVisibleRows();
106
149
  if (Object.keys(query).length > 0) {
107
150
  rows = this._applyFilter(rows, query);
108
151
  }
109
-
110
152
  return rows.map(r => ({ ...r }));
111
153
  }
112
154
 
113
- /**
114
- * 查找第一条匹配的行
115
- */
116
155
  findOne(query = {}) {
117
156
  const rows = this.find(query);
118
157
  return rows.length > 0 ? rows[0] : null;
119
158
  }
120
159
 
121
- /**
122
- * 按主键查找
123
- */
124
160
  findById(id) {
125
- if (!this._primaryKey) {
126
- throw new Error(`Table '${this._name}' has no primary key`);
127
- }
161
+ if (!this._primaryKey) throw new Error(`Table '${this._name}' has no primary key`);
128
162
  return this.findOne({ [this._primaryKey]: id });
129
163
  }
130
164
 
131
- /**
132
- * 获取所有行
133
- */
134
165
  findAll() {
135
- return this._rows.map(r => ({ ...r }));
166
+ return this._getVisibleRows().map(r => ({ ...r }));
136
167
  }
137
168
 
138
- /**
139
- * 计数
140
- */
141
169
  count(query = {}) {
142
- if (Object.keys(query).length === 0) {
143
- return this._rows.length;
144
- }
145
- return this._applyFilter(this._rows, query).length;
170
+ if (Object.keys(query).length === 0) return this._getVisibleRows().length;
171
+ return this._applyFilter(this._getVisibleRows(), query).length;
146
172
  }
147
173
 
148
- // ============================================================
149
- // 链式查询
150
- // ============================================================
151
-
152
174
  where(conditions) {
153
175
  return new Query(this, conditions);
154
176
  }
@@ -158,12 +180,11 @@ class Table {
158
180
  // ============================================================
159
181
 
160
182
  update(query, updates) {
161
- // 触发 beforeUpdate 钩子
162
183
  for (const hook of this._hooks.beforeUpdate) {
163
184
  updates = hook(query, { ...updates }) || updates;
164
185
  }
165
186
 
166
- const matched = this._applyFilter(this._rows, query);
187
+ const matched = this._applyFilter(this._getVisibleRows(), query);
167
188
  let count = 0;
168
189
 
169
190
  for (const row of matched) {
@@ -172,7 +193,7 @@ class Table {
172
193
 
173
194
  const oldData = { ...this._rows[index] };
174
195
 
175
- // 唯一约束检查
196
+ // 唯一约束
176
197
  for (const [field, def] of Object.entries(this._schema)) {
177
198
  if (def.unique && updates[field] !== undefined) {
178
199
  for (let i = 0; i < this._rows.length; i++) {
@@ -183,21 +204,23 @@ class Table {
183
204
  }
184
205
  }
185
206
 
186
- // 更新
187
- Object.assign(this._rows[index], updates);
207
+ // CHECK 约束
208
+ const merged = { ...this._rows[index], ...updates };
209
+ for (const { field, fn } of this._checkConstraints) {
210
+ if (merged[field] !== undefined && !fn(merged[field], merged)) {
211
+ throw new Error(`CHECK constraint failed on '${this._name}.${field}'`);
212
+ }
213
+ }
188
214
 
189
- // 更新索引
215
+ Object.assign(this._rows[index], updates);
190
216
  this._removeFromIndexes(index, oldData);
191
217
  this._updateIndexes(index, this._rows[index]);
192
-
193
218
  count++;
194
219
  }
195
220
 
196
221
  if (count > 0) {
197
222
  this._dirty = true;
198
223
  this._db._markDirty();
199
-
200
- // 触发 afterUpdate 钩子
201
224
  for (const hook of this._hooks.afterUpdate) {
202
225
  hook(query, updates, count);
203
226
  }
@@ -206,9 +229,7 @@ class Table {
206
229
  }
207
230
 
208
231
  updateById(id, updates) {
209
- if (!this._primaryKey) {
210
- throw new Error(`Table '${this._name}' has no primary key`);
211
- }
232
+ if (!this._primaryKey) throw new Error(`Table '${this._name}' has no primary key`);
212
233
  return this.update({ [this._primaryKey]: id }, updates);
213
234
  }
214
235
 
@@ -217,65 +238,183 @@ class Table {
217
238
  // ============================================================
218
239
 
219
240
  remove(query = {}) {
220
- // 触发 beforeRemove 钩子
221
241
  for (const hook of this._hooks.beforeRemove) {
222
- const result = hook(query);
223
- if (result === false) return 0; // 钩子取消删除
242
+ if (hook(query) === false) return 0;
224
243
  }
225
244
 
226
245
  if (Object.keys(query).length === 0) {
227
- // 删除全部
228
- const count = this._rows.length;
229
- this._rows = [];
230
- this._indexes = {};
231
- this._autoIncrement = 0;
232
- this._dirty = true;
233
- this._db._markDirty();
234
- return count;
246
+ return this._truncate();
235
247
  }
236
248
 
237
- const toRemove = this._applyFilter(this._rows, query);
249
+ const toRemove = this._applyFilter(this._getVisibleRows(), query);
250
+ let count = 0;
251
+
238
252
  for (const row of toRemove) {
239
253
  const index = this._rows.indexOf(row);
240
- if (index !== -1) {
241
- this._removeFromIndexes(index, row);
242
- this._rows.splice(index, 1);
243
- // 调整后续索引
244
- for (const idxName of Object.keys(this._indexes)) {
245
- const idx = this._indexes[idxName];
246
- for (const [key, positions] of idx) {
247
- const newPositions = positions
248
- .filter(p => p !== index)
249
- .map(p => p > index ? p - 1 : p);
250
- if (newPositions.length === 0) {
251
- idx.delete(key);
252
- } else {
253
- idx.set(key, newPositions);
254
- }
254
+ if (index === -1) continue;
255
+
256
+ // 外键级联删除
257
+ for (const fk of this._foreignKeys) {
258
+ if (fk.onDelete === 'cascade') {
259
+ const refTable = this._db._tables[fk.table];
260
+ if (refTable) {
261
+ refTable.remove({ [fk.field]: row[this._primaryKey] });
255
262
  }
256
263
  }
257
264
  }
265
+
266
+ if (this._softDelete) {
267
+ this._rows[index]._deleted = true;
268
+ this._rows[index]._deletedAt = Date.now();
269
+ } else {
270
+ this._removeFromIndexes(index, row);
271
+ this._rows.splice(index, 1);
272
+ this._adjustIndexesAfterRemove(index);
273
+ }
274
+ count++;
258
275
  }
259
276
 
260
- if (toRemove.length > 0) {
277
+ if (count > 0) {
261
278
  this._dirty = true;
262
279
  this._db._markDirty();
263
-
264
- // 触发 afterRemove 钩子
265
280
  for (const hook of this._hooks.afterRemove) {
266
- hook(query, toRemove.length);
281
+ hook(query, count);
267
282
  }
268
283
  }
269
- return toRemove.length;
284
+ return count;
270
285
  }
271
286
 
272
287
  removeById(id) {
273
- if (!this._primaryKey) {
274
- throw new Error(`Table '${this._name}' has no primary key`);
275
- }
288
+ if (!this._primaryKey) throw new Error(`Table '${this._name}' has no primary key`);
276
289
  return this.remove({ [this._primaryKey]: id });
277
290
  }
278
291
 
292
+ /**
293
+ * 软删除 — 标记为已删除,可恢复
294
+ */
295
+ removeSoft(query = {}) {
296
+ this._softDelete = true;
297
+ const result = this.remove(query);
298
+ return result;
299
+ }
300
+
301
+ /**
302
+ * 恢复软删除的行
303
+ */
304
+ restore(query = {}) {
305
+ if (!this._softDelete) throw new Error('Soft delete is not enabled');
306
+ const matched = this._applyFilter(this._rows, { ...query, _deleted: true });
307
+ for (const row of matched) {
308
+ const index = this._rows.indexOf(row);
309
+ if (index !== -1) {
310
+ delete this._rows[index]._deleted;
311
+ delete this._rows[index]._deletedAt;
312
+ }
313
+ }
314
+ return matched.length;
315
+ }
316
+
317
+ /**
318
+ * 清空表(保留结构)
319
+ */
320
+ truncate() {
321
+ return this._truncate();
322
+ }
323
+
324
+ _truncate() {
325
+ const count = this._rows.length;
326
+ this._rows = [];
327
+ this._indexes = {};
328
+ this._autoIncrement = 0;
329
+ this._dirty = true;
330
+ this._db._markDirty();
331
+ return count;
332
+ }
333
+
334
+ // ============================================================
335
+ // ALTER TABLE
336
+ // ============================================================
337
+
338
+ addColumn(field, definition) {
339
+ if (this._schema[field]) throw new Error(`Column '${field}' already exists`);
340
+ this._schema[field] = definition;
341
+ if (definition.default !== undefined) this._defaults[field] = definition.default;
342
+ if (definition.check) this._checkConstraints.push({ field, fn: definition.check });
343
+ if (definition.foreignKey) {
344
+ this._foreignKeys.push({
345
+ field,
346
+ table: definition.foreignKey.table,
347
+ onDelete: definition.foreignKey.onDelete || 'restrict'
348
+ });
349
+ }
350
+ if (definition.computed) this._computedFields[field] = definition.computed;
351
+ this._dirty = true;
352
+ this._db._markDirty();
353
+ return this;
354
+ }
355
+
356
+ dropColumn(field) {
357
+ if (!this._schema[field]) throw new Error(`Column '${field}' does not exist`);
358
+ delete this._schema[field];
359
+ delete this._defaults[field];
360
+ this._checkConstraints = this._checkConstraints.filter(c => c.field !== field);
361
+ this._foreignKeys = this._foreignKeys.filter(fk => fk.field !== field);
362
+ delete this._computedFields[field];
363
+ // 移除所有行中的该字段
364
+ for (const row of this._rows) {
365
+ delete row[field];
366
+ }
367
+ this._dirty = true;
368
+ this._db._markDirty();
369
+ return this;
370
+ }
371
+
372
+ renameColumn(oldName, newName) {
373
+ if (!this._schema[oldName]) throw new Error(`Column '${oldName}' does not exist`);
374
+ if (this._schema[newName]) throw new Error(`Column '${newName}' already exists`);
375
+ this._schema[newName] = this._schema[oldName];
376
+ delete this._schema[oldName];
377
+ if (this._defaults[oldName] !== undefined) {
378
+ this._defaults[newName] = this._defaults[oldName];
379
+ delete this._defaults[oldName];
380
+ }
381
+ for (const row of this._rows) {
382
+ row[newName] = row[oldName];
383
+ delete row[oldName];
384
+ }
385
+ if (this._primaryKey === oldName) this._primaryKey = newName;
386
+ if (this._autoIncrementField === oldName) this._autoIncrementField = newName;
387
+ this._dirty = true;
388
+ this._db._markDirty();
389
+ return this;
390
+ }
391
+
392
+ modifyColumn(field, definition) {
393
+ if (!this._schema[field]) throw new Error(`Column '${field}' does not exist`);
394
+ this._schema[field] = { ...this._schema[field], ...definition };
395
+ if (definition.default !== undefined) this._defaults[field] = definition.default;
396
+ this._checkConstraints = this._checkConstraints.filter(c => c.field !== field);
397
+ if (definition.check) this._checkConstraints.push({ field, fn: definition.check });
398
+ this._dirty = true;
399
+ this._db._markDirty();
400
+ return this;
401
+ }
402
+
403
+ /**
404
+ * 获取表结构
405
+ */
406
+ describe() {
407
+ return {
408
+ name: this._name,
409
+ columns: { ...this._schema },
410
+ primaryKey: this._primaryKey,
411
+ rowCount: this._getVisibleRows().length,
412
+ indexCount: Object.keys(this._indexes).length,
413
+ softDelete: this._softDelete,
414
+ foreignKeys: this._foreignKeys.map(fk => ({ field: fk.field, table: fk.table }))
415
+ };
416
+ }
417
+
279
418
  // ============================================================
280
419
  // 索引
281
420
  // ============================================================
@@ -302,17 +441,6 @@ class Table {
302
441
  // 事件钩子
303
442
  // ============================================================
304
443
 
305
- /**
306
- * 注册事件钩子
307
- * @param {string} event - 'beforeInsert' | 'afterInsert' | 'beforeUpdate' | 'afterUpdate' | 'beforeRemove' | 'afterRemove'
308
- * @param {function} callback
309
- * - beforeInsert(data) => 返回修改后的 data
310
- * - afterInsert(data)
311
- * - beforeUpdate(query, updates) => 返回修改后的 updates
312
- * - afterUpdate(query, updates, count)
313
- * - beforeRemove(query) => 返回 false 阻止删除
314
- * - afterRemove(query, count)
315
- */
316
444
  on(event, callback) {
317
445
  if (this._hooks[event]) {
318
446
  this._hooks[event].push(callback);
@@ -328,7 +456,7 @@ class Table {
328
456
  }
329
457
 
330
458
  // ============================================================
331
- // 内部方法
459
+ // 内部: 过滤器
332
460
  // ============================================================
333
461
 
334
462
  _applyFilter(rows, query) {
@@ -343,20 +471,29 @@ class Table {
343
471
 
344
472
  _matchRow(row, query) {
345
473
  for (const [field, condition] of Object.entries(query)) {
346
- const value = row[field];
347
-
348
- // 操作符对象: { $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $like, $regex }
349
- if (condition !== null && typeof condition === 'object' && !Array.isArray(condition)) {
350
- for (const [op, target] of Object.entries(condition)) {
351
- if (!this._matchOperator(value, op, target)) {
474
+ // JSON 路径: 'profile.city' 或 'data.profile.city'
475
+ const value = this._resolvePath(row, field);
476
+
477
+ if (condition !== null && typeof condition === 'object' && !Array.isArray(condition) && !(condition instanceof RegExp)) {
478
+ // 检查是否是操作符
479
+ const keys = Object.keys(condition);
480
+ const isOperator = keys.every(k => k.startsWith('$'));
481
+ if (isOperator && keys.length > 0) {
482
+ for (const [op, target] of Object.entries(condition)) {
483
+ if (!this._matchOperator(value, op, target)) {
484
+ return false;
485
+ }
486
+ }
487
+ } else {
488
+ // 普通对象:递归匹配子文档
489
+ if (value !== null && typeof value === 'object') {
490
+ if (!this._matchRow(value, condition)) return false;
491
+ } else {
352
492
  return false;
353
493
  }
354
494
  }
355
495
  } else {
356
- // 简单等值匹配
357
- if (value !== condition) {
358
- return false;
359
- }
496
+ if (value !== condition) return false;
360
497
  }
361
498
  }
362
499
  return true;
@@ -374,8 +511,7 @@ class Table {
374
511
  case '$nin': return Array.isArray(target) && !target.includes(value);
375
512
  case '$like':
376
513
  if (typeof value !== 'string' || typeof target !== 'string') return false;
377
- const regex = new RegExp(target.replace(/%/g, '.*').replace(/_/g, '.'), 'i');
378
- return regex.test(value);
514
+ return new RegExp(target.replace(/%/g, '.*').replace(/_/g, '.'), 'i').test(value);
379
515
  case '$regex':
380
516
  return new RegExp(target).test(String(value));
381
517
  case '$exists':
@@ -383,13 +519,82 @@ class Table {
383
519
  case '$between':
384
520
  return Array.isArray(target) && target.length === 2
385
521
  && value >= target[0] && value <= target[1];
522
+ case '$and':
523
+ return Array.isArray(target) && target.every(sub => this._matchRow({ _val: value }, { _val: sub }));
386
524
  case '$or':
387
- return Array.isArray(target) && target.some(sub => this._matchRow({ [op]: value }, { [op]: sub }));
525
+ return Array.isArray(target) && target.some(sub => this._matchRow({ _val: value }, { _val: sub }));
526
+ case '$not':
527
+ return !this._matchRow({ _val: value }, { _val: target });
528
+ case '$contains':
529
+ return Array.isArray(value) && value.includes(target);
530
+ case '$type':
531
+ return typeof value === target;
532
+ case '$size':
533
+ return Array.isArray(value) && value.length === target;
534
+ case '$elemMatch':
535
+ return Array.isArray(value) && value.some(el =>
536
+ typeof el === 'object' ? this._matchRow(el, target) : el === target
537
+ );
538
+ case '$isNull':
539
+ return target ? value === null || value === undefined : value !== null && value !== undefined;
540
+ case '$startsWith':
541
+ return typeof value === 'string' && value.startsWith(target);
542
+ case '$endsWith':
543
+ return typeof value === 'string' && value.endsWith(target);
544
+ case '$search':
545
+ // 全文搜索(简单实现)
546
+ if (typeof value !== 'string') return false;
547
+ const terms = Array.isArray(target) ? target : [target];
548
+ return terms.every(t => value.toLowerCase().includes(t.toLowerCase()));
388
549
  default:
389
550
  return false;
390
551
  }
391
552
  }
392
553
 
554
+ /**
555
+ * 解析 JSON 路径,如 'profile.city' -> row.profile.city
556
+ */
557
+ _resolvePath(row, field) {
558
+ if (field.includes('.') && !field.startsWith('$')) {
559
+ const parts = field.split('.');
560
+ let value = row;
561
+ for (const part of parts) {
562
+ if (value === null || value === undefined) return undefined;
563
+ value = value[part];
564
+ }
565
+ return value;
566
+ }
567
+ return row[field];
568
+ }
569
+
570
+ // ============================================================
571
+ // 内部: 辅助
572
+ // ============================================================
573
+
574
+ _applyDefaults(data) {
575
+ const result = { ...data };
576
+ for (const [field, defaultValue] of Object.entries(this._defaults)) {
577
+ if (result[field] === undefined) {
578
+ result[field] = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
579
+ }
580
+ }
581
+ return result;
582
+ }
583
+
584
+ _applyComputed(data) {
585
+ for (const [field, fn] of Object.entries(this._computedFields)) {
586
+ data[field] = fn(data);
587
+ }
588
+ return data;
589
+ }
590
+
591
+ _getVisibleRows() {
592
+ if (this._softDelete) {
593
+ return this._rows.filter(r => !r._deleted);
594
+ }
595
+ return this._rows;
596
+ }
597
+
393
598
  _updateIndexes(rowIndex, data) {
394
599
  for (const field of Object.keys(this._indexes)) {
395
600
  const value = data[field];
@@ -416,9 +621,24 @@ class Table {
416
621
  }
417
622
  }
418
623
 
624
+ _adjustIndexesAfterRemove(removedIndex) {
625
+ for (const idxName of Object.keys(this._indexes)) {
626
+ const idx = this._indexes[idxName];
627
+ for (const [key, positions] of idx) {
628
+ const newPositions = positions
629
+ .filter(p => p !== removedIndex)
630
+ .map(p => p > removedIndex ? p - 1 : p);
631
+ if (newPositions.length === 0) {
632
+ idx.delete(key);
633
+ } else {
634
+ idx.set(key, newPositions);
635
+ }
636
+ }
637
+ }
638
+ }
639
+
419
640
  _loadRows(rows) {
420
641
  this._rows = rows;
421
- // 重建自增
422
642
  if (this._autoIncrementField) {
423
643
  for (const row of rows) {
424
644
  const val = row[this._autoIncrementField];
@@ -427,183 +647,14 @@ class Table {
427
647
  }
428
648
  }
429
649
  }
430
- // 重建所有索引
431
650
  for (const field of Object.keys(this._indexes)) {
432
651
  this.createIndex(field);
433
652
  }
434
653
  }
435
654
 
436
- // ============================================================
437
- // 导出
438
- // ============================================================
439
-
440
655
  toJSON() {
441
656
  return this._rows;
442
657
  }
443
658
  }
444
659
 
445
- // ============================================================
446
- // 链式查询构建器
447
- // ============================================================
448
-
449
- class Query {
450
- constructor(table, conditions) {
451
- this._table = table;
452
- this._conditions = conditions;
453
- this._orderBy = null;
454
- this._orderDir = 'asc';
455
- this._limitCount = null;
456
- this._offsetCount = 0;
457
- this._selectFields = null;
458
- this._groupBy = null;
459
- }
460
-
461
- orderBy(field, dir = 'asc') {
462
- this._orderBy = field;
463
- this._orderDir = dir;
464
- return this;
465
- }
466
-
467
- limit(n) {
468
- this._limitCount = n;
469
- return this;
470
- }
471
-
472
- offset(n) {
473
- this._offsetCount = n;
474
- return this;
475
- }
476
-
477
- select(fields) {
478
- this._selectFields = Array.isArray(fields) ? fields : [fields];
479
- return this;
480
- }
481
-
482
- groupBy(field) {
483
- this._groupBy = field;
484
- return this;
485
- }
486
-
487
- get() {
488
- let rows = this._table._applyFilter(this._table._rows, this._conditions);
489
-
490
- // 排序
491
- if (this._orderBy) {
492
- rows = rows.sort((a, b) => {
493
- const va = a[this._orderBy];
494
- const vb = b[this._orderBy];
495
- if (va < vb) return this._orderDir === 'asc' ? -1 : 1;
496
- if (va > vb) return this._orderDir === 'asc' ? 1 : -1;
497
- return 0;
498
- });
499
- }
500
-
501
- // 分页
502
- if (this._offsetCount > 0) {
503
- rows = rows.slice(this._offsetCount);
504
- }
505
- if (this._limitCount !== null) {
506
- rows = rows.slice(0, this._limitCount);
507
- }
508
-
509
- // 字段选择
510
- rows = rows.map(r => ({ ...r }));
511
- if (this._selectFields) {
512
- rows = rows.map(r => {
513
- const obj = {};
514
- for (const f of this._selectFields) {
515
- obj[f] = r[f];
516
- }
517
- return obj;
518
- });
519
- }
520
-
521
- return rows;
522
- }
523
-
524
- first() {
525
- const rows = this.limit(1).get();
526
- return rows.length > 0 ? rows[0] : null;
527
- }
528
-
529
- count() {
530
- const rows = this._table._applyFilter(this._table._rows, this._conditions);
531
- return rows.length;
532
- }
533
-
534
- sum(field) {
535
- const rows = this._applyGroupedRows();
536
- return rows.reduce((acc, r) => acc + (Number(r[field]) || 0), 0);
537
- }
538
-
539
- avg(field) {
540
- const rows = this._applyGroupedRows();
541
- if (rows.length === 0) return 0;
542
- return this.sum(field) / rows.length;
543
- }
544
-
545
- min(field) {
546
- const rows = this._applyGroupedRows();
547
- if (rows.length === 0) return null;
548
- return rows.reduce((min, r) => {
549
- const v = Number(r[field]);
550
- return (min === null || v < min) ? v : min;
551
- }, null);
552
- }
553
-
554
- max(field) {
555
- const rows = this._applyGroupedRows();
556
- if (rows.length === 0) return null;
557
- return rows.reduce((max, r) => {
558
- const v = Number(r[field]);
559
- return (max === null || v > max) ? v : max;
560
- }, null);
561
- }
562
-
563
- /**
564
- * 获取分组统计结果
565
- * @returns {Array<{ _group: value, _count, _sum, _avg, _min, _max }>}
566
- */
567
- groupStats(field) {
568
- if (!this._groupBy) throw new Error('groupStats() requires groupBy() to be called first');
569
- const rows = this._table._applyFilter(this._table._rows, this._conditions);
570
- const groups = {};
571
-
572
- for (const row of rows) {
573
- const key = String(row[this._groupBy] ?? '__null__');
574
- if (!groups[key]) {
575
- groups[key] = { _group: row[this._groupBy], _count: 0, _sum: 0, _min: null, _max: null, _rows: [] };
576
- }
577
- const g = groups[key];
578
- g._count++;
579
- const v = Number(row[field]) || 0;
580
- g._sum += v;
581
- g._min = g._min === null ? v : Math.min(g._min, v);
582
- g._max = g._max === null ? v : Math.max(g._max, v);
583
- g._rows.push(row);
584
- }
585
-
586
- const result = Object.values(groups);
587
- result.forEach(g => {
588
- g._avg = g._count > 0 ? g._sum / g._count : 0;
589
- delete g._rows;
590
- });
591
-
592
- return result;
593
- }
594
-
595
- _applyGroupedRows() {
596
- let rows = this._table._applyFilter(this._table._rows, this._conditions);
597
- return rows;
598
- }
599
-
600
- update(updates) {
601
- return this._table.update(this._conditions, updates);
602
- }
603
-
604
- remove() {
605
- return this._table.remove(this._conditions);
606
- }
607
- }
608
-
609
660
  module.exports = Table;