jsql-neo 1.0.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,28 +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
-
17
- // 解析主键和自增字段
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: []
22
+ };
23
+ this._softDelete = false; // 是否启用软删除
24
+ this._computedFields = {}; // 计算字段
25
+
26
+ // 解析列定义
18
27
  this._primaryKey = null;
19
28
  this._autoIncrementField = null;
29
+ this._foreignKeys = [];
30
+ this._checkConstraints = [];
31
+ this._defaults = {};
32
+
20
33
  for (const [field, def] of Object.entries(schema)) {
21
34
  if (def.primaryKey) this._primaryKey = field;
22
35
  if (def.autoIncrement) {
23
36
  this._autoIncrementField = field;
24
37
  if (def.primaryKey) this._primaryKey = field;
25
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;
26
59
  }
27
60
  }
28
61
 
@@ -31,14 +64,32 @@ class Table {
31
64
  // ============================================================
32
65
 
33
66
  insert(data) {
34
- // 校验必填字段
67
+ for (const hook of this._hooks.beforeInsert) {
68
+ data = hook({ ...data }) || data;
69
+ }
70
+
71
+ // 填充默认值
72
+ data = this._applyDefaults(data);
73
+
74
+ // 计算字段
75
+ data = this._applyComputed(data);
76
+
77
+ // 校验必填
35
78
  for (const [field, def] of Object.entries(this._schema)) {
79
+ if (field === '_softDelete') continue;
36
80
  if (def.required && (data[field] === undefined || data[field] === null)) {
37
81
  throw new Error(`Field '${field}' is required in table '${this._name}'`);
38
82
  }
39
83
  }
40
84
 
41
- // 唯一约束检查
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
+ // 唯一约束
42
93
  for (const [field, def] of Object.entries(this._schema)) {
43
94
  if (def.unique && data[field] !== undefined) {
44
95
  for (const row of this._rows) {
@@ -50,23 +101,23 @@ class Table {
50
101
  }
51
102
 
52
103
  // 自增
53
- if (this._autoIncrementField) {
54
- if (data[this._autoIncrementField] === undefined) {
55
- this._autoIncrement++;
56
- data[this._autoIncrementField] = this._autoIncrement;
57
- } else if (data[this._autoIncrementField] > this._autoIncrement) {
58
- this._autoIncrement = data[this._autoIncrementField];
59
- }
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];
60
109
  }
61
110
 
62
111
  const rowIndex = this._rows.length;
63
112
  this._rows.push({ ...data });
64
-
65
- // 更新索引
66
113
  this._updateIndexes(rowIndex, data);
67
-
68
114
  this._dirty = true;
69
115
  this._db._markDirty();
116
+
117
+ for (const hook of this._hooks.afterInsert) {
118
+ hook(data);
119
+ }
120
+
70
121
  return data;
71
122
  }
72
123
 
@@ -74,62 +125,52 @@ class Table {
74
125
  return items.map(item => this.insert(item));
75
126
  }
76
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
+
77
143
  // ============================================================
78
144
  // 查询
79
145
  // ============================================================
80
146
 
81
- /**
82
- * 查找所有匹配的行
83
- */
84
147
  find(query = {}) {
85
- let rows = this._rows;
86
-
148
+ let rows = this._getVisibleRows();
87
149
  if (Object.keys(query).length > 0) {
88
150
  rows = this._applyFilter(rows, query);
89
151
  }
90
-
91
152
  return rows.map(r => ({ ...r }));
92
153
  }
93
154
 
94
- /**
95
- * 查找第一条匹配的行
96
- */
97
155
  findOne(query = {}) {
98
156
  const rows = this.find(query);
99
157
  return rows.length > 0 ? rows[0] : null;
100
158
  }
101
159
 
102
- /**
103
- * 按主键查找
104
- */
105
160
  findById(id) {
106
- if (!this._primaryKey) {
107
- throw new Error(`Table '${this._name}' has no primary key`);
108
- }
161
+ if (!this._primaryKey) throw new Error(`Table '${this._name}' has no primary key`);
109
162
  return this.findOne({ [this._primaryKey]: id });
110
163
  }
111
164
 
112
- /**
113
- * 获取所有行
114
- */
115
165
  findAll() {
116
- return this._rows.map(r => ({ ...r }));
166
+ return this._getVisibleRows().map(r => ({ ...r }));
117
167
  }
118
168
 
119
- /**
120
- * 计数
121
- */
122
169
  count(query = {}) {
123
- if (Object.keys(query).length === 0) {
124
- return this._rows.length;
125
- }
126
- 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;
127
172
  }
128
173
 
129
- // ============================================================
130
- // 链式查询
131
- // ============================================================
132
-
133
174
  where(conditions) {
134
175
  return new Query(this, conditions);
135
176
  }
@@ -139,7 +180,11 @@ class Table {
139
180
  // ============================================================
140
181
 
141
182
  update(query, updates) {
142
- const matched = this._applyFilter(this._rows, query);
183
+ for (const hook of this._hooks.beforeUpdate) {
184
+ updates = hook(query, { ...updates }) || updates;
185
+ }
186
+
187
+ const matched = this._applyFilter(this._getVisibleRows(), query);
143
188
  let count = 0;
144
189
 
145
190
  for (const row of matched) {
@@ -148,7 +193,7 @@ class Table {
148
193
 
149
194
  const oldData = { ...this._rows[index] };
150
195
 
151
- // 唯一约束检查
196
+ // 唯一约束
152
197
  for (const [field, def] of Object.entries(this._schema)) {
153
198
  if (def.unique && updates[field] !== undefined) {
154
199
  for (let i = 0; i < this._rows.length; i++) {
@@ -159,27 +204,32 @@ class Table {
159
204
  }
160
205
  }
161
206
 
162
- // 更新
163
- 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
+ }
164
214
 
165
- // 更新索引
215
+ Object.assign(this._rows[index], updates);
166
216
  this._removeFromIndexes(index, oldData);
167
217
  this._updateIndexes(index, this._rows[index]);
168
-
169
218
  count++;
170
219
  }
171
220
 
172
221
  if (count > 0) {
173
222
  this._dirty = true;
174
223
  this._db._markDirty();
224
+ for (const hook of this._hooks.afterUpdate) {
225
+ hook(query, updates, count);
226
+ }
175
227
  }
176
228
  return count;
177
229
  }
178
230
 
179
231
  updateById(id, updates) {
180
- if (!this._primaryKey) {
181
- throw new Error(`Table '${this._name}' has no primary key`);
182
- }
232
+ if (!this._primaryKey) throw new Error(`Table '${this._name}' has no primary key`);
183
233
  return this.update({ [this._primaryKey]: id }, updates);
184
234
  }
185
235
 
@@ -188,54 +238,183 @@ class Table {
188
238
  // ============================================================
189
239
 
190
240
  remove(query = {}) {
241
+ for (const hook of this._hooks.beforeRemove) {
242
+ if (hook(query) === false) return 0;
243
+ }
244
+
191
245
  if (Object.keys(query).length === 0) {
192
- // 删除全部
193
- const count = this._rows.length;
194
- this._rows = [];
195
- this._indexes = {};
196
- this._autoIncrement = 0;
197
- this._dirty = true;
198
- this._db._markDirty();
199
- return count;
246
+ return this._truncate();
200
247
  }
201
248
 
202
- const toRemove = this._applyFilter(this._rows, query);
249
+ const toRemove = this._applyFilter(this._getVisibleRows(), query);
250
+ let count = 0;
251
+
203
252
  for (const row of toRemove) {
204
253
  const index = this._rows.indexOf(row);
205
- if (index !== -1) {
206
- this._removeFromIndexes(index, row);
207
- this._rows.splice(index, 1);
208
- // 调整后续索引
209
- for (const idxName of Object.keys(this._indexes)) {
210
- const idx = this._indexes[idxName];
211
- for (const [key, positions] of idx) {
212
- const newPositions = positions
213
- .filter(p => p !== index)
214
- .map(p => p > index ? p - 1 : p);
215
- if (newPositions.length === 0) {
216
- idx.delete(key);
217
- } else {
218
- idx.set(key, newPositions);
219
- }
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] });
220
262
  }
221
263
  }
222
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++;
223
275
  }
224
276
 
225
- if (toRemove.length > 0) {
277
+ if (count > 0) {
226
278
  this._dirty = true;
227
279
  this._db._markDirty();
280
+ for (const hook of this._hooks.afterRemove) {
281
+ hook(query, count);
282
+ }
228
283
  }
229
- return toRemove.length;
284
+ return count;
230
285
  }
231
286
 
232
287
  removeById(id) {
233
- if (!this._primaryKey) {
234
- throw new Error(`Table '${this._name}' has no primary key`);
235
- }
288
+ if (!this._primaryKey) throw new Error(`Table '${this._name}' has no primary key`);
236
289
  return this.remove({ [this._primaryKey]: id });
237
290
  }
238
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
+
239
418
  // ============================================================
240
419
  // 索引
241
420
  // ============================================================
@@ -259,7 +438,25 @@ class Table {
259
438
  }
260
439
 
261
440
  // ============================================================
262
- // 内部方法
441
+ // 事件钩子
442
+ // ============================================================
443
+
444
+ on(event, callback) {
445
+ if (this._hooks[event]) {
446
+ this._hooks[event].push(callback);
447
+ }
448
+ return this;
449
+ }
450
+
451
+ off(event, callback) {
452
+ if (this._hooks[event]) {
453
+ this._hooks[event] = this._hooks[event].filter(cb => cb !== callback);
454
+ }
455
+ return this;
456
+ }
457
+
458
+ // ============================================================
459
+ // 内部: 过滤器
263
460
  // ============================================================
264
461
 
265
462
  _applyFilter(rows, query) {
@@ -274,20 +471,29 @@ class Table {
274
471
 
275
472
  _matchRow(row, query) {
276
473
  for (const [field, condition] of Object.entries(query)) {
277
- const value = row[field];
278
-
279
- // 操作符对象: { $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $like, $regex }
280
- if (condition !== null && typeof condition === 'object' && !Array.isArray(condition)) {
281
- for (const [op, target] of Object.entries(condition)) {
282
- 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 {
283
492
  return false;
284
493
  }
285
494
  }
286
495
  } else {
287
- // 简单等值匹配
288
- if (value !== condition) {
289
- return false;
290
- }
496
+ if (value !== condition) return false;
291
497
  }
292
498
  }
293
499
  return true;
@@ -305,8 +511,7 @@ class Table {
305
511
  case '$nin': return Array.isArray(target) && !target.includes(value);
306
512
  case '$like':
307
513
  if (typeof value !== 'string' || typeof target !== 'string') return false;
308
- const regex = new RegExp(target.replace(/%/g, '.*').replace(/_/g, '.'), 'i');
309
- return regex.test(value);
514
+ return new RegExp(target.replace(/%/g, '.*').replace(/_/g, '.'), 'i').test(value);
310
515
  case '$regex':
311
516
  return new RegExp(target).test(String(value));
312
517
  case '$exists':
@@ -314,13 +519,82 @@ class Table {
314
519
  case '$between':
315
520
  return Array.isArray(target) && target.length === 2
316
521
  && value >= target[0] && value <= target[1];
522
+ case '$and':
523
+ return Array.isArray(target) && target.every(sub => this._matchRow({ _val: value }, { _val: sub }));
317
524
  case '$or':
318
- 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()));
319
549
  default:
320
550
  return false;
321
551
  }
322
552
  }
323
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
+
324
598
  _updateIndexes(rowIndex, data) {
325
599
  for (const field of Object.keys(this._indexes)) {
326
600
  const value = data[field];
@@ -347,9 +621,24 @@ class Table {
347
621
  }
348
622
  }
349
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
+
350
640
  _loadRows(rows) {
351
641
  this._rows = rows;
352
- // 重建自增
353
642
  if (this._autoIncrementField) {
354
643
  for (const row of rows) {
355
644
  const val = row[this._autoIncrementField];
@@ -358,111 +647,14 @@ class Table {
358
647
  }
359
648
  }
360
649
  }
361
- // 重建所有索引
362
650
  for (const field of Object.keys(this._indexes)) {
363
651
  this.createIndex(field);
364
652
  }
365
653
  }
366
654
 
367
- // ============================================================
368
- // 导出
369
- // ============================================================
370
-
371
655
  toJSON() {
372
656
  return this._rows;
373
657
  }
374
658
  }
375
659
 
376
- // ============================================================
377
- // 链式查询构建器
378
- // ============================================================
379
-
380
- class Query {
381
- constructor(table, conditions) {
382
- this._table = table;
383
- this._conditions = conditions;
384
- this._orderBy = null;
385
- this._orderDir = 'asc';
386
- this._limitCount = null;
387
- this._offsetCount = 0;
388
- this._selectFields = null;
389
- }
390
-
391
- orderBy(field, dir = 'asc') {
392
- this._orderBy = field;
393
- this._orderDir = dir;
394
- return this;
395
- }
396
-
397
- limit(n) {
398
- this._limitCount = n;
399
- return this;
400
- }
401
-
402
- offset(n) {
403
- this._offsetCount = n;
404
- return this;
405
- }
406
-
407
- select(fields) {
408
- this._selectFields = Array.isArray(fields) ? fields : [fields];
409
- return this;
410
- }
411
-
412
- get() {
413
- let rows = this._table._applyFilter(this._table._rows, this._conditions);
414
-
415
- // 排序
416
- if (this._orderBy) {
417
- rows = rows.sort((a, b) => {
418
- const va = a[this._orderBy];
419
- const vb = b[this._orderBy];
420
- if (va < vb) return this._orderDir === 'asc' ? -1 : 1;
421
- if (va > vb) return this._orderDir === 'asc' ? 1 : -1;
422
- return 0;
423
- });
424
- }
425
-
426
- // 分页
427
- if (this._offsetCount > 0) {
428
- rows = rows.slice(this._offsetCount);
429
- }
430
- if (this._limitCount !== null) {
431
- rows = rows.slice(0, this._limitCount);
432
- }
433
-
434
- // 字段选择
435
- rows = rows.map(r => ({ ...r }));
436
- if (this._selectFields) {
437
- rows = rows.map(r => {
438
- const obj = {};
439
- for (const f of this._selectFields) {
440
- obj[f] = r[f];
441
- }
442
- return obj;
443
- });
444
- }
445
-
446
- return rows;
447
- }
448
-
449
- first() {
450
- const rows = this.limit(1).get();
451
- return rows.length > 0 ? rows[0] : null;
452
- }
453
-
454
- count() {
455
- const rows = this._table._applyFilter(this._table._rows, this._conditions);
456
- return rows.length;
457
- }
458
-
459
- update(updates) {
460
- return this._table.update(this._conditions, updates);
461
- }
462
-
463
- remove() {
464
- return this._table.remove(this._conditions);
465
- }
466
- }
467
-
468
660
  module.exports = Table;