jsql-neo 5.4.2 → 5.5.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.
Files changed (72) hide show
  1. package/LICENSE +202 -202
  2. package/README.md +8097 -7971
  3. package/bin/jsql +320 -320
  4. package/bin/jsql-server +90 -90
  5. package/bin/jsql-server-control.js +177 -177
  6. package/index.d.ts +385 -357
  7. package/index.js +124 -118
  8. package/lib/ast.js +214 -0
  9. package/lib/btree.js +413 -413
  10. package/lib/cache.js +328 -328
  11. package/lib/client.js +125 -125
  12. package/lib/database.js +1744 -1744
  13. package/lib/date-types.js +182 -182
  14. package/lib/errors.js +89 -89
  15. package/lib/jsql_format.js +497 -497
  16. package/lib/migrate.js +254 -254
  17. package/lib/mod.js +292 -292
  18. package/lib/mongo_server.js +607 -607
  19. package/lib/multiserver.js +140 -140
  20. package/lib/mysql_compat.js +413 -413
  21. package/lib/mysql_server.js +1237 -1237
  22. package/lib/native_client.js +732 -732
  23. package/lib/nedb_compat.js +506 -506
  24. package/lib/pg_server.js +864 -864
  25. package/lib/plugin.js +34 -34
  26. package/lib/query.js +815 -815
  27. package/lib/redis_server.js +571 -571
  28. package/lib/sql.js +3460 -2925
  29. package/lib/sqlite_compat.js +323 -323
  30. package/lib/sqlite_worker.js +264 -264
  31. package/lib/table.js +1304 -1304
  32. package/lib/tui.js +502 -502
  33. package/lib/wasm.js +1 -1
  34. package/lib/wasm_client.js +718 -718
  35. package/lib/web_ui.js +257 -257
  36. package/native/jsql-neo-native.node +0 -0
  37. package/nativesrc/jsql-neo-core/Cargo.lock +455 -455
  38. package/nativesrc/jsql-neo-core/Cargo.toml +24 -24
  39. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +455 -455
  40. package/nativesrc/jsql-neo-core/src/engine/memory.rs +168 -168
  41. package/nativesrc/jsql-neo-core/src/engine/mod.rs +62 -62
  42. package/nativesrc/jsql-neo-core/src/engine/table.rs +700 -700
  43. package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
  44. package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
  45. package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
  46. package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -85
  47. package/nativesrc/jsql-neo-core/src/types.rs +93 -93
  48. package/nativesrc/jsql-neo-native/Cargo.lock +606 -606
  49. package/nativesrc/jsql-neo-native/Cargo.toml +16 -16
  50. package/nativesrc/jsql-neo-native/build.rs +3 -3
  51. package/nativesrc/jsql-neo-native/package.json +7 -7
  52. package/nativesrc/jsql-neo-native/src/lib.rs +269 -269
  53. package/nativesrc/jsql-neo-wasm/Cargo.lock +465 -465
  54. package/nativesrc/jsql-neo-wasm/Cargo.toml +15 -15
  55. package/nativesrc/jsql-neo-wasm/src/lib.rs +447 -447
  56. package/package.json +87 -86
  57. package/postinstall.js +158 -158
  58. package/test/btree.test.js +128 -128
  59. package/test/coverage.js +187 -187
  60. package/test/join.test.js +110 -110
  61. package/test/native.test.js +158 -158
  62. package/test/regress-5.1.0.js +271 -271
  63. package/test/smoke.js +58 -58
  64. package/test/sql-parser.test.js +273 -0
  65. package/test/wasm.test.js +110 -110
  66. package/wasm/browser.d.ts +89 -89
  67. package/wasm/browser.mjs +569 -569
  68. package/wasm/browser_bg.mjs +597 -597
  69. package/wasm/jsql_neo_wasm.d.ts +38 -38
  70. package/wasm/jsql_neo_wasm.js +520 -520
  71. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
  72. package/wasm/package.json +11 -11
