jsql-neo 5.4.2 → 6.0.0-beta2

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 (39) hide show
  1. package/README.md +198 -14
  2. package/index.d.ts +29 -0
  3. package/index.js +12 -2
  4. package/lib/ast.js +214 -0
  5. package/lib/database.js +26 -60
  6. package/lib/mongo_server.js +1 -1
  7. package/lib/native_client.js +43 -66
  8. package/lib/plugin.js +227 -3
  9. package/lib/plugins/audit.js +37 -0
  10. package/lib/plugins/index.js +72 -0
  11. package/lib/plugins/logger.js +35 -0
  12. package/lib/plugins/metrics.js +38 -0
  13. package/lib/plugins/timestamps.js +43 -0
  14. package/lib/plugins/validation.js +128 -0
  15. package/lib/sql.js +1212 -84
  16. package/lib/wasm_client.js +41 -57
  17. package/native/jsql-neo-native.node +0 -0
  18. package/nativesrc/jsql-neo-core/Cargo.lock +8 -1
  19. package/nativesrc/jsql-neo-core/Cargo.toml +3 -1
  20. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +50 -13
  21. package/nativesrc/jsql-neo-core/src/engine/memory.rs +46 -23
  22. package/nativesrc/jsql-neo-core/src/engine/mod.rs +13 -1
  23. package/nativesrc/jsql-neo-core/src/engine/table.rs +338 -42
  24. package/nativesrc/jsql-neo-core/src/storage/wal.rs +245 -32
  25. package/nativesrc/jsql-neo-native/Cargo.lock +9 -2
  26. package/nativesrc/jsql-neo-native/Cargo.toml +9 -1
  27. package/nativesrc/jsql-neo-native/src/lib.rs +14 -2
  28. package/nativesrc/jsql-neo-wasm/Cargo.lock +9 -2
  29. package/nativesrc/jsql-neo-wasm/Cargo.toml +8 -1
  30. package/nativesrc/jsql-neo-wasm/src/lib.rs +18 -2
  31. package/package.json +4 -3
  32. package/test/sql-parser.test.js +450 -0
  33. package/wasm/browser.mjs +12 -0
  34. package/wasm/browser_bg.mjs +269 -248
  35. package/wasm/jsql_neo_wasm.d.ts +6 -0
  36. package/wasm/jsql_neo_wasm.js +248 -127
  37. package/wasm/jsql_neo_wasm_bg.wasm +0 -0
  38. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +22 -22
  39. package/wasm/package.json +1 -1
package/lib/sql.js CHANGED
@@ -56,7 +56,13 @@ const KEYWORDS = new Set([
56
56
  'BETWEEN', 'USING', 'FULL', 'UNSIGNED', 'ZEROFILL', 'TRUNCATE', 'COLLATE', 'CHARACTER',
57
57
  'ALTER', 'ADD', 'COLUMN', 'MODIFY', 'CHANGE', 'INDEX', 'FOREIGN', 'REFERENCES',
58
58
  'CONSTRAINT', 'RENAME', 'TO', 'AFTER', 'FIRST', 'ENGINE', 'AUTO_INCREMENT', 'SPATIAL',
59
- 'REGEXP', 'TRUE', 'FALSE', 'RLIKE', 'PRAGMA', 'REPLACE', 'BLOB', 'RAISE', 'IGNORE'
59
+ 'REGEXP', 'TRUE', 'FALSE', 'RLIKE', 'PRAGMA', 'REPLACE', 'BLOB', 'RAISE', 'IGNORE',
60
+ // 5.5.0 新增:CTE 与集合运算符
61
+ 'WITH', 'RECURSIVE', 'INTERSECT', 'EXCEPT'
62
+ // 注意:OVER / PARTITION / UNBOUNDED / PRECEDING / FOLLOWING 等刻意不加入此表。
63
+ // 它们同时是常见标识符(列名 rank、current、row…),若被词法层标记为 keyword
64
+ // 会让 `SELECT rank FROM t` 之类的既有语句解析失败。这些词改由 Parser.isWord()
65
+ // 在语法层按上下文大小写不敏感地匹配,从而不破坏既有解析行为。
60
66
  ]);
61
67
 
