jsql-neo 5.4.2 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -202
- package/README.md +8097 -7971
- package/bin/jsql +320 -320
- package/bin/jsql-server +90 -90
- package/bin/jsql-server-control.js +177 -177
- package/index.d.ts +385 -357
- package/index.js +124 -118
- package/lib/ast.js +214 -0
- package/lib/btree.js +413 -413
- package/lib/cache.js +328 -328
- package/lib/client.js +125 -125
- package/lib/database.js +1744 -1744
- package/lib/date-types.js +182 -182
- package/lib/errors.js +89 -89
- package/lib/jsql_format.js +497 -497
- package/lib/migrate.js +254 -254
- package/lib/mod.js +292 -292
- package/lib/mongo_server.js +607 -607
- package/lib/multiserver.js +140 -140
- package/lib/mysql_compat.js +413 -413
- package/lib/mysql_server.js +1237 -1237
- package/lib/native_client.js +732 -732
- package/lib/nedb_compat.js +506 -506
- package/lib/pg_server.js +864 -864
- package/lib/plugin.js +34 -34
- package/lib/query.js +815 -815
- package/lib/redis_server.js +571 -571
- package/lib/sql.js +3460 -2925
- package/lib/sqlite_compat.js +323 -323
- package/lib/sqlite_worker.js +264 -264
- package/lib/table.js +1304 -1304
- package/lib/tui.js +502 -502
- package/lib/wasm.js +1 -1
- package/lib/wasm_client.js +718 -718
- package/lib/web_ui.js +257 -257
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.lock +455 -455
- package/nativesrc/jsql-neo-core/Cargo.toml +24 -24
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +455 -455
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +168 -168
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +62 -62
- package/nativesrc/jsql-neo-core/src/engine/table.rs +700 -700
- package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -85
- package/nativesrc/jsql-neo-core/src/types.rs +93 -93
- package/nativesrc/jsql-neo-native/Cargo.lock +606 -606
- package/nativesrc/jsql-neo-native/Cargo.toml +16 -16
- package/nativesrc/jsql-neo-native/build.rs +3 -3
- package/nativesrc/jsql-neo-native/package.json +7 -7
- package/nativesrc/jsql-neo-native/src/lib.rs +269 -269
- package/nativesrc/jsql-neo-wasm/Cargo.lock +465 -465
- package/nativesrc/jsql-neo-wasm/Cargo.toml +15 -15
- package/nativesrc/jsql-neo-wasm/src/lib.rs +447 -447
- package/package.json +87 -86
- package/postinstall.js +158 -158
- package/test/btree.test.js +128 -128
- package/test/coverage.js +187 -187
- package/test/join.test.js +110 -110
- package/test/native.test.js +158 -158
- package/test/regress-5.1.0.js +271 -271
- package/test/smoke.js +58 -58
- package/test/sql-parser.test.js +273 -0
- package/test/wasm.test.js +110 -110
- package/wasm/browser.d.ts +89 -89
- package/wasm/browser.mjs +569 -569
- package/wasm/browser_bg.mjs +597 -597
- package/wasm/jsql_neo_wasm.d.ts +38 -38
- package/wasm/jsql_neo_wasm.js +520 -520
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
- package/wasm/package.json +11 -11
package/index.js
CHANGED
|
@@ -1,118 +1,124 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSQL-NEO v5.
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* const jsql = require('jsql-neo');
|
|
6
|
-
* const db = new jsql.JSQL(); // WASM mode (no server needed)
|
|
7
|
-
* await db.start();
|
|
8
|
-
* await db.createTable('users', { name: { type: 'string' }, age: { type: 'integer' } });
|
|
9
|
-
* const ids = await db.insert('users', { name: 'Alice', age: 30 });
|
|
10
|
-
* const user = await db.findById('users', 1);
|
|
11
|
-
* await db.stop();
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const WasmClient = require('./lib/wasm_client');
|
|
15
|
-
const NativeClient = require('./lib/native_client');
|
|
16
|
-
const { Plugin } = require('./lib/plugin');
|
|
17
|
-
const { ModuleManager } = require('./lib/mod');
|
|
18
|
-
const sql = require('./lib/sql');
|
|
19
|
-
const { Datastore } = require('./lib/nedb_compat');
|
|
20
|
-
const mysqlCompat = require('./lib/mysql_compat');
|
|
21
|
-
const { createMysqlServer, MysqlServer } = require('./lib/mysql_server');
|
|
22
|
-
const migrate = require('./lib/migrate');
|
|
23
|
-
const { WebUI } = require('./lib/web_ui');
|
|
24
|
-
const { RedisServer, createRedisServer } = require('./lib/redis_server');
|
|
25
|
-
const { PgServer, createPgServer } = require('./lib/pg_server');
|
|
26
|
-
const { MongoServer, createMongoServer } = require('./lib/mongo_server');
|
|
27
|
-
const { MultiServer, createMultiServer } = require('./lib/multiserver');
|
|
28
|
-
const { TUIShell, createTUI } = require('./lib/tui');
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 全局注入:把项目内 `require('mysql2')` 全部替换为 jsql-neo 内存引擎兼容层。
|
|
32
|
-
* 调用一次后,TypeORM / Drizzle / MikroORM / Kysely 等直接依赖 mysql2 的库
|
|
33
|
-
* 无需改代码即可享受本地内存速度。
|
|
34
|
-
*/
|
|
35
|
-
function enableMySQLCompat() {
|
|
36
|
-
const fs = require('fs');
|
|
37
|
-
const path = require('path');
|
|
38
|
-
const seen = new Set();
|
|
39
|
-
const bases = new Set();
|
|
40
|
-
if (require.main && Array.isArray(require.main.paths)) {
|
|
41
|
-
for (const p of require.main.paths) bases.add(p);
|
|
42
|
-
}
|
|
43
|
-
if (process.env.NODE_PATH) {
|
|
44
|
-
for (const p of process.env.NODE_PATH.split(path.delimiter)) if (p) bases.add(p);
|
|
45
|
-
}
|
|
46
|
-
bases.add(path.join(process.cwd(), 'node_modules'));
|
|
47
|
-
const inject = (p, mod) => {
|
|
48
|
-
try {
|
|
49
|
-
const resolved = p;
|
|
50
|
-
if (seen.has(resolved)) return;
|
|
51
|
-
if (!fs.existsSync(resolved)) return;
|
|
52
|
-
seen.add(resolved);
|
|
53
|
-
// 不覆盖已加载的真实 mysql2,避免全局劫持副作用
|
|
54
|
-
if (require.cache[resolved]) return;
|
|
55
|
-
require.cache[resolved] = { exports: mod, id: resolved, filename: resolved, loaded: true, children: [] };
|
|
56
|
-
} catch (e) { /* ignore */ }
|
|
57
|
-
};
|
|
58
|
-
for (const base of bases) {
|
|
59
|
-
inject(path.join(base, 'mysql2', 'index.js'), mysqlCompat);
|
|
60
|
-
inject(path.join(base, 'mysql2', 'promise.js'), mysqlCompat);
|
|
61
|
-
}
|
|
62
|
-
return mysqlCompat;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
module.exports = {
|
|
66
|
-
JSQL: WasmClient.JSQL,
|
|
67
|
-
NativeJSQL: NativeClient.JSQL,
|
|
68
|
-
Database: require('./lib/database'),
|
|
69
|
-
Table: require('./lib/table'),
|
|
70
|
-
Query: require('./lib/query'),
|
|
71
|
-
BTree: require('./lib/btree'),
|
|
72
|
-
Cache: require('./lib/cache'),
|
|
73
|
-
Plugin,
|
|
74
|
-
ModuleManager,
|
|
75
|
-
JSQL_Error: require('./lib/errors').JSQL_Error,
|
|
76
|
-
ErrorCodes: require('./lib/errors').ErrorCodes,
|
|
77
|
-
JSQLFormat: require('./lib/jsql_format'),
|
|
78
|
-
HttpJSQL: require('./lib/client').JSQL,
|
|
79
|
-
// 兼容层
|
|
80
|
-
SQL: sql,
|
|
81
|
-
executeSQL: sql.executeSQL,
|
|
82
|
-
parseSQL: sql.parseSQL,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
1
|
+
/**
|
|
2
|
+
* JSQL-NEO v5.5.0 — Rust-Powered Embedded Database (WASM + HTTP)
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const jsql = require('jsql-neo');
|
|
6
|
+
* const db = new jsql.JSQL(); // WASM mode (no server needed)
|
|
7
|
+
* await db.start();
|
|
8
|
+
* await db.createTable('users', { name: { type: 'string' }, age: { type: 'integer' } });
|
|
9
|
+
* const ids = await db.insert('users', { name: 'Alice', age: 30 });
|
|
10
|
+
* const user = await db.findById('users', 1);
|
|
11
|
+
* await db.stop();
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const WasmClient = require('./lib/wasm_client');
|
|
15
|
+
const NativeClient = require('./lib/native_client');
|
|
16
|
+
const { Plugin } = require('./lib/plugin');
|
|
17
|
+
const { ModuleManager } = require('./lib/mod');
|
|
18
|
+
const sql = require('./lib/sql');
|
|
19
|
+
const { Datastore } = require('./lib/nedb_compat');
|
|
20
|
+
const mysqlCompat = require('./lib/mysql_compat');
|
|
21
|
+
const { createMysqlServer, MysqlServer } = require('./lib/mysql_server');
|
|
22
|
+
const migrate = require('./lib/migrate');
|
|
23
|
+
const { WebUI } = require('./lib/web_ui');
|
|
24
|
+
const { RedisServer, createRedisServer } = require('./lib/redis_server');
|
|
25
|
+
const { PgServer, createPgServer } = require('./lib/pg_server');
|
|
26
|
+
const { MongoServer, createMongoServer } = require('./lib/mongo_server');
|
|
27
|
+
const { MultiServer, createMultiServer } = require('./lib/multiserver');
|
|
28
|
+
const { TUIShell, createTUI } = require('./lib/tui');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 全局注入:把项目内 `require('mysql2')` 全部替换为 jsql-neo 内存引擎兼容层。
|
|
32
|
+
* 调用一次后,TypeORM / Drizzle / MikroORM / Kysely 等直接依赖 mysql2 的库
|
|
33
|
+
* 无需改代码即可享受本地内存速度。
|
|
34
|
+
*/
|
|
35
|
+
function enableMySQLCompat() {
|
|
36
|
+
const fs = require('fs');
|
|
37
|
+
const path = require('path');
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
const bases = new Set();
|
|
40
|
+
if (require.main && Array.isArray(require.main.paths)) {
|
|
41
|
+
for (const p of require.main.paths) bases.add(p);
|
|
42
|
+
}
|
|
43
|
+
if (process.env.NODE_PATH) {
|
|
44
|
+
for (const p of process.env.NODE_PATH.split(path.delimiter)) if (p) bases.add(p);
|
|
45
|
+
}
|
|
46
|
+
bases.add(path.join(process.cwd(), 'node_modules'));
|
|
47
|
+
const inject = (p, mod) => {
|
|
48
|
+
try {
|
|
49
|
+
const resolved = p;
|
|
50
|
+
if (seen.has(resolved)) return;
|
|
51
|
+
if (!fs.existsSync(resolved)) return;
|
|
52
|
+
seen.add(resolved);
|
|
53
|
+
// 不覆盖已加载的真实 mysql2,避免全局劫持副作用
|
|
54
|
+
if (require.cache[resolved]) return;
|
|
55
|
+
require.cache[resolved] = { exports: mod, id: resolved, filename: resolved, loaded: true, children: [] };
|
|
56
|
+
} catch (e) { /* ignore */ }
|
|
57
|
+
};
|
|
58
|
+
for (const base of bases) {
|
|
59
|
+
inject(path.join(base, 'mysql2', 'index.js'), mysqlCompat);
|
|
60
|
+
inject(path.join(base, 'mysql2', 'promise.js'), mysqlCompat);
|
|
61
|
+
}
|
|
62
|
+
return mysqlCompat;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
JSQL: WasmClient.JSQL,
|
|
67
|
+
NativeJSQL: NativeClient.JSQL,
|
|
68
|
+
Database: require('./lib/database'),
|
|
69
|
+
Table: require('./lib/table'),
|
|
70
|
+
Query: require('./lib/query'),
|
|
71
|
+
BTree: require('./lib/btree'),
|
|
72
|
+
Cache: require('./lib/cache'),
|
|
73
|
+
Plugin,
|
|
74
|
+
ModuleManager,
|
|
75
|
+
JSQL_Error: require('./lib/errors').JSQL_Error,
|
|
76
|
+
ErrorCodes: require('./lib/errors').ErrorCodes,
|
|
77
|
+
JSQLFormat: require('./lib/jsql_format'),
|
|
78
|
+
HttpJSQL: require('./lib/client').JSQL,
|
|
79
|
+
// 兼容层
|
|
80
|
+
SQL: sql,
|
|
81
|
+
executeSQL: sql.executeSQL,
|
|
82
|
+
parseSQL: sql.parseSQL,
|
|
83
|
+
// AST 访问与改写(5.5.0+)
|
|
84
|
+
AST: sql.AST,
|
|
85
|
+
walk: sql.walk,
|
|
86
|
+
transform: sql.transform,
|
|
87
|
+
tokenize: sql.tokenize,
|
|
88
|
+
splitStatements: sql.splitStatements,
|
|
89
|
+
Datastore,
|
|
90
|
+
createConnection: mysqlCompat.createConnection,
|
|
91
|
+
createPool: mysqlCompat.createPool,
|
|
92
|
+
mysql: mysqlCompat,
|
|
93
|
+
mysql2: mysqlCompat,
|
|
94
|
+
enableMySQLCompat,
|
|
95
|
+
createMysqlServer,
|
|
96
|
+
MysqlServer,
|
|
97
|
+
// 迁移工具: mysqldump 导入 / JSON / CSV
|
|
98
|
+
migrate,
|
|
99
|
+
exportTableToJSON: migrate.exportTableToJSON,
|
|
100
|
+
exportAllToJSON: migrate.exportAllToJSON,
|
|
101
|
+
importFromJSON: migrate.importFromJSON,
|
|
102
|
+
exportTableToCSV: migrate.exportTableToCSV,
|
|
103
|
+
importFromCSV: migrate.importFromCSV,
|
|
104
|
+
importDump: migrate.importDump,
|
|
105
|
+
importDumpFile: migrate.importDumpFile,
|
|
106
|
+
exportToFile: migrate.exportToFile,
|
|
107
|
+
// Web UI
|
|
108
|
+
WebUI,
|
|
109
|
+
// Redis 兼容服务器
|
|
110
|
+
RedisServer,
|
|
111
|
+
createRedisServer,
|
|
112
|
+
// PostgreSQL wire protocol 服务器
|
|
113
|
+
PgServer,
|
|
114
|
+
createPgServer,
|
|
115
|
+
// MongoDB wire protocol 服务器
|
|
116
|
+
MongoServer,
|
|
117
|
+
createMongoServer,
|
|
118
|
+
// 多协议嗅探服务器(MySQL + PG + Redis + Mongo 同端口)
|
|
119
|
+
MultiServer,
|
|
120
|
+
createMultiServer,
|
|
121
|
+
// 交互式 TUI
|
|
122
|
+
TUIShell,
|
|
123
|
+
createTUI,
|
|
124
|
+
};
|
package/lib/ast.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/ast.js — JSQL-NEO AST 访问与改写
|
|
3
|
+
*
|
|
4
|
+
* parseSQL() 返回的是普通 JS 对象树。本模块提供在不了解每种节点类型的
|
|
5
|
+
* 前提下遍历 / 查找 / 改写这棵树的能力:
|
|
6
|
+
*
|
|
7
|
+
* walk(ast, visitor[, opts]) 深度优先遍历(前序 + 可选后序)
|
|
8
|
+
* collect(ast, predicate) 收集所有满足条件的节点
|
|
9
|
+
* find(ast, predicate) 返回第一个满足条件的节点
|
|
10
|
+
* transform(ast, fn) 不可变改写,返回新树
|
|
11
|
+
* rewrite(ast, fn) 原地改写
|
|
12
|
+
* tables(ast) 列出语句引用的所有表名
|
|
13
|
+
* columns(ast) 列出语句引用的所有列名
|
|
14
|
+
* StatementVisitor 基类,按节点类型分派 visitXxx 方法
|
|
15
|
+
*
|
|
16
|
+
* 设计约定:
|
|
17
|
+
* - 「节点」指带字符串 type 字段的对象;但遍历会穿透所有普通对象与数组,
|
|
18
|
+
* 因为 SELECT 列描述符({ expr, scalar, alias })这类中间结构没有 type。
|
|
19
|
+
* - seen 集合防止共享子树被重复访问或死循环。
|
|
20
|
+
* - transform 返回新树、不改入参;rewrite 原地修改。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const SKIP_KEYS = new Set(['pos']);
|
|
24
|
+
|
|
25
|
+
function isNode(v) {
|
|
26
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v) && typeof v.type === 'string';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function childKeys(obj) {
|
|
30
|
+
const out = [];
|
|
31
|
+
for (const k of Object.keys(obj)) if (!SKIP_KEYS.has(k)) out.push(k);
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 深度优先遍历。visitor(node, parent, key) 返回 false 可跳过该子树。 */
|
|
36
|
+
function walk(ast, visitor, opts = {}) {
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
const post = opts.post || null;
|
|
39
|
+
const fn = typeof visitor === 'function' ? visitor : null;
|
|
40
|
+
|
|
41
|
+
const visit = (value, parent, key) => {
|
|
42
|
+
if (value === null || typeof value !== 'object') return;
|
|
43
|
+
if (seen.has(value)) return;
|
|
44
|
+
seen.add(value);
|
|
45
|
+
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
for (const item of value) visit(item, parent, key);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const node = isNode(value);
|
|
52
|
+
let descend = true;
|
|
53
|
+
if (node && fn) {
|
|
54
|
+
const r = fn(value, parent, key);
|
|
55
|
+
if (r === false) descend = false;
|
|
56
|
+
}
|
|
57
|
+
if (descend) {
|
|
58
|
+
for (const k of childKeys(value)) visit(value[k], value, k);
|
|
59
|
+
}
|
|
60
|
+
if (node && post) post(value, parent, key);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (Array.isArray(ast)) { for (const n of ast) visit(n, null, null); }
|
|
64
|
+
else visit(ast, null, null);
|
|
65
|
+
return ast;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 收集所有满足 predicate 的节点。 */
|
|
69
|
+
function collect(ast, predicate) {
|
|
70
|
+
const out = [];
|
|
71
|
+
walk(ast, (node) => { if (predicate(node)) out.push(node); });
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 返回第一个满足 predicate 的节点,找不到返回 null。 */
|
|
76
|
+
function find(ast, predicate) {
|
|
77
|
+
let hit = null;
|
|
78
|
+
walk(ast, (node) => {
|
|
79
|
+
if (hit) return false;
|
|
80
|
+
if (predicate(node)) { hit = node; return false; }
|
|
81
|
+
});
|
|
82
|
+
return hit;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function cloneValue(v, seen) {
|
|
86
|
+
if (v === null || typeof v !== 'object') return v;
|
|
87
|
+
if (seen.has(v)) return v;
|
|
88
|
+
seen.add(v);
|
|
89
|
+
if (Array.isArray(v)) return v.map(x => cloneValue(x, seen));
|
|
90
|
+
const out = {};
|
|
91
|
+
for (const k of Object.keys(v)) out[k] = cloneValue(v[k], seen);
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function cloneNode(node) { return cloneValue(node, new Set()); }
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 不可变改写:对每个节点调用 fn(node),返回值非 undefined 时替换该节点。
|
|
99
|
+
* 返回新树,入参保持不变。
|
|
100
|
+
*/
|
|
101
|
+
function transform(ast, fn) {
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
|
|
104
|
+
const visit = (value) => {
|
|
105
|
+
if (value === null || typeof value !== 'object') {
|
|
106
|
+
const r = fn(value);
|
|
107
|
+
return r === undefined ? value : r;
|
|
108
|
+
}
|
|
109
|
+
if (seen.has(value)) return value;
|
|
110
|
+
seen.add(value);
|
|
111
|
+
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
const arr = value.map(visit);
|
|
114
|
+
const rep = fn(arr);
|
|
115
|
+
return rep === undefined ? arr : rep;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const out = {};
|
|
119
|
+
for (const k of Object.keys(value)) out[k] = visit(value[k]);
|
|
120
|
+
|
|
121
|
+
if (isNode(value)) {
|
|
122
|
+
const rep = fn(out);
|
|
123
|
+
return rep === undefined ? out : rep;
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return visit(ast);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 原地改写:fn(node) 返回值非 undefined 时替换原节点。 */
|
|
132
|
+
function rewrite(ast, fn) {
|
|
133
|
+
const seen = new Set();
|
|
134
|
+
|
|
135
|
+
const visit = (value, parent, key, index) => {
|
|
136
|
+
if (value === null || typeof value !== 'object') {
|
|
137
|
+
if (parent && key) {
|
|
138
|
+
const r = fn(value);
|
|
139
|
+
if (r !== undefined) {
|
|
140
|
+
if (index === undefined) parent[key] = r; else parent[key][index] = r;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (seen.has(value)) return;
|
|
146
|
+
seen.add(value);
|
|
147
|
+
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
for (let i = 0; i < value.length; i++) visit(value[i], parent, key, i);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const k of childKeys(value)) visit(value[k], value, k);
|
|
154
|
+
|
|
155
|
+
if (isNode(value) && parent && key) {
|
|
156
|
+
const r = fn(value);
|
|
157
|
+
if (r !== undefined) {
|
|
158
|
+
if (index === undefined) parent[key] = r; else parent[key][index] = r;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
visit(ast, null, null);
|
|
164
|
+
return ast;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** 语句引用的所有表名(含 JOIN / 子查询)。 */
|
|
168
|
+
function tables(ast) {
|
|
169
|
+
const out = new Set();
|
|
170
|
+
walk(ast, (node) => {
|
|
171
|
+
if (node.type === 'select' && node.from) {
|
|
172
|
+
for (const t of (node.from.tables || [])) if (t && t.table) out.add(t.table);
|
|
173
|
+
for (const j of (node.from.joins || [])) if (j.item && j.item.table) out.add(j.item.table);
|
|
174
|
+
}
|
|
175
|
+
if (node.type === 'insert' || node.type === 'update' || node.type === 'delete' ||
|
|
176
|
+
node.type === 'createTable' || node.type === 'dropTable' || node.type === 'truncate') {
|
|
177
|
+
if (node.name) out.add(node.name);
|
|
178
|
+
if (node.table) out.add(node.table);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
return [...out];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 语句引用的所有列名(去重)。 */
|
|
185
|
+
function columns(ast) {
|
|
186
|
+
const out = new Set();
|
|
187
|
+
walk(ast, (node) => {
|
|
188
|
+
if (node.type === 'column' && node.name) out.add(node.name);
|
|
189
|
+
});
|
|
190
|
+
return [...out];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 按节点类型分派的访问器基类。
|
|
195
|
+
* 子类定义 visitSelect(node) / visitCompare(node) … 即可;未定义类型走 visitDefault。
|
|
196
|
+
*
|
|
197
|
+
* class MyVisitor extends StatementVisitor {
|
|
198
|
+
* visitColumn(node) { node.name = String(node.name).toLowerCase(); }
|
|
199
|
+
* }
|
|
200
|
+
* new MyVisitor().run(ast);
|
|
201
|
+
*/
|
|
202
|
+
class StatementVisitor {
|
|
203
|
+
run(ast) {
|
|
204
|
+
walk(ast, (node) => {
|
|
205
|
+
const camel = String(node.type).replace(/(^|[-_])([a-z])/g, (_, __, ch) => ch.toUpperCase());
|
|
206
|
+
const method = this['visit' + camel];
|
|
207
|
+
if (typeof method === 'function') method.call(this, node);
|
|
208
|
+
else if (typeof this.visitDefault === 'function') this.visitDefault(node);
|
|
209
|
+
});
|
|
210
|
+
return ast;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = { walk, collect, find, transform, rewrite, tables, columns, StatementVisitor, isNode, cloneNode };
|