jsql-neo 5.5.0 → 6.0.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 +8155 -8097
- package/bin/jsql +320 -320
- package/bin/jsql-server +90 -90
- package/bin/jsql-server-control.js +177 -177
- package/index.d.ts +412 -385
- package/index.js +128 -124
- package/lib/ast.js +214 -214
- package/lib/btree.js +478 -413
- package/lib/cache.js +328 -328
- package/lib/client.js +125 -125
- package/lib/database.js +1741 -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 +722 -732
- package/lib/nedb_compat.js +506 -506
- package/lib/pg_server.js +864 -864
- package/lib/plugin.js +259 -35
- package/lib/plugins/audit.js +37 -0
- package/lib/plugins/index.js +72 -0
- package/lib/plugins/logger.js +35 -0
- package/lib/plugins/metrics.js +38 -0
- package/lib/plugins/timestamps.js +57 -0
- package/lib/plugins/validation.js +128 -0
- package/lib/prepared.js +37 -0
- package/lib/query.js +834 -815
- package/lib/redis_server.js +571 -571
- package/lib/sql.js +4053 -3460
- package/lib/sqlite_compat.js +323 -323
- package/lib/sqlite_worker.js +264 -264
- package/lib/table.js +1351 -1304
- package/lib/tui.js +502 -502
- package/lib/wasm.js +1 -1
- package/lib/wasm_client.js +715 -718
- package/lib/web_ui.js +257 -257
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.lock +462 -455
- package/nativesrc/jsql-neo-core/Cargo.toml +26 -24
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +492 -455
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +191 -168
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +74 -62
- package/nativesrc/jsql-neo-core/src/engine/table.rs +996 -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 +298 -85
- package/nativesrc/jsql-neo-core/src/types.rs +93 -93
- package/nativesrc/jsql-neo-native/Cargo.lock +613 -606
- package/nativesrc/jsql-neo-native/Cargo.toml +24 -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 +281 -269
- package/nativesrc/jsql-neo-wasm/Cargo.lock +472 -465
- package/nativesrc/jsql-neo-wasm/Cargo.toml +22 -15
- package/nativesrc/jsql-neo-wasm/src/lib.rs +463 -447
- package/package.json +87 -87
- package/postinstall.js +158 -158
- package/test/btree.test.js +144 -128
- package/test/coverage.js +187 -187
- package/test/join.test.js +110 -110
- package/test/native.test.js +158 -158
- package/test/query-opt.test.js +109 -0
- package/test/regress-5.1.0.js +271 -271
- package/test/smoke.js +58 -58
- package/test/sql-parser.test.js +450 -273
- package/test/wasm.test.js +110 -110
- package/wasm/browser.d.ts +89 -89
- package/wasm/browser.mjs +591 -569
- package/wasm/browser_bg.mjs +618 -597
- package/wasm/jsql_neo_wasm.d.ts +44 -38
- package/wasm/jsql_neo_wasm.js +641 -520
- package/wasm/jsql_neo_wasm_bg.wasm +0 -0
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
- package/wasm/package.json +11 -11
package/lib/ast.js
CHANGED
|
@@ -1,214 +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 };
|
|
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 };
|