@yanit/jsondb 0.1.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.
Files changed (118) hide show
  1. package/README.md +903 -0
  2. package/dist/bin/cli-export.d.ts +7 -0
  3. package/dist/bin/cli-export.d.ts.map +1 -0
  4. package/dist/bin/cli-export.js +318 -0
  5. package/dist/bin/cli-export.js.map +1 -0
  6. package/dist/bin/cli-import.d.ts +7 -0
  7. package/dist/bin/cli-import.d.ts.map +1 -0
  8. package/dist/bin/cli-import.js +298 -0
  9. package/dist/bin/cli-import.js.map +1 -0
  10. package/dist/bin/server.d.ts +7 -0
  11. package/dist/bin/server.d.ts.map +1 -0
  12. package/dist/bin/server.js +92 -0
  13. package/dist/bin/server.js.map +1 -0
  14. package/dist/examples/sql-example.d.ts +7 -0
  15. package/dist/examples/sql-example.d.ts.map +1 -0
  16. package/dist/examples/sql-example.js +131 -0
  17. package/dist/examples/sql-example.js.map +1 -0
  18. package/dist/src/BulkOp.d.ts +74 -0
  19. package/dist/src/BulkOp.d.ts.map +1 -0
  20. package/dist/src/BulkOp.js +143 -0
  21. package/dist/src/BulkOp.js.map +1 -0
  22. package/dist/src/Collection.d.ts +232 -0
  23. package/dist/src/Collection.d.ts.map +1 -0
  24. package/dist/src/Collection.js +705 -0
  25. package/dist/src/Collection.js.map +1 -0
  26. package/dist/src/Cursor.d.ts +94 -0
  27. package/dist/src/Cursor.d.ts.map +1 -0
  28. package/dist/src/Cursor.js +259 -0
  29. package/dist/src/Cursor.js.map +1 -0
  30. package/dist/src/Database.d.ts +98 -0
  31. package/dist/src/Database.d.ts.map +1 -0
  32. package/dist/src/Database.js +198 -0
  33. package/dist/src/Database.js.map +1 -0
  34. package/dist/src/Operators.d.ts +73 -0
  35. package/dist/src/Operators.d.ts.map +1 -0
  36. package/dist/src/Operators.js +339 -0
  37. package/dist/src/Operators.js.map +1 -0
  38. package/dist/src/QueryCache.d.ts +87 -0
  39. package/dist/src/QueryCache.d.ts.map +1 -0
  40. package/dist/src/QueryCache.js +155 -0
  41. package/dist/src/QueryCache.js.map +1 -0
  42. package/dist/src/SQLExecutor.d.ts +60 -0
  43. package/dist/src/SQLExecutor.d.ts.map +1 -0
  44. package/dist/src/SQLExecutor.js +317 -0
  45. package/dist/src/SQLExecutor.js.map +1 -0
  46. package/dist/src/SQLParser.d.ts +181 -0
  47. package/dist/src/SQLParser.d.ts.map +1 -0
  48. package/dist/src/SQLParser.js +640 -0
  49. package/dist/src/SQLParser.js.map +1 -0
  50. package/dist/src/Schema.d.ts +92 -0
  51. package/dist/src/Schema.d.ts.map +1 -0
  52. package/dist/src/Schema.js +253 -0
  53. package/dist/src/Schema.js.map +1 -0
  54. package/dist/src/Transaction.d.ts +118 -0
  55. package/dist/src/Transaction.d.ts.map +1 -0
  56. package/dist/src/Transaction.js +233 -0
  57. package/dist/src/Transaction.js.map +1 -0
  58. package/dist/src/Utils.d.ts +68 -0
  59. package/dist/src/Utils.d.ts.map +1 -0
  60. package/dist/src/Utils.js +187 -0
  61. package/dist/src/Utils.js.map +1 -0
  62. package/dist/src/errors.d.ts +58 -0
  63. package/dist/src/errors.d.ts.map +1 -0
  64. package/dist/src/errors.js +85 -0
  65. package/dist/src/errors.js.map +1 -0
  66. package/dist/src/index.d.ts +39 -0
  67. package/dist/src/index.d.ts.map +1 -0
  68. package/dist/src/index.js +44 -0
  69. package/dist/src/index.js.map +1 -0
  70. package/dist/test/basic.test.d.ts +5 -0
  71. package/dist/test/basic.test.d.ts.map +1 -0
  72. package/dist/test/basic.test.js +283 -0
  73. package/dist/test/basic.test.js.map +1 -0
  74. package/dist/test/index.test.d.ts +5 -0
  75. package/dist/test/index.test.d.ts.map +1 -0
  76. package/dist/test/index.test.js +126 -0
  77. package/dist/test/index.test.js.map +1 -0
  78. package/dist/test/jsonb.test.d.ts +5 -0
  79. package/dist/test/jsonb.test.d.ts.map +1 -0
  80. package/dist/test/jsonb.test.js +165 -0
  81. package/dist/test/jsonb.test.js.map +1 -0
  82. package/dist/test/optimization.test.d.ts +6 -0
  83. package/dist/test/optimization.test.d.ts.map +1 -0
  84. package/dist/test/optimization.test.js +196 -0
  85. package/dist/test/optimization.test.js.map +1 -0
  86. package/dist/test/schema.test.d.ts +5 -0
  87. package/dist/test/schema.test.d.ts.map +1 -0
  88. package/dist/test/schema.test.js +197 -0
  89. package/dist/test/schema.test.js.map +1 -0
  90. package/dist/test/sql.test.d.ts +7 -0
  91. package/dist/test/sql.test.d.ts.map +1 -0
  92. package/dist/test/sql.test.js +21 -0
  93. package/dist/test/sql.test.js.map +1 -0
  94. package/package.json +73 -0
  95. package/src/BulkOp.js +181 -0
  96. package/src/BulkOp.ts +191 -0
  97. package/src/Collection.js +843 -0
  98. package/src/Collection.ts +896 -0
  99. package/src/Cursor.js +315 -0
  100. package/src/Cursor.ts +319 -0
  101. package/src/Database.js +244 -0
  102. package/src/Database.ts +268 -0
  103. package/src/Operators.js +382 -0
  104. package/src/Operators.ts +375 -0
  105. package/src/QueryCache.js +190 -0
  106. package/src/QueryCache.ts +208 -0
  107. package/src/SQLExecutor.ts +391 -0
  108. package/src/SQLParser.ts +814 -0
  109. package/src/Schema.js +292 -0
  110. package/src/Schema.ts +317 -0
  111. package/src/Transaction.js +291 -0
  112. package/src/Transaction.ts +313 -0
  113. package/src/Utils.js +205 -0
  114. package/src/Utils.ts +205 -0
  115. package/src/errors.js +93 -0
  116. package/src/errors.ts +93 -0
  117. package/src/index.js +90 -0
  118. package/src/index.ts +106 -0