62
68
  function tokenize(sql) {
@@ -160,6 +166,17 @@ function tokenize(sql) {
160
166
  continue;
161
167
  }
162
168
 
169
+ // 参数占位符:? (顺序)、?N (编号)、?? (标识符占位)
170
+ if (c === '?') {
171
+ if (sql[i + 1] === '?') { tokens.push(new SQLToken('param', '??', i)); i += 2; continue; }
172
+ let j = i + 1;
173
+ let num = '';
174
+ while (j < n && sql[j] >= '0' && sql[j] <= '9') { num += sql[j]; j++; }
175
+ tokens.push(new SQLToken('param', num ? parseInt(num, 10) : null, i));
176
+ i = j;
177
+ continue;
178
+ }
179
+
163
180
  const two = sql.slice(i, i + 2);
164
181
  if (two === '<=' || two === '>=' || two === '!=' || two === '<>' || two === '==') {
165
182
  tokens.push(new SQLToken('op', two, i));
@@ -184,6 +201,7 @@ class Parser {
184
201
  constructor(tokens) {
185
202
  this.tokens = tokens;
186
203
  this.pos = 0;
204
+ this.paramCount = 0; // 顺序占位符 ? 的序号(从 0 开始)
187
205
  }
188
206
 
189
207
  peek(offset = 0) { return this.tokens[this.pos + offset]; }
@@ -220,27 +238,70 @@ class Parser {
220
238
  parseStatement() {
221
239
  const t = this.peek();
222
240
  if (t.type === 'eof') return null;
223
- if (t.type !== 'keyword') throw new Error(`Expected SQL statement, got '${t.value}'`);
241
+ if (t.type !== 'keyword') {
242
+ // 少数语句起始词刻意不进关键字表(它们同时是常见标识符),按上下文识别
243
+ const w = String(t.value).toUpperCase();
244
+ if (w === 'EXPLAIN') {
245
+ this.next();
246
+ if (this.isWord('ANALYZE')) this.next();
247
+ const stmt = this.parseStatement();
248
+ if (!stmt) throw new Error('EXPLAIN requires a following statement');
249
+ return { type: 'explain', statement: stmt };
250
+ }
251
+ if (w === 'SAVEPOINT') {
252
+ this.next();
253
+ const nm = this.next();
254
+ if (nm.type !== 'ident') throw new Error(`Expected savepoint name, got '${nm.value}'`);
255
+ this.optionalTailSemicolon();
256
+ return { type: 'savepoint', name: nm.value };
257
+ }
258
+ if (w === 'RELEASE') {
259
+ this.next();
260
+ if (this.isWord('SAVEPOINT')) this.next();
261
+ const nm = this.next();
262
+ if (nm.type !== 'ident') throw new Error(`Expected savepoint name, got '${nm.value}'`);
263
+ this.optionalTailSemicolon();
264
+ return { type: 'releaseSavepoint', name: nm.value };
265
+ }
266
+ throw new Error(`Expected SQL statement, got '${t.value}'`);
267
+ }
224
268
 
225
269
  switch (t.value) {
226
270
  case 'CREATE':
227
271
  if (this.isKeyword('TABLE', 1)) return this.parseCreateTable();
228
272
  if (this.isKeyword('DATABASE', 1)) return this.parseCreateDatabase();
273
+ if (this.isWord('VIEW', 1) || this.isWord('OR', 1)) return this.parseCreateView();
229
274
  throw new Error('Unsupported CREATE statement');
230
275
  case 'DROP':
231
276
  if (this.isKeyword('TABLE', 1)) return this.parseDropTable();
232
277
  if (this.isKeyword('DATABASE', 1)) return this.parseDropDatabase();
278
+ if (this.isWord('VIEW', 1)) return this.parseDropView();
233
279
  throw new Error('Unsupported DROP statement');
234
280
  case 'INSERT': return this.parseInsert();
281
+ case 'REPLACE': return this.parseReplace();
235
282
  case 'ALTER': return this.parseAlter();
236
283
  case 'TRUNCATE': this.expectKeyword('TRUNCATE'); if (this.isKeyword('TABLE')) this.next(); return { type: 'truncate', name: this.parseTableName() };
237
284
  case 'SELECT': return this.parseSelect();
285
+ case 'WITH': return this.parseWith();
238
286
  case 'UPDATE': return this.parseUpdate();
239
287
  case 'DELETE': return this.parseDelete();
240
288
  case 'BEGIN': this.expectKeyword('BEGIN'); this.optionalTransaction(); return { type: 'begin' };
241
289
  case 'START': this.expectKeyword('START'); if (this.isKeyword('TRANSACTION')) this.next(); return { type: 'begin' };
242
290
  case 'COMMIT': this.expectKeyword('COMMIT'); this.optionalTransaction(); return { type: 'commit' };
243
- case 'ROLLBACK': this.expectKeyword('ROLLBACK'); this.optionalTransaction(); return { type: 'rollback' };
291
+ case 'ROLLBACK': {
292
+ this.expectKeyword('ROLLBACK');
293
+ // ROLLBACK TO [SAVEPOINT] name —— 回滚到保存点而非回滚整个事务
294
+ if (this.isKeyword('TO')) {
295
+ this.next();
296
+ if (this.isWord('SAVEPOINT')) this.next();
297
+ const nm = this.next();
298
+ if (nm.type !== 'ident') throw new Error(`Expected savepoint name, got '${nm.value}'`);
299
+ this.optionalTailSemicolon();
300
+ return { type: 'rollbackTo', name: nm.value };
301
+ }
302
+ this.optionalTransaction();
303
+ return { type: 'rollback' };
304
+ }
244
305
  case 'SHOW': return this.parseShow();
245
306
  case 'SET': return this.parseSet();
246
307
  case 'DESCRIBE': case 'DESC': return this.parseDescribe();
@@ -255,6 +316,54 @@ class Parser {
255
316
  if (this.isKeyword('WORK')) this.next();
256
317
  }
257
318
 
319
+ /**
320
+ * CREATE [OR REPLACE] VIEW name [(cols)] AS SELECT ...
321
+ * 视图在语法层解析成 { type:'createView', name, columns, select };
322
+ * 执行层由引擎侧决定如何注册(不存在视图存储时退化为"可用即查询")。
323
+ */
324
+ parseCreateView() {
325
+ this.expectKeyword('CREATE');
326
+ let orReplace = false;
327
+ if (this.isWord('OR')) {
328
+ this.next();
329
+ if (!this.isWord('REPLACE')) throw new Error(`Expected REPLACE after OR, got '${this.peek().value}'`);
330
+ this.next();
331
+ orReplace = true;
332
+ }
333
+ if (!this.isWord('VIEW')) throw new Error(`Expected VIEW, got '${this.peek().value}'`);
334
+ this.next();
335
+ const name = this.parseTableName();
336
+ let columns = null;
337
+ if (this.peek().type === 'op' && this.peek().value === '(') {
338
+ this.next();
339
+ columns = [];
340
+ for (;;) {
341
+ columns.push(this.parseColumnRef());
342
+ if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
343
+ break;
344
+ }
345
+ this.expect('op', ')');
346
+ }
347
+ if (!this.isKeyword('AS')) throw new Error(`Expected AS in CREATE VIEW, got '${this.peek().value}'`);
348
+ this.next();
349
+ const select = this.parseSelect();
350
+ this.optionalTailSemicolon();
351
+ return { type: 'createView', name, columns, select, orReplace };
352
+ }
353
+
354
+ parseDropView() {
355
+ this.expectKeyword('DROP');
356
+ if (!this.isWord('VIEW')) throw new Error(`Expected VIEW, got '${this.peek().value}'`);
357
+ this.next();
358
+ let ifExists = false;
359
+ if (this.isKeyword('IF')) {
360
+ this.expectKeyword('IF'); this.expectKeyword('EXISTS'); ifExists = true;
361
+ }
362
+ const name = this.parseTableName();
363
+ this.optionalTailSemicolon();
364
+ return { type: 'dropView', name, ifExists };
365
+ }
366
+
258
367
  parseTableName() {
259
368
  const t = this.next();
260
369
  if (t.type !== 'ident') throw new Error(`Expected table name, got '${t.value}'`);
@@ -425,9 +534,26 @@ class Parser {
425
534
  }
426
535
  }
427
536
 
537
+ // REPLACE INTO t ... —— 等价于"存在则替换",解析结构与 INSERT 一致
538
+ parseReplace() {
539
+ this.expectKeyword('REPLACE');
540
+ if (this.isKeyword('INTO')) this.next();
541
+ const stmt = this.parseInsertBody();
542
+ stmt.replace = true;
543
+ return stmt;
544
+ }
545
+
428
546
  parseInsert() {
429
547
  this.expectKeyword('INSERT');
548
+ let ignore = false;
549
+ if (this.isKeyword('IGNORE')) { this.next(); ignore = true; }
430
550
  this.expectKeyword('INTO');
551
+ const stmt = this.parseInsertBody();
552
+ stmt.ignore = ignore;
553
+ return stmt;
554
+ }
555
+
556
+ parseInsertBody() {
431
557
  const name = this.parseTableName();
432
558
  let columns = null;
433
559
  if (this.peek().type === 'op' && this.peek().value === '(') {
@@ -440,6 +566,12 @@ class Parser {
440
566
  }
441
567
  this.expect('op', ')');
442
568
  }
569
+ // INSERT INTO t [(cols)] SELECT ... —— 与 VALUES 互斥的另一条插入来源
570
+ if (this.isKeyword('SELECT')) {
571
+ const select = this.parseSelect();
572
+ this.optionalTailSemicolon();
573
+ return { type: 'insert', name, columns, select, dataRows: null, values: null, onDuplicate: null };
574
+ }
443
575
  this.expectKeyword('VALUES');
444
576
  const rows = [];
445
577
  while (true) {
@@ -468,31 +600,94 @@ class Parser {
468
600
  });
469
601
 
470
602
  let onDuplicate = null;
603
+ let onConflict = null;
471
604
  if (this.isKeyword('ON')) {
472
605
  this.expectKeyword('ON');
473
- this.expectKeyword('DUPLICATE');
474
- this.expectKeyword('KEY');
475
- this.expectKeyword('UPDATE');
476
- onDuplicate = [];
477
- while (true) {
478
- const col = this.parseTableName();
479
- this.expect('op', '=');
480
- const val = this.parseValue();
481
- onDuplicate.push([col, val]);
482
- if (this.peek().value === ',') { this.next(); continue; }
483
- break;
606
+ // PostgreSQL: ON CONFLICT [(cols)] DO NOTHING | DO UPDATE SET ...
607
+ if (this.isWord('CONFLICT')) {
608
+ this.next();
609
+ let target = null;
610
+ if (this.peek().type === 'op' && this.peek().value === '(') {
611
+ this.next();
612
+ target = [];
613
+ for (;;) {
614
+ target.push(this.parseColumnRef());
615
+ if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
616
+ break;
617
+ }
618
+ this.expect('op', ')');
619
+ }
620
+ if (!this.isWord('DO')) throw new Error(`Expected DO after ON CONFLICT, got '${this.peek().value}'`);
621
+ this.next();
622
+ if (this.isWord('NOTHING')) { this.next(); onConflict = { target, action: 'nothing' }; }
623
+ else if (this.isWord('UPDATE')) {
624
+ this.next();
625
+ if (!this.isKeyword('SET')) throw new Error(`Expected SET after DO UPDATE, got '${this.peek().value}'`);
626
+ this.next();
627
+ const sets = [];
628
+ for (;;) {
629
+ const col = this.parseColumnRef();
630
+ this.expect('op', '=');
631
+ const val = this.parseValue();
632
+ sets.push([col, val]);
633
+ if (this.peek().value === ',') { this.next(); continue; }
634
+ break;
635
+ }
636
+ onConflict = { target, action: 'update', sets };
637
+ } else {
638
+ throw new Error(`Expected NOTHING or UPDATE after DO, got '${this.peek().value}'`);
639
+ }
640
+ } else {
641
+ this.expectKeyword('DUPLICATE');
642
+ this.expectKeyword('KEY');
643
+ this.expectKeyword('UPDATE');
644
+ onDuplicate = [];
645
+ while (true) {
646
+ const col = this.parseTableName();
647
+ this.expect('op', '=');
648
+ const val = this.parseValue();
649
+ onDuplicate.push([col, val]);
650
+ if (this.peek().value === ',') { this.next(); continue; }
651
+ break;
652
+ }
484
653
  }
485
654
  }
486
655
  this.optionalTailSemicolon();
487
656
 
488
- return { type: 'insert', name, columns, dataRows: columns ? dataRows : null, values: columns ? null : rows, onDuplicate };
657
+ // RETURNING * | RETURNING col [, col]...
658
+ const returning = this.parseOptionalReturning();
659
+ return { type: 'insert', name, columns, dataRows: columns ? dataRows : null, values: columns ? null : rows, onDuplicate, onConflict, returning };
660
+ }
661
+
662
+ /**
663
+ * 冲突处理策略 —— 把四种写法归一成一个动作:
664
+ * REPLACE INTO → 'replace'
665
+ * INSERT IGNORE / ON CONFLICT DO NOTHING → 'ignore'
666
+ * ON DUPLICATE KEY UPDATE / ON CONFLICT DO UPDATE → 'update'
667
+ * 默认 → 'throw'(ER_DUP_ENTRY)
668
+ */
669
+ /** 解析可选的 RETURNING 子句(INSERT / UPDATE / DELETE 通用) */
670
+ parseOptionalReturning() {
671
+ if (!this.isWord('RETURNING')) return null;
672
+ this.next();
673
+ if (this.peek().type === 'op' && this.peek().value === '*') { this.next(); return ['*']; }
674
+ const cols = [];
675
+ for (;;) {
676
+ cols.push(this.parseColumnRef());
677
+ if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
678
+ break;
679
+ }
680
+ return cols;
489
681
  }
490
682
 
491
683
  parseValue() {
492
684
  const t = this.next();
493
685
  if (t.type === 'number' || t.type === 'string') return t.value;
494
686
  if (t.type === 'keyword' && t.value === 'NULL') return null;
687
+ if (t.type === 'keyword' && t.value === 'TRUE') return true;
688
+ if (t.type === 'keyword' && t.value === 'FALSE') return false;
495
689
  if (t.type === 'keyword' && t.value === 'DEFAULT') return { _default: true };
690
+ if (t.type === 'param') return { type: 'param', index: t.value, position: this.paramCount++ };
496
691
  if (t.type === 'op' && t.value === '-') {
497
692
  const num = this.next();
498
693
  if (num.type !== 'number') throw new Error('Expected number after -');
@@ -506,6 +701,126 @@ class Parser {
506
701
  throw new Error(`Expected value, got '${t.value}'`);
507
702
  }
508
703
 
704
+ /**
705
+ * WITH [RECURSIVE] name [(col, ...)] AS (SELECT ...), ... <statement>
706
+ * CTE 在语法层解析为 { type:'with', ctes:[{name, columns, select}], statement }。
707
+ * 执行层会把 CTE 内联为 FROM 子查询,因此不需要引擎侧临时表。
708
+ */
709
+ parseWith() {
710
+ this.expectKeyword('WITH');
711
+ let recursive = false;
712
+ if (this.isKeyword('RECURSIVE')) { this.next(); recursive = true; }
713
+ const ctes = [];
714
+ for (;;) {
715
+ const nameTok = this.next();
716
+ if (nameTok.type !== 'ident') throw new Error(`Expected CTE name, got '${nameTok.value}'`);
717
+ let columns = null;
718
+ if (this.peek().type === 'op' && this.peek().value === '(') {
719
+ this.next();
720
+ columns = [];
721
+ for (;;) {
722
+ columns.push(this.parseColumnRef());
723
+ if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
724
+ break;
725
+ }
726
+ this.expect('op', ')');
727
+ }
728
+ this.expectKeyword('AS');
729
+ this.expect('op', '(');
730
+ const select = this.parseSelect();
731
+ this.expect('op', ')');
732
+ ctes.push({ name: nameTok.value, columns, select, recursive });
733
+ if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
734
+ break;
735
+ }
736
+ const statement = this.parseStatement();
737
+ if (!statement) throw new Error('WITH requires a following SELECT/INSERT/UPDATE/DELETE statement');
738
+ return { type: 'with', recursive, ctes, statement };
739
+ }
740
+
741
+ /**
742
+ * 可选的窗口定义:OVER ( [PARTITION BY ...] [ORDER BY ...] [frame] ) 或 OVER win_name
743
+ * 只在真的出现 OVER 时才改写节点,因此不影响既有 func / aggregate 节点形状。
744
+ */
745
+ parseOptionalOver(node) {
746
+ if (!this.isWord('OVER')) return node;
747
+ this.next();
748
+ // 命名窗口:OVER w(后面必须不是 '(' )
749
+ if (!(this.peek().type === 'op' && this.peek().value === '(')) {
750
+ const nameTok = this.next();
751
+ if (nameTok.type === 'eof') throw new Error('Expected window name or "(" after OVER');
752
+ node.over = { windowName: nameTok.value };
753
+ return node;
754
+ }
755
+ this.expect('op', '(');
756
+ const spec = {};
757
+ if (this.isWord('PARTITION')) {
758
+ this.next();
759
+ if (!this.isWord('BY')) throw new Error(`Expected BY after PARTITION, got '${this.peek().value}'`);
760
+ this.next();
761
+ spec.partitionBy = [];
762
+ for (;;) {
763
+ spec.partitionBy.push(this.parseOperand());
764
+ if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
765
+ break;
766
+ }
767
+ }
768
+ if (this.isWord('ORDER')) {
769
+ this.next();
770
+ if (!this.isWord('BY')) throw new Error(`Expected BY after ORDER, got '${this.peek().value}'`);
771
+ this.next();
772
+ spec.orderBy = [];
773
+ for (;;) {
774
+ const col = this.parseOperand();
775
+ let dir = 'asc';
776
+ if (this.isWord('ASC')) { this.next(); }
777
+ else if (this.isWord('DESC')) { this.next(); dir = 'desc'; }
778
+ spec.orderBy.push({ column: col, dir });
779
+ if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
780
+ break;
781
+ }
782
+ }
783
+ if (this.isWord('ROWS') || this.isWord('RANGE')) spec.frame = this.parseFrameSpec();
784
+ this.expect('op', ')');
785
+ node.over = spec;
786
+ return node;
787
+ }
788
+
789
+ // ROWS/RANGE BETWEEN <bound> AND <bound> | ROWS <bound>
790
+ parseFrameSpec() {
791
+ const unit = String(this.next().value).toUpperCase() === 'RANGE' ? 'RANGE' : 'ROWS';
792
+ const bound = () => {
793
+ if (this.isWord('UNBOUNDED')) {
794
+ this.next();
795
+ const d = String(this.next().value).toUpperCase();
796
+ if (d !== 'PRECEDING' && d !== 'FOLLOWING') throw new Error(`Expected PRECEDING or FOLLOWING after UNBOUNDED, got '${d}'`);
797
+ return { type: 'unbounded', direction: d === 'FOLLOWING' ? 'following' : 'preceding' };
798
+ }
799
+ if (this.isWord('CURRENT')) {
800
+ this.next();
801
+ this.next(); // ROW
802
+ return { type: 'currentRow' };
803
+ }
804
+ const nTok = this.next();
805
+ const num = nTok.type === 'number' ? nTok.value : parseInt(String(nTok.value), 10);
806
+ if (!Number.isFinite(num)) throw new Error(`Expected number in window frame bound, got '${nTok.value}'`);
807
+ const d = String(this.next().value).toUpperCase();
808
+ if (d !== 'PRECEDING' && d !== 'FOLLOWING') throw new Error(`Expected PRECEDING or FOLLOWING, got '${d}'`);
809
+ return { type: 'offset', value: num, direction: d === 'FOLLOWING' ? 'following' : 'preceding' };
810
+ };
811
+ let start = null, end = null;
812
+ if (this.isWord('BETWEEN')) {
813
+ this.next();
814
+ start = bound();
815
+ if (!this.isWord('AND')) throw new Error(`Expected AND in window frame, got '${this.peek().value}'`);
816
+ this.next();
817
+ end = bound();
818
+ } else {
819
+ start = bound();
820
+ }
821
+ return { unit, start, end };
822
+ }
823
+
509
824
  parseSelect() {
510
825
  this.expectKeyword('SELECT');
511
826
  let distinct = false;
@@ -523,8 +838,9 @@ class Parser {
523
838
  else if (!(this.peek().type === 'op' && this.peek().value === ')')) col = this.parseScalar();
524
839
  this.expect('op', ')');
525
840
  aggregate = { type: 'COUNT', column: col };
841
+ aggregate = this.parseOptionalOver(aggregate);
526
842
  aggregate.alias = this.parseOptionalAlias();
527
- columns.push({ expr: col, aggregate: 'COUNT', column: col, alias: aggregate.alias });
843
+ columns.push({ expr: col, aggregate: 'COUNT', column: col, alias: aggregate.alias, over: aggregate.over || null, window: !!aggregate.over });
528
844
  } else if (t.type === 'keyword' && ['SUM', 'AVG', 'MIN', 'MAX'].includes(t.value)) {
529
845
  this.next();
530
846
  const fn = t.value;
@@ -532,8 +848,9 @@ class Parser {
532
848
  const col = this.parseScalar();
533
849
  this.expect('op', ')');
534
850
  aggregate = { type: fn, column: col };
851
+ aggregate = this.parseOptionalOver(aggregate);
535
852
  aggregate.alias = this.parseOptionalAlias();
536
- columns.push({ expr: col, aggregate: fn, column: col, alias: aggregate.alias });
853
+ columns.push({ expr: col, aggregate: fn, column: col, alias: aggregate.alias, over: aggregate.over || null, window: !!aggregate.over });
537
854
  } else if (t.type === 'op' && t.value === '*') {
538
855
  this.next();
539
856
  columns.push({ expr: '*' });
@@ -585,7 +902,28 @@ class Parser {
585
902
  this.expectKeyword('ORDER'); this.expectKeyword('BY');
586
903
  orderBy = [];
587
904
  while (true) {
588
- const col = this.parseColumnRef();
905
+ // 支持表达式排序:ORDER BY a + b DESC、ORDER BY UPPER(name)
906
+ // 纯列名仍走 parseColumnRef,保持既有字符串形态不破坏下游
907
+ let col;
908
+ const save = this.pos;
909
+ // ORDER BY 项的合法结束符;不是这些就说明是表达式(如 a+b、UPPER(name))
910
+ const ORDER_END = ['ASC', 'DESC', 'LIMIT', 'OFFSET', 'FETCH', 'UNION', 'INTERSECT',
911
+ 'EXCEPT', 'RETURNING', 'FOR', 'WITH'];
912
+ const endsClause = (tk) => {
913
+ if (tk.type === 'eof') return true;
914
+ if (tk.type === 'op' && (tk.value === ',' || tk.value === ';' || tk.value === ')')) return true;
915
+ return ORDER_END.some(w => this.isWord(w));
916
+ };
917
+ try {
918
+ col = this.parseColumnRef();
919
+ if (!endsClause(this.peek())) {
920
+ this.pos = save;
921
+ col = this.parseScalar();
922
+ }
923
+ } catch (e) {
924
+ this.pos = save;
925
+ col = this.parseScalar();
926
+ }
589
927
  let dir = 'asc';
590
928
  if (this.isKeyword('ASC')) { this.next(); }
591
929
  else if (this.isKeyword('DESC')) { this.next(); dir = 'desc'; }
@@ -602,16 +940,49 @@ class Parser {
602
940
  if (this.isKeyword('OFFSET')) { this.next(); offset = this.parseValue(); if (typeof offset === 'number' && offset < 0) throw new Error(`OFFSET must be a non-negative integer, got ${offset}`); }
603
941
  else if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); offset = limit; limit = this.parseValue(); if (typeof limit === 'number' && limit < 0) throw new Error(`LIMIT must be a non-negative integer, got ${limit}`); }
604
942
  }
605
- let union = null;
943
+ // 集合运算:UNION [ALL|DISTINCT] / INTERSECT [ALL|DISTINCT] / EXCEPT [ALL|DISTINCT]
944
+ let union = null, intersect = null, except = null;
606
945
  if (this.isKeyword('UNION')) {
607
946
  this.next();
608
947
  const all = this.isKeyword('ALL');
609
948
  if (all) this.next();
949
+ else if (this.isKeyword('DISTINCT')) this.next();
610
950
  union = { all, select: this.parseSelect() };
951
+ } else if (this.isKeyword('INTERSECT')) {
952
+ this.next();
953
+ const all = this.isKeyword('ALL');
954
+ if (all) this.next();
955
+ else if (this.isKeyword('DISTINCT')) this.next();
956
+ intersect = { all, select: this.parseSelect() };
957
+ } else if (this.isKeyword('EXCEPT')) {
958
+ this.next();
959
+ const all = this.isKeyword('ALL');
960
+ if (all) this.next();
961
+ else if (this.isKeyword('DISTINCT')) this.next();
962
+ except = { all, select: this.parseSelect() };
963
+ }
964
+ // GROUP BY ... WITH ROLLUP
965
+ let rollup = false;
966
+ if (groupBy && this.isKeyword('WITH')) {
967
+ this.next();
968
+ if (this.isWord('ROLLUP')) { this.next(); rollup = true; }
969
+ else throw new Error(`Expected ROLLUP after WITH, got '${this.peek().value}'`);
970
+ }
971
+ // FETCH FIRST n ROWS ONLY(SQL 标准的 LIMIT 写法)
972
+ if (this.isWord('FETCH')) {
973
+ this.next();
974
+ if (this.isWord('FIRST') || this.isWord('NEXT')) this.next();
975
+ else throw new Error(`Expected FIRST or NEXT after FETCH, got '${this.peek().value}'`);
976
+ const nTok = this.next();
977
+ const n = nTok.type === 'number' ? nTok.value : parseInt(String(nTok.value), 10);
978
+ if (!Number.isFinite(n)) throw new Error(`Expected row count after FETCH FIRST, got '${nTok.value}'`);
979
+ limit = n;
980
+ if (this.isWord('ROW') || this.isWord('ROWS')) this.next();
981
+ if (this.isWord('ONLY')) this.next();
611
982
  }
612
983
  this.optionalTailSemicolon();
613
984
 
614
- return { type: 'select', columns, aggregate, distinct, from, where, groupBy, having, orderBy, limit, offset, union };
985
+ return { type: 'select', columns, aggregate, distinct, from, where, groupBy, having, orderBy, limit, offset, union, intersect, except, rollup };
615
986
  }
616
987
 
617
988
  parseFrom() {
@@ -619,7 +990,12 @@ class Parser {
619
990
  const joins = [];
620
991
  while (true) {
621
992
  let type = null;
622
- if (this.isKeyword('LEFT') || this.isKeyword('RIGHT')) {
993
+ if (this.isKeyword('FULL')) {
994
+ this.next();
995
+ if (this.isKeyword('OUTER')) this.next();
996
+ this.expectKeyword('JOIN');
997
+ type = 'full';
998
+ } else if (this.isKeyword('LEFT') || this.isKeyword('RIGHT')) {
623
999
  type = this.peek().value.toLowerCase();
624
1000
  this.next();
625
1001
  if (this.isKeyword('OUTER')) this.next();
@@ -659,8 +1035,13 @@ class Parser {
659
1035
  }
660
1036
  const table = this.parseTableName();
661
1037
  let alias = null;
1038
+ // 这里排除的是"未进关键字表、但会跟在表名之后"的子句起始词,
1039
+ // 否则 `FROM t FETCH FIRST 10 ROWS ONLY` 会把 FETCH 当成表别名吃掉。
1040
+ const NOT_ALIAS = ['JOIN', 'LEFT', 'RIGHT', 'FULL', 'WHERE', 'GROUP', 'ORDER', 'LIMIT',
1041
+ 'ON', 'HAVING', 'UNION', 'INNER', 'CROSS', 'OUTER', 'INTERSECT', 'EXCEPT',
1042
+ 'FETCH', 'RETURNING', 'WINDOW'];
662
1043
  if (this.isKeyword('AS')) { this.next(); alias = this.parseAlias(); }
663
- else if (this.peek().type === 'ident' && !this.isKeyword('JOIN') && !this.isKeyword('LEFT') && !this.isKeyword('RIGHT') && !this.isKeyword('WHERE') && !this.isKeyword('GROUP') && !this.isKeyword('ORDER') && !this.isKeyword('LIMIT') && !this.isKeyword('ON') && !this.isKeyword('HAVING') && !this.isKeyword('UNION') && !this.isKeyword('INNER') && !this.isKeyword('CROSS')) {
1044
+ else if (this.peek().type === 'ident' && !NOT_ALIAS.some(w => this.isWord(w))) {
664
1045
  alias = this.next().value;
665
1046
  }
666
1047
  return { table, alias };
@@ -704,8 +1085,9 @@ class Parser {
704
1085
  }
705
1086
  let where = null;
706
1087
  if (this.isKeyword('WHERE')) { this.next(); where = this.parseExpr(); }
1088
+ const returning = this.parseOptionalReturning();
707
1089
  this.optionalTailSemicolon();
708
- return { type: 'update', table, assignments, where };
1090
+ return { type: 'update', table, assignments, where, returning };
709
1091
  }
710
1092
 
711
1093
  parseDelete() {
@@ -714,8 +1096,9 @@ class Parser {
714
1096
  const table = this.parseTableName();
715
1097
  let where = null;
716
1098
  if (this.isKeyword('WHERE')) { this.next(); where = this.parseExpr(); }
1099
+ const returning = this.parseOptionalReturning();
717
1100
  this.optionalTailSemicolon();
718
- return { type: 'delete', table, where };
1101
+ return { type: 'delete', table, where, returning };
719
1102
  }
720
1103
 
721
1104
  parseDropTable() {
@@ -1026,14 +1409,28 @@ class Parser {
1026
1409
  parseNot() {
1027
1410
  if (this.isKeyword('NOT')) {
1028
1411
  this.next();
1029
- return { type: 'not', expr: this.parseNot() };
1030
- }
1031
- if (this.peek().type === 'op' && this.peek().value === '(') {
1412
+ const inner = this.parseNot();
1413
+ // NOT EXISTS (...) 折叠成 exists.not,而不是包一层 not —— 语义等价且便于执行层物化
1414
+ if (inner && inner.type === 'exists') { inner.not = !inner.not; return inner; }
1415
+ return { type: 'not', expr: inner };
1416
+ }
1417
+ // 括号分组 —— 但 `(SELECT ...)` 是标量子查询,不能当分组吃掉
1418
+ const isSubqueryParen = this.peek().type === 'op' && this.peek().value === '(' &&
1419
+ this.peek(1) && this.peek(1).type === 'keyword' && this.peek(1).value === 'SELECT';
1420
+ if (!isSubqueryParen && this.peek().type === 'op' && this.peek().value === '(') {
1032
1421
  this.next();
1033
1422
  const e = this.parseExpr();
1034
1423
  this.expect('op', ')');
1035
1424
  return e;
1036
1425
  }
1426
+ // EXISTS (SELECT ...):子查询是否存在至少一行
1427
+ if (this.isKeyword('EXISTS')) {
1428
+ this.next();
1429
+ this.expect('op', '(');
1430
+ const select = this.parseSelect();
1431
+ this.expect('op', ')');
1432
+ return { type: 'exists', select, not: false };
1433
+ }
1037
1434
  return this.parseComparison();
1038
1435
  }
1039
1436
 
@@ -1048,6 +1445,30 @@ class Parser {
1048
1445
  if (col.type !== 'ident') throw new Error(`Expected column name after '.', got '${col.value}'`);
1049
1446
  return { type: 'column', name: t.value + '.' + col.value };
1050
1447
  }
1448
+ // CAST(expr AS type)
1449
+ if (String(t.value).toUpperCase() === 'CAST' && this.peek().type === 'op' && this.peek().value === '(') {
1450
+ this.next();
1451
+ const expr = this.parseScalar();
1452
+ if (!this.isWord('AS')) throw new Error(`Expected AS in CAST, got '${this.peek().value}'`);
1453
+ this.next();
1454
+ const typeTok = this.next();
1455
+ let dataType = String(typeTok.value).toUpperCase();
1456
+ // VARCHAR(10) / DECIMAL(10,2) 这类带长度的类型
1457
+ if (this.peek().type === 'op' && this.peek().value === '(') {
1458
+ this.next();
1459
+ let depth = 1;
1460
+ let spec = '';
1461
+ while (depth > 0 && this.peek().type !== 'eof') {
1462
+ const tk = this.next();
1463
+ if (tk.type === 'op' && tk.value === '(') depth++;
1464
+ else if (tk.type === 'op' && tk.value === ')') { depth--; if (depth === 0) break; }
1465
+ spec += tk.value;
1466
+ }
1467
+ dataType += '(' + spec + ')';
1468
+ }
1469
+ this.expect('op', ')');
1470
+ return { type: 'cast', expr, dataType };
1471
+ }
1051
1472
  // 函数调用: VERSION() / CONCAT(a, b) / NOW() ...
1052
1473
  if (this.peek().type === 'op' && this.peek().value === '(') {
1053
1474
  const name = t.value;
@@ -1062,13 +1483,17 @@ class Parser {
1062
1483
  }
1063
1484
  }
1064
1485
  this.expect('op', ')');
1065
- return { type: 'func', name, args };
1486
+ return this.parseOptionalOver({ type: 'func', name, args });
1066
1487
  }
1067
1488
  return { type: 'column', name: t.value };
1068
1489
  }
1069
1490
  if (t.type === 'number' || t.type === 'string') return { type: 'value', value: t.value };
1070
1491
  if (t.type === 'sysvar') return { type: 'sysvar', name: t.value };
1492
+ if (t.type === 'param') return { type: 'param', index: t.value, position: this.paramCount++ };
1071
1493
  if (t.type === 'keyword' && t.value === 'NULL') return { type: 'value', value: null };
1494
+ // MySQL: TRUE/FALSE 是 1/0 的别名,可作为值字面量出现在任意表达式位置
1495
+ if (t.type === 'keyword' && t.value === 'TRUE') return { type: 'value', value: true };
1496
+ if (t.type === 'keyword' && t.value === 'FALSE') return { type: 'value', value: false };
1072
1497
  if (t.type === 'keyword' && t.value === 'CASE') return this.parseCase();
1073
1498
  if (t.type === 'keyword' && !['SUM', 'AVG', 'MIN', 'MAX', 'COUNT'].includes(t.value) && this.peek().type === 'op' && this.peek().value === '(') {
1074
1499
  const name = t.value;
@@ -1083,7 +1508,7 @@ class Parser {
1083
1508
  }
1084
1509
  }
1085
1510
  this.expect('op', ')');
1086
- return { type: 'func', name, args };
1511
+ return this.parseOptionalOver({ type: 'func', name, args });
1087
1512
  }
1088
1513
  if (t.type === 'keyword' && ['SUM', 'AVG', 'MIN', 'MAX', 'COUNT'].includes(t.value)) {
1089
1514
  const fn = t.value;
@@ -1092,7 +1517,7 @@ class Parser {
1092
1517
  if (this.peek().type === 'op' && this.peek().value === '*') { this.next(); }
1093
1518
  else if (!(this.peek().type === 'op' && this.peek().value === ')')) column = this.parseScalar();
1094
1519
  this.expect('op', ')');
1095
- return { type: 'aggregate', fn, column };
1520
+ return this.parseOptionalOver({ type: 'aggregate', fn, column });
1096
1521
  }
1097
1522
  if (t.type === 'op' && t.value === '(' && this.peek().type === 'keyword' && this.peek().value === 'SELECT') {
1098
1523
  const sub = this.parseSelect();
@@ -1206,8 +1631,8 @@ class Parser {
1206
1631
  not = true;
1207
1632
  // NOT 后必须紧跟比较关键字
1208
1633
  const n = this.peek();
1209
- if (!['IN', 'BETWEEN', 'LIKE', 'REGEXP'].includes(n.value)) {
1210
- throw new Error(`Expected IN, BETWEEN, LIKE or REGEXP after NOT, got '${n.value}'`);
1634
+ if (!['IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', 'ILIKE'].includes(n.value)) {
1635
+ throw new Error(`Expected IN, BETWEEN, LIKE, ILIKE, REGEXP or RLIKE after NOT, got '${n.value}'`);
1211
1636
  }
1212
1637
  }
1213
1638
  const t2 = this.peek();
@@ -1256,13 +1681,16 @@ class Parser {
1256
1681
  throw new Error(`Expected AND in BETWEEN, got '${andTok.value}'`);
1257
1682
  }
1258
1683
 
1259
- if (t2.type === 'keyword' && t2.value === 'LIKE') {
1684
+ // ILIKE 刻意不加进 KEYWORDS(避免影响同名标识符),用 isWord 按上下文匹配
1685
+ if ((t2.type === 'keyword' && t2.value === 'LIKE') || this.isWord('ILIKE')) {
1686
+ const isILike = !(t2.type === 'keyword' && t2.value === 'LIKE');
1260
1687
  this.next();
1261
1688
  const pattern = this.parseValue();
1262
- return { type: 'like', operand: left, pattern, not };
1689
+ return { type: 'like', operand: left, pattern, not, ci: isILike };
1263
1690
  }
1264
1691
 
1265
- if (t2.type === 'keyword' && t2.value === 'REGEXP') {
1692
+ // MySQL 中 RLIKE 是 REGEXP 的同义词
1693
+ if (t2.type === 'keyword' && (t2.value === 'REGEXP' || t2.value === 'RLIKE')) {
1266
1694
  this.next();
1267
1695
  const pattern = this.parseValue();
1268
1696
  return { type: 'regexp', operand: left, pattern, not };
@@ -1270,6 +1698,15 @@ class Parser {
1270
1698
 
1271
1699
  if (t2.type === 'op' && ['=', '!=', '<>', '<', '<=', '>', '>='].includes(t2.value)) {
1272
1700
  this.next();
1701
+ // = ANY (SELECT ...) / > ALL (SELECT ...)
1702
+ const anyAll = this.isWord('ANY') || this.isWord('ALL') || this.isWord('SOME');
1703
+ if (anyAll) {
1704
+ const quantifier = String(this.next().value).toUpperCase() === 'ALL' ? 'ALL' : 'ANY';
1705
+ this.expect('op', '(');
1706
+ const sub = this.parseSelect();
1707
+ this.expect('op', ')');
1708
+ return { type: 'quantified', op: t2.value === '<>' ? '!=' : t2.value, operand: left, select: sub, quantifier, not };
1709
+ }
1273
1710
  const right = this.parseOperand();
1274
1711
  return { type: 'compare', op: t2.value === '<>' ? '!=' : t2.value, left, right };
1275
1712
  }
@@ -1288,6 +1725,34 @@ const OPERATORS = {
1288
1725
  '>=': (a, b) => a >= b
1289
1726
  };
1290
1727
 
1728
+ /** CAST(expr AS type) 的类型转换 */
1729
+ function applyCast(value, dataType) {
1730
+ const t = String(dataType || '').toUpperCase().replace(/\(.*\)$/, '').trim();
1731
+ if (value === null || value === undefined) return null;
1732
+ if (t === 'INTEGER' || t === 'INT' || t === 'BIGINT' || t === 'SMALLINT' || t === 'TINYINT') {
1733
+ const n = Number(value);
1734
+ return Number.isFinite(n) ? Math.trunc(n) : null;
1735
+ }
1736
+ if (t === 'FLOAT' || t === 'DOUBLE' || t === 'REAL' || t === 'NUMERIC' || t === 'DECIMAL') {
1737
+ const n = Number(value);
1738
+ return Number.isFinite(n) ? n : null;
1739
+ }
1740
+ if (t === 'BOOLEAN' || t === 'BOOL') {
1741
+ if (typeof value === 'boolean') return value;
1742
+ if (typeof value === 'number') return value !== 0;
1743
+ const s = String(value).toLowerCase();
1744
+ return s === 'true' || s === '1' || s === 't' || s === 'yes';
1745
+ }
1746
+ if (t === 'TEXT' || t === 'VARCHAR' || t === 'CHAR' || t === 'STRING') return String(value);
1747
+ return value;
1748
+ }
1749
+
1750
+ // 行对象里是否存在 `alias.col` 形式的键(即该行是否已被加过表前缀)
1751
+ function rowHasPrefixedKeys(row) {
1752
+ for (const k in row) if (k.indexOf('.') !== -1) return true;
1753
+ return false;
1754
+ }
1755
+
1291
1756
  function resolveOperand(operand, row, ctx) {
1292
1757
  if (operand === null || operand === undefined) return null;
1293
1758
  if (typeof operand === 'string') return resolveOperand({ type: 'column', name: operand }, row, ctx);
@@ -1321,11 +1786,15 @@ function resolveOperand(operand, row, ctx) {
1321
1786
  const n = operand.name;
1322
1787
  if (n === undefined) return undefined;
1323
1788
  if (row[n] !== undefined) return row[n];
1324
- if (n.includes('.')) {
1325
- if (row[n] !== undefined) return row[n];
1789
+ if (n.indexOf('.') !== -1) {
1326
1790
  const col = n.slice(n.lastIndexOf('.') + 1);
1327
- return row[col];
1791
+ // 只有当行完全没有带前缀的键(即行未被 _prefixRow 处理过)时才回退到裸列名。
1792
+ // 否则 `e.dept` 这种未知限定符会静默取到当前行自己的 dept,
1793
+ // 让相关子查询变成“自匹配”,EXISTS 恒为真。
1794
+ if (!rowHasPrefixedKeys(row) && row[col] !== undefined) return row[col];
1328
1795
  }
1796
+ // 相关子查询:回退到外层行(ctx.__outer 由 _evalExists 临时注入)
1797
+ if (ctx && ctx.__outer && ctx.__outer[n] !== undefined) return ctx.__outer[n];
1329
1798
  return undefined;
1330
1799
  }
1331
1800
  case 'arith': {
@@ -1352,8 +1821,35 @@ function resolveOperand(operand, row, ctx) {
1352
1821
  case 'like':
1353
1822
  case 'between':
1354
1823
  return evaluateExpr(operand, row, ctx);
1355
- case 'aggregate':
1824
+ case 'param': {
1825
+ // 原生 ? 占位符。executeSQL 默认会先用 applyParams 内联替换,
1826
+ // 只有在显式传入 ctx.params 时才走这条路径。
1827
+ const params = ctx && ctx.params;
1828
+ if (params == null) return undefined;
1829
+ if (operand.index === '??') {
1830
+ return typeof params === 'object' && !Array.isArray(params) ? params['@@'] : undefined;
1831
+ }
1832
+ const i = operand.index != null && operand.index !== '??' ? operand.index - 1 : (operand.position || 0);
1833
+ return Array.isArray(params) ? params[i] : params[i];
1834
+ }
1835
+ case 'cast': {
1836
+ const v = resolveOperand(operand.expr, row, ctx);
1837
+ return applyCast(v, operand.dataType);
1838
+ }
1839
+ case 'quantified': {
1840
+ // = ANY / > ALL (SELECT ...) —— 需先由执行层物化成 operand._list
1841
+ const v = resolveOperand(operand.operand, row, ctx);
1842
+ const list = operand._list || [];
1843
+ if (list.length === 0) return operand.quantifier === 'ALL';
1844
+ const fn = OPERATORS[operand.op];
1845
+ if (!fn) return false;
1846
+ const results = list.map(x => (v === null || x === null) ? false : fn(v, x));
1847
+ return operand.quantifier === 'ALL' ? results.every(Boolean) : results.some(Boolean);
1848
+ }
1356
1849
  case 'subquery':
1850
+ // 相关子查询:值由 _evalCorrelated 按外层行算好写回 _value
1851
+ return operand._value !== undefined ? operand._value : null;
1852
+ case 'aggregate':
1357
1853
  return undefined;
1358
1854
  default:
1359
1855
  return undefined;
@@ -1478,13 +1974,14 @@ function seedRand(seed) {
1478
1974
  };
1479
1975
  }
1480
1976
 
1481
- function likeMatch(value, pattern) {
1977
+ // ci 默认 true:保持 LIKE 既有的大小写不敏感行为不变;ILIKE 同为不敏感。
1978
+ function likeMatch(value, pattern, ci = true) {
1482
1979
  if (typeof value !== 'string') return false;
1483
1980
  const regex = pattern
1484
1981
  .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
1485
1982
  .replace(/%/g, '.*')
1486
1983
  .replace(/_/g, '.');
1487
- return new RegExp('^' + regex + '$', 'i').test(value);
1984
+ return new RegExp('^' + regex + '$', ci ? 'i' : '').test(value);
1488
1985
  }
1489
1986
 
1490
1987
  function extractEqualPushdown(expr, schema) {
@@ -1512,6 +2009,25 @@ function extractEqualPushdown(expr, schema) {
1512
2009
  return { filter, rest };
1513
2010
  }
1514
2011
 
2012
+ // 行去重(保持首次出现顺序),供 UNION / INTERSECT / EXCEPT 的 DISTINCT 语义使用
2013
+ function dedupeRows(rows) {
2014
+ const seen = new Set();
2015
+ return rows.filter(r => {
2016
+ const k = JSON.stringify(r);
2017
+ if (seen.has(k)) return false;
2018
+ seen.add(k);
2019
+ return true;
2020
+ });
2021
+ }
2022
+
2023
+ // 比较前把布尔归一化为 1/0,但只在“另一侧是数值/布尔”时做,
2024
+ // 避免把 true 与字符串 'true' 的比较悄悄改成 1 与 'true' 的比较。
2025
+ function normalizeCmpValue(a, other) {
2026
+ if (typeof a !== 'boolean') return a;
2027
+ const otherIsNumeric = typeof other === 'number' || typeof other === 'boolean';
2028
+ return otherIsNumeric ? (a ? 1 : 0) : a;
2029
+ }
2030
+
1515
2031
  function evaluateExpr(expr, row, ctx) {
1516
2032
  if (expr === null || expr === undefined) return false;
1517
2033
  switch (expr.type) {
@@ -1523,9 +2039,12 @@ function evaluateExpr(expr, row, ctx) {
1523
2039
  const r = resolveOperand(expr.right, row, ctx);
1524
2040
  // SQL 标准:任何与 NULL 的比较结果为 UNKNOWN(在 WHERE/ON/HAVING 中视为 false)
1525
2041
  if (l === null || r === null) return false;
1526
- if (expr.op === '=') return l === r || (l !== null && r !== null && String(l) === String(r));
2042
+ // 布尔与数值互通(MySQL: 1 = TRUE、0 = FALSE)
2043
+ const nl = normalizeCmpValue(l, r);
2044
+ const nr = normalizeCmpValue(r, l);
2045
+ if (expr.op === '=') return nl === nr || String(nl) === String(nr);
1527
2046
  const fn = OPERATORS[expr.op];
1528
- return typeof l === 'number' && typeof r === 'number' ? fn(l, r) : fn(String(l), String(r));
2047
+ return typeof nl === 'number' && typeof nr === 'number' ? fn(nl, nr) : fn(String(nl), String(nr));
1529
2048
  }
1530
2049
  case 'isNull': {
1531
2050
  const v = resolveOperand(expr.operand, row, ctx);
@@ -1539,9 +2058,12 @@ function evaluateExpr(expr, row, ctx) {
1539
2058
  return expr.list.some(x => x === v || String(x) === String(v));
1540
2059
  }
1541
2060
  case 'like': {
1542
- const r = likeMatch(resolveOperand(expr.operand, row, ctx), expr.pattern);
2061
+ const v = resolveOperand(expr.operand, row, ctx);
2062
+ const r = likeMatch(v, expr.pattern, !!expr.ci);
1543
2063
  return expr.not ? !r : r;
1544
2064
  }
2065
+ case 'quantified':
2066
+ return !!resolveOperand(expr, row, ctx);
1545
2067
  case 'regexp': {
1546
2068
  const v = resolveOperand(expr.operand, row, ctx);
1547
2069
  if (v === null || v === undefined) return false;
@@ -1566,6 +2088,12 @@ function evaluateExpr(expr, row, ctx) {
1566
2088
  : String(v) >= String(lo) && String(v) <= String(hi);
1567
2089
  return expr.not ? !inRange : inRange;
1568
2090
  }
2091
+ case 'exists': {
2092
+ // 执行层会把子查询物化成 expr._rowCount,这里只做布尔判定
2093
+ const n = typeof expr._rowCount === 'number' ? expr._rowCount : (expr._rows ? expr._rows.length : 0);
2094
+ const has = n > 0;
2095
+ return expr.not ? !has : has;
2096
+ }
1569
2097
  case 'case': {
1570
2098
  for (const b of expr.branches) {
1571
2099
  if (evaluateExpr(b.cond, row, ctx)) return resolveOperand(b.val, row, ctx);
@@ -1602,6 +2130,11 @@ function scalarName(node) {
1602
2130
  }
1603
2131
 
1604
2132
  function scalarColumnName(c) {
2133
+ // 未起别名的窗口函数列给一个可读的名字(仅影响新增语法,不动既有命名规则)
2134
+ if ((c.over || (c.scalar && c.scalar.over)) && !c.alias) {
2135
+ const fn = String(c.aggregate || (c.scalar && c.scalar.name) || 'WINDOW').toUpperCase();
2136
+ return fn + '() OVER ()';
2137
+ }
1605
2138
  if (c.alias) return c.alias;
1606
2139
  if (c.scalar) return scalarName(c.scalar);
1607
2140
  if (c.aggregate) return c.alias || (c.aggregate === 'COUNT' ? 'COUNT(*)' : c.aggregate + '(' + c.column + ')');
@@ -1612,7 +2145,15 @@ function scalarColumnName(c) {
1612
2145
  return dot !== -1 ? c.expr.slice(dot + 1) : c.expr;
1613
2146
  }
1614
2147
 
2148
+ // 窗口函数列:值由 _computeWindows 预先写回行对象,投影时直接取用
2149
+ function windowColumnValue(c, r) {
2150
+ const key = c._windowKey || c.alias || scalarColumnName(c);
2151
+ const v = r ? r[key] : undefined;
2152
+ return v === undefined ? null : v;
2153
+ }
2154
+
1615
2155
  function scalarColumnValue(c, r, ctx) {
2156
+ if (c.over || (c.scalar && c.scalar.over)) return windowColumnValue(c, r);
1616
2157
  if (c.scalar) {
1617
2158
  const s = c.scalar;
1618
2159
  if (s && s.type === 'func' && ctx && ctx.ctxAggregates && Object.prototype.hasOwnProperty.call(ctx.ctxAggregates, String(s.name).toUpperCase())) {
@@ -1775,6 +2316,16 @@ class SQLExecutor {
1775
2316
  });
1776
2317
  dataRows = statement.dataRows;
1777
2318
  }
2319
+ // INSERT ... SELECT:先跑 SELECT,再把结果行映射成待插入行
2320
+ if (statement.select) {
2321
+ const res = await this.executeSelect(statement.select);
2322
+ const cols = statement.columns && statement.columns.length ? statement.columns : res.columns;
2323
+ dataRows = res.rows.map(arr => {
2324
+ const row = {};
2325
+ cols.forEach((c, i) => { row[c] = arr[i]; });
2326
+ return row;
2327
+ });
2328
+ }
1778
2329
  if (dataRows === null && statement.values) {
1779
2330
  const colNames = Object.keys(schema);
1780
2331
  const skipAuto = statement.values[0].length < colNames.length;
@@ -1798,6 +2349,7 @@ class SQLExecutor {
1798
2349
  const pkCols = schema ? Object.keys(schema).filter(k => schema[k].primaryKey) : [];
1799
2350
  let toInsert = dataRows;
1800
2351
  let updated = 0;
2352
+ let skipped = 0;
1801
2353
  if (pkCols.length > 0) {
1802
2354
  const keyOf = (row) => pkCols.map(c => (row[c] !== undefined && row[c] !== null ? String(row[c]) : '')).join('|');
1803
2355
  const hasExplicitPk = (row) => pkCols.some(c => row[c] !== undefined && row[c] !== null);
@@ -1819,25 +2371,49 @@ class SQLExecutor {
1819
2371
  fresh.push(d);
1820
2372
  }
1821
2373
  }
1822
- if (conflicts.length > 0 && !statement.onDuplicate) {
2374
+ const conflictAction = this._conflictAction(statement);
2375
+ const conflictSets = this._conflictSets(statement);
2376
+ if (conflicts.length > 0 && conflictAction === 'throw') {
1823
2377
  throw new Error('ER_DUP_ENTRY: Duplicate entry for primary key');
1824
2378
  }
1825
- if (statement.onDuplicate && conflicts.length > 0) {
2379
+ if (conflicts.length > 0 && conflictAction === 'ignore') {
2380
+ // INSERT IGNORE / ON CONFLICT DO NOTHING:静默跳过冲突行
2381
+ skipped = conflicts.length;
2382
+ }
2383
+ if (conflicts.length > 0 && conflictAction === 'replace') {
2384
+ // REPLACE INTO:删除旧行后按新行插入
2385
+ const oldIds = [];
2386
+ for (const { existingId } of conflicts) {
2387
+ if (existingId.length === 1) oldIds.push(existingId[0]);
2388
+ }
2389
+ if (oldIds.length > 0) {
2390
+ if (this.engine.removeByIds) await this.engine.removeByIds(statement.name, oldIds);
2391
+ else for (const id of oldIds) await this.engine.removeById(statement.name, id);
2392
+ }
2393
+ toInsert = dataRows;
2394
+ await this.engine.flush();
2395
+ } else if (conflicts.length > 0 && conflictAction === 'update') {
1826
2396
  for (const { d, existingId } of conflicts) {
1827
2397
  const data = {};
1828
- for (const [col, val] of statement.onDuplicate) data[col] = val;
2398
+ if (conflictSets) {
2399
+ for (const [col, val] of conflictSets) data[col] = val;
2400
+ } else {
2401
+ for (const k of Object.keys(d)) if (!pkCols.includes(k)) data[k] = d[k];
2402
+ }
1829
2403
  if (this.engine.updateById && existingId.length === 1) {
1830
- this.engine.updateById(statement.name, existingId[0], data);
2404
+ await this.engine.updateById(statement.name, existingId[0], data);
1831
2405
  } else if (this.engine.update) {
1832
2406
  const filter = {};
1833
2407
  pkCols.forEach((c, i) => { filter[c] = existingId[i]; });
1834
- this.engine.update(statement.name, filter, data);
2408
+ await this.engine.update(statement.name, filter, data);
1835
2409
  }
1836
2410
  updated++;
1837
2411
  }
1838
2412
  await this.engine.flush();
2413
+ toInsert = fresh;
2414
+ } else if (conflictAction !== 'replace') {
2415
+ toInsert = fresh;
1839
2416
  }
1840
- toInsert = fresh;
1841
2417
  }
1842
2418
  const seen = new Map();
1843
2419
  let kept = [];
@@ -1845,10 +2421,12 @@ class SQLExecutor {
1845
2421
  if (!hasExplicitPk(d)) { kept.push(d); continue; }
1846
2422
  const key = keyOf(d);
1847
2423
  if (seen.has(key)) {
1848
- if (!statement.onDuplicate) {
2424
+ const act = this._conflictAction(statement);
2425
+ if (act === 'throw') {
1849
2426
  throw new Error('ER_DUP_ENTRY: Duplicate entry for primary key');
1850
2427
  }
1851
- seen.get(key).row = d;
2428
+ if (act === 'ignore') { skipped++; continue; }
2429
+ seen.get(key).row = d; // update / replace:后出现的行覆盖先出现的行
1852
2430
  } else {
1853
2431
  seen.set(key, { row: d });
1854
2432
  }
@@ -1861,17 +2439,109 @@ class SQLExecutor {
1861
2439
  ids = await this.engine.insert(statement.name, toInsert);
1862
2440
  await this.engine.flush();
1863
2441
  }
1864
- return {
2442
+ const out = {
1865
2443
  ok: true, type: 'insert', table: statement.name,
1866
2444
  affectedRows: toInsert.length + updated,
1867
2445
  insertId: Array.isArray(ids) && ids.length > 0 ? ids[0] : null,
1868
2446
  ids,
1869
2447
  duplicateUpdated: updated,
1870
2448
  };
2449
+ if (skipped > 0) out.duplicateSkipped = skipped;
2450
+ if (statement.returning) this._attachReturning(out, statement, toInsert, schema);
2451
+ return out;
1871
2452
  }
1872
2453
  case 'select': {
1873
2454
  return await this.executeSelect(statement);
1874
2455
  }
2456
+ case 'with': {
2457
+ // CTE:按声明顺序把已展开的 CTE 内联进引用它的 FROM(含嵌套 CTE 引用)
2458
+ const map = new Map();
2459
+ for (const cte of statement.ctes) {
2460
+ const sel = JSON.parse(JSON.stringify(cte.select));
2461
+ // CTE 显式列名清单 `name (a, b) AS (...)`:按顺序重命名输出列
2462
+ if (cte.columns && cte.columns.length) {
2463
+ sel.columns.forEach((c, i) => {
2464
+ if (cte.columns[i] && !c.alias) c.alias = cte.columns[i];
2465
+ });
2466
+ }
2467
+ this._inlineCteRefs(sel, map);
2468
+ map.set(String(cte.name).toLowerCase(), sel);
2469
+ }
2470
+ const stmt = JSON.parse(JSON.stringify(statement.statement));
2471
+ this._inlineCteRefs(stmt, map);
2472
+ return await this.execute(stmt);
2473
+ }
2474
+ case 'explain': {
2475
+ const lines = [];
2476
+ const describe = (s, indent) => {
2477
+ if (!s) return;
2478
+ const pad = indent || '';
2479
+ if (s.type === 'select') {
2480
+ lines.push([pad + 'SELECT', s.from ? '' : '(no FROM)']);
2481
+ if (s.from) {
2482
+ for (const t of (s.from.tables || [])) lines.push([pad + ' SCAN', t.table || '(subquery)']);
2483
+ for (const j of (s.from.joins || [])) lines.push([pad + ' ' + String(j.type).toUpperCase() + ' JOIN', (j.item && (j.item.table || '(subquery)')) || '']);
2484
+ }
2485
+ if (s.where) lines.push([pad + ' FILTER', 'WHERE']);
2486
+ if (s.groupBy) lines.push([pad + ' AGGREGATE', 'GROUP BY ' + s.groupBy.join(', ') + (s.rollup ? ' WITH ROLLUP' : '')]);
2487
+ if (s.having) lines.push([pad + ' FILTER', 'HAVING']);
2488
+ if (s.orderBy) lines.push([pad + ' SORT', 'ORDER BY ' + s.orderBy.map(o => (typeof o.column === 'string' ? o.column : 'expr') + ' ' + o.dir).join(', ')]);
2489
+ if (s.limit !== null && s.limit !== undefined) lines.push([pad + ' LIMIT', String(s.limit)]);
2490
+ if (s.union) describe(s.union.select, pad + ' ');
2491
+ } else if (s.type === 'with') {
2492
+ for (const c of s.ctes) { lines.push([pad + 'CTE ' + c.name, '']); describe(c.select, pad + ' '); }
2493
+ describe(s.statement, indent);
2494
+ } else {
2495
+ lines.push([String(s.type).toUpperCase(), s.table || s.name || '']);
2496
+ }
2497
+ };
2498
+ describe(statement.statement, '');
2499
+ return {
2500
+ ok: true, type: 'explain', columns: ['step', 'detail'], rows: lines,
2501
+ rowCount: lines.length, affectedRows: 0, message: `${lines.length} step(s) — 无代价模型,仅展示计划结构`,
2502
+ command: 'EXPLAIN', warnings: [],
2503
+ };
2504
+ }
2505
+ case 'createView': {
2506
+ if (!this.engine._views) this.engine._views = Object.create(null);
2507
+ const key = String(statement.name).toLowerCase();
2508
+ if (this.engine._views[key] && !statement.orReplace) {
2509
+ throw new Error(`View '${statement.name}' already exists (use CREATE OR REPLACE VIEW)`);
2510
+ }
2511
+ this.engine._views[key] = {
2512
+ name: statement.name,
2513
+ columns: statement.columns || null,
2514
+ select: statement.select,
2515
+ };
2516
+ return { ok: true, type: 'createView', message: `View '${statement.name}' created`, affectedRows: 0, warnings: [] };
2517
+ }
2518
+ case 'dropView': {
2519
+ if (!this.engine._views) this.engine._views = Object.create(null);
2520
+ const key = String(statement.name).toLowerCase();
2521
+ if (!this.engine._views[key]) {
2522
+ if (statement.ifExists) return { ok: true, type: 'dropView', message: `View '${statement.name}' did not exist`, affectedRows: 0, skipped: true, warnings: [] };
2523
+ throw new Error(`View '${statement.name}' does not exist`);
2524
+ }
2525
+ delete this.engine._views[key];
2526
+ return { ok: true, type: 'dropView', message: `View '${statement.name}' dropped`, affectedRows: 0, warnings: [] };
2527
+ }
2528
+ case 'savepoint': {
2529
+ // 引擎侧没有保存点/快照能力。这里只登记名字,绝不假装能回滚。
2530
+ if (!this.engine._savepoints) this.engine._savepoints = new Set();
2531
+ this.engine._savepoints.add(String(statement.name).toLowerCase());
2532
+ return { ok: true, type: 'savepoint', message: `Savepoint '${statement.name}' noted`, affectedRows: 0, warnings: [] };
2533
+ }
2534
+ case 'releaseSavepoint': {
2535
+ if (this.engine._savepoints) this.engine._savepoints.delete(String(statement.name).toLowerCase());
2536
+ return { ok: true, type: 'releaseSavepoint', message: `Savepoint '${statement.name}' released`, affectedRows: 0, warnings: [] };
2537
+ }
2538
+ case 'rollbackTo': {
2539
+ // 明确报错而不是静默无效:静默的"回滚成功"会让调用方以为数据已还原。
2540
+ throw new Error(
2541
+ `ROLLBACK TO SAVEPOINT '${statement.name}' is not supported: the engine has no savepoint/snapshot support. ` +
2542
+ `Use ROLLBACK to abort the whole transaction.`
2543
+ );
2544
+ }
1875
2545
  case 'update': {
1876
2546
  const schema = this.engine.getTableSchema
1877
2547
  ? await this.engine.getTableSchema(statement.table)
@@ -1893,7 +2563,16 @@ class SQLExecutor {
1893
2563
  }
1894
2564
  }
1895
2565
  await this.engine.flush();
1896
- return { ok: true, type: 'update', table: statement.table, affectedRows: count };
2566
+ const updOut = { ok: true, type: 'update', table: statement.table, affectedRows: count };
2567
+ if (statement.returning) {
2568
+ const after = (await this.engine.find(statement.table, {}, { limit: 1e9, offset: 0 })).map(r => normalizeRow(r, schema));
2569
+ const changed = [];
2570
+ for (const row of after) {
2571
+ if (!statement.where || evaluateExpr(statement.where, row, this.ctx)) changed.push(row);
2572
+ }
2573
+ this._attachReturning(updOut, statement, changed, schema);
2574
+ }
2575
+ return updOut;
1897
2576
  }
1898
2577
  case 'delete': {
1899
2578
  const schema = this.engine.getTableSchema
@@ -1902,18 +2581,22 @@ class SQLExecutor {
1902
2581
  const pkCols = schema ? Object.keys(schema).filter(k => schema[k].primaryKey) : [];
1903
2582
  const all = (await this.engine.find(statement.table, {}, { limit: 1e9, offset: 0 })).map(r => normalizeRow(r, schema));
1904
2583
  const ids = [];
2584
+ const removedRows = [];
1905
2585
  for (const row of all) {
1906
2586
  if (!statement.where || evaluateExpr(statement.where, row, this.ctx)) {
1907
2587
  const id = this._rowPkId(row, pkCols);
1908
- if (id !== undefined) ids.push(id);
2588
+ if (id !== undefined) { ids.push(id); removedRows.push(row); }
1909
2589
  }
1910
2590
  }
1911
2591
  if (ids.length > 0) {
1912
- if (this.engine.removeByIds) this.engine.removeByIds(statement.table, ids);
1913
- else for (const id of ids) this.engine.removeById(statement.table, id);
2592
+ if (this.engine.removeByIds) await this.engine.removeByIds(statement.table, ids);
2593
+ else for (const id of ids) await this.engine.removeById(statement.table, id);
1914
2594
  }
1915
2595
  await this.engine.flush();
1916
- return { ok: true, type: 'delete', table: statement.table, affectedRows: ids.length };
2596
+ const delOut = { ok: true, type: 'delete', table: statement.table, affectedRows: ids.length };
2597
+ // DELETE ... RETURNING 返回被删除的行
2598
+ if (statement.returning) this._attachReturning(delOut, statement, removedRows, schema);
2599
+ return delOut;
1917
2600
  }
1918
2601
  case 'begin':
1919
2602
  if (this.engine.beginTx) this.engine._txId = await this.engine.beginTx();
@@ -2345,15 +3028,23 @@ class SQLExecutor {
2345
3028
  if (expr.type === 'in' && expr.subquery) {
2346
3029
  const res = await this.executeSelect(expr.subquery);
2347
3030
  expr.list = res.rows.map(r => r[0]);
2348
- delete expr.subquery;
3031
+ // 不 delete expr.subquery:相关子查询需要在逐行求值时重跑
2349
3032
  return;
2350
3033
  }
2351
3034
  if (expr.type === 'subquery') {
2352
3035
  const res = await this.executeSelect(expr.select);
2353
- const v = res.rows.length > 0 ? res.rows[0][0] : null;
2354
- expr.type = 'value';
2355
- expr.value = v;
2356
- delete expr.select;
3036
+ expr._value = res.rows.length > 0 ? res.rows[0][0] : null;
3037
+ // 不改写类型、不 delete select:保留节点以便相关场景逐行重算
3038
+ return;
3039
+ }
3040
+ if (expr.type === 'exists' && expr.select) {
3041
+ // 非相关场景(HAVING / JOIN ON)只求值一次;保留 select 以便 WHERE 里逐行关联求值
3042
+ await this._evalCorrelated(expr, null);
3043
+ return;
3044
+ }
3045
+ if (expr.type === 'quantified' && expr.select) {
3046
+ const res = await this.executeSelect(expr.select);
3047
+ expr._list = res.rows.map(r => (Array.isArray(r) ? r[0] : r));
2357
3048
  return;
2358
3049
  }
2359
3050
  for (const k of Object.keys(expr)) {
@@ -2400,6 +3091,29 @@ class SQLExecutor {
2400
3091
  return out;
2401
3092
  }
2402
3093
 
3094
+ /**
3095
+ * 构造 WITH ROLLUP 的汇总行:把各组的原始行合并成一个"超级组",
3096
+ * 分组列不设值(投影时会取 null),聚合列则覆盖全部行。
3097
+ */
3098
+ _rollupRow(groupReps) {
3099
+ const all = [];
3100
+ for (const rep of groupReps) {
3101
+ if (rep._group) all.push(...rep._group);
3102
+ else all.push(rep);
3103
+ }
3104
+ const out = {};
3105
+ if (all.length > 0) {
3106
+ // 分组列显式置为 null,避免取到某一组的代表值
3107
+ for (const k of Object.keys(all[0])) {
3108
+ if (k === '_group' || k === '_rid') continue;
3109
+ out[k] = null;
3110
+ }
3111
+ }
3112
+ out._group = all;
3113
+ out._rollup = true;
3114
+ return out;
3115
+ }
3116
+
2403
3117
  _aggValue(rows, fn, column) {
2404
3118
  if (fn === 'COUNT') return rows.length;
2405
3119
  const op = typeof column === 'string' ? { type: 'column', name: column } : column;
@@ -2438,6 +3152,331 @@ class SQLExecutor {
2438
3152
  }
2439
3153
  }
2440
3154
 
3155
+ /**
3156
+ * 收集表达式树里所有「带子查询」的节点:
3157
+ * exists(select) / subquery(select) / in(subquery)。
3158
+ * 这些节点都可能需要按外层行逐行重新求值(相关子查询)。
3159
+ */
3160
+ _collectSubqueries(node, out = []) {
3161
+ if (!node || typeof node !== 'object') return out;
3162
+ if (Array.isArray(node)) { for (const n of node) this._collectSubqueries(n, out); return out; }
3163
+ if ((node.select || node.subquery) &&
3164
+ (node.type === 'exists' || node.type === 'subquery' || node.type === 'in')) out.push(node);
3165
+ for (const k of Object.keys(node)) {
3166
+ const v = node[k];
3167
+ if (v && typeof v === 'object') this._collectSubqueries(v, out);
3168
+ }
3169
+ return out;
3170
+ }
3171
+
3172
+ /**
3173
+ * 冲突处理策略 —— 把四种写法归一成一个动作:
3174
+ * REPLACE INTO → 'replace'
3175
+ * INSERT IGNORE / ON CONFLICT DO NOTHING → 'ignore'
3176
+ * ON DUPLICATE KEY UPDATE / ON CONFLICT DO UPDATE→ 'update'
3177
+ * 默认 → 'throw'(ER_DUP_ENTRY)
3178
+ */
3179
+ _conflictAction(statement) {
3180
+ if (statement.replace) return 'replace';
3181
+ if (statement.ignore) return 'ignore';
3182
+ const oc = statement.onConflict;
3183
+ if (oc) return oc.action === 'nothing' ? 'ignore' : 'update';
3184
+ if (statement.onDuplicate) return 'update';
3185
+ return 'throw';
3186
+ }
3187
+
3188
+ /** 冲突时要写入的列值(ON DUPLICATE KEY UPDATE / ON CONFLICT DO UPDATE SET) */
3189
+ _conflictSets(statement) {
3190
+ if (statement.onDuplicate) return statement.onDuplicate;
3191
+ if (statement.onConflict && statement.onConflict.action === 'update') return statement.onConflict.sets;
3192
+ return null;
3193
+ }
3194
+
3195
+ /**
3196
+ * 把 RETURNING 结果挂到返回对象上。
3197
+ * RETURNING * → 返回完整行;RETURNING a, b → 只取指定列。
3198
+ */
3199
+ _attachReturning(out, statement, rows, schema) {
3200
+ const cols = Array.isArray(statement.returning) ? statement.returning : [];
3201
+ const isStar = cols.length === 1 && cols[0] === '*';
3202
+ const outCols = isStar
3203
+ ? (schema ? Object.keys(schema).filter(k => k !== '_softDelete') : Object.keys((rows && rows[0]) || {}))
3204
+ : cols;
3205
+ out.returning = {
3206
+ columns: outCols,
3207
+ rows: (rows || []).map(r => outCols.map(c => (r && r[c] !== undefined ? r[c] : null))),
3208
+ };
3209
+ out.columns = outCols;
3210
+ out.rows = out.returning.rows;
3211
+ return out;
3212
+ }
3213
+
3214
+ /** 收集子树里所有列名(含限定形式 alias.col) */
3215
+ _collectColumnNames(node, out = []) {
3216
+ if (!node || typeof node !== 'object') return out;
3217
+ if (Array.isArray(node)) { for (const n of node) this._collectColumnNames(n, out); return out; }
3218
+ if (node.type === 'column' && typeof node.name === 'string') out.push(node.name);
3219
+ for (const k of Object.keys(node)) {
3220
+ const v = node[k];
3221
+ if (v && typeof v === 'object') this._collectColumnNames(v, out);
3222
+ }
3223
+ return out;
3224
+ }
3225
+
3226
+ /** 外层 FROM 的表名 / 别名集合 */
3227
+ _outerAliases(statement) {
3228
+ const set = new Set();
3229
+ if (!statement || !statement.from) return set;
3230
+ const add = (it) => { if (it && (it.alias || it.table)) set.add(String(it.alias || it.table).toLowerCase()); };
3231
+ for (const t of (statement.from.tables || [])) add(t);
3232
+ for (const j of (statement.from.joins || [])) add(j.item);
3233
+ return set;
3234
+ }
3235
+
3236
+ /**
3237
+ * 子查询是否引用了外层别名(即相关子查询)。
3238
+ * 非相关子查询只求值一次,避免无谓的 O(n) 逐行重跑。
3239
+ */
3240
+ _isCorrelated(node, outerAliases) {
3241
+ if (!outerAliases || outerAliases.size === 0) return false;
3242
+ for (const n of this._collectColumnNames(node, [])) {
3243
+ const dot = n.indexOf('.');
3244
+ if (dot === -1) continue;
3245
+ if (outerAliases.has(n.slice(0, dot).toLowerCase())) return true;
3246
+ }
3247
+ return false;
3248
+ }
3249
+
3250
+ /**
3251
+ * 投影行数组。若 SELECT 列表里有相关子查询(如
3252
+ * `(SELECT COUNT(*) FROM o WHERE o.uid = u.id)`),必须"求值一行、投影一行"交错进行,
3253
+ * 因为子查询节点是共享对象,先全算再投影只会留下最后一行的结果。
3254
+ */
3255
+ async _projectRows(statement, rows, projectRow) {
3256
+ const outerAliases = this._outerAliases(statement);
3257
+ const found = [];
3258
+ for (const c of statement.columns) {
3259
+ if (c.scalar) this._collectSubqueries(c.scalar, found);
3260
+ if (c.caseExpr) this._collectSubqueries(c.caseExpr, found);
3261
+ }
3262
+ const subs = found.filter(n => this._isCorrelated(n, outerAliases));
3263
+ if (subs.length === 0) return rows.map(projectRow);
3264
+ const out = [];
3265
+ for (const r of rows) {
3266
+ for (const n of subs) await this._evalCorrelated(n, r);
3267
+ out.push(projectRow(r));
3268
+ }
3269
+ return out;
3270
+ }
3271
+
3272
+ /**
3273
+ * 按外层行求值一个子查询节点。
3274
+ * 临时把外层行挂到 ctx.__outer,让 `u.id` 这类外层引用能解析到。
3275
+ * 结果写回节点:exists→_rowCount,subquery→_value,in→list。
3276
+ */
3277
+ async _evalCorrelated(node, outerRow) {
3278
+ const prevCtx = this.ctx;
3279
+ this.ctx = outerRow ? Object.assign({}, prevCtx || {}, { __outer: outerRow }) : prevCtx;
3280
+ try {
3281
+ if (node.type === 'exists') {
3282
+ const res = await this.executeSelect(node.select);
3283
+ node._rowCount = res.rows.length;
3284
+ } else if (node.type === 'subquery') {
3285
+ const res = await this.executeSelect(node.select);
3286
+ node._value = res.rows.length > 0 ? res.rows[0][0] : null;
3287
+ } else if (node.type === 'in') {
3288
+ const res = await this.executeSelect(node.subquery);
3289
+ node.list = res.rows.map(r => (Array.isArray(r) ? r[0] : r));
3290
+ }
3291
+ } finally {
3292
+ this.ctx = prevCtx;
3293
+ }
3294
+ }
3295
+
3296
+ /**
3297
+ * 把 AST 中引用 CTE 名称的 FROM 项替换成对应的子查询(就地改写)。
3298
+ * map: 小写 CTE 名 -> 已展开的 select AST
3299
+ */
3300
+ _inlineCteRefs(node, map) {
3301
+ if (!node || typeof node !== 'object' || map.size === 0) return;
3302
+ if (Array.isArray(node)) {
3303
+ for (const n of node) this._inlineCteRefs(n, map);
3304
+ return;
3305
+ }
3306
+ if (node.type === 'select' && node.from) {
3307
+ const rewrite = (item) => {
3308
+ if (!item || !item.table) return;
3309
+ const key = String(item.table).toLowerCase();
3310
+ if (!map.has(key)) return;
3311
+ const sub = JSON.parse(JSON.stringify(map.get(key)));
3312
+ item.subquery = sub;
3313
+ item.alias = item.alias || item.table;
3314
+ delete item.table;
3315
+ };
3316
+ for (const t of (node.from.tables || [])) rewrite(t);
3317
+ for (const j of (node.from.joins || [])) rewrite(j.item);
3318
+ }
3319
+ for (const k of Object.keys(node)) {
3320
+ if (k === 'from') continue; // 已单独处理,避免重复改写
3321
+ const v = node[k];
3322
+ if (v && typeof v === 'object') this._inlineCteRefs(v, map);
3323
+ }
3324
+ }
3325
+
3326
+ /**
3327
+ * 计算 SELECT 列表中的窗口函数列,把结果直接写回每一行(键名为输出列名)。
3328
+ * 放在 WHERE / GROUP BY / HAVING 之后、ORDER BY / LIMIT 之前,符合 SQL 求值顺序。
3329
+ */
3330
+ _computeWindows(statement, rows) {
3331
+ if (!rows || rows.length === 0) return rows;
3332
+ const specs = [];
3333
+ for (const c of statement.columns) {
3334
+ const sc = c.scalar;
3335
+ const over = (sc && sc.over) || c.over;
3336
+ if (!over) continue;
3337
+ const fn = sc && sc.type === 'aggregate' ? String(sc.fn)
3338
+ : (sc && sc.type === 'func' ? String(sc.name).toUpperCase()
3339
+ : String(c.aggregate || '').toUpperCase());
3340
+ specs.push({ col: c, spec: sc, over, fn, name: scalarColumnName(c) });
3341
+ }
3342
+ if (specs.length === 0) return rows;
3343
+ const toNode = (x) => !x ? null : (typeof x === 'string' ? { type: 'column', name: x } : x);
3344
+ specs.forEach((w, i) => { w.col._windowKey = w.col.alias || ('__win_' + i); });
3345
+
3346
+ for (const w of specs) {
3347
+ const partBy = w.over.partitionBy || [];
3348
+ const orderBy = w.over.orderBy || [];
3349
+ // 分区
3350
+ const buckets = new Map();
3351
+ rows.forEach((r, idx) => {
3352
+ const key = partBy.length
3353
+ ? partBy.map(p => JSON.stringify(resolveOperand(p, r, this.ctx))).join('')
3354
+ : '';
3355
+ if (!buckets.has(key)) buckets.set(key, []);
3356
+ buckets.get(key).push(idx);
3357
+ });
3358
+
3359
+ for (const idxs of buckets.values()) {
3360
+ let ordered = idxs.slice();
3361
+ if (orderBy.length) {
3362
+ ordered = ordered.slice().sort((ia, ib) => {
3363
+ const ra = rows[ia], rb = rows[ib];
3364
+ for (const o of orderBy) {
3365
+ const av = resolveOperand(o.column, ra, this.ctx);
3366
+ const bv = resolveOperand(o.column, rb, this.ctx);
3367
+ if (av === bv) continue;
3368
+ if (av === undefined || av === null) return -1;
3369
+ if (bv === undefined || bv === null) return 1;
3370
+ const d = typeof av === 'number' && typeof bv === 'number' ? av - bv : String(av).localeCompare(String(bv));
3371
+ if (d !== 0) return o.dir === 'asc' ? d : -d;
3372
+ }
3373
+ return ia - ib;
3374
+ });
3375
+ }
3376
+ // 帧范围(ROWS 帧;RANGE 在当前实现下退化为整个分区,保守处理)
3377
+ const frameIdx = (i) => {
3378
+ const f = w.over.frame;
3379
+ if (!f) return { start: 0, end: ordered.length - 1 };
3380
+ let start = 0, end = ordered.length - 1;
3381
+ const apply = (b, isStart) => {
3382
+ if (!b) return isStart ? 0 : ordered.length - 1;
3383
+ if (b.type === 'unbounded') return b.direction === 'preceding' ? 0 : ordered.length - 1;
3384
+ if (b.type === 'currentRow') return i;
3385
+ return b.direction === 'preceding' ? i - b.value : i + b.value;
3386
+ };
3387
+ start = apply(f.start, true);
3388
+ end = apply(f.end, false);
3389
+ return { start: Math.max(0, start), end: Math.min(ordered.length - 1, end) };
3390
+ };
3391
+
3392
+ const argNode = toNode((w.spec && (w.spec.column || (w.spec.args && w.spec.args[0]))) || w.col.column || w.col.expr);
3393
+ const valAt = (rowIdx, node) => resolveOperand(node || { type: 'value', value: 1 }, rows[rowIdx], this.ctx);
3394
+
3395
+ ordered.forEach((rowIdx, pos) => {
3396
+ let v = null;
3397
+ switch (w.fn) {
3398
+ case 'ROW_NUMBER': v = pos + 1; break;
3399
+ case 'RANK': {
3400
+ // RANK = 1 + 分区内排在本行之前、且不是同值(peer)的行数
3401
+ v = 1;
3402
+ for (let k = 0; k < pos; k++) {
3403
+ if (this._windowPeerCmp(rows[ordered[k]], rows[rowIdx], orderBy, this.ctx) !== 0) v++;
3404
+ }
3405
+ break;
3406
+ }
3407
+ case 'DENSE_RANK': {
3408
+ // 1 + 排在本行之前的不同取值个数(同值并列,不跳号)
3409
+ const curKey = this._windowPeerKey(rows[rowIdx], orderBy, this.ctx);
3410
+ const seen = new Set();
3411
+ for (let k = 0; k < pos; k++) {
3412
+ const kk = this._windowPeerKey(rows[ordered[k]], orderBy, this.ctx);
3413
+ if (kk !== curKey) seen.add(kk);
3414
+ }
3415
+ v = seen.size + 1;
3416
+ break;
3417
+ }
3418
+ case 'NTILE': {
3419
+ const n = w.spec && w.spec.args && w.spec.args[0] ? Number(resolveOperand(w.spec.args[0], rows[rowIdx], this.ctx)) : 1;
3420
+ const bucketsN = Math.max(1, Math.floor(n) || 1);
3421
+ v = Math.min(bucketsN, Math.floor((pos * bucketsN) / ordered.length) + 1);
3422
+ break;
3423
+ }
3424
+ case 'LAG':
3425
+ case 'LEAD': {
3426
+ const off = w.spec && w.spec.args && w.spec.args[1] ? Number(resolveOperand(w.spec.args[1], rows[rowIdx], this.ctx)) : 1;
3427
+ const target = w.fn === 'LAG' ? pos - off : pos + off;
3428
+ if (target < 0 || target >= ordered.length) {
3429
+ v = w.spec && w.spec.args && w.spec.args[2] ? resolveOperand(w.spec.args[2], rows[rowIdx], this.ctx) : null;
3430
+ } else {
3431
+ v = valAt(ordered[target], argNode);
3432
+ }
3433
+ break;
3434
+ }
3435
+ case 'FIRST_VALUE':
3436
+ case 'LAST_VALUE': {
3437
+ const f = frameIdx(pos);
3438
+ const t = w.fn === 'FIRST_VALUE' ? f.start : f.end;
3439
+ v = valAt(ordered[t], argNode);
3440
+ break;
3441
+ }
3442
+ case 'COUNT':
3443
+ case 'SUM':
3444
+ case 'AVG':
3445
+ case 'MIN':
3446
+ case 'MAX': {
3447
+ const f = frameIdx(pos);
3448
+ const slice = ordered.slice(f.start, f.end + 1).map(i => rows[i]);
3449
+ v = this._aggValue(slice, w.fn, argNode);
3450
+ break;
3451
+ }
3452
+ default: v = null;
3453
+ }
3454
+ rows[rowIdx][w.col._windowKey] = v;
3455
+ });
3456
+ }
3457
+ }
3458
+ return rows;
3459
+ }
3460
+
3461
+ _windowPeerKey(row, orderBy, ctx) {
3462
+ if (!orderBy.length) return '';
3463
+ return orderBy.map(o => JSON.stringify(resolveOperand(o.column, row, ctx))).join('');
3464
+ }
3465
+
3466
+ _windowPeerCmp(rowA, rowB, orderBy, ctx) {
3467
+ if (!orderBy.length) return 0;
3468
+ for (const o of orderBy) {
3469
+ const av = resolveOperand(o.column, rowA, ctx);
3470
+ const bv = resolveOperand(o.column, rowB, ctx);
3471
+ if (av === bv) continue;
3472
+ if (av === undefined || av === null) return -1;
3473
+ if (bv === undefined || bv === null) return 1;
3474
+ const d = typeof av === 'number' && typeof bv === 'number' ? av - bv : String(av).localeCompare(String(bv));
3475
+ if (d !== 0) return d;
3476
+ }
3477
+ return 0;
3478
+ }
3479
+
2441
3480
  _subQueryRows(res) {
2442
3481
  return res.rows.map(r => {
2443
3482
  if (Array.isArray(r)) {
@@ -2449,24 +3488,65 @@ class SQLExecutor {
2449
3488
  });
2450
3489
  }
2451
3490
 
3491
+ /**
3492
+ * 把 FROM 中引用视图的表名展开为视图定义里的子查询(就地改写)。
3493
+ * 与 CTE 一样走子查询内联,不需要引擎侧物化视图。
3494
+ */
3495
+ _expandViews(statement, depth = 0) {
3496
+ const views = this.engine && this.engine._views;
3497
+ if (!views || !statement || !statement.from) return statement;
3498
+ if (depth > 16) throw new Error('View nesting too deep — possible circular view definition');
3499
+ const rewrite = (item) => {
3500
+ if (!item || !item.table) return;
3501
+ const v = views[String(item.table).toLowerCase()];
3502
+ if (!v) return;
3503
+ const sub = JSON.parse(JSON.stringify(v.select));
3504
+ if (v.columns && v.columns.length) {
3505
+ sub.columns.forEach((c, i) => { if (v.columns[i] && !c.alias) c.alias = v.columns[i]; });
3506
+ }
3507
+ this._expandViews(sub, depth + 1); // 视图里再引用视图
3508
+ item.subquery = sub;
3509
+ item.alias = item.alias || item.table;
3510
+ delete item.table;
3511
+ };
3512
+ for (const t of (statement.from.tables || [])) rewrite(t);
3513
+ for (const j of (statement.from.joins || [])) rewrite(j.item);
3514
+ return statement;
3515
+ }
3516
+
2452
3517
  async executeSelect(statement) {
3518
+ // 视图:FROM 里的视图名展开成子查询
3519
+ if (this.engine && this.engine._views) this._expandViews(statement);
3520
+
2453
3521
  // UNION 处理
2454
3522
  if (statement.union) {
2455
3523
  const left = await this.executeSelect({ ...statement, union: null });
2456
3524
  const right = await this.executeSelect(statement.union.select);
2457
3525
  let rows = left.rows.concat(right.rows);
2458
- if (!statement.union.all) {
2459
- const seen = new Set();
2460
- rows = rows.filter(r => {
2461
- const k = JSON.stringify(r);
2462
- if (seen.has(k)) return false;
2463
- seen.add(k);
2464
- return true;
2465
- });
2466
- }
3526
+ if (!statement.union.all) rows = dedupeRows(rows);
2467
3527
  return { ok: true, type: 'select', columns: left.columns, rows, raw: rows };
2468
3528
  }
2469
3529
 
3530
+ // INTERSECT:保留同时出现在右侧结果中的行(默认 DISTINCT,与 UNION 的处理保持一致)
3531
+ if (statement.intersect) {
3532
+ const left = await this.executeSelect({ ...statement, intersect: null, except: null });
3533
+ const right = await this.executeSelect(statement.intersect.select);
3534
+ const seen = new Set(right.rows.map(r => JSON.stringify(r)));
3535
+ let out = left.rows.filter(r => seen.has(JSON.stringify(r)));
3536
+ if (!statement.intersect.all) out = dedupeRows(out);
3537
+ return { ok: true, type: 'select', columns: left.columns, rows: out, raw: out };
3538
+ }
3539
+
3540
+ // EXCEPT:去掉出现在右侧结果中的行(默认 DISTINCT)
3541
+ if (statement.except) {
3542
+ const left = await this.executeSelect({ ...statement, intersect: null, except: null });
3543
+ const right = await this.executeSelect(statement.except.select);
3544
+ const seen = new Set(right.rows.map(r => JSON.stringify(r)));
3545
+ let out = left.rows.filter(r => !seen.has(JSON.stringify(r)));
3546
+ if (!statement.except.all) out = dedupeRows(out);
3547
+ return { ok: true, type: 'select', columns: left.columns, rows: out, raw: out };
3548
+ }
3549
+
2470
3550
  // 物化 WHERE / HAVING / JOIN ON 中的子查询
2471
3551
  await this._materialize(statement.where);
2472
3552
  await this._materialize(statement.having);
@@ -2475,6 +3555,8 @@ class SQLExecutor {
2475
3555
  }
2476
3556
  for (const c of statement.columns) {
2477
3557
  if (c.caseExpr) await this._materialize(c.caseExpr);
3558
+ // 非相关标量子查询先求值一次;相关子查询稍后由 _projectRows 逐行覆盖
3559
+ if (c.scalar) await this._materialize(c.scalar);
2478
3560
  }
2479
3561
 
2480
3562
  let schema = null;
@@ -2544,11 +3626,11 @@ class SQLExecutor {
2544
3626
  if (m !== null) {
2545
3627
  matched.push({ ...l, ...rightRows[m] });
2546
3628
  unmatchedRight.delete(m);
2547
- } else if (j.type === 'left') {
3629
+ } else if (j.type === 'left' || j.type === 'full') {
2548
3630
  matched.push(rightNulls ? { ...rightNulls, ...l } : { ...l });
2549
3631
  }
2550
3632
  });
2551
- if (j.type === 'right') {
3633
+ if (j.type === 'right' || j.type === 'full') {
2552
3634
  for (const ri of unmatchedRight) matched.push(leftNulls ? { ...leftNulls, ...rightRows[ri] } : { ...rightRows[ri] });
2553
3635
  }
2554
3636
  rows = matched;
@@ -2558,7 +3640,20 @@ class SQLExecutor {
2558
3640
 
2559
3641
  let rows = all;
2560
3642
  if (statement.where) {
2561
- rows = rows.filter(r => evaluateExpr(statement.where, r, this.ctx));
3643
+ // 相关子查询:必须逐行重新求值,否则外层引用拿不到当前行的值
3644
+ const outerAliases = this._outerAliases(statement);
3645
+ const subNodes = this._collectSubqueries(statement.where)
3646
+ .filter(n => this._isCorrelated(n, outerAliases));
3647
+ if (subNodes.length > 0) {
3648
+ const kept = [];
3649
+ for (const r of rows) {
3650
+ for (const node of subNodes) await this._evalCorrelated(node, r);
3651
+ if (evaluateExpr(statement.where, r, this.ctx)) kept.push(r);
3652
+ }
3653
+ rows = kept;
3654
+ } else {
3655
+ rows = rows.filter(r => evaluateExpr(statement.where, r, this.ctx));
3656
+ }
2562
3657
  }
2563
3658
 
2564
3659
  // 分组聚合
@@ -2596,6 +3691,11 @@ class SQLExecutor {
2596
3691
  });
2597
3692
  }
2598
3693
 
3694
+ // 窗口函数:在 WHERE/GROUP BY/HAVING 之后、DISTINCT/ORDER BY/LIMIT 之前求值
3695
+ if (statement.columns.some(c => (c.scalar && c.scalar.over) || c.over)) {
3696
+ this._computeWindows(statement, rows);
3697
+ }
3698
+
2599
3699
  if (statement.distinct) {
2600
3700
  const seen = new Set();
2601
3701
  rows = rows.filter(r => {
@@ -2613,7 +3713,8 @@ class SQLExecutor {
2613
3713
 
2614
3714
  // 聚合输出(无 GROUP BY 时)
2615
3715
  const hasCustomAgg = this.ctx && this.ctx.aggregates && statement.columns.some(c => c.scalar && c.scalar.type === 'func' && Object.prototype.hasOwnProperty.call(this.ctx.aggregates, String(c.scalar.name).toUpperCase()));
2616
- if (!statement.groupBy && (statement.columns.some(c => c.aggregate) || hasCustomAgg)) {
3716
+ // 注意:带 OVER 的聚合是窗口函数,不是“整表塌缩成一行”的聚合,必须排除
3717
+ if (!statement.groupBy && (statement.columns.some(c => c.aggregate && !c.window) || hasCustomAgg)) {
2617
3718
  const cols = statement.columns.map(c => scalarColumnName(c));
2618
3719
  const valueOf = (c) => {
2619
3720
  if (c.aggregate) return this._aggValue(rows, c.aggregate || 'COUNT', c.column);
@@ -2634,18 +3735,26 @@ class SQLExecutor {
2634
3735
  // GROUP BY 输出:每组的列(含聚合列)
2635
3736
  if (statement.groupBy) {
2636
3737
  const cols = statement.columns.map(c => scalarColumnName(c));
2637
- const mapped = rows.map(r => {
3738
+ const rowsForGroups = (statement.rollup && rows.length > 0)
3739
+ // WITH ROLLUP:追加一个"全表汇总"分组(分组列取 NULL,聚合列覆盖全部行)
3740
+ ? rows.concat([this._rollupRow(rows)])
3741
+ : rows;
3742
+ const mapped = await this._projectRows(statement, rowsForGroups, (r) => {
2638
3743
  const group = r._group || [r];
2639
3744
  return statement.columns.map(c => scalarColumnValue(c, r, { _aggValue: this._aggValue.bind(this), group, ctxAggregates: this.ctx ? this.ctx.aggregates : null }));
2640
3745
  });
2641
- return { ok: true, type: 'select', table: statement.from ? (statement.from.tables[0].table || null) : null, columns: cols, rows: mapped, raw: rows };
3746
+ return { ok: true, type: 'select', table: statement.from ? (statement.from.tables[0].table || null) : null, columns: cols, rows: mapped, raw: rowsForGroups };
2642
3747
  }
2643
3748
 
2644
3749
  if (statement.orderBy) {
3750
+ // ORDER BY 项可能是列名字符串,也可能是表达式节点
3751
+ const ordVal = (row, o) => (typeof o.column === 'string'
3752
+ ? resolveOperand({ type: 'column', name: o.column }, row, this.ctx)
3753
+ : resolveOperand(o.column, row, this.ctx));
2645
3754
  const cmp = (a, b) => {
2646
3755
  for (const o of statement.orderBy) {
2647
- const av = resolveOperand({ type: 'column', name: o.column }, a, this.ctx);
2648
- const bv = resolveOperand({ type: 'column', name: o.column }, b, this.ctx);
3756
+ const av = ordVal(a, o);
3757
+ const bv = ordVal(b, o);
2649
3758
  if (av === bv || (av === undefined && bv === undefined)) continue;
2650
3759
  if (av === undefined || av === null) return o.dir === 'asc' ? -1 : 1;
2651
3760
  if (bv === undefined || bv === null) return o.dir === 'asc' ? 1 : -1;
@@ -2680,7 +3789,8 @@ class SQLExecutor {
2680
3789
  }
2681
3790
 
2682
3791
  const cols = statement.columns.map(c => scalarColumnName(c));
2683
- const mapped = rows.map(r => statement.columns.map(c => {
3792
+ const mapped = await this._projectRows(statement, rows, (r) => statement.columns.map(c => {
3793
+ if (c.over || (c.scalar && c.scalar.over)) return windowColumnValue(c, r);
2684
3794
  if (c.scalar) return resolveOperand(c.scalar, r, this.ctx);
2685
3795
  if (c.expr === '*') return null;
2686
3796
  if (c.literal !== undefined) return c.literal;
@@ -2694,18 +3804,25 @@ class SQLExecutor {
2694
3804
  function splitStatements(sql) {
2695
3805
  const statements = [];
2696
3806
  let current = '';
2697
- let inStr = null;
3807
+ let inStr = null; // "'" | '"' | '`'
2698
3808
  let i = 0;
2699
3809
  while (i < sql.length) {
2700
3810
  const c = sql[i];
2701
3811
  if (inStr) {
2702
3812
  current += c;
3813
+ if (inStr === '`') {
3814
+ // 反引号标识符内部反斜杠不是转义符;两个连续反引号才表示一个字面反引号。
3815
+ if (c === '`' && sql[i + 1] === '`') { current += sql[i + 1]; i += 2; continue; }
3816
+ if (c === '`') inStr = null;
3817
+ i++;
3818
+ continue;
3819
+ }
2703
3820
  if (c === '\\' && i + 1 < sql.length) { current += sql[i + 1]; i += 2; continue; }
2704
3821
  if (c === inStr) inStr = null;
2705
3822
  i++;
2706
3823
  continue;
2707
3824
  }
2708
- if (c === "'" || c === '"') { inStr = c; current += c; i++; continue; }
3825
+ if (c === "'" || c === '"' || c === '`') { inStr = c; current += c; i++; continue; }
2709
3826
  if (c === '-' && sql[i + 1] === '-') {
2710
3827
  while (i < sql.length && sql[i] !== '\n') i++;
2711
3828
  continue;
@@ -2911,6 +4028,7 @@ async function executeSQL(engine, sql, paramsOrOpts, opts = {}) {
2911
4028
  }
2912
4029
  const ctx = {};
2913
4030
  if (opts.session) ctx.session = opts.session;
4031
+ if (opts.params) ctx.params = opts.params; // 原生 ? 占位符(applyParams 之外的另一种用法)
2914
4032
  if (opts.functions && typeof opts.functions === 'object') ctx.functions = opts.functions;
2915
4033
  if (opts.aggregates && typeof opts.aggregates === 'object') ctx.aggregates = opts.aggregates;
2916
4034
  const executor = new SQLExecutor(engine, Object.keys(ctx).length > 0 ? ctx : null);
@@ -2922,4 +4040,14 @@ async function executeSQL(engine, sql, paramsOrOpts, opts = {}) {
2922
4040
  return results.length === 1 ? results[0] : results;
2923
4041
  }
2924
4042
 
2925
- module.exports = { tokenize, parseSQL, executeSQL, SQLExecutor, splitStatements, applyParams, escapeValue, escapeId };
4043
+ // AST 访问与改写能力(纯新增,不改变既有导出项)
4044
+ const AST = require('./ast.js');
4045
+
4046
+ module.exports = {
4047
+ tokenize, parseSQL, executeSQL, SQLExecutor, Parser,
4048
+ splitStatements, applyParams, escapeValue, escapeId,
4049
+ AST,
4050
+ walk: AST.walk,
4051
+ transform: AST.transform,
4052
+ visit: AST.StatementVisitor
4053
+ };