jsql-neo 6.3.5 → 6.3.10

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.
@@ -0,0 +1,289 @@
1
+ /*
2
+ * ALTER TABLE 补全测试(RENAME TO / RENAME COLUMN / DROP INDEX)。
3
+ *
4
+ * 这三条此前各自以不同方式「看起来成功」:
5
+ * - ALTER ... RENAME TO t2 → 返回 ok:true,但表名根本没变
6
+ * - ALTER ... RENAME COLUMN → 解析期就报 "Expected table name, got 'COLUMN'"
7
+ * - ALTER ... DROP INDEX idx → 返回 ok:true,索引还在
8
+ * - ALTER ... ADD FOREIGN KEY→ 返回 ok:true,什么都没加
9
+ * 执行器里这三类 op 落在同一个 `default: break`,也就是静默无操作 ——
10
+ * 比报错更糟,调用方会以为改成功了。
11
+ *
12
+ * 现在:能做的真做,做不到的明确报错(外键在这个引擎里没实现)。
13
+ */
14
+ const fs = require('fs');
15
+ const os = require('os');
16
+ const path = require('path');
17
+
18
+ const ROOT = path.join(__dirname, '..');
19
+ const { Database } = require(path.join(ROOT, 'index.js'));
20
+ const { executeSQL } = require(path.join(ROOT, 'lib/sql.js'));
21
+
22
+ const OPTS = { safety: false };
23
+ let pass = 0, fail = 0;
24
+
25
+ function ok(name, cond, extra) {
26
+ if (cond) { pass++; console.log('[OK] ', name); }
27
+ else { fail++; console.log('[FAIL]', name, extra !== undefined ? '-> ' + JSON.stringify(extra).slice(0, 140) : ''); }
28
+ }
29
+ const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
30
+
31
+ async function t(name, fn) {
32
+ const db = new Database(':memory:');
33
+ const exec = (sql) => executeSQL(db, sql, OPTS);
34
+ const rows = async (sql) => (await exec(sql)).rows;
35
+ const mustFail = async (sql) => {
36
+ try { await exec(sql); return false; } catch (_) { return true; }
37
+ };
38
+ try {
39
+ const r = await fn({ db, exec, rows, mustFail });
40
+ ok(name, r === undefined ? true : !!r);
41
+ } catch (e) {
42
+ ok(name, false, e.message);
43
+ }
44
+ }
45
+
46
+ (async () => {
47
+ console.log('\n--- RENAME TO ---');
48
+ await t('改名真的生效,数据不丢', async ({ db, exec, rows }) => {
49
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, v TEXT)');
50
+ await exec("INSERT INTO t VALUES (1,'x')");
51
+ await exec('ALTER TABLE t RENAME TO t2');
52
+ const tables = (await db.getTables()).slice().sort();
53
+ return eq(tables, ['t2']) && eq(await rows('SELECT id, v FROM t2'), [[1, 'x']]);
54
+ });
55
+
56
+ await t('旧表名不再可用,新名字可取属性', async ({ db, exec, rows, mustFail }) => {
57
+ await exec('CREATE TABLE t (id INT PRIMARY KEY)');
58
+ await exec('ALTER TABLE t RENAME TO t2');
59
+ const oldGone = await mustFail('SELECT * FROM t');
60
+ return oldGone && !!db.t2 && db.t === undefined && rows('SELECT * FROM t2') !== null;
61
+ });
62
+
63
+ await t('RENAME AS 也支持', async ({ db, exec }) => {
64
+ await exec('CREATE TABLE t (id INT PRIMARY KEY)');
65
+ await exec('ALTER TABLE t RENAME AS t3');
66
+ return eq((await db.getTables()).slice().sort(), ['t3']);
67
+ });
68
+
69
+ await t('改成已存在的表名 → 报错', async ({ db, exec, mustFail }) => {
70
+ await exec('CREATE TABLE a (id INT PRIMARY KEY)');
71
+ await exec('CREATE TABLE b (id INT PRIMARY KEY)');
72
+ const bad = await mustFail('ALTER TABLE a RENAME TO b');
73
+ return bad && eq((await db.getTables()).slice().sort(), ['a', 'b']);
74
+ });
75
+
76
+ await t('改名不存在的表 → 报错', async ({ exec, mustFail }) => {
77
+ return mustFail('ALTER TABLE nope RENAME TO x');
78
+ });
79
+
80
+ await t('改成同名 = 无事发生,不算错', async ({ db, exec }) => {
81
+ await exec('CREATE TABLE t (id INT PRIMARY KEY)');
82
+ await exec('ALTER TABLE t RENAME TO t');
83
+ return eq((await db.getTables()).slice().sort(), ['t']);
84
+ });
85
+
86
+ await t('改名后主键/自增/唯一约束仍然生效', async ({ exec, rows, mustFail }) => {
87
+ await exec('CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, e TEXT UNIQUE)');
88
+ await exec("INSERT INTO t (e) VALUES ('a')");
89
+ await exec('ALTER TABLE t RENAME TO t2');
90
+ await exec("INSERT INTO t2 (e) VALUES ('b')");
91
+ const dupPk = await mustFail('INSERT INTO t2 (id, e) VALUES (1, \'c\')');
92
+ const dupUniq = await mustFail("INSERT INTO t2 (e) VALUES ('a')");
93
+ const all = await rows('SELECT id, e FROM t2 ORDER BY id');
94
+ return eq(all, [[1, 'a'], [2, 'b']]) && dupPk && dupUniq;
95
+ });
96
+
97
+ console.log('\n--- RENAME COLUMN ---');
98
+ await t('改列名真的生效,数据不丢', async ({ exec, rows }) => {
99
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
100
+ await exec("INSERT INTO t VALUES (1,'x')");
101
+ await exec('ALTER TABLE t RENAME COLUMN a TO name');
102
+ return eq(await rows('SELECT id, name FROM t'), [[1, 'x']]);
103
+ });
104
+
105
+ await t('列的先后顺序保持不变', async ({ exec }) => {
106
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT, b TEXT, c TEXT)');
107
+ await exec('ALTER TABLE t RENAME COLUMN b TO b2');
108
+ // 直接改 schema 的键会把这一列挪到末尾,SELECT * 的列序就变了
109
+ return eq((await exec('SELECT * FROM t')).columns, ['id', 'a', 'b2', 'c']);
110
+ });
111
+
112
+ await t('列级约束(NOT NULL / UNIQUE / 主键)跟着走', async ({ exec, mustFail, rows }) => {
113
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, code TEXT NOT NULL UNIQUE)');
114
+ await exec("INSERT INTO t VALUES (1,'a')");
115
+ await exec('ALTER TABLE t RENAME COLUMN code TO code2');
116
+ const nullBad = await mustFail('INSERT INTO t (id, code2) VALUES (2, NULL)');
117
+ const dupBad = await mustFail("INSERT INTO t (id, code2) VALUES (3, 'a')");
118
+ await exec("INSERT INTO t VALUES (4, 'b')");
119
+ return nullBad && dupBad && eq(await rows('SELECT id, code2 FROM t ORDER BY id'), [[1, 'a'], [4, 'b']]);
120
+ });
121
+
122
+ await t('改列名后自增字段仍认得出来', async ({ exec, rows }) => {
123
+ await exec('CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT)');
124
+ await exec('ALTER TABLE t RENAME COLUMN id TO sid');
125
+ await exec("INSERT INTO t (v) VALUES ('a'), ('b')");
126
+ return eq(await rows('SELECT sid, v FROM t ORDER BY sid'), [[1, 'a'], [2, 'b']]);
127
+ });
128
+
129
+ await t('改列名会同步列上的索引', async ({ exec, rows }) => {
130
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
131
+ await exec('ALTER TABLE t RENAME COLUMN a TO b');
132
+ await exec("INSERT INTO t VALUES (1,'x')");
133
+ // 表对象上的索引是以旧列名建的,改名后按新列名查必须仍然走得到数据
134
+ return eq(await rows('SELECT id FROM t WHERE b = \'x\''), [[1]]);
135
+ });
136
+
137
+ await t('列不存在 → 报错', async ({ exec, mustFail }) => {
138
+ await exec('CREATE TABLE t (id INT PRIMARY KEY)');
139
+ return mustFail('ALTER TABLE t RENAME COLUMN zzz TO yyy');
140
+ });
141
+
142
+ await t('改成已存在的列名 → 报错', async ({ exec, mustFail, rows }) => {
143
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT, b TEXT)');
144
+ await exec("INSERT INTO t VALUES (1,'x','y')");
145
+ const bad = await mustFail('ALTER TABLE t RENAME COLUMN b TO a');
146
+ return bad && eq(await rows('SELECT id, a, b FROM t'), [[1, 'x', 'y']]);
147
+ });
148
+
149
+ await t('RENAME COLUMN 缺 TO → 报错', async ({ exec, mustFail }) => {
150
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
151
+ return mustFail('ALTER TABLE t RENAME COLUMN a b');
152
+ });
153
+
154
+ await t('改成同名列 = 无事发生', async ({ exec, rows }) => {
155
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
156
+ await exec("INSERT INTO t VALUES (1,'x')");
157
+ await exec('ALTER TABLE t RENAME COLUMN a TO a');
158
+ return eq(await rows('SELECT a FROM t'), [['x']]);
159
+ });
160
+
161
+ console.log('\n--- DROP INDEX / DROP KEY ---');
162
+ await t('DROP INDEX 真的删掉索引', async ({ db, exec }) => {
163
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
164
+ await exec('CREATE INDEX idx_a ON t (a)');
165
+ const before = Object.keys(db.t._indexes).length + Object.keys(db.t._btrees).length;
166
+ await exec('ALTER TABLE t DROP INDEX idx_a');
167
+ const after = Object.keys(db.t._indexes).length + Object.keys(db.t._btrees).length;
168
+ return before > 0 && after < before;
169
+ });
170
+
171
+ await t('DROP KEY 是同一件事的另一种写法', async ({ db, exec }) => {
172
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
173
+ // 记下建索引前的状态:主键会在 _btrees 里留一个 id 索引,那是该留的
174
+ const baseline = Object.keys(db.t._indexes).length + Object.keys(db.t._btrees).length;
175
+ await exec('CREATE INDEX idx_a ON t (a)');
176
+ const withIndex = Object.keys(db.t._indexes).length + Object.keys(db.t._btrees).length;
177
+ await exec('ALTER TABLE t DROP KEY idx_a');
178
+ const after = Object.keys(db.t._indexes).length + Object.keys(db.t._btrees).length;
179
+ return withIndex > baseline && after === baseline;
180
+ });
181
+
182
+ await t('删索引不影响数据', async ({ exec, rows }) => {
183
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
184
+ await exec("INSERT INTO t VALUES (1,'x')");
185
+ await exec('CREATE INDEX idx_a ON t (a)');
186
+ await exec('ALTER TABLE t DROP INDEX idx_a');
187
+ return eq(await rows('SELECT id, a FROM t'), [[1, 'x']]);
188
+ });
189
+
190
+ await t('删不存在的索引 → 报错', async ({ exec, mustFail }) => {
191
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
192
+ return mustFail('ALTER TABLE t DROP INDEX nope');
193
+ });
194
+
195
+ console.log('\n--- 明确报错而不是静默无操作 ---');
196
+ await t('ADD FOREIGN KEY → 明确报不支持', async ({ exec }) => {
197
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
198
+ try {
199
+ await exec('ALTER TABLE t ADD CONSTRAINT fk FOREIGN KEY (a) REFERENCES p(id)');
200
+ return false;
201
+ } catch (e) { return /not supported/i.test(e.message) && /foreign key/i.test(e.message); }
202
+ });
203
+
204
+ await t('DROP FOREIGN KEY → 明确报不支持', async ({ exec }) => {
205
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
206
+ try {
207
+ await exec('ALTER TABLE t DROP FOREIGN KEY fk');
208
+ return false;
209
+ } catch (e) { return /not supported/i.test(e.message) && /foreign key/i.test(e.message); }
210
+ });
211
+
212
+ await t('RENAME INDEX → 明确报不支持并给出替代做法', async ({ exec }) => {
213
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
214
+ try {
215
+ await exec('ALTER TABLE t RENAME INDEX i TO j');
216
+ return false;
217
+ } catch (e) { return /not supported/i.test(e.message) && /DROP INDEX/i.test(e.message); }
218
+ });
219
+
220
+ console.log('\n--- 多操作与组合 ---');
221
+ await t('一条语句里多个操作按序执行', async ({ db, exec, rows }) => {
222
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
223
+ await exec("INSERT INTO t VALUES (1,'x')");
224
+ await exec('ALTER TABLE t ADD COLUMN b TEXT, RENAME COLUMN a TO a2');
225
+ const cols = (await exec('SELECT * FROM t')).columns;
226
+ const r = await rows('SELECT id, a2, b FROM t');
227
+ return eq(r, [[1, 'x', null]]) && cols.includes('a2') && cols.includes('b');
228
+ });
229
+
230
+ await t('改名后的表可以继续 DDL / DML', async ({ db, exec, rows }) => {
231
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, v TEXT)');
232
+ await exec('ALTER TABLE t RENAME TO t2');
233
+ await exec('ALTER TABLE t2 RENAME COLUMN v TO val');
234
+ await exec("INSERT INTO t2 VALUES (1,'a')");
235
+ await exec('CREATE INDEX ix ON t2 (val)');
236
+ await exec('DELETE FROM t2 WHERE val = \'zzz\'');
237
+ return eq(await rows('SELECT id, val FROM t2'), [[1, 'a']]);
238
+ });
239
+
240
+ console.log('\n--- 持久化 ---');
241
+ {
242
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsql-alter-'));
243
+ const file = path.join(dir, 'db.jsql');
244
+ try {
245
+ const db = new Database(file, { wal: true, autoSave: false });
246
+ const exec = (sql) => executeSQL(db, sql, OPTS);
247
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
248
+ await exec("INSERT INTO t VALUES (1,'x')");
249
+ await exec('ALTER TABLE t RENAME TO t2');
250
+ await exec('ALTER TABLE t2 RENAME COLUMN a TO name');
251
+ await db.save();
252
+
253
+ const db2 = new Database(file, { wal: true });
254
+ const exec2 = (sql) => executeSQL(db2, sql, OPTS);
255
+ const tables = (await db2.getTables()).slice().sort();
256
+ const cols = (await exec2('SELECT * FROM t2')).columns;
257
+ const r = (await exec2('SELECT id, name FROM t2')).rows;
258
+ ok('重开后表名与列名都保住', eq(tables, ['t2']) && eq(cols, ['id', 'name']) && eq(r, [[1, 'x']]),
259
+ { tables, cols, r });
260
+ } catch (e) {
261
+ ok('重开后表名与列名都保住', false, e.message);
262
+ } finally {
263
+ fs.rmSync(dir, { recursive: true, force: true });
264
+ }
265
+ }
266
+
267
+ console.log('\n--- 目录模式(改名的物理文件也要跟着挪)---');
268
+ {
269
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsql-alterdir-'));
270
+ try {
271
+ const db = new Database(dir, { mode: 'disk', autoSave: false });
272
+ const exec = (sql) => executeSQL(db, sql, OPTS);
273
+ await exec('CREATE TABLE t (id INT PRIMARY KEY, a TEXT)');
274
+ await exec("INSERT INTO t VALUES (1,'x')");
275
+ await exec('ALTER TABLE t RENAME TO t2');
276
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsql'));
277
+ const tables = (await db.getTables()).slice().sort();
278
+ ok('目录模式改名后表列表正确', eq(tables, ['t2']), tables);
279
+ ok('物理文件名跟着改了', files.some(f => f.includes('t2')) && !files.some(f => /(^|[^2])t\.jsql$/.test(f)), files);
280
+ } catch (e) {
281
+ ok('目录模式改名', false, e.message);
282
+ } finally {
283
+ fs.rmSync(dir, { recursive: true, force: true });
284
+ }
285
+ }
286
+
287
+ console.log(`\n${pass} passed, ${fail} failed`);
288
+ process.exit(fail === 0 ? 0 : 1);
289
+ })().catch(e => { console.error('💥', e); process.exit(1); });
@@ -13,7 +13,7 @@
13
13
  const path = require('path');
