jsql-neo 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/sql.js CHANGED
@@ -47,12 +47,13 @@ const KEYWORDS = new Set([
47
47
  'KEY', 'AUTO_INCREMENT', 'AUTOINCREMENT', 'INTEGER', 'INT', 'BIGINT', 'STRING', 'TEXT',
48
48
  'VARCHAR', 'CHAR', 'FLOAT', 'DOUBLE', 'REAL', 'NUMERIC', 'DECIMAL', 'BOOLEAN', 'BOOL', 'DATE', 'DATETIME',
49
49
  'TIMESTAMP', 'ANY', 'OBJECT', 'ARRAY', 'JSON', 'SMALLINT', 'TINYINT', 'BEGIN', 'COMMIT', 'ROLLBACK',
50
+ 'START',
50
51
  'TRANSACTION', 'WORK', 'COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'AS', 'UNIQUE',
51
52
  'NOTNULL', 'DEFAULT', 'IF', 'EXISTS', 'DISTINCT', 'SHOW', 'USE', 'TABLES',
52
53
  'DATABASES', 'DATABASE', 'DESCRIBE', 'DESC', 'ON', 'DUPLICATE',
53
54
  'JOIN', 'LEFT', 'RIGHT', 'INNER', 'OUTER', 'CROSS',
54
55
  'GROUP', 'HAVING', 'UNION', 'ALL', 'CASE', 'WHEN', 'THEN', 'ELSE', 'END',
55
- 'BETWEEN', 'USING', 'FULL'
56
+ 'BETWEEN', 'USING', 'FULL', 'UNSIGNED', 'ZEROFILL', 'TRUNCATE', 'COLLATE', 'CHARACTER'
56
57
  ]);
57
58
 