@@ -0,0 +1,843 @@
1
+ /**
2
+ * 集合类 - 管理文档集合
3
+ * 支持 async/await 异步操作
4
+ * 支持 JSONB 二进制存储模式
5
+ * 支持 Schema 验证
6
+ * 支持内存缓存优化
7
+ */
8
+
9
+ import { readFile, writeFile } from 'fs/promises';
10
+ import { existsSync } from 'fs';
11
+ import { join } from 'path';
12
+ import { generateId, deepClone, getNestedValue } from './Utils.js';
13
+ import { Cursor } from './Cursor.js';
14
+ import { matchQuery, applyUpdate } from './Operators.js';
15
+ import { CollectionNotFoundError, DocumentNotFoundError, ValidationError } from './errors.js';
16
+
17
+ /**
18
+ * Collection 类
19
+ * 表示一个文档集合,提供 CRUD 操作
20
+ */
21
+ export class Collection {
22
+ /**
23
+ * @param {Database} db - 数据库实例
24
+ * @param {string} name - 集合名称
25
+ */
26
+ constructor(db, name) {
27
+ this.db = db;
28
+ this.name = name;
29
+ this._filePath = join(db.dbPath, `${name}.json`);
30
+ this._data = null;
31
+ this._lock = null; // 简单的锁机制
32
+ this.jsonb = db.options.jsonb; // 继承数据库的 JSONB 选项
33
+ this._schema = null; // Schema 验证
34
+ this._validateOnInsert = true; // 插入时验证
35
+ this._validateOnUpdate = false; // 更新时验证
36
+
37
+ // 内存缓存优化
38
+ this._cache = null;
39
+ this._cacheTime = 0;
40
+ this._cacheTTL = db.options.cacheTTL || 5000; // 默认 5 秒缓存
41
+ this._dirty = false; // 数据是否被修改
42
+ }
43
+
44
+ /**
45
+ * 设置 Schema
46
+ * @param {Schema} schema - Schema 实例
47
+ * @param {Object} options - 选项
48
+ * @returns {Collection}
49
+ */
50
+ setSchema(schema, options = {}) {
51
+ this._schema = schema;
52
+ this._validateOnInsert = options.validateOnInsert !== false;
53
+ this._validateOnUpdate = options.validateOnUpdate || false;
54
+ return this;
55
+ }
56
+
57
+ /**
58
+ * 获取 Schema
59
+ * @returns {Schema|null}
60
+ */
61
+ getSchema() {
62
+ return this._schema;
63
+ }
64
+
65
+ /**
66
+ * 验证文档
67
+ * @private
68
+ * @param {Object} doc - 文档
69
+ * @throws {ValidationError}
70
+ */
71
+ _validate(doc) {
72
+ if (!this._schema) {
73
+ return;
74
+ }
75
+
76
+ const result = this._schema.validate(doc);
77
+ if (!result.valid) {
78
+ throw new ValidationError(`验证失败:${result.errors.map(e => `${e.field}: ${e.message}`).join(', ')}`);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * 加载集合数据(带缓存)
84
+ * @private
85
+ */
86
+ async _load() {
87
+ if (this._data !== null) {
88
+ return;
89
+ }
90
+
91
+ // 检查内存缓存
92
+ const now = Date.now();
93
+ if (this._cache && (now - this._cacheTime) < this._cacheTTL) {
94
+ this._data = this._cache;
95
+ return;
96
+ }
97
+
98
+ // 等待锁
99
+ await this._acquireLock();
100
+
101
+ try {
102
+ if (!existsSync(this._filePath)) {
103
+ // 如果集合文件不存在,自动创建
104
+ this._data = {
105
+ _meta: { name: this.name, count: 0, indexes: [] },
106
+ _documents: [],
107
+ _indexes: {}
108
+ };
109
+ await this._save();
110
+ return;
111
+ }
112
+
113
+ // JSONB 模式:读取二进制文件
114
+ if (this.jsonb) {
115
+ const buffer = await readFile(this._filePath);
116
+ this._data = this._decodeJsonb(buffer);
117
+ } else {
118
+ // 普通模式:读取文本文件
119
+ const content = await readFile(this._filePath, 'utf-8');
120
+ this._data = JSON.parse(content);
121
+ }
122
+
123
+ // 确保数据结构完整
124
+ if (!this._data._meta) {
125
+ this._data._meta = { name: this.name, count: 0, indexes: [] };
126
+ }
127
+ if (!this._data._documents) {
128
+ this._data._documents = [];
129
+ }
130
+ if (!this._data._indexes) {
131
+ this._data._indexes = {};
132
+ }
133
+
134
+ // 更新缓存
135
+ this._cache = this._data;
136
+ this._cacheTime = now;
137
+ } finally {
138
+ this._releaseLock();
139
+ }
140
+ }
141
+
142
+ /**
143
+ * 保存集合数据(带缓存失效)
144
+ * @private
145
+ */
146
+ async _save() {
147
+ if (this._data === null) {
148
+ return;
149
+ }
150
+
151
+ // 标记为脏数据
152
+ this._dirty = true;
153
+
154
+ // 更新元数据
155
+ this._data._meta.count = this._data._documents.length;
156
+
157
+ // 失效缓存
158
+ this._cache = null;
159
+ this._cacheTime = 0;
160
+
161
+ // JSONB 模式:写入二进制文件
162
+ if (this.jsonb) {
163
+ const buffer = this._encodeJsonb();
164
+ await writeFile(this._filePath, buffer);
165
+ } else {
166
+ // 普通模式:写入格式化的 JSON 文本
167
+ const content = JSON.stringify(this._data, null, 2);
168
+ await writeFile(this._filePath, content, 'utf-8');
169
+ }
170
+ }
171
+
172
+ /**
173
+ * JSONB 编码 - 将数据编码为二进制 Buffer
174
+ * 格式:[4 字节长度][JSON 数据的 UTF-8 字节]
175
+ * @private
176
+ * @returns {Buffer} 编码后的 Buffer
177
+ */
178
+ _encodeJsonb() {
179
+ const json = JSON.stringify(this._data);
180
+ const jsonBuffer = Buffer.from(json, 'utf-8');
181
+
182
+ // 创建包含长度前缀的 Buffer
183
+ // 4 字节 (uint32) 长度 + JSON 数据
184
+ const lengthBuffer = Buffer.alloc(4);
185
+ lengthBuffer.writeUInt32BE(jsonBuffer.length, 0);
186
+
187
+ return Buffer.concat([lengthBuffer, jsonBuffer]);
188
+ }
189
+
190
+ /**
191
+ * JSONB 解码 - 将二进制 Buffer 解码为数据
192
+ * @private
193
+ * @param {Buffer} buffer - 编码后的 Buffer
194
+ * @returns {Object} 解码后的数据
195
+ */
196
+ _decodeJsonb(buffer) {
197
+ try {
198
+ // 读取长度前缀
199
+ const length = buffer.readUInt32BE(0);
200
+
201
+ // 验证长度
202
+ if (length !== buffer.length - 4) {
203
+ throw new Error('JSONB 长度不匹配');
204
+ }
205
+
206
+ // 读取 JSON 数据
207
+ const jsonBuffer = buffer.subarray(4);
208
+ const json = jsonBuffer.toString('utf-8');
209
+ return JSON.parse(json);
210
+ } catch (e) {
211
+ // 如果不是有效的 JSONB 格式,尝试直接 JSON 解析(向后兼容)
212
+ try {
213
+ const json = buffer.toString('utf-8');
214
+ return JSON.parse(json);
215
+ } catch (e2) {
216
+ throw new Error(`无法解析数据:${e.message}`);
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * 获取锁
223
+ * @private
224
+ */
225
+ async _acquireLock() {
226
+ while (this._lock) {
227
+ await new Promise(resolve => setTimeout(resolve, 10));
228
+ }
229
+ this._lock = true;
230
+ }
231
+
232
+ /**
233
+ * 释放锁
234
+ * @private
235
+ */
236
+ _releaseLock() {
237
+ this._lock = null;
238
+ }
239
+
240
+ /**
241
+ * 获取所有文档
242
+ * @private
243
+ * @returns {Array} 文档数组
244
+ */
245
+ async _getDocuments() {
246
+ await this._load();
247
+ return this._data._documents;
248
+ }
249
+
250
+ /**
251
+ * 插入单个文档
252
+ * @param {Object} doc - 文档
253
+ * @returns {Promise<Object>} 插入后的文档(包含 _id)
254
+ */
255
+ async insertOne(doc) {
256
+ await this._load();
257
+
258
+ // Schema 验证
259
+ if (this._validateOnInsert) {
260
+ this._validate(doc);
261
+ }
262
+
263
+ // 创建文档副本并添加 _id
264
+ const newDoc = {
265
+ ...deepClone(doc),
266
+ _id: doc._id || generateId(),
267
+ createdAt: new Date().toISOString()
268
+ };
269
+
270
+ this._data._documents.push(newDoc);
271
+ await this._save();
272
+
273
+ return deepClone(newDoc);
274
+ }
275
+
276
+ /**
277
+ * 插入多个文档(优化版 - 单次写入)
278
+ * @param {Array<Object>} docs - 文档数组
279
+ * @param {Object} options - 选项
280
+ * @returns {Promise<Object>} 插入结果
281
+ */
282
+ async insertMany(docs, options = {}) {
283
+ if (!Array.isArray(docs)) {
284
+ throw new Error('insertMany 需要数组参数');
285
+ }
286
+
287
+ await this._load();
288
+
289
+ // 批量验证
290
+ if (this._validateOnInsert) {
291
+ for (const doc of docs) {
292
+ this._validate(doc);
293
+ }
294
+ }
295
+
296
+ // 批量创建文档(带 _id 和 createdAt)
297
+ const now = new Date().toISOString();
298
+ const insertedDocs = docs.map(doc => ({
299
+ ...deepClone(doc),
300
+ _id: doc._id || generateId(),
301
+ createdAt: doc.createdAt || now
302
+ }));
303
+
304
+ // 单次批量添加
305
+ this._data._documents.push(...insertedDocs);
306
+
307
+ // 更新索引(如果有)
308
+ if (this._data._meta.indexes?.length > 0) {
309
+ await this._updateIndexes(insertedDocs, 'insert');
310
+ }
311
+
312
+ // 单次保存
313
+ await this._save();
314
+
315
+ // 失效缓存
316
+ this._cache = null;
317
+ this._cacheTime = 0;
318
+
319
+ return {
320
+ acknowledged: true,
321
+ insertedCount: insertedDocs.length,
322
+ insertedIds: insertedDocs.reduce((acc, doc, i) => {
323
+ acc[i] = doc._id;
324
+ return acc;
325
+ }, {})
326
+ };
327
+ }
328
+
329
+ /**
330
+ * 查询文档
331
+ * @param {Object} query - 查询条件
332
+ * @param {Object} options - 查询选项
333
+ * @returns {Cursor} 游标对象
334
+ */
335
+ find(query = {}, options = {}) {
336
+ return new Cursor(this, query, options);
337
+ }
338
+
339
+ /**
340
+ * 查询单个文档
341
+ * @param {Object} query - 查询条件
342
+ * @param {Object} options - 查询选项
343
+ * @returns {Promise<Object|null>} 文档或 null
344
+ */
345
+ async findOne(query = {}, options = {}) {
346
+ return await new Cursor(this, query, options).first();
347
+ }
348
+
349
+ /**
350
+ * 更新单个文档
351
+ * @param {Object} query - 查询条件
352
+ * @param {Object} update - 更新操作
353
+ * @param {Object} options - 选项
354
+ * @returns {Promise<Object>} 更新结果
355
+ */
356
+ async updateOne(query, update, options = {}) {
357
+ await this._load();
358
+
359
+ const docIndex = this._data._documents.findIndex(doc => matchQuery(doc, query));
360
+
361
+ if (docIndex === -1) {
362
+ if (options.upsert) {
363
+ // 插入新文档
364
+ const newDoc = {
365
+ _id: generateId(),
366
+ ...deepClone(query),
367
+ ...deepClone(update)
368
+ };
369
+ this._data._documents.push(newDoc);
370
+ await this._save();
371
+
372
+ return {
373
+ acknowledged: true,
374
+ matchedCount: 0,
375
+ modifiedCount: 0,
376
+ upsertedId: newDoc._id
377
+ };
378
+ }
379
+
380
+ return {
381
+ acknowledged: true,
382
+ matchedCount: 0,
383
+ modifiedCount: 0
384
+ };
385
+ }
386
+
387
+ // 应用更新
388
+ const updatedDoc = applyUpdate(this._data._documents[docIndex], update);
389
+ updatedDoc.updatedAt = new Date().toISOString();
390
+ this._data._documents[docIndex] = updatedDoc;
391
+
392
+ await this._save();
393
+
394
+ return {
395
+ acknowledged: true,
396
+ matchedCount: 1,
397
+ modifiedCount: 1
398
+ };
399
+ }
400
+
401
+ /**
402
+ * 更新多个文档
403
+ * @param {Object} query - 查询条件
404
+ * @param {Object} update - 更新操作
405
+ * @param {Object} options - 选项
406
+ * @returns {Promise<Object>} 更新结果
407
+ */
408
+ async updateMany(query, update, options = {}) {
409
+ await this._load();
410
+
411
+ let matchedCount = 0;
412
+ let modifiedCount = 0;
413
+
414
+ for (let i = 0; i < this._data._documents.length; i++) {
415
+ const doc = this._data._documents[i];
416
+
417
+ if (matchQuery(doc, query)) {
418
+ matchedCount++;
419
+ const updatedDoc = applyUpdate(doc, update);
420
+ updatedDoc.updatedAt = new Date().toISOString();
421
+
422
+ if (JSON.stringify(doc) !== JSON.stringify(updatedDoc)) {
423
+ modifiedCount++;
424
+ this._data._documents[i] = updatedDoc;
425
+ }
426
+ }
427
+ }
428
+
429
+ if (matchedCount === 0 && options.upsert) {
430
+ // 插入新文档
431
+ const newDoc = {
432
+ _id: generateId(),
433
+ ...deepClone(query),
434
+ ...deepClone(update)
435
+ };
436
+ this._data._documents.push(newDoc);
437
+ await this._save();
438
+
439
+ return {
440
+ acknowledged: true,
441
+ matchedCount: 0,
442
+ modifiedCount: 0,
443
+ upsertedId: newDoc._id
444
+ };
445
+ }
446
+
447
+ await this._save();
448
+
449
+ return {
450
+ acknowledged: true,
451
+ matchedCount,
452
+ modifiedCount
453
+ };
454
+ }
455
+
456
+ /**
457
+ * 替换单个文档
458
+ * @param {Object} query - 查询条件
459
+ * @param {Object} doc - 新文档
460
+ * @param {Object} options - 选项
461
+ * @returns {Promise<Object>} 替换结果
462
+ */
463
+ async replaceOne(query, doc, options = {}) {
464
+ await this._load();
465
+
466
+ const docIndex = this._data._documents.findIndex(doc => matchQuery(doc, query));
467
+
468
+ if (docIndex === -1) {
469
+ if (options.upsert) {
470
+ const newDoc = {
471
+ ...deepClone(doc),
472
+ _id: generateId(),
473
+ createdAt: new Date().toISOString()
474
+ };
475
+ this._data._documents.push(newDoc);
476
+ await this._save();
477
+
478
+ return {
479
+ acknowledged: true,
480
+ matchedCount: 0,
481
+ modifiedCount: 0,
482
+ upsertedId: newDoc._id
483
+ };
484
+ }
485
+
486
+ return {
487
+ acknowledged: true,
488
+ matchedCount: 0,
489
+ modifiedCount: 0
490
+ };
491
+ }
492
+
493
+ // 保留 _id,替换其他字段
494
+ const oldId = this._data._documents[docIndex]._id;
495
+ const newDoc = {
496
+ ...deepClone(doc),
497
+ _id: oldId,
498
+ updatedAt: new Date().toISOString()
499
+ };
500
+
501
+ this._data._documents[docIndex] = newDoc;
502
+ await this._save();
503
+
504
+ return {
505
+ acknowledged: true,
506
+ matchedCount: 1,
507
+ modifiedCount: 1
508
+ };
509
+ }
510
+
511
+ /**
512
+ * 删除单个文档
513
+ * @param {Object} query - 查询条件
514
+ * @returns {Promise<Object>} 删除结果
515
+ */
516
+ async deleteOne(query) {
517
+ await this._load();
518
+
519
+ const docIndex = this._data._documents.findIndex(doc => matchQuery(doc, query));
520
+
521
+ if (docIndex === -1) {
522
+ return {
523
+ acknowledged: true,
524
+ deletedCount: 0
525
+ };
526
+ }
527
+
528
+ this._data._documents.splice(docIndex, 1);
529
+ await this._save();
530
+
531
+ return {
532
+ acknowledged: true,
533
+ deletedCount: 1
534
+ };
535
+ }
536
+
537
+ /**
538
+ * 删除多个文档
539
+ * @param {Object} query - 查询条件
540
+ * @returns {Promise<Object>} 删除结果
541
+ */
542
+ async deleteMany(query) {
543
+ await this._load();
544
+
545
+ const initialCount = this._data._documents.length;
546
+ this._data._documents = this._data._documents.filter(doc => !matchQuery(doc, query));
547
+ const deletedCount = initialCount - this._data._documents.length;
548
+
549
+ await this._save();
550
+
551
+ return {
552
+ acknowledged: true,
553
+ deletedCount
554
+ };
555
+ }
556
+
557
+ /**
558
+ * 计数文档
559
+ * @param {Object} query - 查询条件
560
+ * @returns {Promise<number>} 文档数量
561
+ */
562
+ async countDocuments(query = {}) {
563
+ return await this.find(query).count();
564
+ }
565
+
566
+ /**
567
+ * 获取不同值
568
+ * @param {string} key - 字段名
569
+ * @param {Object} query - 查询条件
570
+ * @returns {Promise<Array>} 不同值数组
571
+ */
572
+ async distinct(key, query = {}) {
573
+ const docs = await this.find(query).toArray();
574
+ const values = new Set();
575
+
576
+ for (const doc of docs) {
577
+ const value = getNestedValue(doc, key);
578
+ if (value !== undefined) {
579
+ values.add(value);
580
+ }
581
+ }
582
+
583
+ return Array.from(values);
584
+ }
585
+
586
+ /**
587
+ * 聚合查询
588
+ * @param {Array} pipeline - 聚合管道
589
+ * @returns {Promise<Array>} 结果数组
590
+ */
591
+ async aggregate(pipeline) {
592
+ await this._load();
593
+
594
+ let results = this._data._documents.map(doc => deepClone(doc));
595
+
596
+ for (const stage of pipeline) {
597
+ const stageName = Object.keys(stage)[0];
598
+ const stageValue = stage[stageName];
599
+
600
+ results = this._applyAggregationStage(results, stageName, stageValue);
601
+ }
602
+
603
+ return results;
604
+ }
605
+
606
+ /**
607
+ * 应用聚合阶段
608
+ * @private
609
+ */
610
+ _applyAggregationStage(docs, stageName, stageValue) {
611
+ switch (stageName) {
612
+ case '$match':
613
+ return docs.filter(doc => matchQuery(doc, stageValue));
614
+
615
+ case '$project':
616
+ return docs.map(doc => {
617
+ const result = {};
618
+ for (const [key, value] of Object.entries(stageValue)) {
619
+ if (typeof value === 'string' && value.startsWith('$')) {
620
+ result[key] = getNestedValue(doc, value.substring(1));
621
+ } else if (value === 0 || value === false) {
622
+ // 排除字段
623
+ } else {
624
+ result[key] = getNestedValue(doc, key);
625
+ }
626
+ }
627
+ if (stageValue._id !== undefined) {
628
+ result._id = stageValue._id === 0 ? undefined : doc._id;
629
+ } else {
630
+ result._id = doc._id;
631
+ }
632
+ return result;
633
+ });
634
+
635
+ case '$group':
636
+ return this._applyGroup(docs, stageValue);
637
+
638
+ case '$sort':
639
+ return docs.sort((a, b) => {
640
+ for (const [key, direction] of Object.entries(stageValue)) {
641
+ const aVal = getNestedValue(a, key);
642
+ const bVal = getNestedValue(b, key);
643
+ if (aVal < bVal) return -1 * direction;
644
+ if (aVal > bVal) return 1 * direction;
645
+ }
646
+ return 0;
647
+ });
648
+
649
+ case '$limit':
650
+ return docs.slice(0, stageValue);
651
+
652
+ case '$skip':
653
+ return docs.slice(stageValue);
654
+
655
+ case '$count':
656
+ return [{ [stageValue]: docs.length }];
657
+
658
+ default:
659
+ console.warn(`未实现的聚合阶段:${stageName}`);
660
+ return docs;
661
+ }
662
+ }
663
+
664
+ /**
665
+ * 应用分组
666
+ * @private
667
+ */
668
+ _applyGroup(docs, stageValue) {
669
+ const groups = new Map();
670
+
671
+ for (const doc of docs) {
672
+ let key;
673
+ if (stageValue._id === null) {
674
+ key = null;
675
+ } else if (typeof stageValue._id === 'string') {
676
+ key = getNestedValue(doc, stageValue._id.substring(1));
677
+ } else {
678
+ key = JSON.stringify(stageValue._id);
679
+ }
680
+
681
+ if (!groups.has(key)) {
682
+ groups.set(key, []);
683
+ }
684
+ groups.get(key).push(doc);
685
+ }
686
+
687
+ return Array.from(groups.entries()).map(([key, groupDocs]) => {
688
+ const result = { _id: key };
689
+
690
+ for (const [aggKey, aggExpr] of Object.entries(stageValue)) {
691
+ if (aggKey === '_id') continue;
692
+
693
+ if (typeof aggExpr === 'object') {
694
+ const aggOp = Object.keys(aggExpr)[0];
695
+ const aggField = aggExpr[aggOp];
696
+
697
+ if (aggOp === '$sum') {
698
+ result[aggKey] = groupDocs.reduce((sum, d) => {
699
+ const val = getNestedValue(d, aggField.substring(1)) || 0;
700
+ return sum + val;
701
+ }, 0);
702
+ } else if (aggOp === '$avg') {
703
+ result[aggKey] = groupDocs.reduce((sum, d) => {
704
+ const val = getNestedValue(d, aggField.substring(1)) || 0;
705
+ return sum + val;
706
+ }, 0) / groupDocs.length;
707
+ } else if (aggOp === '$count') {
708
+ result[aggKey] = groupDocs.length;
709
+ } else if (aggOp === '$min') {
710
+ result[aggKey] = Math.min(...groupDocs.map(d => getNestedValue(d, aggField.substring(1)) || 0));
711
+ } else if (aggOp === '$max') {
712
+ result[aggKey] = Math.max(...groupDocs.map(d => getNestedValue(d, aggField.substring(1)) || 0));
713
+ }
714
+ }
715
+ }
716
+
717
+ return result;
718
+ });
719
+ }
720
+
721
+ /**
722
+ * 创建索引
723
+ * @param {Object} keys - 索引键
724
+ * @param {Object} options - 选项
725
+ * @returns {Promise<Object>} 索引信息
726
+ */
727
+ async createIndex(keys, options = {}) {
728
+ await this._load();
729
+
730
+ const indexName = Object.entries(keys)
731
+ .map(([k, v]) => `${k}_${v}`)
732
+ .join('_');
733
+
734
+ // 检查索引是否已存在
735
+ const existingIndex = this._data._meta.indexes?.find(i => i.name === indexName);
736
+ if (existingIndex) {
737
+ return existingIndex;
738
+ }
739
+
740
+ // 创建索引
741
+ const index = {
742
+ key: keys,
743
+ name: indexName,
744
+ unique: options.unique || false
745
+ };
746
+
747
+ if (!this._data._meta.indexes) {
748
+ this._data._meta.indexes = [];
749
+ }
750
+ this._data._meta.indexes.push(index);
751
+
752
+ // 构建索引数据
753
+ await this._buildIndex(index);
754
+
755
+ await this._save();
756
+
757
+ return index;
758
+ }
759
+
760
+ /**
761
+ * 构建索引数据
762
+ * @private
763
+ * @param {Object} index - 索引对象
764
+ */
765
+ async _buildIndex(index) {
766
+ const indexKeys = Object.keys(index.key);
767
+ const indexData = {};
768
+
769
+ for (const doc of this._data._documents) {
770
+ for (const key of indexKeys) {
771
+ const value = getNestedValue(doc, key);
772
+ if (value !== undefined) {
773
+ const keyStr = String(value);
774
+ if (!indexData[keyStr]) {
775
+ indexData[keyStr] = [];
776
+ }
777
+ indexData[keyStr].push(doc._id);
778
+ }
779
+ }
780
+ }
781
+
782
+ if (!this._data._indexes) {
783
+ this._data._indexes = {};
784
+ }
785
+ this._data._indexes[index.name] = indexData;
786
+ }
787
+
788
+ /**
789
+ * 删除索引
790
+ * @param {string} name - 索引名称
791
+ * @returns {Promise<Object>} 删除结果
792
+ */
793
+ async dropIndex(name) {
794
+ await this._load();
795
+
796
+ if (!this._data._meta.indexes) {
797
+ return { acknowledged: false };
798
+ }
799
+
800
+ const initialLength = this._data._meta.indexes.length;
801
+ this._data._meta.indexes = this._data._meta.indexes.filter(i => i.name !== name);
802
+
803
+ if (this._data._meta.indexes.length === initialLength) {
804
+ return { acknowledged: false };
805
+ }
806
+
807
+ await this._save();
808
+
809
+ return { acknowledged: true };
810
+ }
811
+
812
+ /**
813
+ * 列出所有索引
814
+ * @returns {Promise<Array>} 索引数组
815
+ */
816
+ async listIndexes() {
817
+ await this._load();
818
+ return this._data._meta.indexes || [];
819
+ }
820
+
821
+ /**
822
+ * 获取集合统计信息
823
+ * @returns {Promise<Object>} 统计信息
824
+ */
825
+ async stats() {
826
+ await this._load();
827
+
828
+ const size = this.jsonb
829
+ ? this._encodeJsonb().length
830
+ : JSON.stringify(this._data).length;
831
+
832
+ return {
833
+ ns: this.name,
834
+ count: this._data._documents.length,
835
+ size,
836
+ avgObjSize: this._data._documents.length > 0
837
+ ? size / this._data._documents.length
838
+ : 0,
839
+ indexes: this._data._meta.indexes?.length || 0,
840
+ jsonb: this.jsonb
841
+ };
842
+ }
843
+ }