14
14
 
15
15
  const ROOT = path.join(__dirname, '..');
16
- const { Database } = require(path.join(ROOT, 'index.js'));
16
+ const { Database, JSQL, NativeJSQL } = require(path.join(ROOT, 'index.js'));
17
17
  const { executeSQL } = require(path.join(ROOT, 'lib/sql.js'));
18
18
 
19
19
  const OPTS = { safety: false };
@@ -130,6 +130,46 @@ async function t(name, fn) {
130
130
  return eq(await rows(`SELECT id, v FROM t ORDER BY id`), [[1, 99], [2, 20]]);
131
131
  });
132
132
 
133
+ // 三个 engine 都要拦住 —— 修复曾只加在 lib/database.js,
134
+ // 而 JSQL(wasm)走 lib/wasm_client.js、NativeJSQL 走 lib/native_client.js,
135
+ // 各自的 updateById 是独立实现。
136
+ console.log('\n--- UPDATE 冲突校验:三个 engine 都要拦住 ---');
137
+ const engines = [
138
+ ['JSQL(wasm)', () => new JSQL({ mode: 'memory' })],
139
+ ['NativeJSQL', () => new NativeJSQL({ mode: 'memory' })],
140
+ ['Database', () => new Database(':memory:')],
141
+ ];
142
+ for (const [name, mk] of engines) {
143
+ await t(`${name} UPDATE 主键冲突 → 报错且数据完好`, async () => {
144
+ const db = await mk();
145
+ if (db.start) await db.start();
146
+ try {
147
+ const exec = (sql) => executeSQL(db, sql, OPTS);
148
+ await exec(`CREATE TABLE e (id INT PRIMARY KEY, v INT)`);
149
+ await exec(`INSERT INTO e VALUES (1,10),(2,20),(3,30)`);
150
+ let threw = false;
151
+ try { await exec(`UPDATE e SET id = 1 WHERE id = 2`); } catch (_) { threw = true; }
152
+ const after = (await exec(`SELECT id, v FROM e ORDER BY id`)).rows;
153
+ // 改不冲突的值必须仍然可用
154
+ await exec(`UPDATE e SET id = 9 WHERE id = 2`);
155
+ const ok = (await exec(`SELECT id FROM e ORDER BY id`)).rows;
156
+ return threw && eq(after, [[1, 10], [2, 20], [3, 30]]) && eq(ok, [[1], [3], [9]]);
157
+ } finally { try { await db.stop(); } catch (_) { /* ignore */ } }
158
+ });
159
+ await t(`${name} UPDATE UNIQUE 冲突 → 报错`, async () => {
160
+ const db = await mk();
161
+ if (db.start) await db.start();
162
+ try {
163
+ const exec = (sql) => executeSQL(db, sql, OPTS);
164
+ await exec(`CREATE TABLE q (id INT PRIMARY KEY, e TEXT UNIQUE)`);
165
+ await exec(`INSERT INTO q VALUES (1,'a'),(2,'b')`);
166
+ let threw = false;
167
+ try { await exec(`UPDATE q SET e = 'a' WHERE id = 2`); } catch (_) { threw = true; }
168
+ return threw && eq((await exec(`SELECT e FROM q ORDER BY id`)).rows, [['a'], ['b']]);
169
+ } finally { try { await db.stop(); } catch (_) { /* ignore */ } }
170
+ });
171
+ }
172
+
133
173
  console.log('\n--- 既有约束行为回归对照 ---');