58
59
  function tokenize(sql) {
@@ -84,7 +85,7 @@ function tokenize(sql) {
84
85
  while (j < n) {
85
86
  if (sql[j] === '\\' && j + 1 < n) {
86
87
  const esc = sql[j + 1];
87
- const map = { n: '\n', t: '\t', r: '\r', '0': '\0', "'": "'", '"': '"', '\\': '\\' };
88
+ const map = { n: '\n', t: '\t', r: '\r', '0': '\0', "'": "'", '"': '"', '\\': '\\', b: '\b', Z: '\x1a', a: '\a' };
88
89
  str += map[esc] !== undefined ? map[esc] : esc;
89
90
  j += 2;
90
91
  } else if (sql[j] === quote) {
@@ -140,6 +141,22 @@ function tokenize(sql) {
140
141
  continue;
141
142
  }
142
143
 
144
+ if (c === '@') {
145
+ let j = i;
146
+ while (j < n && sql[j] === '@') j++;
147
+ const ats = j - i;
148
+ let k = j;
149
+ while (k < n && /[A-Za-z0-9_$]/.test(sql[k])) k++;
150
+ if (k > j) {
151
+ tokens.push(new SQLToken('sysvar', sql.slice(j, k), i));
152
+ if (ats > 1) i = k;
153
+ else i = k;
154
+ continue;
155
+ }
156
+ i = j;
157
+ continue;
158
+ }
159
+
143
160
  const two = sql.slice(i, i + 2);
144
161
  if (two === '<=' || two === '>=' || two === '!=' || two === '<>' || two === '==') {
145
162
  tokens.push(new SQLToken('op', two, i));
@@ -191,6 +208,12 @@ class Parser {
191
208
  return t.type === 'keyword' && t.value === kw;
192
209
  }
193
210
 
211
+ // 匹配大小写不敏感的词(无论被 tokenize 成 ident 还是 keyword)
212
+ isWord(kw, offset = 0) {
213
+ const t = this.peek(offset);
214
+ return t.value !== undefined && String(t.value).toUpperCase() === kw;
215
+ }
216
+
194
217
  parseStatement() {
195
218
  const t = this.peek();
196
219
  if (t.type === 'eof') return null;
@@ -206,13 +229,16 @@ class Parser {
206
229
  if (this.isKeyword('DATABASE', 1)) return this.parseDropDatabase();
207
230
  throw new Error('Unsupported DROP statement');
208
231
  case 'INSERT': return this.parseInsert();
232
+ case 'TRUNCATE': this.expectKeyword('TRUNCATE'); if (this.isKeyword('TABLE')) this.next(); return { type: 'truncate', name: this.parseTableName() };
209
233
  case 'SELECT': return this.parseSelect();
210
234
  case 'UPDATE': return this.parseUpdate();
211
235
  case 'DELETE': return this.parseDelete();
212
236
  case 'BEGIN': this.expectKeyword('BEGIN'); this.optionalTransaction(); return { type: 'begin' };
237
+ case 'START': this.expectKeyword('START'); if (this.isKeyword('TRANSACTION')) this.next(); return { type: 'begin' };
213
238
  case 'COMMIT': this.expectKeyword('COMMIT'); this.optionalTransaction(); return { type: 'commit' };
214
239
  case 'ROLLBACK': this.expectKeyword('ROLLBACK'); this.optionalTransaction(); return { type: 'rollback' };
215
240
  case 'SHOW': return this.parseShow();
241
+ case 'SET': return this.parseSet();
216
242
  case 'DESCRIBE': case 'DESC': return this.parseDescribe();
217
243
  case 'USE': return this.parseUse();
218
244
  default: throw new Error(`Unsupported statement: ${t.value}`);
@@ -284,6 +310,11 @@ class Parser {
284
310
  throw new Error(`Expected ',' or ')' in CREATE TABLE, got '${sep.value}'`);
285
311
  }
286
312
  this.expect('op', ')');
313
+
314
+ // 跳过表选项: ENGINE=InnoDB, DEFAULT CHARSET=..., AUTO_INCREMENT=1, COLLATE=...(直到 ; 或语句结束)
315
+ while (!(this.peek().type === 'eof' || (this.peek().type === 'op' && this.peek().value === ';'))) {
316
+ this.next();
317
+ }
287
318
  this.optionalTailSemicolon();
288
319
 
289
320
  if (!hasPk && schema.id === undefined) {
@@ -339,6 +370,9 @@ class Parser {
339
370
  case 'AUTO_INCREMENT':
340
371
  case 'AUTOINCREMENT':
341
372
  this.next(); def.autoIncrement = true; break;
373
+ case 'UNSIGNED':
374
+ case 'ZEROFILL':
375
+ this.next(); def.unsigned = true; break;
342
376
  case 'UNIQUE':
343
377
  this.next(); def.unique = true; break;
344
378
  case 'NOT':
@@ -347,6 +381,13 @@ class Parser {
347
381
  this.next(); break;
348
382
  case 'DEFAULT':
349
383
  this.next(); def.default = this.parseValue(); break;
384
+ case 'COLLATE':
385
+ this.next(); if (this.peek().type !== 'op' && this.peek().type !== 'eof') this.next(); break;
386
+ case 'CHARACTER':
387
+ this.next();
388
+ if (this.isKeyword('SET')) this.next();
389
+ if (this.peek().type !== 'op' && this.peek().type !== 'eof') this.next();
390
+ break;
350
391
  default:
351
392
  return def;
352
393
  }
@@ -423,6 +464,7 @@ class Parser {
423
464
  const t = this.next();
424
465
  if (t.type === 'number' || t.type === 'string') return t.value;
425
466
  if (t.type === 'keyword' && t.value === 'NULL') return null;
467
+ if (t.type === 'keyword' && t.value === 'DEFAULT') return { _default: true };
426
468
  if (t.type === 'op' && t.value === '-') {
427
469
  const num = this.next();
428
470
  if (num.type !== 'number') throw new Error('Expected number after -');
@@ -448,16 +490,18 @@ class Parser {
448
490
  if (t.type === 'keyword' && t.value === 'COUNT') {
449
491
  this.next();
450
492
  this.expect('op', '(');
451
- this.expect('op', '*');
493
+ let col = null;
494
+ if (this.peek().type === 'op' && this.peek().value === '*') { this.next(); }
495
+ else if (!(this.peek().type === 'op' && this.peek().value === ')')) col = this.parseScalar();
452
496
  this.expect('op', ')');
453
- aggregate = { type: 'COUNT' };
497
+ aggregate = { type: 'COUNT', column: col };
454
498
  if (this.isKeyword('AS')) { this.next(); aggregate.alias = this.parseAlias(); }
455
- columns.push({ expr: null, aggregate: 'COUNT' });
499
+ columns.push({ expr: col, aggregate: 'COUNT', column: col, alias: aggregate.alias });
456
500
  } else if (t.type === 'keyword' && ['SUM', 'AVG', 'MIN', 'MAX'].includes(t.value)) {
457
501
  this.next();
458
502
  const fn = t.value;
459
503
  this.expect('op', '(');
460
- const col = this.parseColumnRef();
504
+ const col = this.parseScalar();
461
505
  this.expect('op', ')');
462
506
  aggregate = { type: fn, column: col };
463
507
  if (this.isKeyword('AS')) { this.next(); aggregate.alias = this.parseAlias(); }
@@ -465,21 +509,23 @@ class Parser {
465
509
  } else if (t.type === 'op' && t.value === '*') {
466
510
  this.next();
467
511
  columns.push({ expr: '*' });
468
- } else if (t.type === 'number' || t.type === 'string') {
469
- this.next();
470
- let alias = null;
471
- if (this.isKeyword('AS')) { this.next(); alias = this.parseAlias(); }
472
- columns.push({ expr: null, literal: t.value, alias });
473
512
  } else if (t.type === 'keyword' && t.value === 'CASE') {
474
513
  const caseExpr = this.parseOperand();
475
514
  let alias = null;
476
515
  if (this.isKeyword('AS')) { this.next(); alias = this.parseAlias(); }
477
516
  columns.push({ expr: null, caseExpr, alias });
478
517
  } else {
479
- const col = this.parseColumnRef();
518
+ // / 常量 / 函数 / 算术表达式
519
+ const expr = this.parseScalar();
480
520
  let alias = null;
481
521
  if (this.isKeyword('AS')) { this.next(); alias = this.parseAlias(); }
482
- columns.push({ expr: col, alias });
522
+ if (expr.type === 'aggregate') {
523
+ columns.push({ expr: expr.column, aggregate: expr.fn, column: expr.column, alias, scalar: expr });
524
+ } else if (expr.type === 'column') {
525
+ columns.push({ expr: expr.name, scalar: expr, alias });
526
+ } else {
527
+ columns.push({ expr: null, scalar: expr, alias });
528
+ }
483
529
  }
484
530
  if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
485
531
  break;
@@ -678,13 +724,80 @@ class Parser {
678
724
  this.next();
679
725
  let database = null;
680
726
  if (this.isKeyword('FROM')) { this.next(); database = this.parseTableName(); }
727
+ let like = null;
728
+ if (this.isKeyword('LIKE')) { this.next(); like = this.parseValue(); }
681
729
  this.optionalTailSemicolon();
682
- return { type: 'showTables', database };
730
+ return { type: 'showTables', database, like };
683
731
  }
684
732
  if (this.isKeyword('DATABASES')) { this.next(); this.optionalTailSemicolon(); return { type: 'showDatabases' }; }
733
+ if (this.isWord('FULL')) this.next();
734
+ if (this.isWord('COLUMNS')) {
735
+ this.next();
736
+ if (!(this.isKeyword('FROM') || this.isWord('IN'))) throw new Error("Expected FROM after SHOW COLUMNS");
737
+ this.next();
738
+ let table = this.parseTableName();
739
+ if (table.includes('.')) table = table.slice(table.lastIndexOf('.') + 1);
740
+ let like = null;
741
+ if (this.isKeyword('LIKE')) { this.next(); like = this.parseValue(); }
742
+ this.optionalTailSemicolon();
743
+ return { type: 'showColumns', table, like };
744
+ }
745
+ if (this.isWord('INDEX') || this.isWord('INDEXES') || this.isWord('KEYS')) {
746
+ this.next();
747
+ if (!(this.isKeyword('FROM') || this.isWord('IN'))) throw new Error("Expected FROM after SHOW INDEX");
748
+ this.next();
749
+ let table = this.parseTableName();
750
+ if (table.includes('.')) table = table.slice(table.lastIndexOf('.') + 1);
751
+ this.optionalTailSemicolon();
752
+ return { type: 'showIndex', table };
753
+ }
754
+ if (this.isKeyword('CREATE')) {
755
+ this.next();
756
+ this.expectKeyword('TABLE');
757
+ let table = this.parseTableName();
758
+ if (table.includes('.')) table = table.slice(table.lastIndexOf('.') + 1);
759
+ this.optionalTailSemicolon();
760
+ return { type: 'showCreateTable', table };
761
+ }
762
+ if (this.isWord('SESSION') || this.isWord('GLOBAL')) {
763
+ this.next();
764
+ }
765
+ if (this.isWord('VARIABLES')) {
766
+ this.next();
767
+ let like = null;
768
+ if (this.isKeyword('LIKE')) { this.next(); like = this.parseValue(); }
769
+ this.optionalTailSemicolon();
770
+ return { type: 'showVariables', like };
771
+ }
772
+ if (this.isWord('STATUS')) {
773
+ this.next();
774
+ this.optionalTailSemicolon();
775
+ return { type: 'showStatus' };
776
+ }
777
+ if (this.isWord('GRANTS')) {
778
+ this.next();
779
+ if (this.isKeyword('FOR')) { this.next(); this.parseTableName(); }
780
+ this.optionalTailSemicolon();
781
+ return { type: 'showGrants' };
782
+ }
783
+ if (this.isWord('WARNINGS') || this.isWord('ERRORS')) {
784
+ this.next();
785
+ this.optionalTailSemicolon();
786
+ return { type: 'showWarnings' };
787
+ }
685
788
  throw new Error('Unsupported SHOW statement');
686
789
  }
687
790
 
791
+ parseSet() {
792
+ this.expectKeyword('SET');
793
+ const parts = [];
794
+ while (!(this.peek().type === 'eof' || (this.peek().type === 'op' && this.peek().value === ';'))) {
795
+ parts.push(this.next().value);
796
+ }
797
+ this.optionalTailSemicolon();
798
+ return { type: 'set', raw: parts.join(' ') };
799
+ }
800
+
688
801
  parseDescribe() {
689
802
  this.next();
690
803
  const table = this.parseTableName();
@@ -751,17 +864,47 @@ class Parser {
751
864
  if (col.type !== 'ident') throw new Error(`Expected column name after '.', got '${col.value}'`);
752
865
  return { type: 'column', name: t.value + '.' + col.value };
753
866
  }
867
+ // 函数调用: VERSION() / CONCAT(a, b) / NOW() ...
868
+ if (this.peek().type === 'op' && this.peek().value === '(') {
869
+ const name = t.value;
870
+ this.next();
871
+ const args = [];
872
+ if (!(this.peek().type === 'op' && this.peek().value === ')')) {
873
+ for (;;) {
874
+ args.push(this.parseOperand());
875
+ if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
876
+ break;
877
+ }
878
+ }
879
+ this.expect('op', ')');
880
+ return { type: 'func', name, args };
881
+ }
754
882
  return { type: 'column', name: t.value };
755
883
  }
756
884
  if (t.type === 'number' || t.type === 'string') return { type: 'value', value: t.value };
885
+ if (t.type === 'sysvar') return { type: 'sysvar', name: t.value };
757
886
  if (t.type === 'keyword' && t.value === 'NULL') return { type: 'value', value: null };
758
887
  if (t.type === 'keyword' && t.value === 'CASE') return this.parseCase();
888
+ if (t.type === 'keyword' && this.peek().type === 'op' && this.peek().value === '(') {
889
+ const name = t.value;
890
+ this.next();
891
+ const args = [];
892
+ if (!(this.peek().type === 'op' && this.peek().value === ')')) {
893
+ for (;;) {
894
+ args.push(this.parseOperand());
895
+ if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
896
+ break;
897
+ }
898
+ }
899
+ this.expect('op', ')');
900
+ return { type: 'func', name, args };
901
+ }
759
902
  if (t.type === 'keyword' && ['SUM', 'AVG', 'MIN', 'MAX', 'COUNT'].includes(t.value)) {
760
903
  const fn = t.value;
761
904
  this.expect('op', '(');
762
905
  let column = null;
763
906
  if (this.peek().type === 'op' && this.peek().value === '*') { this.next(); }
764
- else column = this.parseColumnRef();
907
+ else if (!(this.peek().type === 'op' && this.peek().value === ')')) column = this.parseScalar();
765
908
  this.expect('op', ')');
766
909
  return { type: 'aggregate', fn, column };
767
910
  }
@@ -778,6 +921,37 @@ class Parser {
778
921
  throw new Error(`Expected value or column, got '${t.value}'`);
779
922
  }
780
923
 
924
+ // 算术表达式: + - * / % (左结合, * / 优先)
925
+ parseScalar() {
926
+ let node = this.parseTerm();
927
+ for (;;) {
928
+ const t = this.peek();
929
+ if (t.type === 'op' && (t.value === '+' || t.value === '-')) {
930
+ this.next();
931
+ const right = this.parseTerm();
932
+ node = { type: 'arith', op: t.value, left: node, right };
933
+ continue;
934
+ }
935
+ break;
936
+ }
937
+ return node;
938
+ }
939
+
940
+ parseTerm() {
941
+ let node = this.parseOperand();
942
+ for (;;) {
943
+ const t = this.peek();
944
+ if (t.type === 'op' && (t.value === '*' || t.value === '/' || t.value === '%')) {
945
+ this.next();
946
+ const right = this.parseOperand();
947
+ node = { type: 'arith', op: t.value, left: node, right };
948
+ continue;
949
+ }
950
+ break;
951
+ }
952
+ return node;
953
+ }
954
+
781
955
  parseCase() {
782
956
  let base = null;
783
957
  if (!this.isKeyword('WHEN')) {
@@ -874,18 +1048,136 @@ const OPERATORS = {
874
1048
  '>=': (a, b) => a >= b
875
1049
  };
876
1050
 
877
- function resolveOperand(operand, row) {
878
- if (operand.type === 'value') return operand.value;
879
- const n = operand.name;
880
- if (n === undefined) return undefined;
881
- if (row[n] !== undefined) return row[n];
882
- if (n.includes('.')) {
883
- const k = n;
884
- if (row[k] !== undefined) return row[k];
885
- const col = n.slice(n.lastIndexOf('.') + 1);
886
- return row[col];
887
- }
888
- return undefined;
1051
+ function resolveOperand(operand, row, ctx) {
1052
+ if (operand === null || operand === undefined) return null;
1053
+ if (typeof operand === 'string') return resolveOperand({ type: 'column', name: operand }, row, ctx);
1054
+ switch (operand.type) {
1055
+ case 'value':
1056
+ case 'literal':
1057
+ return operand.value;
1058
+ case 'sysvar': {
1059
+ const s = (ctx && ctx.session && ctx.session.sysvars) || {};
1060
+ const name = String(operand.name).toLowerCase();
1061
+ if (s[name] !== undefined) return s[name];
1062
+ const DEFAULTS = {
1063
+ 'version': '8.0.0-jsql-neo',
1064
+ 'version_comment': 'jsql-neo',
1065
+ 'version_compile_os': 'linux',
1066
+ 'sql_mode': '',
1067
+ 'autocommit': 1,
1068
+ 'character_set_client': 'utf8mb4',
1069
+ 'character_set_connection': 'utf8mb4',
1070
+ 'character_set_results': 'utf8mb4',
1071
+ 'collation_connection': 'utf8mb4_general_ci',
1072
+ 'transaction_isolation': 'REPEATABLE-READ',
1073
+ 'max_allowed_packet': 67108864,
1074
+ 'wait_timeout': 28800,
1075
+ 'lower_case_table_names': 0,
1076
+ 'sql_auto_is_null': 0,
1077
+ };
1078
+ return DEFAULTS[name] !== undefined ? DEFAULTS[name] : '';
1079
+ }
1080
+ case 'column': {
1081
+ const n = operand.name;
1082
+ if (n === undefined) return undefined;
1083
+ if (row[n] !== undefined) return row[n];
1084
+ if (n.includes('.')) {
1085
+ if (row[n] !== undefined) return row[n];
1086
+ const col = n.slice(n.lastIndexOf('.') + 1);
1087
+ return row[col];
1088
+ }
1089
+ return undefined;
1090
+ }
1091
+ case 'arith': {
1092
+ const l = resolveOperand(operand.left, row, ctx);
1093
+ const r = resolveOperand(operand.right, row, ctx);
1094
+ if (l === null || r === null || l === undefined || r === undefined) return null;
1095
+ switch (operand.op) {
1096
+ case '+': return l + r;
1097
+ case '-': return l - r;
1098
+ case '*': return l * r;
1099
+ case '/': return r === 0 ? null : l / r;
1100
+ case '%': return r === 0 ? null : l % r;
1101
+ }
1102
+ return null;
1103
+ }
1104
+ case 'func':
1105
+ return applyScalarFunction(operand, row, ctx);
1106
+ case 'case':
1107
+ return evaluateCaseVal(operand, row, ctx);
1108
+ case 'aggregate':
1109
+ case 'subquery':
1110
+ return undefined;
1111
+ default:
1112
+ return undefined;
1113
+ }
1114
+ }
1115
+
1116
+ function applyScalarFunction(fnNode, row, ctx) {
1117
+ const name = (fnNode.name || '').toUpperCase();
1118
+ const args = (fnNode.args || []).map(a => resolveOperand(a, row, ctx));
1119
+ const session = ctx && ctx.session;
1120
+ switch (name) {
1121
+ case 'VERSION': return '8.0.0-jsql-neo';
1122
+ case 'LAST_INSERT_ID': {
1123
+ if (args.length > 0) {
1124
+ if (session) session.lastInsertId = args[0];
1125
+ return args[0];
1126
+ }
1127
+ return session && session.lastInsertId !== undefined ? session.lastInsertId : 0;
1128
+ }
1129
+ case 'ROW_COUNT': return session && session.rowCount !== undefined ? session.rowCount : 0;
1130
+ case 'FOUND_ROWS': return session && session.foundRows !== undefined ? session.foundRows : 0;
1131
+ case 'CONNECTION_ID': return session && session.connectionId !== undefined ? session.connectionId : 0;
1132
+ case 'DATABASE': case 'SCHEMA': return session && session.currentDb ? session.currentDb : 'default';
1133
+ case 'NOW': case 'CURRENT_TIMESTAMP': return new Date().toISOString().slice(0, 19).replace('T', ' ');
1134
+ case 'CURDATE': case 'CURRENT_DATE': return new Date().toISOString().slice(0, 10);
1135
+ case 'CURTIME': return new Date().toISOString().slice(11, 19);
1136
+ case 'UTC_TIMESTAMP': return new Date().toISOString().slice(0, 19).replace('T', ' ') + ' UTC';
1137
+ case 'CONCAT': return args.map(a => a === null || a === undefined ? '' : String(a)).join('');
1138
+ case 'CONCAT_WS': return (args.slice(1).map(a => a === null || a === undefined ? '' : String(a))).join(args[0] == null ? ',' : String(args[0]));
1139
+ case 'UPPER': case 'UCASE': return args[0] == null ? null : String(args[0]).toUpperCase();
1140
+ case 'LOWER': case 'LCASE': return args[0] == null ? null : String(args[0]).toLowerCase();
1141
+ case 'LENGTH': case 'CHAR_LENGTH': case 'CHARACTER_LENGTH': return args[0] == null ? null : String(args[0]).length;
1142
+ case 'TRIM': return args[0] == null ? null : String(args[0]).trim();
1143
+ case 'LTRIM': return args[0] == null ? null : String(args[0]).replace(/^\s+/, '');
1144
+ case 'RTRIM': return args[0] == null ? null : String(args[0]).replace(/\s+$/, '');
1145
+ case 'ABS': return args[0] == null ? null : Math.abs(args[0]);
1146
+ case 'ROUND': return args[0] == null ? null : (args[1] !== undefined ? Number(args[0].toFixed(args[1])) : Math.round(args[0]));
1147
+ case 'FLOOR': return args[0] == null ? null : Math.floor(args[0]);
1148
+ case 'CEIL': case 'CEILING': return args[0] == null ? null : Math.ceil(args[0]);
1149
+ case 'MOD': return (args[0] == null || args[1] === 0) ? null : args[0] % args[1];
1150
+ case 'POWER': case 'POW': return args[0] == null ? null : Math.pow(args[0], args[1]);
1151
+ case 'SQRT': return args[0] == null ? null : Math.sqrt(args[0]);
1152
+ case 'IFNULL': case 'NVL': return args[0] != null ? args[0] : args[1];
1153
+ case 'COALESCE': return args.find(a => a != null);
1154
+ case 'NULLIF': return args[0] === args[1] ? null : args[0];
1155
+ case 'IF': return args[0] ? args[1] : args[2];
1156
+ case 'REPLACE': return args[0] == null ? null : String(args[0]).split(args[1]).join(args[2]);
1157
+ case 'SUBSTRING': case 'SUBSTR': {
1158
+ if (args[0] == null) return null;
1159
+ const s = String(args[0]);
1160
+ const start = Number(args[1]);
1161
+ if (args[2] !== undefined) return s.substr(start - 1, Number(args[2]));
1162
+ return s.substr(start - 1);
1163
+ }
1164
+ case 'LEFT': return args[0] == null ? null : String(args[0]).slice(0, Number(args[1]));
1165
+ case 'RIGHT': return args[0] == null ? null : String(args[0]).slice(-Number(args[1]));
1166
+ case 'LOCATE': case 'INSTR': {
1167
+ if (args[0] == null || args[1] == null) return null;
1168
+ const idx = String(args[1]).indexOf(String(args[0]));
1169
+ return idx + 1;
1170
+ }
1171
+ case 'GREATEST': return args.reduce((m, a) => a > m ? a : m, args[0]);
1172
+ case 'LEAST': return args.reduce((m, a) => a < m ? a : m, args[0]);
1173
+ case 'UUID': return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
1174
+ const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8);
1175
+ return v.toString(16);
1176
+ });
1177
+ case 'DATABASE': case 'SCHEMA': return row && row.__db !== undefined ? row.__db : 'default';
1178
+ default:
1179
+ return null;
1180
+ }
889
1181
  }
890
1182
 
891
1183
  function likeMatch(value, pattern) {
@@ -922,36 +1214,36 @@ function extractEqualPushdown(expr, schema) {
922
1214
  return { filter, rest };
923
1215
  }
924
1216
 
925
- function evaluateExpr(expr, row) {
1217
+ function evaluateExpr(expr, row, ctx) {
926
1218
  if (expr === null || expr === undefined) return false;
927
1219
  switch (expr.type) {
928
- case 'and': return evaluateExpr(expr.left, row) && evaluateExpr(expr.right, row);
929
- case 'or': return evaluateExpr(expr.left, row) || evaluateExpr(expr.right, row);
930
- case 'not': return !evaluateExpr(expr.expr, row);
1220
+ case 'and': return evaluateExpr(expr.left, row, ctx) && evaluateExpr(expr.right, row, ctx);
1221
+ case 'or': return evaluateExpr(expr.left, row, ctx) || evaluateExpr(expr.right, row, ctx);
1222
+ case 'not': return !evaluateExpr(expr.expr, row, ctx);
931
1223
  case 'compare': {
932
- const l = resolveOperand(expr.left, row);
933
- const r = resolveOperand(expr.right, row);
1224
+ const l = resolveOperand(expr.left, row, ctx);
1225
+ const r = resolveOperand(expr.right, row, ctx);
934
1226
  if (expr.op === '=') return l === r || (l === null && r === null) || (l !== null && r !== null && String(l) === String(r));
935
1227
  if (l === null || r === null) return false;
936
1228
  const fn = OPERATORS[expr.op];
937
1229
  return typeof l === 'number' && typeof r === 'number' ? fn(l, r) : fn(String(l), String(r));
938
1230
  }
939
1231
  case 'isNull': {
940
- const v = resolveOperand(expr.operand, row);
1232
+ const v = resolveOperand(expr.operand, row, ctx);
941
1233
  const isNull = v === null || v === undefined;
942
1234
  return expr.not ? !isNull : isNull;
943
1235
  }
944
1236
  case 'in': {
945
- const v = resolveOperand(expr.operand, row);
1237
+ const v = resolveOperand(expr.operand, row, ctx);
946
1238
  if (expr.subquery && expr.subquery._values !== undefined) expr.list = expr.subquery._values;
947
1239
  if (!expr.list) return false;
948
1240
  return expr.list.some(x => x === v || String(x) === String(v));
949
1241
  }
950
- case 'like': return likeMatch(resolveOperand(expr.operand, row), expr.pattern);
1242
+ case 'like': return likeMatch(resolveOperand(expr.operand, row, ctx), expr.pattern);
951
1243
  case 'between': {
952
- const v = resolveOperand(expr.operand, row);
953
- const lo = resolveOperand(expr.low, row);
954
- const hi = resolveOperand(expr.high, row);
1244
+ const v = resolveOperand(expr.operand, row, ctx);
1245
+ const lo = resolveOperand(expr.low, row, ctx);
1246
+ const hi = resolveOperand(expr.high, row, ctx);
955
1247
  if (v === null || lo === null || hi === null) return false;
956
1248
  const inRange = typeof v === 'number' && typeof lo === 'number' && typeof hi === 'number'
957
1249
  ? v >= lo && v <= hi
@@ -960,24 +1252,84 @@ function evaluateExpr(expr, row) {
960
1252
  }
961
1253
  case 'case': {
962
1254
  for (const b of expr.branches) {
963
- if (evaluateExpr(b.cond, row)) return resolveOperand(b.val, row);
1255
+ if (evaluateExpr(b.cond, row, ctx)) return resolveOperand(b.val, row, ctx);
964
1256
  }
965
- return expr.elseVal ? resolveOperand(expr.elseVal, row) : null;
1257
+ return expr.elseVal ? resolveOperand(expr.elseVal, row, ctx) : null;
966
1258
  }
967
1259
  default: return false;
968
1260
  }
969
1261
  }
970
1262
 
971
- function evaluateCaseVal(caseExpr, row) {
1263
+ function evaluateCaseVal(caseExpr, row, ctx) {
972
1264
  if (!caseExpr || caseExpr.type !== 'case') return undefined;
973
1265
  for (const b of caseExpr.branches) {
974
- if (evaluateExpr(b.cond, row)) return resolveOperand(b.val, row);
1266
+ if (evaluateExpr(b.cond, row, ctx)) return resolveOperand(b.val, row, ctx);
1267
+ }
1268
+ return caseExpr.elseVal ? resolveOperand(caseExpr.elseVal, row, ctx) : null;
1269
+ }
1270
+
1271
+ function scalarName(node) {
1272
+ if (!node) return 'expr';
1273
+ switch (node.type) {
1274
+ case 'column': {
1275
+ const dot = node.name.indexOf('.');
1276
+ return dot !== -1 ? node.name.slice(dot + 1) : node.name;
1277
+ }
1278
+ case 'func': return node.name + '()';
1279
+ case 'value':
1280
+ case 'literal': return String(node.value);
1281
+ case 'arith': return scalarName(node.left) + ' ' + node.op + ' ' + scalarName(node.right);
1282
+ case 'case': return 'CASE';
1283
+ case 'aggregate': return node.fn + '(' + (node.column || '*') + ')';
1284
+ default: return 'expr';
975
1285
  }
976
- return caseExpr.elseVal ? resolveOperand(caseExpr.elseVal, row) : null;
977
1286
  }
978
1287
 
979
- function normalizeRow(row, schema) {
980
- if (row && typeof row === 'object' && row.fields && typeof row.fields === 'object') {
1288
+ function scalarColumnName(c) {
1289
+ if (c.alias) return c.alias;
1290
+ if (c.scalar) return scalarName(c.scalar);
1291
+ if (c.aggregate) return c.alias || (c.aggregate === 'COUNT' ? 'COUNT(*)' : c.aggregate + '(' + c.column + ')');
1292
+ if (c.literal !== undefined) return String(c.literal);
1293
+ if (c.caseExpr) return 'CASE';
1294
+ if (c.expr === '*') return '*';
1295
+ const dot = (c.expr || '').indexOf('.');
1296
+ return dot !== -1 ? c.expr.slice(dot + 1) : c.expr;
1297
+ }
1298
+
1299
+ function scalarColumnValue(c, r, ctx) {
1300
+ if (c.scalar) return resolveOperand(c.scalar, r, ctx);
1301
+ if (c.aggregate) return ctx._aggValue(ctx.group, c.aggregate || 'COUNT', c.column);
1302
+ if (c.literal !== undefined) return c.literal;
1303
+ if (c.caseExpr) return evaluateCaseVal(c.caseExpr, r, ctx);
1304
+ if (c.expr === '*') return r[Object.keys(r).find(k => !k.startsWith('_'))];
1305
+ return resolveOperand({ type: 'column', name: c.expr }, r, ctx);
1306
+ }
1307
+
1308
+ function sqlTypeName(type) {
1309
+ const t = String(type || 'string').toLowerCase();
1310
+ if (t === 'string' || t === 'text') return 'varchar(255)';
1311
+ if (t === 'integer') return 'int';
1312
+ if (t === 'float' || t === 'double') return 'float';
1313
+ if (t === 'boolean') return 'tinyint(1)';
1314
+ if (t === 'date' || t === 'datetime' || t === 'timestamp') return 'datetime';
1315
+ if (t === 'object' || t === 'array') return 'json';
1316
+ return t;
1317
+ }
1318
+
1319
+ function buildCreateTableSql(name, schema) {
1320
+ const parts = Object.entries(schema).map(([col, def]) => {
1321
+ const seg = ['`' + col + '`', sqlTypeName(def.type)];
1322
+ if (def.autoIncrement) seg.push('AUTO_INCREMENT');
1323
+ if (def.nullable === false) seg.push('NOT NULL');
1324
+ if (def.default !== undefined) seg.push('DEFAULT ' + (typeof def.default === 'string' ? "'" + def.default + "'" : def.default));
1325
+ return seg.join(' ');
1326
+ });
1327
+ const pks = Object.keys(schema).filter(k => schema[k].primaryKey);
1328
+ if (pks.length > 0) parts.push('PRIMARY KEY (' + pks.map(k => '`' + k + '`').join(', ') + ')');
1329
+ return 'CREATE TABLE `' + name + '` (\n ' + parts.join(',\n ') + '\n) ENGINE=JSQL DEFAULT CHARSET=utf8mb4';
1330
+ }
1331
+
1332
+ function normalizeRow(row, schema) { if (row && typeof row === 'object' && row.fields && typeof row.fields === 'object') {
981
1333
  const flat = { ...row.fields };
982
1334
  if (schema) {
983
1335
  const pkCols = Object.keys(schema).filter(k => schema[k].primaryKey);
@@ -994,8 +1346,9 @@ function normalizeRow(row, schema) {
994
1346
  }
995
1347
 
996
1348
  class SQLExecutor {
997
- constructor(engine) {
1349
+ constructor(engine, ctx) {
998
1350
  this.engine = engine;
1351
+ this.ctx = ctx || null;
999
1352
  }
1000
1353
 
1001
1354
  async execute(statement) {
@@ -1014,9 +1367,25 @@ class SQLExecutor {
1014
1367
  await this.engine.dropTable(statement.table);
1015
1368
  return { ok: true, type: 'dropTable', table: statement.table, affectedRows: 0 };
1016
1369
  }
1370
+ case 'truncate': {
1371
+ if (this.engine.hasTable(statement.name)) await this.engine.truncate(statement.name);
1372
+ return { ok: true, type: 'truncate', table: statement.name, affectedRows: 0 };
1373
+ }
1017
1374
  case 'insert': {
1018
1375
  let dataRows = statement.dataRows;
1019
1376
  let schema = null;
1377
+ const stripDefault = (row) => {
1378
+ const out = {};
1379
+ for (const [k, v] of Object.entries(row)) {
1380
+ if (v && typeof v === 'object' && v._default) continue;
1381
+ out[k] = v;
1382
+ }
1383
+ return out;
1384
+ };
1385
+ if (statement.dataRows) {
1386
+ statement.dataRows = statement.dataRows.map(stripDefault);
1387
+ dataRows = statement.dataRows;
1388
+ }
1020
1389
  if (dataRows === null && statement.values) {
1021
1390
  schema = this.engine.getTableSchema
1022
1391
  ? await this.engine.getTableSchema(statement.name)
@@ -1028,7 +1397,8 @@ class SQLExecutor {
1028
1397
  const row = {};
1029
1398
  let vi = 0;
1030
1399
  colNames.forEach(c => {
1031
- if (schema[c].autoIncrement && (skipAuto || vals[vi] === undefined || vals[vi] === null)) {
1400
+ const isDefault = vals[vi] && typeof vals[vi] === 'object' && vals[vi]._default;
1401
+ if (schema[c].autoIncrement && (skipAuto || vals[vi] === undefined || vals[vi] === null || isDefault)) {
1032
1402
  if (!skipAuto && vi < vals.length) vi++;
1033
1403
  return;
1034
1404
  }
@@ -1038,6 +1408,7 @@ class SQLExecutor {
1038
1408
  });
1039
1409
  return row;
1040
1410
  });
1411
+ dataRows = dataRows.map(stripDefault);
1041
1412
  }
1042
1413
  if (!schema) {
1043
1414
  schema = this.engine.getTableSchema
@@ -1119,7 +1490,7 @@ class SQLExecutor {
1119
1490
  const all = (await this.engine.find(statement.table, {}, { limit: 1e9, offset: 0 })).map(r => normalizeRow(r, schema));
1120
1491
  let count = 0;
1121
1492
  for (const row of all) {
1122
- if (!statement.where || evaluateExpr(statement.where, row)) {
1493
+ if (!statement.where || evaluateExpr(statement.where, row, this.ctx)) {
1123
1494
  const id = row._rid !== undefined ? row._rid : row.id;
1124
1495
  if (id !== undefined) {
1125
1496
  const data = {};
@@ -1139,7 +1510,7 @@ class SQLExecutor {
1139
1510
  const all = (await this.engine.find(statement.table, {}, { limit: 1e9, offset: 0 })).map(r => normalizeRow(r, schema));
1140
1511
  const ids = [];
1141
1512
  for (const row of all) {
1142
- if (!statement.where || evaluateExpr(statement.where, row)) {
1513
+ if (!statement.where || evaluateExpr(statement.where, row, this.ctx)) {
1143
1514
  const id = row._rid !== undefined ? row._rid : row.id;
1144
1515
  if (id !== undefined) ids.push(id);
1145
1516
  }
@@ -1169,7 +1540,12 @@ class SQLExecutor {
1169
1540
  return { ok: true, type: 'rollback' };
1170
1541
  case 'showTables': {
1171
1542
  const tables = this.engine.getTables ? this.engine.getTables() : (this.engine.tables ? this.engine.tables() : []);
1172
- return { ok: true, type: 'showTables', columns: ['Tables'], rows: tables.map(t => [t]) };
1543
+ let list = tables.map(t => [t]);
1544
+ if (statement.like) {
1545
+ const re = new RegExp('^' + statement.like.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/%/g, '.*').replace(/_/g, '.') + '$', 'i');
1546
+ list = list.filter(r => re.test(r[0]));
1547
+ }
1548
+ return { ok: true, type: 'showTables', columns: ['Tables_in_' + (statement.database || 'default')], rows: list };
1173
1549
  }
1174
1550
  case 'showDatabases': {
1175
1551
  const list = this.engine.listDatabases
@@ -1177,6 +1553,83 @@ class SQLExecutor {
1177
1553
  : ['jsql'];
1178
1554
  return { ok: true, type: 'showDatabases', columns: ['Database'], rows: list.map(d => [d]) };
1179
1555
  }
1556
+ case 'showColumns': {
1557
+ const schema = this.engine.getTableSchema ? await this.engine.getTableSchema(statement.table) : null;
1558
+ if (!schema) throw new Error(`Table '${statement.table}' does not exist`);
1559
+ let rows = Object.entries(schema).map(([col, def]) => [
1560
+ col,
1561
+ sqlTypeName(def.type),
1562
+ def.nullable === false ? 'NO' : 'YES',
1563
+ def.primaryKey ? 'PRI' : (def.unique ? 'UNI' : ''),
1564
+ def.default !== undefined && def.default !== null ? String(def.default) : null,
1565
+ def.autoIncrement ? 'auto_increment' : '',
1566
+ ]);
1567
+ if (statement.like) {
1568
+ const re = new RegExp('^' + statement.like.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/%/g, '.*').replace(/_/g, '.') + '$', 'i');
1569
+ rows = rows.filter(r => re.test(r[0]));
1570
+ }
1571
+ return { ok: true, type: 'showColumns', table: statement.table, columns: ['Field', 'Type', 'Null', 'Key', 'Default', 'Extra'], rows };
1572
+ }
1573
+ case 'showIndex': {
1574
+ const schema = this.engine.getTableSchema ? await this.engine.getTableSchema(statement.table) : null;
1575
+ if (!schema) throw new Error(`Table '${statement.table}' does not exist`);
1576
+ const rows = [];
1577
+ let seq = 0;
1578
+ for (const col of Object.keys(schema).filter(k => schema[k].primaryKey)) {
1579
+ rows.push([statement.table, 0, 'PRIMARY', ++seq, col, 'A', 0, null, null, schema[col].nullable === false ? '' : 'YES', 'BTREE', '']);
1580
+ }
1581
+ for (const col of Object.keys(schema).filter(k => schema[k].unique && !schema[k].primaryKey)) {
1582
+ rows.push([statement.table, 0, col, ++seq, col, 'A', 0, null, null, schema[col].nullable === false ? '' : 'YES', 'BTREE', '']);
1583
+ }
1584
+ return { ok: true, type: 'showIndex', table: statement.table, columns: ['Table', 'Non_unique', 'Key_name', 'Seq_in_index', 'Column_name', 'Collation', 'Cardinality', 'Sub_part', 'Packed', 'Null', 'Index_type', 'Comment'], rows };
1585
+ }
1586
+ case 'showCreateTable': {
1587
+ const schema = this.engine.getTableSchema ? await this.engine.getTableSchema(statement.table) : null;
1588
+ if (!schema) throw new Error(`Table '${statement.table}' does not exist`);
1589
+ const ddl = buildCreateTableSql(statement.table, schema);
1590
+ return { ok: true, type: 'showCreateTable', table: statement.table, columns: ['Table', 'Create Table'], rows: [[statement.table, ddl]] };
1591
+ }
1592
+ case 'showVariables': {
1593
+ const vars = {
1594
+ 'version': '8.0.0-jsql-neo',
1595
+ 'version_comment': 'JSQL-NEO',
1596
+ 'version_compile_os': 'any',
1597
+ 'sql_mode': '',
1598
+ 'character_set_client': 'utf8mb4',
1599
+ 'character_set_connection': 'utf8mb4',
1600
+ 'character_set_server': 'utf8mb4',
1601
+ 'collation_server': 'utf8mb4_general_ci',
1602
+ 'lower_case_table_names': '1',
1603
+ 'max_allowed_packet': '1048576',
1604
+ 'autocommit': 'ON',
1605
+ 'transaction_isolation': 'REPEATABLE-READ',
1606
+ };
1607
+ let entries = Object.entries(vars);
1608
+ if (statement.like) {
1609
+ const re = new RegExp('^' + statement.like.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/%/g, '.*').replace(/_/g, '.') + '$', 'i');
1610
+ entries = entries.filter(([k]) => re.test(k));
1611
+ }
1612
+ return { ok: true, type: 'showVariables', columns: ['Variable_name', 'Value'], rows: entries };
1613
+ }
1614
+ case 'showStatus':
1615
+ return { ok: true, type: 'showStatus', columns: ['Variable_name', 'Value'], rows: [] };
1616
+ case 'showGrants':
1617
+ return { ok: true, type: 'showGrants', columns: ['Grants for root@localhost'], rows: ['GRANT ALL PRIVILEGES ON *.* TO `root`@`localhost`'].map(g => [g]) };
1618
+ case 'showWarnings':
1619
+ return { ok: true, type: 'showWarnings', columns: ['Level', 'Code', 'Message'], rows: [] };
1620
+ case 'set': {
1621
+ const session = this.ctx && this.ctx.session;
1622
+ if (session) {
1623
+ const raw = String(statement.raw || '');
1624
+ const m = raw.match(/^\s*([A-Za-z0-9_]+)(?:\.[A-Za-z0-9_]+)?\s*=\s*(.+)$/);
1625
+ if (m) {
1626
+ let val = m[2].trim().replace(/^'|'$/g, '').replace(/^"|"$/g, '');
1627
+ if (/^-?\d+(\.\d+)?$/.test(val)) val = Number(val);
1628
+ session.sysvars[m[1].toLowerCase()] = val;
1629
+ }
1630
+ }
1631
+ return { ok: true, type: 'set', raw: statement.raw };
1632
+ }
1180
1633
  case 'createDatabase': {
1181
1634
  if (!this.engine.createDatabase) throw new Error('CREATE DATABASE is not supported by this engine');
1182
1635
  await this.engine.createDatabase(statement.database, { ifNotExists: statement.ifNotExists });
@@ -1278,7 +1731,8 @@ class SQLExecutor {
1278
1731
 
1279
1732
  _aggValue(rows, fn, column) {
1280
1733
  if (fn === 'COUNT') return rows.length;
1281
- const values = rows.map(r => resolveOperand({ type: 'column', name: column }, r)).filter(v => v !== null && v !== undefined);
1734
+ const op = typeof column === 'string' ? { type: 'column', name: column } : column;
1735
+ const values = rows.map(r => resolveOperand(op, r, this.ctx)).filter(v => v !== null && v !== undefined);
1282
1736
  if (fn === 'SUM') return values.reduce((s, v) => s + (typeof v === 'number' ? v : Number(v) || 0), 0);
1283
1737
  if (fn === 'AVG') return values.length ? values.reduce((s, v) => s + (typeof v === 'number' ? v : Number(v) || 0), 0) / values.length : null;
1284
1738
  if (fn === 'MIN') return values.length ? Math.min(...values.map(v => Number(v))) : null;
@@ -1350,6 +1804,10 @@ class SQLExecutor {
1350
1804
 
1351
1805
  // 读第一表
1352
1806
  const firstItem = statement.from.tables[0];
1807
+ if (firstItem && firstItem.table && String(firstItem.table).toLowerCase().startsWith('information_schema.')) {
1808
+ const cols = statement.columns.map(c => scalarColumnName(c));
1809
+ return { ok: true, type: 'select', table: null, columns: cols, rows: [], raw: [] };
1810
+ }
1353
1811
  let rowsAll;
1354
1812
  if (firstItem.subquery) {
1355
1813
  const res = await this.executeSelect(firstItem.subquery);
@@ -1401,14 +1859,14 @@ class SQLExecutor {
1401
1859
 
1402
1860
  let rows = all;
1403
1861
  if (statement.where) {
1404
- rows = rows.filter(r => evaluateExpr(statement.where, r));
1862
+ rows = rows.filter(r => evaluateExpr(statement.where, r, this.ctx));
1405
1863
  }
1406
1864
 
1407
1865
  // 分组聚合
1408
1866
  if (statement.groupBy) {
1409
1867
  const groups = new Map();
1410
1868
  for (const row of rows) {
1411
- const key = JSON.stringify(statement.groupBy.map(g => resolveOperand({ type: 'column', name: g }, row)));
1869
+ const key = JSON.stringify(statement.groupBy.map(g => resolveOperand({ type: 'column', name: g }, row, this.ctx)));
1412
1870
  if (!groups.has(key)) groups.set(key, []);
1413
1871
  groups.get(key).push(row);
1414
1872
  }
@@ -1443,8 +1901,10 @@ class SQLExecutor {
1443
1901
  const seen = new Set();
1444
1902
  rows = rows.filter(r => {
1445
1903
  const key = JSON.stringify(statement.columns.map(c => {
1904
+ if (c.scalar) return resolveOperand(c.scalar, r, this.ctx);
1905
+ if (c.caseExpr) return evaluateCaseVal(c.caseExpr, r);
1446
1906
  if (c.aggregate) return this._aggValue(r._group || [r], c.aggregate || 'COUNT', c.column);
1447
- return resolveOperand(c.caseExpr || { type: 'column', name: c.expr }, r);
1907
+ return resolveOperand({ type: 'column', name: c.expr }, r, this.ctx);
1448
1908
  }));
1449
1909
  if (seen.has(key)) return false;
1450
1910
  seen.add(key);
@@ -1453,33 +1913,24 @@ class SQLExecutor {
1453
1913
  }
1454
1914
 
1455
1915
  // 聚合输出(无 GROUP BY 时)
1456
- if (statement.aggregate && !statement.groupBy) {
1457
- const agg = statement.aggregate;
1458
- const rowsOnly = rows;
1459
- const aggName = agg.alias || (agg.type === 'COUNT' ? 'COUNT(*)' : agg.type + '(' + agg.column + ')');
1460
- const value = this._aggValue(rowsOnly, agg.type, agg.column);
1461
- return { ok: true, type: 'select', table: statement.from ? (statement.from.tables[0].table || null) : null, columns: [aggName], rows: [[value]], aggregate: agg };
1916
+ if (!statement.groupBy && statement.columns.some(c => c.aggregate)) {
1917
+ const cols = statement.columns.map(c => scalarColumnName(c));
1918
+ const valueOf = (c) => {
1919
+ if (c.aggregate) return this._aggValue(rows, c.aggregate || 'COUNT', c.column);
1920
+ if (c.scalar) return resolveOperand(c.scalar, rows[0] || {}, this.ctx);
1921
+ if (c.caseExpr) return resolveOperand(c.caseExpr, rows[0] || {}, this.ctx);
1922
+ if (c.expr !== null && c.expr !== '*') return rows[0] ? resolveOperand({ type: 'column', name: c.expr }, rows[0]) : null;
1923
+ return null;
1924
+ };
1925
+ return { ok: true, type: 'select', table: statement.from ? (statement.from.tables[0].table || null) : null, columns: cols, rows: [[...statement.columns.map(valueOf)]], aggregate: statement.aggregate };
1462
1926
  }
1463
1927
 
1464
1928
  // GROUP BY 输出:每组的列(含聚合列)
1465
1929
  if (statement.groupBy) {
1466
- const aggName = (c) => c.alias || (c.aggregate === 'COUNT' ? 'COUNT(*)' : c.aggregate + '(' + c.column + ')');
1467
- const cols = statement.columns.map(c => {
1468
- if (c.alias) return c.alias;
1469
- if (c.aggregate) return aggName(c);
1470
- if (c.literal !== undefined) return String(c.literal);
1471
- if (c.caseExpr) return 'CASE';
1472
- const dot = c.expr.indexOf('.');
1473
- return dot !== -1 ? c.expr.slice(dot + 1) : c.expr;
1474
- });
1930
+ const cols = statement.columns.map(c => scalarColumnName(c));
1475
1931
  const mapped = rows.map(r => {
1476
1932
  const group = r._group || [r];
1477
- return statement.columns.map(c => {
1478
- if (c.aggregate) return this._aggValue(group, c.aggregate || 'COUNT', c.column);
1479
- if (c.caseExpr) return evaluateCaseVal(c.caseExpr, r);
1480
- if (c.expr === '*') return r[Object.keys(r).find(k => !k.startsWith('_'))];
1481
- return resolveOperand({ type: 'column', name: c.expr }, r);
1482
- });
1933
+ return statement.columns.map(c => scalarColumnValue(c, r, { _aggValue: this._aggValue.bind(this), group }));
1483
1934
  });
1484
1935
  return { ok: true, type: 'select', table: statement.from ? (statement.from.tables[0].table || null) : null, columns: cols, rows: mapped, raw: rows };
1485
1936
  }
@@ -1487,8 +1938,8 @@ class SQLExecutor {
1487
1938
  if (statement.orderBy) {
1488
1939
  const cmp = (a, b) => {
1489
1940
  for (const o of statement.orderBy) {
1490
- const av = resolveOperand({ type: 'column', name: o.column }, a);
1491
- const bv = resolveOperand({ type: 'column', name: o.column }, b);
1941
+ const av = resolveOperand({ type: 'column', name: o.column }, a, this.ctx);
1942
+ const bv = resolveOperand({ type: 'column', name: o.column }, b, this.ctx);
1492
1943
  if (av === bv || (av === undefined && bv === undefined)) continue;
1493
1944
  if (av === undefined || av === null) return o.dir === 'asc' ? -1 : 1;
1494
1945
  if (bv === undefined || bv === null) return o.dir === 'asc' ? 1 : -1;
@@ -1515,18 +1966,13 @@ class SQLExecutor {
1515
1966
  return { ok: true, type: 'select', table: tableName, columns: cols, rows: rows.map(r => cols.map(c => r[c])), raw: rows };
1516
1967
  }
1517
1968
 
1518
- const cols = statement.columns.map(c => {
1519
- if (c.alias) return c.alias;
1520
- if (c.literal !== undefined) return String(c.literal);
1521
- if (c.caseExpr) return 'CASE';
1522
- const dot = c.expr.indexOf('.');
1523
- return dot !== -1 ? c.expr.slice(dot + 1) : c.expr;
1524
- });
1969
+ const cols = statement.columns.map(c => scalarColumnName(c));
1525
1970
  const mapped = rows.map(r => statement.columns.map(c => {
1971
+ if (c.scalar) return resolveOperand(c.scalar, r, this.ctx);
1526
1972
  if (c.expr === '*') return null;
1527
1973
  if (c.literal !== undefined) return c.literal;
1528
1974
  if (c.caseExpr) return evaluateCaseVal(c.caseExpr, r);
1529
- return resolveOperand({ type: 'column', name: c.expr }, r);
1975
+ return resolveOperand({ type: 'column', name: c.expr }, r, this.ctx);
1530
1976
  }));
1531
1977
  return { ok: true, type: 'select', table: tableName, columns: cols, rows: mapped, raw: rows };
1532
1978
  }
@@ -1696,7 +2142,7 @@ async function executeSQL(engine, sql, paramsOrOpts, opts = {}) {
1696
2142
  if (dangerous) throw new Error(`SQL statement blocked by security policy: ${dangerous}`);
1697
2143
  }
1698
2144
  }
1699
- const executor = new SQLExecutor(engine);
2145
+ const executor = new SQLExecutor(engine, opts.session ? { session: opts.session } : null);
1700
2146
  const results = [];
1701
2147
  for (const stmtSql of statements) {
1702
2148
  const stmt = parseSQL(stmtSql);