package/lib/query.js CHANGED
@@ -1,816 +1,816 @@
1
- // © Vexify 2026 All Rights Reserved.
2
- /**
3
- * JSQL Query — 链式查询构建器 v2.0
4
- * 哈希 JOIN 优化、B-Tree 加速、字符串表名、查询优化器
5
- */
6
-
7
- const { createError } = require('./errors');
8
-
9
- class Query {
10
- constructor(table, conditions = {}) {
11
- this._table = table;
12
- this._conditions = conditions;
13
- this._orderBy = [];
14
- this._limitCount = null;
15
- this._offsetCount = 0;
16
- this._selectFields = null;
17
- this._groupBy = null;
18
- this._having = null;
19
- this._distinct = false;
20
- this._distinctField = null;
21
- this._joins = [];
22
- this._union = null;
23
- this._explain = false;
24
- this._window = null;
25
- this._cache = null; // { ttl, result, timestamp }
26
- this._caseExpr = null; // [{ when, then }, ...] + else
27
- this._optimizerHints = {}; // { useHashJoin: true }
28
- }
29
-
30
- // ============================================================
31
- // 条件
32
- // ============================================================
33
-
34
- where(conditions) {
35
- Object.assign(this._conditions, conditions);
36
- return this;
37
- }
38
-
39
- // ============================================================
40
- // 排序
41
- // ============================================================
42
-
43
- orderBy(field, dir = 'asc') {
44
- this._orderBy.push({ field, dir: dir.toLowerCase() });
45
- return this;
46
- }
47
-
48
- orderByDesc(field) {
49
- return this.orderBy(field, 'desc');
50
- }
51
-
52
- // ============================================================
53
- // 分页
54
- // ============================================================
55
-
56
- limit(n) {
57
- this._limitCount = n;
58
- return this;
59
- }
60
-
61
- offset(n) {
62
- this._offsetCount = n;
63
- return this;
64
- }
65
-
66
- paginate(page = 1, perPage = 20) {
67
- this._limitCount = perPage;
68
- this._offsetCount = (page - 1) * perPage;
69
- const total = this._countRaw();
70
- const rows = this.get();
71
- return { rows, total, page, perPage, totalPages: Math.ceil(total / perPage) };
72
- }
73
-
74
- // ============================================================
75
- // 字段选择
76
- // ============================================================
77
-
78
- select(fields) {
79
- this._selectFields = Array.isArray(fields) ? fields : [fields];
80
- return this;
81
- }
82
-
83
- distinct(field) {
84
- this._distinct = true;
85
- this._distinctField = field;
86
- return this;
87
- }
88
-
89
- // ============================================================
90
- // CASE WHEN
91
- // ============================================================
92
-
93
- /**
94
- * CASE WHEN 表达式
95
- * @example
96
- * db.users.where({}).case([
97
- * { when: { age: { $lt: 18 } }, then: 'minor' },
98
- * { when: { age: { $lt: 60 } }, then: 'adult' }
99
- * ], 'senior').as('ageGroup').get()
100
- */
101
- case(whenClauses, elseExpr) {
102
- this._caseExpr = { clauses: whenClauses, else: elseExpr };
103
- return this;
104
- }
105
-
106
- /**
107
- * 给 CASE WHEN 结果命名
108
- */
109
- as(fieldName) {
110
- this._caseAs = fieldName;
111
- return this;
112
- }
113
-
114
- // ============================================================
115
- // 分组 & 聚合后过滤
116
- // ============================================================
117
-
118
- groupBy(field) {
119
- this._groupBy = field;
120
- return this;
121
- }
122
-
123
- having(conditions) {
124
- this._having = conditions;
125
- return this;
126
- }
127
-
128
- // ============================================================
129
- // 窗口函数
130
- // ============================================================
131
-
132
- /**
133
- * 窗口函数
134
- * @param {string} fn - 'rowNumber' | 'rank' | 'denseRank'
135
- * @param {string} field - 排序字段
136
- * @param {string} partitionBy - 分区字段
137
- * @param {string} dir - 'asc' | 'desc'
138
- */
139
- window(fn, field, partitionBy, dir = 'asc') {
140
- this._window = { fn, field, partitionBy, dir };
141
- return this;
142
- }
143
-
144
- // ============================================================
145
- // 查询优化器提示
146
- // ============================================================
147
-
148
- /**
149
- * 强制使用哈希 JOIN
150
- */
151
- useHashJoin() {
152
- this._optimizerHints.useHashJoin = true;
153
- return this;
154
- }
155
-
156
- /**
157
- * 强制使用嵌套循环 JOIN
158
- */
159
- useNestedLoop() {
160
- this._optimizerHints.useHashJoin = false;
161
- return this;
162
- }
163
-
164
- // ============================================================
165
- // JOIN(支持字符串表名 + 哈希 JOIN 优化)
166
- // ============================================================
167
-
168
- /**
169
- * INNER JOIN
170
- * @param {string|Table} otherTable - 表名或 Table 对象
171
- * @param {string} localField - 本地字段
172
- * @param {string} foreignField - 外键字段
173
- * @param {string} as - 别名前缀
174
- */
175
- join(otherTable, localField, foreignField, as) {
176
- this._joins.push({ type: 'inner', table: otherTable, localField, foreignField, as });
177
- return this;
178
- }
179
-
180
- leftJoin(otherTable, localField, foreignField, as) {
181
- this._joins.push({ type: 'left', table: otherTable, localField, foreignField, as });
182
- return this;
183
- }
184
-
185
- rightJoin(otherTable, localField, foreignField, as) {
186
- this._joins.push({ type: 'right', table: otherTable, localField, foreignField, as });
187
- return this;
188
- }
189
-
190
- // ============================================================
191
- // UNION
192
- // ============================================================
193
-
194
- union(query) {
195
- this._union = query;
196
- return this;
197
- }
198
-
199
- // ============================================================
200
- // EXPLAIN
201
- // ============================================================
202
-
203
- explain() {
204
- this._explain = true;
205
- return this;
206
- }
207
-
208
- // ============================================================
209
- // 缓存
210
- // ============================================================
211
-
212
- /**
213
- * 缓存查询结果
214
- * @param {number} ttlMs - 缓存有效期(毫秒),默认 60000
215
- */
216
- cache(ttlMs = 60000) {
217
- this._cache = { ttl: ttlMs, result: null, timestamp: 0 };
218
- return this;
219
- }
220
-
221
- // ============================================================
222
- // 执行
223
- // ============================================================
224
-
225
- get() {
226
- const startTime = Date.now();
227
- if (this._explain) return this._getExplain();
228
-
229
- // 缓存命中
230
- if (this._cache && this._cache.result && (Date.now() - this._cache.timestamp < this._cache.ttl)) {
231
- return this._cache.result;
232
- }
233
-
234
- let rows = this._table._applyFilterOptimized(this._table._rows, this._conditions);
235
-
236
- // JOIN
237
- for (const join of this._joins) {
238
- rows = this._applyJoinOptimized(rows, join);
239
- }
240
-
241
- // UNION
242
- if (this._union) {
243
- const unionRows = this._union.get();
244
- rows = [...rows, ...unionRows];
245
- }
246
-
247
- // GROUP BY
248
- if (this._groupBy) {
249
- rows = this._applyGroupBy(rows);
250
- }
251
-
252
- // HAVING
253
- if (this._having) {
254
- rows = this._table._applyFilter(rows, this._having);
255
- }
256
-
257
- // 窗口函数
258
- if (this._window) {
259
- rows = this._applyWindow(rows);
260
- }
261
-
262
- // DISTINCT
263
- if (this._distinct && this._distinctField) {
264
- const seen = new Set();
265
- rows = rows.filter(r => {
266
- const val = r[this._distinctField];
267
- if (val === undefined) return false;
268
- const key = String(val);
269
- if (seen.has(key)) return false;
270
- seen.add(key);
271
- return true;
272
- });
273
- }
274
-
275
- // 排序
276
- if (this._orderBy.length > 0) {
277
- rows = this._applyOrderBy(rows);
278
- }
279
-
280
- // 分页
281
- if (this._offsetCount > 0) {
282
- rows = rows.slice(this._offsetCount);
283
- }
284
- if (this._limitCount !== null) {
285
- rows = rows.slice(0, this._limitCount);
286
- }
287
-
288
- // CASE WHEN
289
- if (this._caseExpr) {
290
- rows = rows.map(r => {
291
- const row = { ...r };
292
- let val = this._caseExpr.else;
293
- for (const clause of this._caseExpr.clauses) {
294
- if (this._table._matchRow(row, clause.when)) {
295
- val = clause.then;
296
- break;
297
- }
298
- }
299
- const asField = this._caseAs || '_case';
300
- row[asField] = val;
301
- return row;
302
- });
303
- }
304
-
305
- // 字段选择
306
- rows = rows.map(r => ({ ...r }));
307
- if (this._selectFields) {
308
- rows = rows.map(r => {
309
- const obj = {};
310
- for (const f of this._selectFields) {
311
- obj[f] = r[f];
312
- }
313
- return obj;
314
- });
315
- }
316
-
317
- // 缓存存储
318
- if (this._cache) {
319
- this._cache.result = rows;
320
- this._cache.timestamp = Date.now();
321
- }
322
-
323
- this._table._db._logSlowQuery(`get ${this._table._name}`, Date.now() - startTime, rows.length);
324
- return rows;
325
- }
326
-
327
- first() {
328
- const rows = this.limit(1).get();
329
- return rows.length > 0 ? rows[0] : null;
330
- }
331
-
332
- count() {
333
- return this._countRaw();
334
- }
335
-
336
- sum(field) {
337
- const rows = this._getFilteredRows();
338
- return rows.reduce((acc, r) => acc + (Number(r[field]) || 0), 0);
339
- }
340
-
341
- avg(field) {
342
- const rows = this._getFilteredRows();
343
- if (rows.length === 0) return 0;
344
- return this.sum(field) / rows.length;
345
- }
346
-
347
- min(field) {
348
- const rows = this._getFilteredRows();
349
- if (rows.length === 0) return null;
350
- return rows.reduce((min, r) => {
351
- const v = Number(r[field]);
352
- return (min === null || v < min) ? v : min;
353
- }, null);
354
- }
355
-
356
- max(field) {
357
- const rows = this._getFilteredRows();
358
- if (rows.length === 0) return null;
359
- return rows.reduce((max, r) => {
360
- const v = Number(r[field]);
361
- return (max === null || v > max) ? v : max;
362
- }, null);
363
- }
364
-
365
- groupStats(field) {
366
- if (!this._groupBy) throw createError('ER_PARSE_ERROR', 'groupStats() requires groupBy()');
367
- const rows = this._table._applyFilterOptimized(this._table._rows, this._conditions);
368
- const groups = {};
369
-
370
- for (const row of rows) {
371
- const key = String(row[this._groupBy] ?? '__null__');
372
- if (!groups[key]) {
373
- groups[key] = { _group: row[this._groupBy], _count: 0, _sum: 0, _min: null, _max: null, _rows: [] };
374
- }
375
- const g = groups[key];
376
- g._count++;
377
- const v = Number(row[field]) || 0;
378
- g._sum += v;
379
- g._min = g._min === null ? v : Math.min(g._min, v);
380
- g._max = g._max === null ? v : Math.max(g._max, v);
381
- g._rows.push(row);
382
- }
383
-
384
- const result = Object.values(groups);
385
- result.forEach(g => {
386
- g._avg = g._count > 0 ? g._sum / g._count : 0;
387
- delete g._rows;
388
- });
389
- return result;
390
- }
391
-
392
- update(updates) {
393
- return this._table.update(this._conditions, updates);
394
- }
395
-
396
- remove() {
397
- return this._table.remove(this._conditions);
398
- }
399
-
400
- /**
401
- * 无效缓存
402
- */
403
- invalidateCache() {
404
- if (this._cache) {
405
- this._cache.result = null;
406
- this._cache.timestamp = 0;
407
- }
408
- return this;
409
- }
410
-
411
- // ============================================================
412
- // toSQL
413
- // ============================================================
414
-
415
- /**
416
- * 导出为类 SQL 字符串(调试用)
417
- */
418
- toSQL() {
419
- const parts = ['SELECT'];
420
- if (this._selectFields) {
421
- parts.push(this._selectFields.join(', '));
422
- } else {
423
- parts.push('*');
424
- }
425
- parts.push('FROM', this._table._name);
426
-
427
- if (Object.keys(this._conditions).length > 0) {
428
- parts.push('WHERE', JSON.stringify(this._conditions));
429
- }
430
-
431
- for (const join of this._joins) {
432
- const tableName = typeof join.table === 'string' ? join.table : join.table._name;
433
- parts.push(join.type.toUpperCase() + ' JOIN', tableName,
434
- 'ON', `${this._table._name}.${join.localField} = ${tableName}.${join.foreignField}`);
435
- }
436
-
437
- if (this._groupBy) {
438
- parts.push('GROUP BY', this._groupBy);
439
- if (this._having) parts.push('HAVING', JSON.stringify(this._having));
440
- }
441
-
442
- if (this._orderBy.length > 0) {
443
- parts.push('ORDER BY', this._orderBy.map(o => `${o.field} ${o.dir}`).join(', '));
444
- }
445
-
446
- if (this._limitCount !== null) {
447
- parts.push('LIMIT', String(this._limitCount));
448
- }
449
-
450
- if (this._offsetCount > 0) {
451
- parts.push('OFFSET', String(this._offsetCount));
452
- }
453
-
454
- return parts.join(' ');
455
- }
456
-
457
- // ============================================================
458
- // 内部
459
- // ============================================================
460
-
461
- _getFilteredRows() {
462
- return this._table._applyFilterOptimized(this._table._rows, this._conditions);
463
- }
464
-
465
- _countRaw() {
466
- return this._table._applyFilterOptimized(this._table._rows, this._conditions).length;
467
- }
468
-
469
- // ============================================================
470
- // 内部: JOIN 优化
471
- // ============================================================
472
-
473
- /**
474
- * 解析 JOIN 表引用(支持字符串表名和 Table 对象)
475
- */
476
- _resolveJoinTable(tableRef) {
477
- if (typeof tableRef === 'string') {
478
- const resolved = this._table._db._tables[tableRef];
479
- if (!resolved) {
480
- throw createError('ER_NO_SUCH_TABLE', tableRef);
481
- }
482
- return resolved;
483
- }
484
- return tableRef;
485
- }
486
-
487
- /**
488
- * 优化的 JOIN 执行
489
- * 自动选择哈希 JOIN 或嵌套循环
490
- */
491
- _applyJoinOptimized(localRows, join) {
492
- const foreignTable = this._resolveJoinTable(join.table);
493
-
494
- // 如果强制或自动选择哈希 JOIN
495
- const useHash = this._optimizerHints.useHashJoin !== false
496
- && (this._optimizerHints.useHashJoin === true
497
- || localRows.length > 100
498
- || foreignTable._rows.length > 100);
499
-
500
- if (useHash) {
501
- return this._applyHashJoin(localRows, foreignTable, join);
502
- }
503
- return this._applyNestedLoopJoin(localRows, foreignTable, join);
504
- }
505
-
506
- /**
507
- * 哈希 JOIN — O(n + m) 时间复杂度
508
- * 构建外键表的哈希映射,然后一次遍历本地行完成 JOIN
509
- */
510
- _applyHashJoin(localRows, foreignTable, join) {
511
- // 构建哈希表:foreignField -> [foreignRows]
512
- const hashMap = new Map();
513
- for (const fr of foreignTable._rows) {
514
- const key = fr[join.foreignField];
515
- if (key === undefined || key === null) continue;
516
- if (!hashMap.has(key)) {
517
- hashMap.set(key, []);
518
- }
519
- hashMap.get(key).push(fr);
520
- }
521
-
522
- const result = [];
523
-
524
- if (join.type === 'left') {
525
- for (const lr of localRows) {
526
- const matches = hashMap.get(lr[join.localField]) || [];
527
- if (matches.length === 0) {
528
- result.push({ ...lr, ...this._nullRow(foreignTable) });
529
- } else {
530
- for (const fr of matches) {
531
- result.push(this._mergeJoinRow(lr, fr, join));
532
- }
533
- }
534
- }
535
- } else if (join.type === 'right') {
536
- for (const fr of foreignTable._rows) {
537
- const key = fr[join.foreignField];
538
- const matches = localRows.filter(lr => lr[join.localField] === key);
539
- if (matches.length === 0) {
540
- result.push(this._rightNullRow(localRows, fr, join));
541
- } else {
542
- for (const lr of matches) {
543
- result.push(this._mergeJoinRow(lr, fr, join));
544
- }
545
- }
546
- }
547
- } else {
548
- // INNER JOIN
549
- for (const lr of localRows) {
550
- const matches = hashMap.get(lr[join.localField]);
551
- if (matches) {
552
- for (const fr of matches) {
553
- result.push(this._mergeJoinRow(lr, fr, join));
554
- }
555
- }
556
- }
557
- }
558
-
559
- return result;
560
- }
561
-
562
- /**
563
- * 嵌套循环 JOIN — O(n * m) 时间复杂度
564
- * 用于小数据量场景
565
- */
566
- _applyNestedLoopJoin(localRows, foreignTable, join) {
567
- const result = [];
568
- const foreignRows = foreignTable._rows;
569
-
570
- if (join.type === 'left') {
571
- for (const lr of localRows) {
572
- const matches = foreignRows.filter(fr => fr[join.foreignField] === lr[join.localField]);
573
- if (matches.length === 0) {
574
- result.push({ ...lr, ...this._nullRow(foreignTable) });
575
- } else {
576
- for (const fr of matches) {
577
- result.push(this._mergeJoinRow(lr, fr, join));
578
- }
579
- }
580
- }
581
- } else if (join.type === 'right') {
582
- for (const fr of foreignRows) {
583
- const matches = localRows.filter(lr => lr[join.localField] === fr[join.foreignField]);
584
- if (matches.length === 0) {
585
- result.push(this._rightNullRow(localRows, fr, join));
586
- } else {
587
- for (const lr of matches) {
588
- result.push(this._mergeJoinRow(lr, fr, join));
589
- }
590
- }
591
- }
592
- } else {
593
- // INNER JOIN
594
- for (const lr of localRows) {
595
- for (const fr of foreignRows) {
596
- if (fr[join.foreignField] === lr[join.localField]) {
597
- result.push(this._mergeJoinRow(lr, fr, join));
598
- }
599
- }
600
- }
601
- }
602
- return result;
603
- }
604
-
605
- _mergeJoinRow(lr, fr, join) {
606
- const merged = { ...lr };
607
- const prefix = join.as ? join.as + '_' : '';
608
- for (const [key, value] of Object.entries(fr)) {
609
- merged[prefix + key] = value;
610
- }
611
- return merged;
612
- }
613
-
614
- _nullRow(tableRef) {
615
- // tableRef 可能是 join 对象(用于 right join 的本地表)或 Table 对象
616
- const schema = tableRef._schema || tableRef.table?._schema || {};
617
- const nulls = {};
618
- for (const field of Object.keys(schema)) {
619
- if (field !== '_softDelete') nulls[field] = null;
620
- }
621
- return nulls;
622
- }
623
-
624
- /**
625
- * RIGHT JOIN 未匹配右表行:保留右表数据,本地表字段填 null。
626
- * 返回 { localNulls, merged },其中 merged = { ...nulls, ...fr }
627
- */
628
- _rightNullRow(localRows, foreignRow, join) {
629
- const localSchema = this._table._schema || {};
630
- const localNulls = {};
631
- for (const field of Object.keys(localSchema)) {
632
- if (field !== '_softDelete') localNulls[field] = null;
633
- }
634
- const prefix = join.as ? join.as + '_' : '';
635
- const foreignPrefixed = {};
636
- for (const [key, value] of Object.entries(foreignRow)) {
637
- foreignPrefixed[prefix + key] = value;
638
- }
639
- return { ...localNulls, ...foreignPrefixed };
640
- }
641
-
642
- // ============================================================
643
- // 内部: 排序
644
- // ============================================================
645
-
646
- _applyOrderBy(rows) {
647
- return [...rows].sort((a, b) => {
648
- for (const { field, dir } of this._orderBy) {
649
- const va = a[field];
650
- const vb = b[field];
651
- if (va < vb) return dir === 'asc' ? -1 : 1;
652
- if (va > vb) return dir === 'asc' ? 1 : -1;
653
- }
654
- return 0;
655
- });
656
- }
657
-
658
- // ============================================================
659
- // 内部: 窗口函数
660
- // ============================================================
661
-
662
- _applyWindow(rows) {
663
- const { fn, field, partitionBy, dir } = this._window;
664
-
665
- if (partitionBy) {
666
- const groups = {};
667
- for (const row of rows) {
668
- const key = String(row[partitionBy] ?? '__null__');
669
- if (!groups[key]) groups[key] = [];
670
- groups[key].push(row);
671
- }
672
-
673
- const result = [];
674
- for (const group of Object.values(groups)) {
675
- this._applyWindowToGroup(group, fn, field, dir);
676
- result.push(...group);
677
- }
678
- return result;
679
- }
680
-
681
- this._applyWindowToGroup(rows, fn, field, dir);
682
- return rows;
683
- }
684
-
685
- _applyWindowToGroup(rows, fn, field, dir) {
686
- if (field) {
687
- rows.sort((a, b) => {
688
- const va = a[field];
689
- const vb = b[field];
690
- if (va < vb) return dir === 'asc' ? -1 : 1;
691
- if (va > vb) return dir === 'asc' ? 1 : -1;
692
- return 0;
693
- });
694
- }
695
-
696
- const colName = fn === 'rowNumber' ? '_row_number' : fn === 'rank' ? '_rank' : '_dense_rank';
697
- let rank = 1;
698
- let denseRank = 1;
699
- let prevVal = null;
700
-
701
- for (let i = 0; i < rows.length; i++) {
702
- const curVal = field ? rows[i][field] : null;
703
-
704
- if (fn === 'rowNumber') {
705
- rows[i][colName] = i + 1;
706
- } else if (fn === 'rank') {
707
- if (i > 0 && curVal !== prevVal) {
708
- rank = i + 1;
709
- }
710
- rows[i][colName] = rank;
711
- } else if (fn === 'denseRank') {
712
- if (i > 0 && curVal !== prevVal) {
713
- denseRank++;
714
- }
715
- rows[i][colName] = denseRank;
716
- }
717
-
718
- prevVal = curVal;
719
- }
720
- }
721
-
722
- // ============================================================
723
- // 内部: GROUP BY
724
- // ============================================================
725
-
726
- _applyGroupBy(rows) {
727
- const groups = {};
728
- for (const row of rows) {
729
- const key = String(row[this._groupBy] ?? '__null__');
730
- if (!groups[key]) {
731
- groups[key] = { _group: row[this._groupBy], _rows: [], _count: 0 };
732
- }
733
- groups[key]._rows.push(row);
734
- groups[key]._count++;
735
- }
736
-
737
- const result = [];
738
- for (const g of Object.values(groups)) {
739
- const base = { ...g._rows[0] };
740
- base._group = g._group;
741
- base._count = g._count;
742
- result.push(base);
743
- }
744
- return result;
745
- }
746
-
747
- // ============================================================
748
- // 内部: EXPLAIN(增强版)
749
- // ============================================================
750
-
751
- _getExplain() {
752
- const totalRows = this._table._rows.length;
753
- const filtered = this._table._applyFilterOptimized(this._table._rows, this._conditions);
754
- const plan = {
755
- table: this._table._name,
756
- totalRows,
757
- conditions: this._conditions,
758
- filteredRows: filtered.length,
759
- selectivity: totalRows > 0 ? (filtered.length / totalRows * 100).toFixed(1) + '%' : '0%',
760
- hasBTree: false,
761
- hasHashIndex: false,
762
- indexUsed: null,
763
- btreeIndexes: Object.keys(this._table._btrees),
764
- hashIndexes: Object.keys(this._table._indexes),
765
- joins: [],
766
- groupBy: this._groupBy,
767
- having: this._having,
768
- window: this._window ? this._window.fn : null,
769
- orderBy: this._orderBy.map(o => `${o.field} ${o.dir}`),
770
- limit: this._limitCount,
771
- offset: this._offsetCount,
772
- estimatedCost: filtered.length * (1 + this._joins.length),
773
- optimizerHints: this._optimizerHints
774
- };
775
-
776
- // 检查 B-Tree 使用
777
- for (const [field] of Object.entries(this._conditions)) {
778
- if (this._table._btrees[field]) {
779
- plan.hasBTree = true;
780
- plan.indexUsed = field;
781
- plan.estimatedCost = Math.floor(plan.estimatedCost * 0.05);
782
- break;
783
- }
784
- if (this._table._indexes[field]) {
785
- plan.hasHashIndex = true;
786
- plan.indexUsed = field;
787
- plan.estimatedCost = Math.floor(plan.estimatedCost * 0.1);
788
- break;
789
- }
790
- }
791
-
792
- // JOIN 分析
793
- for (const join of this._joins) {
794
- const foreignTable = this._resolveJoinTable(join.table);
795
- const joinInfo = {
796
- type: join.type,
797
- table: foreignTable._name,
798
- localField: join.localField,
799
- foreignField: join.foreignField,
800
- foreignRows: foreignTable._rows.length,
801
- strategy: (this._optimizerHints.useHashJoin !== false
802
- && (filtered.length > 100 || foreignTable._rows.length > 100))
803
- ? 'hash' : 'nested_loop'
804
- };
805
- // 检查外键表是否有索引
806
- if (foreignTable._btrees[join.foreignField]) {
807
- joinInfo.hasBTree = true;
808
- }
809
- plan.joins.push(joinInfo);
810
- }
811
-
812
- return plan;
813
- }
814
- }
815
-
1
+ // © Vexify 2026 All Rights Reserved.
2
+ /**
3
+ * JSQL Query — 链式查询构建器 v2.0
4
+ * 哈希 JOIN 优化、B-Tree 加速、字符串表名、查询优化器
5
+ */
6
+
7
+ const { createError } = require('./errors');
8
+
9
+ class Query {
10
+ constructor(table, conditions = {}) {
11
+ this._table = table;
12
+ this._conditions = conditions;
13
+ this._orderBy = [];
14
+ this._limitCount = null;
15
+ this._offsetCount = 0;
16
+ this._selectFields = null;
17
+ this._groupBy = null;
18
+ this._having = null;
19
+ this._distinct = false;
20
+ this._distinctField = null;
21
+ this._joins = [];
22
+ this._union = null;
23
+ this._explain = false;
24
+ this._window = null;
25
+ this._cache = null; // { ttl, result, timestamp }
26
+ this._caseExpr = null; // [{ when, then }, ...] + else
27
+ this._optimizerHints = {}; // { useHashJoin: true }
28
+ }
29
+
30
+ // ============================================================
31
+ // 条件
32
+ // ============================================================
33
+
34
+ where(conditions) {
35
+ Object.assign(this._conditions, conditions);
36
+ return this;
37
+ }
38
+
39
+ // ============================================================
40
+ // 排序
41
+ // ============================================================
42
+
43
+ orderBy(field, dir = 'asc') {
44
+ this._orderBy.push({ field, dir: dir.toLowerCase() });
45
+ return this;
46
+ }
47
+
48
+ orderByDesc(field) {
49
+ return this.orderBy(field, 'desc');
50
+ }
51
+
52
+ // ============================================================
53
+ // 分页
54
+ // ============================================================
55
+
56
+ limit(n) {
57
+ this._limitCount = n;
58
+ return this;
59
+ }
60
+
61
+ offset(n) {
62
+ this._offsetCount = n;
63
+ return this;
64
+ }
65
+
66
+ paginate(page = 1, perPage = 20) {
67
+ this._limitCount = perPage;
68
+ this._offsetCount = (page - 1) * perPage;
69
+ const total = this._countRaw();
70
+ const rows = this.get();
71
+ return { rows, total, page, perPage, totalPages: Math.ceil(total / perPage) };
72
+ }
73
+
74
+ // ============================================================
75
+ // 字段选择
76
+ // ============================================================
77
+
78
+ select(fields) {
79
+ this._selectFields = Array.isArray(fields) ? fields : [fields];
80
+ return this;
81
+ }
82
+
83
+ distinct(field) {
84
+ this._distinct = true;
85
+ this._distinctField = field;
86
+ return this;
87
+ }
88
+
89
+ // ============================================================
90
+ // CASE WHEN
91
+ // ============================================================
92
+
93
+ /**
94
+ * CASE WHEN 表达式
95
+ * @example
96
+ * db.users.where({}).case([
97
+ * { when: { age: { $lt: 18 } }, then: 'minor' },
98
+ * { when: { age: { $lt: 60 } }, then: 'adult' }
99
+ * ], 'senior').as('ageGroup').get()
100
+ */
101
+ case(whenClauses, elseExpr) {
102
+ this._caseExpr = { clauses: whenClauses, else: elseExpr };
103
+ return this;
104
+ }
105
+
106
+ /**
107
+ * 给 CASE WHEN 结果命名
108
+ */
109
+ as(fieldName) {
110
+ this._caseAs = fieldName;
111
+ return this;
112
+ }
113
+
114
+ // ============================================================
115
+ // 分组 & 聚合后过滤
116
+ // ============================================================
117
+
118
+ groupBy(field) {
119
+ this._groupBy = field;
120
+ return this;
121
+ }
122
+
123
+ having(conditions) {
124
+ this._having = conditions;
125
+ return this;
126
+ }
127
+
128
+ // ============================================================
129
+ // 窗口函数
130
+ // ============================================================
131
+
132
+ /**
133
+ * 窗口函数
134
+ * @param {string} fn - 'rowNumber' | 'rank' | 'denseRank'
135
+ * @param {string} field - 排序字段
136
+ * @param {string} partitionBy - 分区字段
137
+ * @param {string} dir - 'asc' | 'desc'
138
+ */
139
+ window(fn, field, partitionBy, dir = 'asc') {
140
+ this._window = { fn, field, partitionBy, dir };
141
+ return this;
142
+ }
143
+
144
+ // ============================================================
145
+ // 查询优化器提示
146
+ // ============================================================
147
+
148
+ /**
149
+ * 强制使用哈希 JOIN
150
+ */
151
+ useHashJoin() {
152
+ this._optimizerHints.useHashJoin = true;
153
+ return this;
154
+ }
155
+
156
+ /**
157
+ * 强制使用嵌套循环 JOIN
158
+ */
159
+ useNestedLoop() {
160
+ this._optimizerHints.useHashJoin = false;
161
+ return this;
162
+ }
163
+
164
+ // ============================================================
165
+ // JOIN(支持字符串表名 + 哈希 JOIN 优化)
166
+ // ============================================================
167
+
168
+ /**
169
+ * INNER JOIN
170
+ * @param {string|Table} otherTable - 表名或 Table 对象
171
+ * @param {string} localField - 本地字段
172
+ * @param {string} foreignField - 外键字段
173
+ * @param {string} as - 别名前缀
174
+ */
175
+ join(otherTable, localField, foreignField, as) {
176
+ this._joins.push({ type: 'inner', table: otherTable, localField, foreignField, as });
177
+ return this;
178
+ }
179
+
180
+ leftJoin(otherTable, localField, foreignField, as) {
181
+ this._joins.push({ type: 'left', table: otherTable, localField, foreignField, as });
182
+ return this;
183
+ }
184
+
185
+ rightJoin(otherTable, localField, foreignField, as) {
186
+ this._joins.push({ type: 'right', table: otherTable, localField, foreignField, as });
187
+ return this;
188
+ }
189
+
190
+ // ============================================================
191
+ // UNION
192
+ // ============================================================
193
+
194
+ union(query) {
195
+ this._union = query;
196
+ return this;
197
+ }
198
+
199
+ // ============================================================
200
+ // EXPLAIN
201
+ // ============================================================
202
+
203
+ explain() {
204
+ this._explain = true;
205
+ return this;
206
+ }
207
+
208
+ // ============================================================
209
+ // 缓存
210
+ // ============================================================
211
+
212
+ /**
213
+ * 缓存查询结果
214
+ * @param {number} ttlMs - 缓存有效期(毫秒),默认 60000
215
+ */
216
+ cache(ttlMs = 60000) {
217
+ this._cache = { ttl: ttlMs, result: null, timestamp: 0 };
218
+ return this;
219
+ }
220
+
221
+ // ============================================================
222
+ // 执行
223
+ // ============================================================
224
+
225
+ get() {
226
+ const startTime = Date.now();
227
+ if (this._explain) return this._getExplain();
228
+
229
+ // 缓存命中
230
+ if (this._cache && this._cache.result && (Date.now() - this._cache.timestamp < this._cache.ttl)) {
231
+ return this._cache.result;
232
+ }
233
+
234
+ let rows = this._table._applyFilterOptimized(this._table._rows, this._conditions);
235
+
236
+ // JOIN
237
+ for (const join of this._joins) {
238
+ rows = this._applyJoinOptimized(rows, join);
239
+ }
240
+
241
+ // UNION
242
+ if (this._union) {
243
+ const unionRows = this._union.get();
244
+ rows = [...rows, ...unionRows];
245
+ }
246
+
247
+ // GROUP BY
248
+ if (this._groupBy) {
249
+ rows = this._applyGroupBy(rows);
250
+ }
251
+
252
+ // HAVING
253
+ if (this._having) {
254
+ rows = this._table._applyFilter(rows, this._having);
255
+ }
256
+
257
+ // 窗口函数
258
+ if (this._window) {
259
+ rows = this._applyWindow(rows);
260
+ }
261
+
262
+ // DISTINCT
263
+ if (this._distinct && this._distinctField) {
264
+ const seen = new Set();
265
+ rows = rows.filter(r => {
266
+ const val = r[this._distinctField];
267
+ if (val === undefined) return false;
268
+ const key = String(val);
269
+ if (seen.has(key)) return false;
270
+ seen.add(key);
271
+ return true;
272
+ });
273
+ }
274
+
275
+ // 排序
276
+ if (this._orderBy.length > 0) {
277
+ rows = this._applyOrderBy(rows);
278
+ }
279
+
280
+ // 分页
281
+ if (this._offsetCount > 0) {
282
+ rows = rows.slice(this._offsetCount);
283
+ }
284
+ if (this._limitCount !== null) {
285
+ rows = rows.slice(0, this._limitCount);
286
+ }
287
+
288
+ // CASE WHEN
289
+ if (this._caseExpr) {
290
+ rows = rows.map(r => {
291
+ const row = { ...r };
292
+ let val = this._caseExpr.else;
293
+ for (const clause of this._caseExpr.clauses) {
294
+ if (this._table._matchRow(row, clause.when)) {
295
+ val = clause.then;
296
+ break;
297
+ }
298
+ }
299
+ const asField = this._caseAs || '_case';
300
+ row[asField] = val;
301
+ return row;
302
+ });
303
+ }
304
+
305
+ // 字段选择
306
+ rows = rows.map(r => ({ ...r }));
307
+ if (this._selectFields) {
308
+ rows = rows.map(r => {
309
+ const obj = {};
310
+ for (const f of this._selectFields) {
311
+ obj[f] = r[f];
312
+ }
313
+ return obj;
314
+ });
315
+ }
316
+
317
+ // 缓存存储
318
+ if (this._cache) {
319
+ this._cache.result = rows;
320
+ this._cache.timestamp = Date.now();
321
+ }
322
+
323
+ this._table._db._logSlowQuery(`get ${this._table._name}`, Date.now() - startTime, rows.length);
324
+ return rows;
325
+ }
326
+
327
+ first() {
328
+ const rows = this.limit(1).get();
329
+ return rows.length > 0 ? rows[0] : null;
330
+ }
331
+
332
+ count() {
333
+ return this._countRaw();
334
+ }
335
+
336
+ sum(field) {
337
+ const rows = this._getFilteredRows();
338
+ return rows.reduce((acc, r) => acc + (Number(r[field]) || 0), 0);
339
+ }
340
+
341
+ avg(field) {
342
+ const rows = this._getFilteredRows();
343
+ if (rows.length === 0) return 0;
344
+ return this.sum(field) / rows.length;
345
+ }
346
+
347
+ min(field) {
348
+ const rows = this._getFilteredRows();
349
+ if (rows.length === 0) return null;
350
+ return rows.reduce((min, r) => {
351
+ const v = Number(r[field]);
352
+ return (min === null || v < min) ? v : min;
353
+ }, null);
354
+ }
355
+
356
+ max(field) {
357
+ const rows = this._getFilteredRows();
358
+ if (rows.length === 0) return null;
359
+ return rows.reduce((max, r) => {
360
+ const v = Number(r[field]);
361
+ return (max === null || v > max) ? v : max;
362
+ }, null);
363
+ }
364
+
365
+ groupStats(field) {
366
+ if (!this._groupBy) throw createError('ER_PARSE_ERROR', 'groupStats() requires groupBy()');
367
+ const rows = this._table._applyFilterOptimized(this._table._rows, this._conditions);
368
+ const groups = {};
369
+
370
+ for (const row of rows) {
371
+ const key = String(row[this._groupBy] ?? '__null__');
372
+ if (!groups[key]) {
373
+ groups[key] = { _group: row[this._groupBy], _count: 0, _sum: 0, _min: null, _max: null, _rows: [] };
374
+ }
375
+ const g = groups[key];
376
+ g._count++;
377
+ const v = Number(row[field]) || 0;
378
+ g._sum += v;
379
+ g._min = g._min === null ? v : Math.min(g._min, v);
380
+ g._max = g._max === null ? v : Math.max(g._max, v);
381
+ g._rows.push(row);
382
+ }
383
+
384
+ const result = Object.values(groups);
385
+ result.forEach(g => {
386
+ g._avg = g._count > 0 ? g._sum / g._count : 0;
387
+ delete g._rows;
388
+ });
389
+ return result;
390
+ }
391
+
392
+ update(updates) {
393
+ return this._table.update(this._conditions, updates);
394
+ }
395
+
396
+ remove() {
397
+ return this._table.remove(this._conditions);
398
+ }
399
+
400
+ /**
401
+ * 无效缓存
402
+ */
403
+ invalidateCache() {
404
+ if (this._cache) {
405
+ this._cache.result = null;
406
+ this._cache.timestamp = 0;
407
+ }
408
+ return this;
409
+ }
410
+
411
+ // ============================================================
412
+ // toSQL
413
+ // ============================================================
414
+
415
+ /**
416
+ * 导出为类 SQL 字符串(调试用)
417
+ */
418
+ toSQL() {
419
+ const parts = ['SELECT'];
420
+ if (this._selectFields) {
421
+ parts.push(this._selectFields.join(', '));
422
+ } else {
423
+ parts.push('*');
424
+ }
425
+ parts.push('FROM', this._table._name);
426
+
427
+ if (Object.keys(this._conditions).length > 0) {
428
+ parts.push('WHERE', JSON.stringify(this._conditions));
429
+ }
430
+
431
+ for (const join of this._joins) {
432
+ const tableName = typeof join.table === 'string' ? join.table : join.table._name;
433
+ parts.push(join.type.toUpperCase() + ' JOIN', tableName,
434
+ 'ON', `${this._table._name}.${join.localField} = ${tableName}.${join.foreignField}`);
435
+ }
436
+
437
+ if (this._groupBy) {
438
+ parts.push('GROUP BY', this._groupBy);
439
+ if (this._having) parts.push('HAVING', JSON.stringify(this._having));
440
+ }
441
+
442
+ if (this._orderBy.length > 0) {
443
+ parts.push('ORDER BY', this._orderBy.map(o => `${o.field} ${o.dir}`).join(', '));
444
+ }
445
+
446
+ if (this._limitCount !== null) {
447
+ parts.push('LIMIT', String(this._limitCount));
448
+ }
449
+
450
+ if (this._offsetCount > 0) {
451
+ parts.push('OFFSET', String(this._offsetCount));
452
+ }
453
+
454
+ return parts.join(' ');
455
+ }
456
+
457
+ // ============================================================
458
+ // 内部
459
+ // ============================================================
460
+
461
+ _getFilteredRows() {
462
+ return this._table._applyFilterOptimized(this._table._rows, this._conditions);
463
+ }
464
+
465
+ _countRaw() {
466
+ return this._table._applyFilterOptimized(this._table._rows, this._conditions).length;
467
+ }
468
+
469
+ // ============================================================
470
+ // 内部: JOIN 优化
471
+ // ============================================================
472
+
473
+ /**
474
+ * 解析 JOIN 表引用(支持字符串表名和 Table 对象)
475
+ */
476
+ _resolveJoinTable(tableRef) {
477
+ if (typeof tableRef === 'string') {
478
+ const resolved = this._table._db._tables[tableRef];
479
+ if (!resolved) {
480
+ throw createError('ER_NO_SUCH_TABLE', tableRef);
481
+ }
482
+ return resolved;
483
+ }
484
+ return tableRef;
485
+ }
486
+
487
+ /**
488
+ * 优化的 JOIN 执行
489
+ * 自动选择哈希 JOIN 或嵌套循环
490
+ */
491
+ _applyJoinOptimized(localRows, join) {
492
+ const foreignTable = this._resolveJoinTable(join.table);
493
+
494
+ // 如果强制或自动选择哈希 JOIN
495
+ const useHash = this._optimizerHints.useHashJoin !== false
496
+ && (this._optimizerHints.useHashJoin === true
497
+ || localRows.length > 100
498
+ || foreignTable._rows.length > 100);
499
+
500
+ if (useHash) {
501
+ return this._applyHashJoin(localRows, foreignTable, join);
502
+ }
503
+ return this._applyNestedLoopJoin(localRows, foreignTable, join);
504
+ }
505
+
506
+ /**
507
+ * 哈希 JOIN — O(n + m) 时间复杂度
508
+ * 构建外键表的哈希映射,然后一次遍历本地行完成 JOIN
509
+ */
510
+ _applyHashJoin(localRows, foreignTable, join) {
511
+ // 构建哈希表:foreignField -> [foreignRows]
512
+ const hashMap = new Map();
513
+ for (const fr of foreignTable._rows) {
514
+ const key = fr[join.foreignField];
515
+ if (key === undefined || key === null) continue;
516
+ if (!hashMap.has(key)) {
517
+ hashMap.set(key, []);
518
+ }
519
+ hashMap.get(key).push(fr);
520
+ }
521
+
522
+ const result = [];
523
+
524
+ if (join.type === 'left') {
525
+ for (const lr of localRows) {
526
+ const matches = hashMap.get(lr[join.localField]) || [];
527
+ if (matches.length === 0) {
528
+ result.push({ ...lr, ...this._nullRow(foreignTable) });
529
+ } else {
530
+ for (const fr of matches) {
531
+ result.push(this._mergeJoinRow(lr, fr, join));
532
+ }
533
+ }
534
+ }
535
+ } else if (join.type === 'right') {
536
+ for (const fr of foreignTable._rows) {
537
+ const key = fr[join.foreignField];
538
+ const matches = localRows.filter(lr => lr[join.localField] === key);
539
+ if (matches.length === 0) {
540
+ result.push(this._rightNullRow(localRows, fr, join));
541
+ } else {
542
+ for (const lr of matches) {
543
+ result.push(this._mergeJoinRow(lr, fr, join));
544
+ }
545
+ }
546
+ }
547
+ } else {
548
+ // INNER JOIN
549
+ for (const lr of localRows) {
550
+ const matches = hashMap.get(lr[join.localField]);
551
+ if (matches) {
552
+ for (const fr of matches) {
553
+ result.push(this._mergeJoinRow(lr, fr, join));
554
+ }
555
+ }
556
+ }
557
+ }
558
+
559
+ return result;
560
+ }
561
+
562
+ /**
563
+ * 嵌套循环 JOIN — O(n * m) 时间复杂度
564
+ * 用于小数据量场景
565
+ */
566
+ _applyNestedLoopJoin(localRows, foreignTable, join) {
567
+ const result = [];
568
+ const foreignRows = foreignTable._rows;
569
+
570
+ if (join.type === 'left') {
571
+ for (const lr of localRows) {
572
+ const matches = foreignRows.filter(fr => fr[join.foreignField] === lr[join.localField]);
573
+ if (matches.length === 0) {
574
+ result.push({ ...lr, ...this._nullRow(foreignTable) });
575
+ } else {
576
+ for (const fr of matches) {
577
+ result.push(this._mergeJoinRow(lr, fr, join));
578
+ }
579
+ }
580
+ }
581
+ } else if (join.type === 'right') {
582
+ for (const fr of foreignRows) {
583
+ const matches = localRows.filter(lr => lr[join.localField] === fr[join.foreignField]);
584
+ if (matches.length === 0) {
585
+ result.push(this._rightNullRow(localRows, fr, join));
586
+ } else {
587
+ for (const lr of matches) {
588
+ result.push(this._mergeJoinRow(lr, fr, join));
589
+ }
590
+ }
591
+ }
592
+ } else {
593
+ // INNER JOIN
594
+ for (const lr of localRows) {
595
+ for (const fr of foreignRows) {
596
+ if (fr[join.foreignField] === lr[join.localField]) {
597
+ result.push(this._mergeJoinRow(lr, fr, join));
598
+ }
599
+ }
600
+ }
601
+ }
602
+ return result;
603
+ }
604
+
605
+ _mergeJoinRow(lr, fr, join) {
606
+ const merged = { ...lr };
607
+ const prefix = join.as ? join.as + '_' : '';
608
+ for (const [key, value] of Object.entries(fr)) {
609
+ merged[prefix + key] = value;
610
+ }
611
+ return merged;
612
+ }
613
+
614
+ _nullRow(tableRef) {
615
+ // tableRef 可能是 join 对象(用于 right join 的本地表)或 Table 对象
616
+ const schema = tableRef._schema || tableRef.table?._schema || {};
617
+ const nulls = {};
618
+ for (const field of Object.keys(schema)) {
619
+ if (field !== '_softDelete') nulls[field] = null;
620
+ }
621
+ return nulls;
622
+ }
623
+
624
+ /**
625
+ * RIGHT JOIN 未匹配右表行:保留右表数据,本地表字段填 null。
626
+ * 返回 { localNulls, merged },其中 merged = { ...nulls, ...fr }
627
+ */
628
+ _rightNullRow(localRows, foreignRow, join) {
629
+ const localSchema = this._table._schema || {};
630
+ const localNulls = {};
631
+ for (const field of Object.keys(localSchema)) {
632
+ if (field !== '_softDelete') localNulls[field] = null;
633
+ }
634
+ const prefix = join.as ? join.as + '_' : '';
635
+ const foreignPrefixed = {};
636
+ for (const [key, value] of Object.entries(foreignRow)) {
637
+ foreignPrefixed[prefix + key] = value;
638
+ }
639
+ return { ...localNulls, ...foreignPrefixed };
640
+ }
641
+
642
+ // ============================================================
643
+ // 内部: 排序
644
+ // ============================================================
645
+
646
+ _applyOrderBy(rows) {
647
+ return [...rows].sort((a, b) => {
648
+ for (const { field, dir } of this._orderBy) {
649
+ const va = a[field];
650
+ const vb = b[field];
651
+ if (va < vb) return dir === 'asc' ? -1 : 1;
652
+ if (va > vb) return dir === 'asc' ? 1 : -1;
653
+ }
654
+ return 0;
655
+ });
656
+ }
657
+
658
+ // ============================================================
659
+ // 内部: 窗口函数
660
+ // ============================================================
661
+
662
+ _applyWindow(rows) {
663
+ const { fn, field, partitionBy, dir } = this._window;
664
+
665
+ if (partitionBy) {
666
+ const groups = {};
667
+ for (const row of rows) {
668
+ const key = String(row[partitionBy] ?? '__null__');
669
+ if (!groups[key]) groups[key] = [];
670
+ groups[key].push(row);
671
+ }
672
+
673
+ const result = [];
674
+ for (const group of Object.values(groups)) {
675
+ this._applyWindowToGroup(group, fn, field, dir);
676
+ result.push(...group);
677
+ }
678
+ return result;
679
+ }
680
+
681
+ this._applyWindowToGroup(rows, fn, field, dir);
682
+ return rows;
683
+ }
684
+
685
+ _applyWindowToGroup(rows, fn, field, dir) {
686
+ if (field) {
687
+ rows.sort((a, b) => {
688
+ const va = a[field];
689
+ const vb = b[field];
690
+ if (va < vb) return dir === 'asc' ? -1 : 1;
691
+ if (va > vb) return dir === 'asc' ? 1 : -1;
692
+ return 0;
693
+ });
694
+ }
695
+
696
+ const colName = fn === 'rowNumber' ? '_row_number' : fn === 'rank' ? '_rank' : '_dense_rank';
697
+ let rank = 1;
698
+ let denseRank = 1;
699
+ let prevVal = null;
700
+
701
+ for (let i = 0; i < rows.length; i++) {
702
+ const curVal = field ? rows[i][field] : null;
703
+
704
+ if (fn === 'rowNumber') {
705
+ rows[i][colName] = i + 1;
706
+ } else if (fn === 'rank') {
707
+ if (i > 0 && curVal !== prevVal) {
708
+ rank = i + 1;
709
+ }
710
+ rows[i][colName] = rank;
711
+ } else if (fn === 'denseRank') {
712
+ if (i > 0 && curVal !== prevVal) {
713
+ denseRank++;
714
+ }
715
+ rows[i][colName] = denseRank;
716
+ }
717
+
718
+ prevVal = curVal;
719
+ }
720
+ }
721
+
722
+ // ============================================================
723
+ // 内部: GROUP BY
724
+ // ============================================================
725
+
726
+ _applyGroupBy(rows) {
727
+ const groups = {};
728
+ for (const row of rows) {
729
+ const key = String(row[this._groupBy] ?? '__null__');
730
+ if (!groups[key]) {
731
+ groups[key] = { _group: row[this._groupBy], _rows: [], _count: 0 };
732
+ }
733
+ groups[key]._rows.push(row);
734
+ groups[key]._count++;
735
+ }
736
+
737
+ const result = [];
738
+ for (const g of Object.values(groups)) {
739
+ const base = { ...g._rows[0] };
740
+ base._group = g._group;
741
+ base._count = g._count;
742
+ result.push(base);
743
+ }
744
+ return result;
745
+ }
746
+
747
+ // ============================================================
748
+ // 内部: EXPLAIN(增强版)
749
+ // ============================================================
750
+
751
+ _getExplain() {
752
+ const totalRows = this._table._rows.length;
753
+ const filtered = this._table._applyFilterOptimized(this._table._rows, this._conditions);
754
+ const plan = {
755
+ table: this._table._name,
756
+ totalRows,
757
+ conditions: this._conditions,
758
+ filteredRows: filtered.length,
759
+ selectivity: totalRows > 0 ? (filtered.length / totalRows * 100).toFixed(1) + '%' : '0%',
760
+ hasBTree: false,
761
+ hasHashIndex: false,
762
+ indexUsed: null,
763
+ btreeIndexes: Object.keys(this._table._btrees),
764
+ hashIndexes: Object.keys(this._table._indexes),
765
+ joins: [],
766
+ groupBy: this._groupBy,
767
+ having: this._having,
768
+ window: this._window ? this._window.fn : null,
769
+ orderBy: this._orderBy.map(o => `${o.field} ${o.dir}`),
770
+ limit: this._limitCount,
771
+ offset: this._offsetCount,
772
+ estimatedCost: filtered.length * (1 + this._joins.length),
773
+ optimizerHints: this._optimizerHints
774
+ };
775
+
776
+ // 检查 B-Tree 使用
777
+ for (const [field] of Object.entries(this._conditions)) {
778
+ if (this._table._btrees[field]) {
779
+ plan.hasBTree = true;
780
+ plan.indexUsed = field;
781
+ plan.estimatedCost = Math.floor(plan.estimatedCost * 0.05);
782
+ break;
783
+ }
784
+ if (this._table._indexes[field]) {
785
+ plan.hasHashIndex = true;
786
+ plan.indexUsed = field;
787
+ plan.estimatedCost = Math.floor(plan.estimatedCost * 0.1);
788
+ break;
789
+ }
790
+ }
791
+
792
+ // JOIN 分析
793
+ for (const join of this._joins) {
794
+ const foreignTable = this._resolveJoinTable(join.table);
795
+ const joinInfo = {
796
+ type: join.type,
797
+ table: foreignTable._name,
798
+ localField: join.localField,
799
+ foreignField: join.foreignField,
800
+ foreignRows: foreignTable._rows.length,
801
+ strategy: (this._optimizerHints.useHashJoin !== false
802
+ && (filtered.length > 100 || foreignTable._rows.length > 100))
803
+ ? 'hash' : 'nested_loop'
804
+ };
805
+ // 检查外键表是否有索引
806
+ if (foreignTable._btrees[join.foreignField]) {
807
+ joinInfo.hasBTree = true;
808
+ }
809
+ plan.joins.push(joinInfo);
810
+ }
811
+
812
+ return plan;
813
+ }
814
+ }
815
+
816
816
  module.exports = Query;