134
174
  await t('INSERT 主键重复 → 报错', async ({ exec, mustFail }) => {
135
175
  await exec(`CREATE TABLE t (id INT PRIMARY KEY)`);
@@ -0,0 +1,140 @@
1
+ /*
2
+ * 非保留关键字当标识符测试。
3
+ *
4
+ * 问题:词法层的 KEYWORDS 表把一些「MySQL 里其实是非保留」的词也标成了 keyword,
5
+ * 而 parseTableName / parseColumnRef 只认 ident,于是这些最普通的写法被拒:
6
+ * CREATE TABLE t (date DATE, text TEXT, count INT)
7
+ * SELECT date, count FROM t
8
+ * 实测 64 个常见列名里有 31 个被拒,其中 date / text / count / engine /
9
+ * first / after / unsigned / zerofill / spatial / ignore / start
10
+ * 在 MySQL 里都不是保留字 —— 用户没有理由必须加反引号。
11
+ *
12
+ * 修法:不让词法层去区分(parseColumnDef 还依赖 type === 'keyword' 来认类型),
13
+ * 改在「期望一个名字」的位置放行 NON_RESERVED_KEYWORDS 里的词。
14
+ * 这与文件里 OVER / PARTITION 那条注释是同一个思路。
15
+ *
16
+ * 真正的保留字(TABLE / SELECT / KEY / ORDER / INDEX / GROUP / CASE ...)
17
+ * 仍然要求反引号 —— 这些用例用来防止放行范围被不小心扩大。
18
+ */
19
+ const path = require('path');
20
+
21
+ const ROOT = path.join(__dirname, '..');
22
+ const { Database } = require(path.join(ROOT, 'index.js'));
23
+ const { executeSQL } = require(path.join(ROOT, 'lib/sql.js'));
24
+
25
+ const OPTS = { safety: false };
26
+ let pass = 0, fail = 0;
27
+
28
+ function ok(name, cond, extra) {
29
+ if (cond) { pass++; console.log('[OK] ', name); }
30
+ else { fail++; console.log('[FAIL]', name, extra !== undefined ? '-> ' + JSON.stringify(extra).slice(0, 140) : ''); }
31
+ }
32
+
33
+ (async () => {
34
+ const db = new Database(':memory:');
35
+ const q = (sql) => executeSQL(db, sql, OPTS);
36
+ const rows = async (sql) => (await q(sql)).rows;
37
+ const okSql = async (sql) => { try { await q(sql); return true; } catch (_) { return false; } };
38
+
39
+ console.log('\n--- 非保留关键字可以当列名 ---');
40
+ const asColumn = ['first', 'after', 'date', 'time', 'datetime', 'timestamp', 'text',
41
+ 'engine', 'ignore', 'spatial', 'unsigned', 'zerofill', 'start', 'count',
42
+ 'number', 'value', 'type', 'state', 'level', 'source', 'data', 'total'];
43
+ for (const n of asColumn) {
44
+ ok(`列名 ${n}`, await okSql(`CREATE TABLE c_${n} (id INT PRIMARY KEY, ${n} TEXT)`));
45
+ }
46
+
47
+ console.log('\n--- 非保留关键字可以当表名 ---');
48
+ for (const n of ['date', 'text', 'count', 'engine', 'first']) {
49
+ ok(`表名 ${n}`, await okSql(`CREATE TABLE ${n} (id INT PRIMARY KEY)`));
50
+ }
51
+
52
+ console.log('\n--- 建表后能正常读写 ---');
53
+ {
54
+ await q('CREATE TABLE events (id INT PRIMARY KEY, date TEXT, text TEXT, count INT, engine TEXT)');
55
+ await q("INSERT INTO events VALUES (1, '2026-01-01', 'hello', 3, 'innodb')");
56
+ const r = await rows('SELECT id, date, text, count, engine FROM events');
57
+ ok('按这些列名查询正常', JSON.stringify(r) === '[[1,"2026-01-01","hello",3,"innodb"]]', r);
58
+
59
+ const w = await rows("SELECT id FROM events WHERE date = '2026-01-01' AND count = 3");
60
+ ok('按这些列名过滤正常', JSON.stringify(w) === '[[1]]', w);
61
+
62
+ await q('UPDATE events SET count = 4 WHERE id = 1');
63
+ ok('按这些列名更新正常', (await rows('SELECT count FROM events'))[0][0] === 4);
64
+
65
+ await q('CREATE INDEX idx_date ON events (date)');
66
+ ok('按这些列名建索引正常', (await rows("SELECT id FROM events WHERE date = '2026-01-01'"))[0][0] === 1);
67
+ }
68
+
69
+ console.log('\n--- ALTER TABLE 上也适用 ---');
70
+ {
71
+ await q('CREATE TABLE a1 (id INT PRIMARY KEY)');
72
+ await q('ALTER TABLE a1 ADD COLUMN date TEXT');
73
+ await q('ALTER TABLE a1 ADD COLUMN count INT');
74
+ ok('ADD COLUMN 用非保留关键字', (await q('SELECT * FROM a1')).columns.join(',') === 'id,date,count');
75
+
76
+ await q('ALTER TABLE a1 RENAME COLUMN date TO time');
77
+ ok('RENAME COLUMN 用非保留关键字', (await q('SELECT * FROM a1')).columns.join(',') === 'id,time,count');
78
+
79
+ await q('ALTER TABLE a1 DROP COLUMN time');
80
+ ok('DROP COLUMN 用非保留关键字', (await q('SELECT * FROM a1')).columns.join(',') === 'id,count');
81
+ }
82
+
83
+ console.log('\n--- 主键 / 唯一约束里也能用 ---');
84
+ {
85
+ await q('CREATE TABLE k1 (date TEXT PRIMARY KEY, count INT UNIQUE)');
86
+ await q("INSERT INTO k1 VALUES ('a', 1)");
87
+ let threw = false;
88
+ try { await q("INSERT INTO k1 VALUES ('a', 2)"); } catch (_) { threw = true; }
89
+ ok('非保留关键字作主键仍受约束', threw);
90
+ }
91
+
92
+ console.log('\n--- ORDER BY / GROUP BY / 别名 不受影响 ---');
93
+ {
94
+ await q('CREATE TABLE s1 (id INT PRIMARY KEY, count INT, date TEXT)');
95
+ await q("INSERT INTO s1 VALUES (1,1,'a'),(2,2,'b'),(3,1,'c')");
96
+ const ob = await rows('SELECT count FROM s1 ORDER BY count DESC, id');
97
+ ok('ORDER BY 用这些列名', JSON.stringify(ob) === '[[2],[1],[1]]', ob);
98
+ const gb = await rows('SELECT count, COUNT(*) AS n FROM s1 GROUP BY count ORDER BY count');
99
+ ok('GROUP BY 用这些列名', JSON.stringify(gb) === '[[1,2],[2,1]]', gb);
100
+ const al = await rows('SELECT count AS total FROM s1 WHERE id = 2');
101
+ ok('AS 别名不受影响', JSON.stringify(al) === '[[2]]', al);
102
+ }
103
+
104
+ console.log('\n--- 函数名仍然优先当函数解析 ---');
105
+ {
106
+ const one = async (sql) => (await q(sql)).rows[0][0];
107
+ ok('COUNT(*) 仍是聚合函数', await one('SELECT COUNT(*) FROM (SELECT 1 AS x) t') === 1);
108
+ ok('MAX() 仍是函数', await one('SELECT MAX(x) FROM (SELECT 1 AS x UNION ALL SELECT 2) t') === 2);
109
+ ok('MIN() 仍是函数', await one('SELECT MIN(x) FROM (SELECT 1 AS x UNION ALL SELECT 2) t') === 1);
110
+ ok('SUM() 仍是函数', await one('SELECT SUM(x) FROM (SELECT 3 AS x) t') === 3);
111
+ ok('DATE() 仍是函数', typeof await one("SELECT DATE('2026-01-01')") === 'string');
112
+ ok('COUNT() 紧跟括号才是函数', await one('SELECT COUNT(*) FROM (SELECT 1 AS x) t') === 1);
113
+ ok('裸 count 当列时不吞掉后面的 FROM', await okSql('SELECT count FROM (SELECT 1 AS count) t'));
114
+ }
115
+
116
+ console.log('\n--- 真保留字仍要求反引号(防止放行范围被扩大)---');
117
+ const reserved = ['key', 'order', 'group', 'index', 'column', 'table',
118
+ 'case', 'when', 'then', 'else', 'end', 'select', 'from', 'where'];
119
+ for (const n of reserved) {
120
+ ok(`裸写 ${n} 作列名仍被拒`, !(await okSql(`CREATE TABLE r_${n} (${n} TEXT)`)));
121
+ }
122
+ ok('反引号包裹后可用', await okSql('CREATE TABLE r_ok (`key` TEXT, `order` INT)'));
123
+ {
124
+ await q('CREATE TABLE r_ok2 (`key` TEXT, `order` INT)');
125
+ await q("INSERT INTO r_ok2 VALUES ('k', 1)");
126
+ const r = await rows('SELECT `key`, `order` FROM r_ok2');
127
+ ok('反引号包裹后可读写', JSON.stringify(r) === '[["k",1]]', r);
128
+ }
129
+
130
+ console.log('\n--- 带点号的限定名 ---');
131
+ {
132
+ await q('CREATE TABLE q1 (date TEXT)');
133
+ await q("INSERT INTO q1 VALUES ('x')");
134
+ const r = await rows('SELECT q1.date FROM q1');
135
+ ok('表名.非保留关键字列名', JSON.stringify(r) === '[["x"]]', r);
136
+ }
137
+
138
+ console.log(`\n${pass} passed, ${fail} failed`);
139
+ process.exit(fail === 0 ? 0 : 1);
140
+ })().catch(e => { console.error('💥', e); process.exit(1); });