jsql-neo 4.4.2 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +35 -0
- package/lib/mysql_compat.js +95 -32
- package/lib/sql.js +359 -20
- package/package.json +5 -2
package/index.js
CHANGED
|
@@ -23,6 +23,39 @@ const migrate = require('./lib/migrate');
|
|
|
23
23
|
const { WebUI } = require('./lib/web_ui');
|
|
24
24
|
const { RedisServer, createRedisServer } = require('./lib/redis_server');
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* 全局注入:把项目内 `require('mysql2')` 全部替换为 jsql-neo 内存引擎兼容层。
|
|
28
|
+
* 调用一次后,TypeORM / Drizzle / MikroORM / Kysely 等直接依赖 mysql2 的库
|
|
29
|
+
* 无需改代码即可享受本地内存速度。
|
|
30
|
+
*/
|
|
31
|
+
function enableMySQLCompat() {
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
const bases = new Set();
|
|
36
|
+
if (require.main && Array.isArray(require.main.paths)) {
|
|
37
|
+
for (const p of require.main.paths) bases.add(p);
|
|
38
|
+
}
|
|
39
|
+
if (process.env.NODE_PATH) {
|
|
40
|
+
for (const p of process.env.NODE_PATH.split(path.delimiter)) if (p) bases.add(p);
|
|
41
|
+
}
|
|
42
|
+
bases.add(path.join(process.cwd(), 'node_modules'));
|
|
43
|
+
const inject = (p, mod) => {
|
|
44
|
+
try {
|
|
45
|
+
const resolved = p;
|
|
46
|
+
if (seen.has(resolved)) return;
|
|
47
|
+
if (!fs.existsSync(resolved)) return;
|
|
48
|
+
seen.add(resolved);
|
|
49
|
+
require.cache[resolved] = { exports: mod, id: resolved, filename: resolved, loaded: true, children: [] };
|
|
50
|
+
} catch (e) { /* ignore */ }
|
|
51
|
+
};
|
|
52
|
+
for (const base of bases) {
|
|
53
|
+
inject(path.join(base, 'mysql2', 'index.js'), mysqlCompat);
|
|
54
|
+
inject(path.join(base, 'mysql2', 'promise.js'), mysqlCompat);
|
|
55
|
+
}
|
|
56
|
+
return mysqlCompat;
|
|
57
|
+
}
|
|
58
|
+
|
|
26
59
|
module.exports = {
|
|
27
60
|
JSQL: WasmClient.JSQL,
|
|
28
61
|
NativeJSQL: NativeClient.JSQL,
|
|
@@ -45,6 +78,8 @@ module.exports = {
|
|
|
45
78
|
createConnection: mysqlCompat.createConnection,
|
|
46
79
|
createPool: mysqlCompat.createPool,
|
|
47
80
|
mysql: mysqlCompat,
|
|
81
|
+
mysql2: mysqlCompat,
|
|
82
|
+
enableMySQLCompat,
|
|
48
83
|
createMysqlServer,
|
|
49
84
|
MysqlServer,
|
|
50
85
|
// 迁移工具: mysqldump 导入 / JSON / CSV
|
package/lib/mysql_compat.js
CHANGED
|
@@ -6,19 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
const { executeSQL, applyParams, escapeValue, escapeId } = require('./sql');
|
|
8
8
|
const Database = require('./database');
|
|
9
|
+
const EventEmitter = require('events');
|
|
9
10
|
|
|
10
11
|
function isQueryResult(r) {
|
|
11
12
|
return !!r && typeof r === 'object' && Array.isArray(r.rows) && Array.isArray(r.columns);
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
function toResultPacket(r, table) {
|
|
15
|
+
function toResultPacket(r, table, rowsAsArray) {
|
|
15
16
|
if (isQueryResult(r)) {
|
|
16
17
|
const columns = r.columns || [];
|
|
17
|
-
const
|
|
18
|
-
const obj = {};
|
|
19
|
-
columns.forEach((c, j) => { obj[c] = vals[j]; });
|
|
20
|
-
return obj;
|
|
21
|
-
});
|
|
18
|
+
const rawRows = r.rows || [];
|
|
22
19
|
const fields = columns.map(name => ({
|
|
23
20
|
name,
|
|
24
21
|
table: table || '',
|
|
@@ -27,6 +24,13 @@ function toResultPacket(r, table) {
|
|
|
27
24
|
flags: 0,
|
|
28
25
|
charsetNr: 45,
|
|
29
26
|
}));
|
|
27
|
+
const rows = rowsAsArray
|
|
28
|
+
? rawRows
|
|
29
|
+
: rawRows.map(vals => {
|
|
30
|
+
const obj = {};
|
|
31
|
+
columns.forEach((c, j) => { obj[c] = vals[j]; });
|
|
32
|
+
return obj;
|
|
33
|
+
});
|
|
30
34
|
return { rows, fields };
|
|
31
35
|
}
|
|
32
36
|
return {
|
|
@@ -41,8 +45,9 @@ function toResultPacket(r, table) {
|
|
|
41
45
|
};
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
class Connection {
|
|
48
|
+
class Connection extends EventEmitter {
|
|
45
49
|
constructor(options = {}) {
|
|
50
|
+
super();
|
|
46
51
|
this.config = {
|
|
47
52
|
host: options.host || 'localhost',
|
|
48
53
|
port: options.port || 3306,
|
|
@@ -56,20 +61,38 @@ class Connection {
|
|
|
56
61
|
this.database = options.database && typeof options.database === 'object' && !options.filename && typeof options.database.start === 'function'
|
|
57
62
|
? options.database
|
|
58
63
|
: (options.engine || null);
|
|
59
|
-
this.filename = options.filename ||
|
|
64
|
+
this.filename = options.filename || null;
|
|
60
65
|
this.engine = null;
|
|
61
66
|
this._ownEngine = false;
|
|
62
67
|
this.state = 'disconnected';
|
|
68
|
+
this.stream = {
|
|
69
|
+
destroyed: false,
|
|
70
|
+
writable: true,
|
|
71
|
+
destroy: () => { this.stream.destroyed = true; },
|
|
72
|
+
};
|
|
73
|
+
if (options.host !== undefined || options.database || options.engine || options.filename) {
|
|
74
|
+
queueMicrotask(() => {
|
|
75
|
+
this._getEngine().then(() => this.emit('connect'), err => {
|
|
76
|
+
if (this.listenerCount('error') > 0) this.emit('error', err);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
63
80
|
}
|
|
64
81
|
|
|
65
82
|
async _getEngine() {
|
|
66
83
|
if (this.engine) return this.engine;
|
|
67
|
-
if (this.database) {
|
|
84
|
+
if (this.database && typeof this.database.start === 'function') {
|
|
68
85
|
this.engine = this.database;
|
|
69
|
-
|
|
86
|
+
await this.engine.start();
|
|
87
|
+
} else if (this._pool) {
|
|
88
|
+
this.engine = await this._pool._sharedEngineFor(this.config.database || 'default');
|
|
89
|
+
} else if (this.filename) {
|
|
90
|
+
this.engine = new Database(this.filename);
|
|
91
|
+
await this.engine.start();
|
|
92
|
+
this._ownEngine = true;
|
|
70
93
|
} else {
|
|
71
|
-
this.engine = new Database(
|
|
72
|
-
|
|
94
|
+
this.engine = new Database(':memory:');
|
|
95
|
+
await this.engine.start();
|
|
73
96
|
this._ownEngine = true;
|
|
74
97
|
}
|
|
75
98
|
this.state = 'connected';
|
|
@@ -77,17 +100,21 @@ class Connection {
|
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
connect(cb) {
|
|
80
|
-
const p = this._getEngine().then(
|
|
103
|
+
const p = this._getEngine().then(c => {
|
|
104
|
+
this.emit('connect');
|
|
105
|
+
return c;
|
|
106
|
+
});
|
|
81
107
|
if (cb) p.then(c => cb(null, c), err => cb(err));
|
|
82
108
|
return p;
|
|
83
109
|
}
|
|
84
110
|
|
|
85
|
-
|
|
86
|
-
let sql, values, cb;
|
|
111
|
+
query(...args) {
|
|
112
|
+
let sql, values, cb, rowsAsArray;
|
|
87
113
|
if (typeof args[0] === 'object' && args[0] !== null && typeof args[0].sql === 'string') {
|
|
88
114
|
sql = args[0].sql;
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
rowsAsArray = args[0].rowsAsArray === true;
|
|
116
|
+
values = args[0].values !== undefined ? args[0].values : (Array.isArray(args[1]) ? args[1] : null);
|
|
117
|
+
cb = typeof args[1] === 'function' ? args[1] : (typeof args[2] === 'function' ? args[2] : null);
|
|
91
118
|
} else {
|
|
92
119
|
sql = args[0];
|
|
93
120
|
values = Array.isArray(args[1]) ? args[1] : null;
|
|
@@ -103,7 +130,7 @@ class Connection {
|
|
|
103
130
|
});
|
|
104
131
|
const results = Array.isArray(r) ? r : [r];
|
|
105
132
|
const last = results[results.length - 1];
|
|
106
|
-
const packet = toResultPacket(last, last ? last.table : null);
|
|
133
|
+
const packet = toResultPacket(last, last ? last.table : null, rowsAsArray);
|
|
107
134
|
if (isQueryResult(last)) {
|
|
108
135
|
const arr = [packet.rows, packet.fields];
|
|
109
136
|
arr.rows = packet.rows;
|
|
@@ -115,17 +142,34 @@ class Connection {
|
|
|
115
142
|
arr.fields = null;
|
|
116
143
|
return arr;
|
|
117
144
|
})();
|
|
145
|
+
const q = new EventEmitter();
|
|
146
|
+
q.setMaxListeners = EventEmitter.prototype.setMaxListeners;
|
|
147
|
+
p.then(res => {
|
|
148
|
+
q.emit('result', res.rows || res[0]);
|
|
149
|
+
q.emit('fields', res.fields || []);
|
|
150
|
+
}, err => {
|
|
151
|
+
if (q.listenerCount('error') > 0) q.emit('error', err);
|
|
152
|
+
});
|
|
118
153
|
if (cb) {
|
|
119
154
|
p.then(res => {
|
|
120
155
|
if (res && res.rows && res.fields) cb(null, res.rows, res.fields);
|
|
121
156
|
else if (res && res[0] && res[0].insertId !== undefined) cb(null, res[0]);
|
|
122
157
|
else cb(null, res[0]);
|
|
123
158
|
}, err => cb(err));
|
|
124
|
-
return
|
|
159
|
+
return q;
|
|
125
160
|
}
|
|
161
|
+
p.setMaxListeners = q.setMaxListeners;
|
|
126
162
|
return p;
|
|
127
163
|
}
|
|
128
164
|
|
|
165
|
+
execute(sql, values, cb) {
|
|
166
|
+
if (typeof values === 'function') {
|
|
167
|
+
cb = values;
|
|
168
|
+
values = undefined;
|
|
169
|
+
}
|
|
170
|
+
return this.query(sql, values, cb);
|
|
171
|
+
}
|
|
172
|
+
|
|
129
173
|
beginTransaction(cb) {
|
|
130
174
|
const p = this.query('BEGIN');
|
|
131
175
|
if (cb) p.then(() => cb(null), err => cb(err));
|
|
@@ -156,6 +200,8 @@ class Connection {
|
|
|
156
200
|
|
|
157
201
|
destroy() {
|
|
158
202
|
this.state = 'destroyed';
|
|
203
|
+
this.stream.destroy();
|
|
204
|
+
this.emit('close');
|
|
159
205
|
}
|
|
160
206
|
|
|
161
207
|
end(cb) {
|
|
@@ -164,6 +210,8 @@ class Connection {
|
|
|
164
210
|
await this.engine.stop();
|
|
165
211
|
}
|
|
166
212
|
this.state = 'closed';
|
|
213
|
+
this.stream.destroy();
|
|
214
|
+
this.emit('close');
|
|
167
215
|
return undefined;
|
|
168
216
|
})();
|
|
169
217
|
if (cb) p.then(() => cb(null), err => cb(err));
|
|
@@ -184,17 +232,25 @@ class Pool {
|
|
|
184
232
|
this._connections = [];
|
|
185
233
|
this._waiters = [];
|
|
186
234
|
this._closed = false;
|
|
187
|
-
this.
|
|
235
|
+
this._sharedEngines = new Map();
|
|
188
236
|
this._reaper = setInterval(() => this._reapIdle(), Math.max(1000, Math.floor(this.idleTimeout / 10) || 10000));
|
|
189
237
|
this._reaper.unref();
|
|
190
238
|
}
|
|
191
239
|
|
|
240
|
+
async _sharedEngineFor(database) {
|
|
241
|
+
const key = database || 'default';
|
|
242
|
+
let engine = this._sharedEngines.get(key);
|
|
243
|
+
if (!engine) {
|
|
244
|
+
engine = new Database(':memory:');
|
|
245
|
+
if (typeof engine.start === 'function') await engine.start();
|
|
246
|
+
this._sharedEngines.set(key, engine);
|
|
247
|
+
}
|
|
248
|
+
return engine;
|
|
249
|
+
}
|
|
250
|
+
|
|
192
251
|
_createConnection() {
|
|
193
252
|
const conn = new Connection(this.config);
|
|
194
253
|
conn._pool = this;
|
|
195
|
-
if (!conn.database && !conn.engine && !conn.filename) {
|
|
196
|
-
conn.database = this._sharedEngine;
|
|
197
|
-
}
|
|
198
254
|
const origDestroy = conn.destroy.bind(conn);
|
|
199
255
|
conn.destroy = () => {
|
|
200
256
|
this._remove(conn);
|
|
@@ -203,14 +259,6 @@ class Pool {
|
|
|
203
259
|
return conn;
|
|
204
260
|
}
|
|
205
261
|
|
|
206
|
-
async _ensureSharedEngine() {
|
|
207
|
-
if (!this._sharedEngine) {
|
|
208
|
-
this._sharedEngine = new Database(':memory:');
|
|
209
|
-
if (typeof this._sharedEngine.start === 'function') await this._sharedEngine.start();
|
|
210
|
-
}
|
|
211
|
-
return this._sharedEngine;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
262
|
_remove(conn) {
|
|
215
263
|
const idx = this._connections.indexOf(conn);
|
|
216
264
|
if (idx !== -1) this._connections.splice(idx, 1);
|
|
@@ -239,7 +287,7 @@ class Pool {
|
|
|
239
287
|
return Promise.resolve(free);
|
|
240
288
|
}
|
|
241
289
|
if (this._connections.length < this.connectionLimit) {
|
|
242
|
-
return
|
|
290
|
+
return Promise.resolve().then(() => {
|
|
243
291
|
if (this._connections.length >= this.connectionLimit) {
|
|
244
292
|
return this._waitForConnection();
|
|
245
293
|
}
|
|
@@ -321,6 +369,9 @@ class Pool {
|
|
|
321
369
|
for (const w of this._waiters.splice(0)) w.reject(err);
|
|
322
370
|
const conns = this._connections.splice(0);
|
|
323
371
|
await Promise.allSettled(conns.map(c => c.end()));
|
|
372
|
+
const engines = Array.from(this._sharedEngines.values());
|
|
373
|
+
this._sharedEngines.clear();
|
|
374
|
+
await Promise.allSettled(engines.map(e => (typeof e.stop === 'function' ? e.stop() : null)));
|
|
324
375
|
return undefined;
|
|
325
376
|
})();
|
|
326
377
|
if (cb) p.then(() => cb(null), err => cb(err));
|
|
@@ -340,9 +391,21 @@ function createPool(options) {
|
|
|
340
391
|
return new Pool(options || {});
|
|
341
392
|
}
|
|
342
393
|
|
|
394
|
+
class RowDataPacket {}
|
|
395
|
+
class OkPacket {}
|
|
396
|
+
class ResultSetHeader {}
|
|
397
|
+
class FieldPacket {}
|
|
398
|
+
|
|
343
399
|
module.exports = {
|
|
344
400
|
createConnection,
|
|
345
401
|
createPool,
|
|
402
|
+
createConnectionPromise: () => Promise.resolve(createConnection()),
|
|
403
|
+
Connection,
|
|
404
|
+
Pool,
|
|
405
|
+
RowDataPacket,
|
|
406
|
+
OkPacket,
|
|
407
|
+
ResultSetHeader,
|
|
408
|
+
FieldPacket,
|
|
346
409
|
escape: escapeValue,
|
|
347
410
|
escapeId,
|
|
348
411
|
format: applyParams,
|
package/lib/sql.js
CHANGED
|
@@ -53,7 +53,9 @@ const KEYWORDS = new Set([
|
|
|
53
53
|
'DATABASES', 'DATABASE', 'DESCRIBE', 'DESC', 'ON', 'DUPLICATE',
|
|
54
54
|
'JOIN', 'LEFT', 'RIGHT', 'INNER', 'OUTER', 'CROSS',
|
|
55
55
|
'GROUP', 'HAVING', 'UNION', 'ALL', 'CASE', 'WHEN', 'THEN', 'ELSE', 'END',
|
|
56
|
-
'BETWEEN', 'USING', 'FULL', 'UNSIGNED', 'ZEROFILL', 'TRUNCATE', 'COLLATE', 'CHARACTER'
|
|
56
|
+
'BETWEEN', 'USING', 'FULL', 'UNSIGNED', 'ZEROFILL', 'TRUNCATE', 'COLLATE', 'CHARACTER',
|
|
57
|
+
'ALTER', 'ADD', 'COLUMN', 'MODIFY', 'CHANGE', 'INDEX', 'FOREIGN', 'REFERENCES',
|
|
58
|
+
'CONSTRAINT', 'RENAME', 'TO', 'AFTER', 'FIRST', 'ENGINE', 'AUTO_INCREMENT', 'SPATIAL'
|
|
57
59
|
]);
|
|
58
60
|
|
|
59
61
|
function tokenize(sql) {
|
|
@@ -229,6 +231,7 @@ class Parser {
|
|
|
229
231
|
if (this.isKeyword('DATABASE', 1)) return this.parseDropDatabase();
|
|
230
232
|
throw new Error('Unsupported DROP statement');
|
|
231
233
|
case 'INSERT': return this.parseInsert();
|
|
234
|
+
case 'ALTER': return this.parseAlter();
|
|
232
235
|
case 'TRUNCATE': this.expectKeyword('TRUNCATE'); if (this.isKeyword('TABLE')) this.next(); return { type: 'truncate', name: this.parseTableName() };
|
|
233
236
|
case 'SELECT': return this.parseSelect();
|
|
234
237
|
case 'UPDATE': return this.parseUpdate();
|
|
@@ -495,7 +498,7 @@ class Parser {
|
|
|
495
498
|
else if (!(this.peek().type === 'op' && this.peek().value === ')')) col = this.parseScalar();
|
|
496
499
|
this.expect('op', ')');
|
|
497
500
|
aggregate = { type: 'COUNT', column: col };
|
|
498
|
-
|
|
501
|
+
aggregate.alias = this.parseOptionalAlias();
|
|
499
502
|
columns.push({ expr: col, aggregate: 'COUNT', column: col, alias: aggregate.alias });
|
|
500
503
|
} else if (t.type === 'keyword' && ['SUM', 'AVG', 'MIN', 'MAX'].includes(t.value)) {
|
|
501
504
|
this.next();
|
|
@@ -504,7 +507,7 @@ class Parser {
|
|
|
504
507
|
const col = this.parseScalar();
|
|
505
508
|
this.expect('op', ')');
|
|
506
509
|
aggregate = { type: fn, column: col };
|
|
507
|
-
|
|
510
|
+
aggregate.alias = this.parseOptionalAlias();
|
|
508
511
|
columns.push({ expr: col, aggregate: fn, column: col, alias: aggregate.alias });
|
|
509
512
|
} else if (t.type === 'op' && t.value === '*') {
|
|
510
513
|
this.next();
|
|
@@ -512,14 +515,16 @@ class Parser {
|
|
|
512
515
|
} else if (t.type === 'keyword' && t.value === 'CASE') {
|
|
513
516
|
const caseExpr = this.parseOperand();
|
|
514
517
|
let alias = null;
|
|
515
|
-
|
|
518
|
+
alias = this.parseOptionalAlias();
|
|
516
519
|
columns.push({ expr: null, caseExpr, alias });
|
|
517
520
|
} else {
|
|
518
521
|
// 列 / 常量 / 函数 / 算术表达式
|
|
519
522
|
const expr = this.parseScalar();
|
|
520
523
|
let alias = null;
|
|
521
|
-
|
|
522
|
-
if (expr.type === '
|
|
524
|
+
alias = this.parseOptionalAlias();
|
|
525
|
+
if (expr.type === 'star') {
|
|
526
|
+
columns.push({ expr: '*' });
|
|
527
|
+
} else if (expr.type === 'aggregate') {
|
|
523
528
|
columns.push({ expr: expr.column, aggregate: expr.fn, column: expr.column, alias, scalar: expr });
|
|
524
529
|
} else if (expr.type === 'column') {
|
|
525
530
|
columns.push({ expr: expr.name, scalar: expr, alias });
|
|
@@ -641,6 +646,12 @@ class Parser {
|
|
|
641
646
|
return t.value;
|
|
642
647
|
}
|
|
643
648
|
|
|
649
|
+
parseOptionalAlias() {
|
|
650
|
+
if (this.isKeyword('AS')) { this.next(); return this.parseAlias(); }
|
|
651
|
+
if (this.peek().type === 'ident') return this.parseAlias();
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
|
|
644
655
|
parseColumnRef() {
|
|
645
656
|
const t = this.next();
|
|
646
657
|
if (t.type !== 'ident') throw new Error(`Expected column name, got '${t.value}'`);
|
|
@@ -718,6 +729,121 @@ class Parser {
|
|
|
718
729
|
return { type: 'dropDatabase', database: name, ifExists };
|
|
719
730
|
}
|
|
720
731
|
|
|
732
|
+
_skipAlterTail() {
|
|
733
|
+
while (!(this.peek().type === 'eof' || this.peek().value === ';')) {
|
|
734
|
+
if (this.peek().type === 'op' && this.peek().value === ',') return;
|
|
735
|
+
if (this.peek().type === 'keyword' && ['ADD', 'DROP', 'MODIFY', 'CHANGE', 'RENAME', 'ENGINE', 'CONVERT', 'DEFAULT'].includes(this.peek().value)) return;
|
|
736
|
+
this.next();
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
_skipFirstAfter() {
|
|
741
|
+
if (this.isKeyword('FIRST')) { this.next(); return; }
|
|
742
|
+
if (this.isKeyword('AFTER')) { this.next(); if (this.peek().type === 'ident') this.next(); }
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
_parseIndexColumns() {
|
|
746
|
+
this.expect('op', '(');
|
|
747
|
+
const columns = [];
|
|
748
|
+
for (;;) {
|
|
749
|
+
const col = this.parseTableName();
|
|
750
|
+
if (this.isKeyword('ASC') || this.isKeyword('DESC')) this.next();
|
|
751
|
+
columns.push(col);
|
|
752
|
+
if (this.peek().value === ',') { this.next(); continue; }
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
this.expect('op', ')');
|
|
756
|
+
return columns;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
parseAlter() {
|
|
760
|
+
this.expectKeyword('ALTER');
|
|
761
|
+
this.expectKeyword('TABLE');
|
|
762
|
+
const name = this.parseTableName();
|
|
763
|
+
const ops = [];
|
|
764
|
+
for (;;) {
|
|
765
|
+
const t = this.next();
|
|
766
|
+
if (t.type !== 'keyword') throw new Error(`Expected ALTER operation, got '${t.value}'`);
|
|
767
|
+
switch (t.value) {
|
|
768
|
+
case 'ADD': {
|
|
769
|
+
if (this.isKeyword('COLUMN')) this.next();
|
|
770
|
+
if (this.isKeyword('INDEX') || this.isKeyword('KEY') || this.isKeyword('UNIQUE') || this.isKeyword('FULLTEXT') || this.isKeyword('SPATIAL')) {
|
|
771
|
+
const unique = this.isKeyword('UNIQUE');
|
|
772
|
+
if (unique || this.isKeyword('FULLTEXT') || this.isKeyword('SPATIAL')) this.next();
|
|
773
|
+
if (this.isKeyword('INDEX') || this.isKeyword('KEY')) this.next();
|
|
774
|
+
let indexName = null;
|
|
775
|
+
if (this.peek().type === 'ident') indexName = this.parseTableName();
|
|
776
|
+
if (this.isKeyword('USING')) { this.next(); this.next(); }
|
|
777
|
+
const columns = this._parseIndexColumns();
|
|
778
|
+
ops.push({ op: 'addIndex', columns, unique, name: indexName });
|
|
779
|
+
} else if (this.isKeyword('PRIMARY')) {
|
|
780
|
+
this.expectKeyword('PRIMARY'); this.expectKeyword('KEY');
|
|
781
|
+
if (this.peek().type === 'op' && this.peek().value === '(') {
|
|
782
|
+
const columns = this._parseIndexColumns();
|
|
783
|
+
ops.push({ op: 'addPrimary', columns });
|
|
784
|
+
}
|
|
785
|
+
} else if (this.isKeyword('CONSTRAINT')) {
|
|
786
|
+
this.next();
|
|
787
|
+
if (this.peek().type === 'ident') this.next();
|
|
788
|
+
if (this.isKeyword('UNIQUE')) { this.next(); }
|
|
789
|
+
if (this.isKeyword('INDEX') || this.isKeyword('KEY')) { this.next(); if (this.peek().type === 'ident') this.next(); }
|
|
790
|
+
if (this.isKeyword('FOREIGN')) {
|
|
791
|
+
this.expectKeyword('FOREIGN'); this.expectKeyword('KEY');
|
|
792
|
+
const columns = this._parseIndexColumns();
|
|
793
|
+
this.expectKeyword('REFERENCES');
|
|
794
|
+
const refTable = this.parseTableName();
|
|
795
|
+
const refCols = this._parseIndexColumns();
|
|
796
|
+
this._skipAlterTail();
|
|
797
|
+
ops.push({ op: 'addForeign', columns, refTable, refCols });
|
|
798
|
+
} else {
|
|
799
|
+
const columns = this._parseIndexColumns();
|
|
800
|
+
ops.push({ op: 'addIndex', columns, unique: true });
|
|
801
|
+
}
|
|
802
|
+
} else {
|
|
803
|
+
const column = this.parseTableName();
|
|
804
|
+
const def = this.parseColumnDef();
|
|
805
|
+
this._skipFirstAfter();
|
|
806
|
+
ops.push({ op: 'addColumn', column, def });
|
|
807
|
+
}
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
case 'DROP': {
|
|
811
|
+
if (this.isKeyword('COLUMN')) this.next();
|
|
812
|
+
if (this.isKeyword('PRIMARY')) { this.expectKeyword('PRIMARY'); this.expectKeyword('KEY'); ops.push({ op: 'dropPrimary' }); break; }
|
|
813
|
+
if (this.isKeyword('FOREIGN')) { this.next(); this.expectKeyword('KEY'); if (this.peek().type === 'ident') this.next(); ops.push({ op: 'dropIndex' }); break; }
|
|
814
|
+
if (this.isKeyword('INDEX') || this.isKeyword('KEY')) { this.next(); if (this.peek().type === 'ident') this.next(); ops.push({ op: 'dropIndex' }); break; }
|
|
815
|
+
const column = this.parseTableName();
|
|
816
|
+
ops.push({ op: 'dropColumn', column });
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
case 'MODIFY':
|
|
820
|
+
case 'CHANGE': {
|
|
821
|
+
if (this.isKeyword('COLUMN')) this.next();
|
|
822
|
+
const column = this.parseTableName();
|
|
823
|
+
let newColumn = column;
|
|
824
|
+
if (t.value === 'CHANGE') newColumn = this.parseTableName();
|
|
825
|
+
const def = this.parseColumnDef();
|
|
826
|
+
this._skipFirstAfter();
|
|
827
|
+
ops.push({ op: t.value === 'CHANGE' ? 'changeColumn' : 'modifyColumn', column, newColumn, def });
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
case 'RENAME': {
|
|
831
|
+
if (this.isKeyword('TO')) this.next();
|
|
832
|
+
const newName = this.parseTableName();
|
|
833
|
+
ops.push({ op: 'rename', newName });
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
default:
|
|
837
|
+
this._skipAlterTail();
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
if (this.peek().type === 'op' && this.peek().value === ',') { this.next(); continue; }
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
this.optionalTailSemicolon();
|
|
844
|
+
return { type: 'alterTable', name, ops };
|
|
845
|
+
}
|
|
846
|
+
|
|
721
847
|
parseShow() {
|
|
722
848
|
this.expectKeyword('SHOW');
|
|
723
849
|
if (this.isKeyword('TABLES')) {
|
|
@@ -861,6 +987,7 @@ class Parser {
|
|
|
861
987
|
if (this.peek().type === 'op' && this.peek().value === '.') {
|
|
862
988
|
this.next();
|
|
863
989
|
const col = this.next();
|
|
990
|
+
if (col.type === 'op' && col.value === '*') return { type: 'star', alias: t.value };
|
|
864
991
|
if (col.type !== 'ident') throw new Error(`Expected column name after '.', got '${col.value}'`);
|
|
865
992
|
return { type: 'column', name: t.value + '.' + col.value };
|
|
866
993
|
}
|
|
@@ -1371,9 +1498,69 @@ class SQLExecutor {
|
|
|
1371
1498
|
if (this.engine.hasTable(statement.name)) await this.engine.truncate(statement.name);
|
|
1372
1499
|
return { ok: true, type: 'truncate', table: statement.name, affectedRows: 0 };
|
|
1373
1500
|
}
|
|
1501
|
+
case 'alterTable': {
|
|
1502
|
+
const table = this.engine._tables ? this.engine._tables[statement.name] : null;
|
|
1503
|
+
if (!table) throw new Error(`Table '${statement.name}' does not exist`);
|
|
1504
|
+
for (const op of statement.ops) {
|
|
1505
|
+
switch (op.op) {
|
|
1506
|
+
case 'addColumn': {
|
|
1507
|
+
table._schema[op.column] = op.def;
|
|
1508
|
+
for (const row of table._rows) if (!(op.column in row)) row[op.column] = null;
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
case 'dropColumn': {
|
|
1512
|
+
delete table._schema[op.column];
|
|
1513
|
+
for (const row of table._rows) delete row[op.column];
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1516
|
+
case 'changeColumn':
|
|
1517
|
+
case 'modifyColumn': {
|
|
1518
|
+
const def = { ...op.def };
|
|
1519
|
+
const prev = table._schema[op.column];
|
|
1520
|
+
if (prev && op.column === op.newColumn) {
|
|
1521
|
+
if (prev.autoIncrement && !def.autoIncrement) def.autoIncrement = true;
|
|
1522
|
+
if (prev.primaryKey && !def.primaryKey) { def.primaryKey = true; def.unique = true; }
|
|
1523
|
+
}
|
|
1524
|
+
table._schema[op.column] = def;
|
|
1525
|
+
if (op.newColumn !== op.column) {
|
|
1526
|
+
table._schema[op.newColumn] = def;
|
|
1527
|
+
delete table._schema[op.column];
|
|
1528
|
+
for (const row of table._rows) {
|
|
1529
|
+
if (op.column in row) { row[op.newColumn] = row[op.column]; delete row[op.column]; }
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
break;
|
|
1533
|
+
}
|
|
1534
|
+
case 'addPrimary': {
|
|
1535
|
+
for (const c of op.columns) { table._schema[c].primaryKey = true; table._schema[c].unique = true; }
|
|
1536
|
+
break;
|
|
1537
|
+
}
|
|
1538
|
+
case 'dropPrimary': {
|
|
1539
|
+
for (const def of Object.values(table._schema)) { if (def && typeof def === 'object') { def.primaryKey = false; } }
|
|
1540
|
+
break;
|
|
1541
|
+
}
|
|
1542
|
+
case 'addIndex': {
|
|
1543
|
+
if (!op.unique) break;
|
|
1544
|
+
for (const c of op.columns) table._schema[c].unique = true;
|
|
1545
|
+
break;
|
|
1546
|
+
}
|
|
1547
|
+
case 'addForeign':
|
|
1548
|
+
case 'dropIndex':
|
|
1549
|
+
case 'rename':
|
|
1550
|
+
default:
|
|
1551
|
+
break;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
await this._rebuildTableCache(table);
|
|
1555
|
+
await this.engine.flush();
|
|
1556
|
+
return { ok: true, type: 'alterTable', table: statement.name, affectedRows: 0 };
|
|
1557
|
+
}
|
|
1374
1558
|
case 'insert': {
|
|
1375
1559
|
let dataRows = statement.dataRows;
|
|
1376
|
-
let schema =
|
|
1560
|
+
let schema = this.engine.getTableSchema
|
|
1561
|
+
? await this.engine.getTableSchema(statement.name)
|
|
1562
|
+
: (this.engine._schemas ? this.engine._schemas[statement.name] : null);
|
|
1563
|
+
if (!schema) throw new Error(`Table '${statement.name}' does not exist`);
|
|
1377
1564
|
const stripDefault = (row) => {
|
|
1378
1565
|
const out = {};
|
|
1379
1566
|
for (const [k, v] of Object.entries(row)) {
|
|
@@ -1383,14 +1570,15 @@ class SQLExecutor {
|
|
|
1383
1570
|
return out;
|
|
1384
1571
|
};
|
|
1385
1572
|
if (statement.dataRows) {
|
|
1386
|
-
statement.dataRows = statement.dataRows.map(
|
|
1573
|
+
statement.dataRows = statement.dataRows.map(row => {
|
|
1574
|
+
for (const [c, def] of Object.entries(schema)) {
|
|
1575
|
+
if (def.autoIncrement && (row[c] === null || row[c] === undefined)) delete row[c];
|
|
1576
|
+
}
|
|
1577
|
+
return stripDefault(row);
|
|
1578
|
+
});
|
|
1387
1579
|
dataRows = statement.dataRows;
|
|
1388
1580
|
}
|
|
1389
1581
|
if (dataRows === null && statement.values) {
|
|
1390
|
-
schema = this.engine.getTableSchema
|
|
1391
|
-
? await this.engine.getTableSchema(statement.name)
|
|
1392
|
-
: (this.engine._schemas ? this.engine._schemas[statement.name] : null);
|
|
1393
|
-
if (!schema) throw new Error(`Table '${statement.name}' does not exist`);
|
|
1394
1582
|
const colNames = Object.keys(schema);
|
|
1395
1583
|
const skipAuto = statement.values[0].length < colNames.length;
|
|
1396
1584
|
dataRows = statement.values.map(vals => {
|
|
@@ -1410,11 +1598,6 @@ class SQLExecutor {
|
|
|
1410
1598
|
});
|
|
1411
1599
|
dataRows = dataRows.map(stripDefault);
|
|
1412
1600
|
}
|
|
1413
|
-
if (!schema) {
|
|
1414
|
-
schema = this.engine.getTableSchema
|
|
1415
|
-
? await this.engine.getTableSchema(statement.name)
|
|
1416
|
-
: (this.engine._schemas ? this.engine._schemas[statement.name] : null);
|
|
1417
|
-
}
|
|
1418
1601
|
const pkCols = schema ? Object.keys(schema).filter(k => schema[k].primaryKey) : [];
|
|
1419
1602
|
let toInsert = dataRows;
|
|
1420
1603
|
let updated = 0;
|
|
@@ -1681,6 +1864,144 @@ class SQLExecutor {
|
|
|
1681
1864
|
return this._readTable(item.table);
|
|
1682
1865
|
}
|
|
1683
1866
|
|
|
1867
|
+
_infoSchemaColumns(view) {
|
|
1868
|
+
const defs = {
|
|
1869
|
+
'tables': ['TABLE_CATALOG', 'TABLE_SCHEMA', 'TABLE_NAME', 'TABLE_TYPE', 'ENGINE', 'VERSION', 'ROW_FORMAT', 'TABLE_ROWS', 'AVG_ROW_LENGTH', 'DATA_LENGTH', 'MAX_DATA_LENGTH', 'INDEX_LENGTH', 'DATA_FREE', 'AUTO_INCREMENT', 'CREATE_TIME', 'UPDATE_TIME', 'CHECK_TIME', 'TABLE_COLLATION', 'CHECKSUM', 'CREATE_OPTIONS', 'TABLE_COMMENT'],
|
|
1870
|
+
'columns': ['TABLE_CATALOG', 'TABLE_SCHEMA', 'TABLE_NAME', 'COLUMN_NAME', 'ORDINAL_POSITION', 'COLUMN_DEFAULT', 'IS_NULLABLE', 'DATA_TYPE', 'CHARACTER_MAXIMUM_LENGTH', 'CHARACTER_OCTET_LENGTH', 'NUMERIC_PRECISION', 'NUMERIC_SCALE', 'DATETIME_PRECISION', 'CHARACTER_SET_NAME', 'COLLATION_NAME', 'COLUMN_TYPE', 'COLUMN_KEY', 'EXTRA', 'PRIVILEGES', 'COLUMN_COMMENT', 'GENERATION_EXPRESSION'],
|
|
1871
|
+
'schemata': ['CATALOG_NAME', 'SCHEMA_NAME', 'DEFAULT_CHARACTER_SET_NAME', 'DEFAULT_COLLATION_NAME', 'SQL_PATH', 'DEFAULT_ENCRYPTION'],
|
|
1872
|
+
'statistics': ['TABLE_CATALOG', 'TABLE_SCHEMA', 'TABLE_NAME', 'NON_UNIQUE', 'INDEX_SCHEMA', 'INDEX_NAME', 'SEQ_IN_INDEX', 'COLUMN_NAME', 'COLLATION', 'CARDINALITY', 'SUB_PART', 'PACKED', 'NULLABLE', 'INDEX_TYPE', 'COMMENT', 'INDEX_COMMENT'],
|
|
1873
|
+
'key_column_usage': ['CONSTRAINT_CATALOG', 'CONSTRAINT_SCHEMA', 'CONSTRAINT_NAME', 'TABLE_CATALOG', 'TABLE_SCHEMA', 'TABLE_NAME', 'COLUMN_NAME', 'ORDINAL_POSITION', 'POSITION_IN_UNIQUE_CONSTRAINT', 'REFERENCED_TABLE_SCHEMA', 'REFERENCED_TABLE_NAME', 'REFERENCED_COLUMN_NAME'],
|
|
1874
|
+
'referential_constraints': ['CONSTRAINT_CATALOG', 'CONSTRAINT_SCHEMA', 'CONSTRAINT_NAME', 'UNIQUE_CONSTRAINT_CATALOG', 'UNIQUE_CONSTRAINT_SCHEMA', 'UNIQUE_CONSTRAINT_NAME', 'MATCH_OPTION', 'UPDATE_RULE', 'DELETE_RULE', 'TABLE_NAME', 'REFERENCED_TABLE_NAME'],
|
|
1875
|
+
'table_constraints': ['CONSTRAINT_CATALOG', 'CONSTRAINT_SCHEMA', 'CONSTRAINT_NAME', 'TABLE_SCHEMA', 'TABLE_NAME', 'CONSTRAINT_TYPE'],
|
|
1876
|
+
};
|
|
1877
|
+
return defs[view] || ['COLUMN_NAME'];
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
_infoSchemaType(def) {
|
|
1881
|
+
const t = String(def.type || '').toLowerCase();
|
|
1882
|
+
const m = { int: 'int', integer: 'int', smallint: 'smallint', mediumint: 'mediumint', bigint: 'bigint', tinyint: 'tinyint', string: 'varchar', varchar: 'varchar', char: 'char', text: 'text', tinytext: 'tinytext', mediumtext: 'mediumtext', longtext: 'longtext', blob: 'blob', float: 'float', double: 'double', real: 'double', decimal: 'decimal', numeric: 'decimal', boolean: 'tinyint', bool: 'tinyint', date: 'date', datetime: 'datetime', timestamp: 'timestamp', time: 'time', year: 'year', json: 'json', enum: 'enum', uuid: 'varchar', binary: 'varbinary' };
|
|
1883
|
+
return m[t] || t || 'varchar';
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
async _infoSchemaRows(view) {
|
|
1887
|
+
const tables = this.engine._tableNames ? Array.from(this.engine._tableNames) : (this.engine.tables ? this.engine.tables() : []);
|
|
1888
|
+
const dbName = 'default';
|
|
1889
|
+
const base = { TABLE_CATALOG: 'def' };
|
|
1890
|
+
if (view === 'tables' || view === 'views') {
|
|
1891
|
+
const out = [];
|
|
1892
|
+
for (const name of tables) {
|
|
1893
|
+
const schema = this.engine.getTableSchema(name) || {};
|
|
1894
|
+
const table = this.engine._tables ? this.engine._tables[name] : null;
|
|
1895
|
+
const rowCount = table && table._rows ? table._rows.length : 0;
|
|
1896
|
+
out.push(Object.assign({}, base, {
|
|
1897
|
+
TABLE_SCHEMA: dbName,
|
|
1898
|
+
TABLE_NAME: name,
|
|
1899
|
+
TABLE_TYPE: view === 'views' ? 'VIEW' : 'BASE TABLE',
|
|
1900
|
+
ENGINE: 'InnoDB',
|
|
1901
|
+
VERSION: 10,
|
|
1902
|
+
ROW_FORMAT: 'Dynamic',
|
|
1903
|
+
TABLE_ROWS: rowCount,
|
|
1904
|
+
AVG_ROW_LENGTH: 0,
|
|
1905
|
+
DATA_LENGTH: 0,
|
|
1906
|
+
MAX_DATA_LENGTH: 0,
|
|
1907
|
+
INDEX_LENGTH: 0,
|
|
1908
|
+
DATA_FREE: 0,
|
|
1909
|
+
AUTO_INCREMENT: table && table._autoIncrement ? table._autoIncrement : null,
|
|
1910
|
+
CREATE_TIME: null,
|
|
1911
|
+
UPDATE_TIME: null,
|
|
1912
|
+
CHECK_TIME: null,
|
|
1913
|
+
TABLE_COLLATION: 'utf8mb4_general_ci',
|
|
1914
|
+
CHECKSUM: null,
|
|
1915
|
+
CREATE_OPTIONS: '',
|
|
1916
|
+
TABLE_COMMENT: '',
|
|
1917
|
+
}));
|
|
1918
|
+
}
|
|
1919
|
+
return out;
|
|
1920
|
+
}
|
|
1921
|
+
if (view === 'columns') {
|
|
1922
|
+
const out = [];
|
|
1923
|
+
for (const name of tables) {
|
|
1924
|
+
const schema = this.engine.getTableSchema(name) || {};
|
|
1925
|
+
let pos = 0;
|
|
1926
|
+
for (const [col, def] of Object.entries(schema)) {
|
|
1927
|
+
pos++;
|
|
1928
|
+
const dataType = this._infoSchemaType(def);
|
|
1929
|
+
const len = def.length != null ? def.length : (def.maxLength != null ? def.maxLength : null);
|
|
1930
|
+
const colType = len != null ? `${dataType}(${len})` : dataType;
|
|
1931
|
+
out.push(Object.assign({}, base, {
|
|
1932
|
+
TABLE_SCHEMA: dbName,
|
|
1933
|
+
TABLE_NAME: name,
|
|
1934
|
+
COLUMN_NAME: col,
|
|
1935
|
+
ORDINAL_POSITION: pos,
|
|
1936
|
+
COLUMN_DEFAULT: def.default !== undefined ? def.default : null,
|
|
1937
|
+
IS_NULLABLE: def.required || def.autoIncrement ? 'NO' : 'YES',
|
|
1938
|
+
DATA_TYPE: dataType,
|
|
1939
|
+
CHARACTER_MAXIMUM_LENGTH: /char|text/.test(dataType) ? len : null,
|
|
1940
|
+
CHARACTER_OCTET_LENGTH: /char|text/.test(dataType) ? (len ? len * 4 : null) : null,
|
|
1941
|
+
NUMERIC_PRECISION: /int|decimal|float|double|numeric/.test(dataType) ? 10 : null,
|
|
1942
|
+
NUMERIC_SCALE: /decimal|numeric/.test(dataType) ? 0 : null,
|
|
1943
|
+
DATETIME_PRECISION: null,
|
|
1944
|
+
CHARACTER_SET_NAME: /char|text/.test(dataType) ? 'utf8mb4' : null,
|
|
1945
|
+
COLLATION_NAME: /char|text/.test(dataType) ? 'utf8mb4_general_ci' : null,
|
|
1946
|
+
COLUMN_TYPE: colType,
|
|
1947
|
+
COLUMN_KEY: def.primaryKey ? 'PRI' : (def.unique ? 'UNI' : ''),
|
|
1948
|
+
EXTRA: def.autoIncrement ? 'auto_increment' : '',
|
|
1949
|
+
PRIVILEGES: 'select,insert,update,references',
|
|
1950
|
+
COLUMN_COMMENT: def.comment || '',
|
|
1951
|
+
GENERATION_EXPRESSION: '',
|
|
1952
|
+
}));
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
return out;
|
|
1956
|
+
}
|
|
1957
|
+
if (view === 'schemata') {
|
|
1958
|
+
return [Object.assign({}, base, {
|
|
1959
|
+
SCHEMA_NAME: dbName,
|
|
1960
|
+
CATALOG_NAME: 'def',
|
|
1961
|
+
DEFAULT_CHARACTER_SET_NAME: 'utf8mb4',
|
|
1962
|
+
DEFAULT_COLLATION_NAME: 'utf8mb4_general_ci',
|
|
1963
|
+
SQL_PATH: null,
|
|
1964
|
+
DEFAULT_ENCRYPTION: 'NO',
|
|
1965
|
+
})];
|
|
1966
|
+
}
|
|
1967
|
+
return [];
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
async _rebuildTableCache(table) {
|
|
1971
|
+
const schema = table._schema;
|
|
1972
|
+
table._primaryKey = null;
|
|
1973
|
+
table._autoIncrementField = null;
|
|
1974
|
+
table._dateFields = {};
|
|
1975
|
+
for (const [f, def] of Object.entries(schema)) {
|
|
1976
|
+
if (f === '_softDelete') continue;
|
|
1977
|
+
const isPk = def.primaryKey || def.primary === true;
|
|
1978
|
+
if (isPk && !table._primaryKey) table._primaryKey = f;
|
|
1979
|
+
if (def.autoIncrement) {
|
|
1980
|
+
table._autoIncrementField = f;
|
|
1981
|
+
if (isPk) table._primaryKey = f;
|
|
1982
|
+
}
|
|
1983
|
+
if (['date', 'datetime', 'timestamp', 'time'].includes(def.type)) table._dateFields[f] = def.type;
|
|
1984
|
+
}
|
|
1985
|
+
table._cachedSchemaFields = Object.keys(schema).filter(f => f !== '_softDelete');
|
|
1986
|
+
table._cachedDateFields = Object.keys(table._dateFields);
|
|
1987
|
+
table._cachedUniqueFields = table._cachedSchemaFields.filter(f => schema[f].unique);
|
|
1988
|
+
table._cachedRequiredFields = table._cachedSchemaFields.filter(f => schema[f].required);
|
|
1989
|
+
table._pkIndex = table._primaryKey ? new Map() : null;
|
|
1990
|
+
table._btrees = {};
|
|
1991
|
+
for (const [f, def] of Object.entries(schema)) {
|
|
1992
|
+
if (f === '_softDelete') continue;
|
|
1993
|
+
if (def.primaryKey || def.unique) {
|
|
1994
|
+
table._btrees[f] = new (require('./btree'))(64, true);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
table._rows.forEach((row, idx) => {
|
|
1998
|
+
for (const [f, tree] of Object.entries(table._btrees)) {
|
|
1999
|
+
const v = row[f];
|
|
2000
|
+
if (v !== undefined && v !== null) tree.insert(v, idx);
|
|
2001
|
+
}
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
|
|
1684
2005
|
// 物化表达式树中的子查询:IN (SELECT ...) -> expr.list
|
|
1685
2006
|
async _materialize(expr) {
|
|
1686
2007
|
if (!expr || typeof expr !== 'object') return;
|
|
@@ -1805,13 +2126,24 @@ class SQLExecutor {
|
|
|
1805
2126
|
// 读第一表
|
|
1806
2127
|
const firstItem = statement.from.tables[0];
|
|
1807
2128
|
if (firstItem && firstItem.table && String(firstItem.table).toLowerCase().startsWith('information_schema.')) {
|
|
2129
|
+
const view = String(firstItem.table).toLowerCase().split('.')[1];
|
|
2130
|
+
const all = await this._infoSchemaRows(view);
|
|
2131
|
+
const filtered = statement.where ? all.filter(r => evaluateExpr(statement.where, r)) : all;
|
|
1808
2132
|
const cols = statement.columns.map(c => scalarColumnName(c));
|
|
1809
|
-
|
|
2133
|
+
const isStar = cols.length === 1 && cols[0] === '*';
|
|
2134
|
+
let outCols;
|
|
2135
|
+
if (isStar) {
|
|
2136
|
+
outCols = filtered.length > 0 ? Object.keys(filtered[0]) : this._infoSchemaColumns(view);
|
|
2137
|
+
} else {
|
|
2138
|
+
outCols = cols;
|
|
2139
|
+
}
|
|
2140
|
+
const rows = filtered.map(r => outCols.map(c => (c in r ? r[c] : null)));
|
|
2141
|
+
return { ok: true, type: 'select', table: firstItem.table, columns: outCols, rows, raw: filtered };
|
|
1810
2142
|
}
|
|
1811
2143
|
let rowsAll;
|
|
1812
2144
|
if (firstItem.subquery) {
|
|
1813
2145
|
const res = await this.executeSelect(firstItem.subquery);
|
|
1814
|
-
rowsAll = { rows: this._subQueryRows(res), schema: null };
|
|
2146
|
+
rowsAll = { rows: this._subQueryRows(res), schema: null, columns: res.columns };
|
|
1815
2147
|
} else {
|
|
1816
2148
|
rowsAll = await this._readTable(firstItem.table);
|
|
1817
2149
|
}
|
|
@@ -1962,7 +2294,14 @@ class SQLExecutor {
|
|
|
1962
2294
|
const schemaKeys = schema ? Object.keys(schema) : [];
|
|
1963
2295
|
const pkCols = schemaKeys.filter(k => schema && schema[k] && schema[k].primaryKey);
|
|
1964
2296
|
const pk = pkCols.length > 0 ? pkCols[0] : (schemaKeys[0] || 'id');
|
|
1965
|
-
|
|
2297
|
+
let cols;
|
|
2298
|
+
if (schema) {
|
|
2299
|
+
cols = [pk, ...schemaKeys.filter(k => k !== pk)];
|
|
2300
|
+
} else if (rowsAll && rowsAll.columns) {
|
|
2301
|
+
cols = rowsAll.columns;
|
|
2302
|
+
} else {
|
|
2303
|
+
cols = Object.keys(all[0] || {}).filter((v, i, a) => a.indexOf(v) === i);
|
|
2304
|
+
}
|
|
1966
2305
|
return { ok: true, type: 'select', table: tableName, columns: cols, rows: rows.map(r => cols.map(c => r[c])), raw: rows };
|
|
1967
2306
|
}
|
|
1968
2307
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsql-neo",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "JSQL-NEO — Rust-powered embedded database with WASM, REST API, B-Tree indexes, WAL, crash recovery",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"default": "./index.js"
|
|
14
14
|
},
|
|
15
15
|
"./cli": "./bin/jsql",
|
|
16
|
+
"./mysql2": "./lib/mysql_compat.js",
|
|
17
|
+
"./mysql": "./lib/mysql_compat.js",
|
|
16
18
|
"./wasm/browser.mjs": {
|
|
17
19
|
"types": "./wasm/browser.d.ts",
|
|
18
20
|
"default": "./wasm/browser.mjs"
|
|
@@ -20,7 +22,8 @@
|
|
|
20
22
|
"./wasm/browser_bg.mjs": "./wasm/browser_bg.mjs",
|
|
21
23
|
"./wasm/browser.d.ts": "./wasm/browser.d.ts",
|
|
22
24
|
"./wasm/*": "./wasm/*",
|
|
23
|
-
"./lib
|
|
25
|
+
"./lib/*.js": "./lib/*.js",
|
|
26
|
+
"./lib/*": "./lib/*.js",
|
|
24
27
|
"./package.json": "./package.json"
|
|
25
28
|
},
|
|
26
29
|
"bin": {
|