jsql-neo 5.4.2 → 6.0.0-beta2
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/README.md +198 -14
- package/index.d.ts +29 -0
- package/index.js +12 -2
- package/lib/ast.js +214 -0
- package/lib/database.js +26 -60
- package/lib/mongo_server.js +1 -1
- package/lib/native_client.js +43 -66
- package/lib/plugin.js +227 -3
- 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 +43 -0
- package/lib/plugins/validation.js +128 -0
- package/lib/sql.js +1212 -84
- package/lib/wasm_client.js +41 -57
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.lock +8 -1
- package/nativesrc/jsql-neo-core/Cargo.toml +3 -1
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +50 -13
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +46 -23
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +13 -1
- package/nativesrc/jsql-neo-core/src/engine/table.rs +338 -42
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +245 -32
- package/nativesrc/jsql-neo-native/Cargo.lock +9 -2
- package/nativesrc/jsql-neo-native/Cargo.toml +9 -1
- package/nativesrc/jsql-neo-native/src/lib.rs +14 -2
- package/nativesrc/jsql-neo-wasm/Cargo.lock +9 -2
- package/nativesrc/jsql-neo-wasm/Cargo.toml +8 -1
- package/nativesrc/jsql-neo-wasm/src/lib.rs +18 -2
- package/package.json +4 -3
- package/test/sql-parser.test.js +450 -0
- package/wasm/browser.mjs +12 -0
- package/wasm/browser_bg.mjs +269 -248
- package/wasm/jsql_neo_wasm.d.ts +6 -0
- package/wasm/jsql_neo_wasm.js +248 -127
- package/wasm/jsql_neo_wasm_bg.wasm +0 -0
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +22 -22
- package/wasm/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **One engine to rule them all** — a Rust-powered embedded database that speaks your language:
|
|
4
4
|
> MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser. **And it fits in one npm package.**
|
|
5
5
|
|
|
6
|
-
> **v5.
|
|
6
|
+
> **v5.6.0** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|

|
|
@@ -232,7 +232,7 @@ npm install && npm run build # option 3: from source
|
|
|
232
232
|
Verify:
|
|
233
233
|
|
|
234
234
|
```bash
|
|
235
|
-
node -e "console.log(require('jsql-neo/package.json').version)" # 5.
|
|
235
|
+
node -e "console.log(require('jsql-neo/package.json').version)" # 5.6.0
|
|
236
236
|
```
|
|
237
237
|
|
|
238
238
|
### 30-second demo
|
|
@@ -835,6 +835,7 @@ CREATE TABLE IF NOT EXISTS orders (
|
|
|
835
835
|
**SELECT grammar**
|
|
836
836
|
|
|
837
837
|
```sql
|
|
838
|
+
WITH [RECURSIVE] cte_name [(col, ...)] AS (SELECT ...)[, ...] -- CTE,见下节
|
|
838
839
|
SELECT [DISTINCT] select_list
|
|
839
840
|
FROM table_reference
|
|
840
841
|
[JOIN table_reference ON condition]
|
|
@@ -843,9 +844,69 @@ FROM table_reference
|
|
|
843
844
|
[HAVING condition]
|
|
844
845
|
[ORDER BY column [ASC|DESC] [, ...]]
|
|
845
846
|
[LIMIT { count | offset, count | count OFFSET offset }]
|
|
847
|
+
[UNION [ALL|DISTINCT] SELECT ...]
|
|
848
|
+
[INTERSECT [ALL|DISTINCT] SELECT ...]
|
|
849
|
+
[EXCEPT [ALL|DISTINCT] SELECT ...]
|
|
846
850
|
[RETURNING ...]
|
|
847
851
|
```
|
|
848
852
|
|
|
853
|
+
**表达式与函数** — `CAST(expr AS type)`(INTEGER / FLOAT / DECIMAL / BOOLEAN / TEXT 等,
|
|
854
|
+
支持 `VARCHAR(10)` 带长度写法);`LIKE` / `ILIKE`(大小写不敏感)/ `REGEXP` / `RLIKE`;
|
|
855
|
+
`= ANY (SELECT ...)` / `> ALL (SELECT ...)`(含 `SOME`);
|
|
856
|
+
`ORDER BY <表达式>`(如 `ORDER BY sal * 12 DESC`、`ORDER BY UPPER(name)`)。
|
|
857
|
+
|
|
858
|
+
```sql
|
|
859
|
+
SELECT CAST(sal AS TEXT) AS s, CAST(ratio AS FLOAT) AS r FROM emp;
|
|
860
|
+
SELECT name FROM users WHERE name ILIKE 'a%'; -- 大小写不敏感
|
|
861
|
+
SELECT name FROM emp WHERE sal > ALL (SELECT sal FROM emp WHERE dept = 'ops');
|
|
862
|
+
SELECT name FROM emp ORDER BY sal * 12 DESC;
|
|
863
|
+
SELECT name FROM emp FETCH FIRST 10 ROWS ONLY; -- 等价于 LIMIT 10
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
**分组汇总** — `GROUP BY ... WITH ROLLUP` 会在结果末尾追加一行汇总
|
|
867
|
+
(分组列置 `NULL`,聚合列覆盖全部行):
|
|
868
|
+
|
|
869
|
+
```sql
|
|
870
|
+
SELECT dept, SUM(sal) AS total FROM emp GROUP BY dept WITH ROLLUP;
|
|
871
|
+
-- [['eng', 300], ['ops', 300], [null, 600]]
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
**写入冲突处理** — 四种写法统一处理主键/唯一键冲突:
|
|
875
|
+
|
|
876
|
+
| 写法 | 冲突时行为 | 返回字段 |
|
|
877
|
+
|---|---|---|
|
|
878
|
+
| 默认 | 抛 `ER_DUP_ENTRY` | — |
|
|
879
|
+
| `INSERT IGNORE` | 跳过冲突行 | `duplicateSkipped` |
|
|
880
|
+
| `ON CONFLICT (col) DO NOTHING` | 跳过冲突行 | `duplicateSkipped` |
|
|
881
|
+
| `ON DUPLICATE KEY UPDATE ...` / `ON CONFLICT (col) DO UPDATE SET ...` | 更新已有行 | `duplicateUpdated` |
|
|
882
|
+
| `REPLACE INTO` | 删除旧行后插入新行 | — |
|
|
883
|
+
|
|
884
|
+
**`RETURNING`** — `INSERT` / `UPDATE` / `DELETE` 均可带 `RETURNING *` 或 `RETURNING col, ...`,
|
|
885
|
+
结果在返回对象的 `returning: { columns, rows }` 上(同时填充 `columns` / `rows`):
|
|
886
|
+
|
|
887
|
+
```sql
|
|
888
|
+
INSERT INTO emp (name, sal) VALUES ('z', 100) RETURNING id, name;
|
|
889
|
+
UPDATE emp SET sal = 555 WHERE id = 1 RETURNING id, sal;
|
|
890
|
+
DELETE FROM emp WHERE id = 1 RETURNING *;
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
**视图** — `CREATE [OR REPLACE] VIEW name [(cols)] AS SELECT ...` / `DROP VIEW [IF EXISTS] name`。
|
|
894
|
+
视图定义保存在 `engine._views`,查询时内联为 FROM 子查询(支持视图引用视图,嵌套上限 16 层):
|
|
895
|
+
|
|
896
|
+
```sql
|
|
897
|
+
CREATE VIEW v_eng AS SELECT * FROM emp WHERE dept = 'eng';
|
|
898
|
+
SELECT name FROM v_eng;
|
|
899
|
+
CREATE OR REPLACE VIEW v_eng AS SELECT * FROM emp;
|
|
900
|
+
DROP VIEW v_eng;
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
**`EXPLAIN`** — `EXPLAIN [ANALYZE] <statement>` 输出 `step` / `detail` 两列的计划结构
|
|
904
|
+
(扫描 / 连接 / 过滤 / 聚合 / 排序 / 限制)。**没有代价模型**,只展示计划形态,不预测行数或耗时。
|
|
905
|
+
|
|
906
|
+
```sql
|
|
907
|
+
EXPLAIN SELECT name FROM emp WHERE sal > 50;
|
|
908
|
+
```
|
|
909
|
+
|
|
849
910
|
**JOINs** — `INNER JOIN`, `LEFT JOIN`, `RIGHT JOIN`, `FULL OUTER JOIN`, `CROSS JOIN`,
|
|
850
911
|
implicit `FROM a, b WHERE`, self-joins, multi-table chains.
|
|
851
912
|
|
|
@@ -866,21 +927,94 @@ FROM (SELECT dept FROM emp WHERE salary > 5000) t
|
|
|
866
927
|
GROUP BY t.dept;
|
|
867
928
|
```
|
|
868
929
|
|
|
869
|
-
**
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
930
|
+
**CTE(公用表表达式)** — `WITH [RECURSIVE] name [(cols)] AS (SELECT ...)`,
|
|
931
|
+
可声明多个,后面的 CTE 可以引用前面的。执行层把 CTE 内联为 `FROM` 子查询,
|
|
932
|
+
不需要引擎侧临时表。
|
|
933
|
+
|
|
934
|
+
```sql
|
|
935
|
+
WITH engineers AS (
|
|
936
|
+
SELECT * FROM emp WHERE dept = 'eng'
|
|
937
|
+
)
|
|
938
|
+
SELECT name, salary FROM engineers WHERE salary > 8000;
|
|
939
|
+
|
|
940
|
+
-- 多个 CTE,后者引用前者
|
|
941
|
+
WITH all_emp AS (SELECT * FROM emp),
|
|
942
|
+
high AS (SELECT * FROM all_emp WHERE salary > 8000)
|
|
943
|
+
SELECT dept, COUNT(*) AS n FROM high GROUP BY dept;
|
|
944
|
+
|
|
945
|
+
-- 显式列名清单
|
|
946
|
+
WITH t (who, howmuch) AS (SELECT name, salary FROM emp)
|
|
947
|
+
SELECT who FROM t;
|
|
948
|
+
|
|
949
|
+
-- CTE 也可接 INSERT / UPDATE / DELETE
|
|
950
|
+
WITH src AS (SELECT * FROM emp WHERE dept = 'eng')
|
|
951
|
+
INSERT INTO eng_backup SELECT * FROM src;
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
**窗口函数** — `function() OVER ( [PARTITION BY ...] [ORDER BY ...] [frame] )`。
|
|
955
|
+
在 `WHERE` / `GROUP BY` / `HAVING` 之后、`DISTINCT` / `ORDER BY` / `LIMIT` 之前求值,
|
|
956
|
+
符合 SQL 标准求值顺序。窗口聚合**不会**把结果塌缩成一行。
|
|
957
|
+
|
|
958
|
+
| 函数 | 说明 |
|
|
959
|
+
|---|---|
|
|
960
|
+
| `ROW_NUMBER()` | 分区内连续行号 |
|
|
961
|
+
| `RANK()` / `DENSE_RANK()` | 排名(并列时 `RANK` 跳号、`DENSE_RANK` 不跳号) |
|
|
962
|
+
| `NTILE(n)` | 把分区切成 n 个桶 |
|
|
963
|
+
| `LAG(expr[, n[, default]])` / `LEAD(...)` | 取分区内前/后第 n 行的值 |
|
|
964
|
+
| `FIRST_VALUE(expr)` / `LAST_VALUE(expr)` | 帧内首/末行的值 |
|
|
965
|
+
| `SUM/AVG/MIN/MAX/COUNT(...) OVER (...)` | 窗口聚合 |
|
|
966
|
+
|
|
967
|
+
```sql
|
|
968
|
+
SELECT name, dept, salary,
|
|
969
|
+
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn,
|
|
970
|
+
RANK() OVER (ORDER BY salary DESC) AS rk,
|
|
971
|
+
SUM(salary) OVER (PARTITION BY dept) AS dept_total,
|
|
972
|
+
LAG(salary) OVER (PARTITION BY dept ORDER BY salary) AS prev
|
|
973
|
+
FROM emp;
|
|
974
|
+
|
|
975
|
+
-- 窗口帧
|
|
976
|
+
SELECT name,
|
|
977
|
+
AVG(salary) OVER (ORDER BY salary
|
|
978
|
+
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS moving_avg
|
|
979
|
+
FROM emp;
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
**集合运算符** — `UNION [ALL|DISTINCT]`、`INTERSECT [ALL|DISTINCT]`、`EXCEPT [ALL|DISTINCT]`。
|
|
983
|
+
三者默认都是 `DISTINCT`;`UNION ALL` 保留重复行。
|
|
984
|
+
|
|
985
|
+
```sql
|
|
986
|
+
SELECT dept FROM emp WHERE salary > 5000
|
|
987
|
+
INTERSECT
|
|
988
|
+
SELECT dept FROM emp WHERE salary < 20000;
|
|
989
|
+
|
|
990
|
+
SELECT dept FROM emp
|
|
991
|
+
EXCEPT
|
|
992
|
+
SELECT dept FROM emp WHERE salary < 5000;
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
**`EXISTS` / `NOT EXISTS`** — 支持**相关子查询**(逐行求值,外层行的列可在子查询中引用):
|
|
996
|
+
|
|
997
|
+
```sql
|
|
998
|
+
SELECT name FROM emp e
|
|
999
|
+
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.emp_id = e.id AND o.amount > 1000);
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
**Transactions** — `BEGIN` / `START TRANSACTION`, `COMMIT`, `ROLLBACK`.
|
|
1003
|
+
Read-committed-equivalent isolation; per-statement atomicity without explicit transactions;
|
|
1004
|
+
cross-table transactions; rolled-back changes are invisible to other connections until commit.
|
|
873
1005
|
|
|
874
1006
|
```sql
|
|
875
1007
|
BEGIN;
|
|
876
1008
|
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
|
|
877
1009
|
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
|
|
878
|
-
SAVEPOINT sp1;
|
|
879
|
-
UPDATE accounts SET balance = balance - 100 WHERE id = 3;
|
|
880
|
-
ROLLBACK TO SAVEPOINT sp1;
|
|
881
1010
|
COMMIT;
|
|
882
1011
|
```
|
|
883
1012
|
|
|
1013
|
+
> **SAVEPOINT — 部分支持。** `SAVEPOINT name` 与 `RELEASE SAVEPOINT name` 可以解析并登记,
|
|
1014
|
+
> 但 **`ROLLBACK TO SAVEPOINT` 会抛出明确错误**:引擎没有保存点/快照能力,
|
|
1015
|
+
> 无法只回退到某个保存点。用 `ROLLBACK` 回滚整个事务。
|
|
1016
|
+
> 这里刻意选择"报错"而不是"静默成功"——假装回滚成功会让调用方误以为数据已还原。
|
|
1017
|
+
|
|
884
1018
|
**Other** — `SHOW DATABASES/TABLES/COLUMNS/CREATE TABLE`, `DESCRIBE t`, `USE db`,
|
|
885
1019
|
`EXPLAIN SELECT`, `SET SESSION x = y`, no-table queries (`SELECT VERSION()`), SQLite-style `PRAGMA`.
|
|
886
1020
|
|
|
@@ -1141,6 +1275,56 @@ Result matrix: SELECT → `rows`; INSERT → `affectedRows` + `insertId`; UPDATE
|
|
|
1141
1275
|
`affectedRows`; DDL/txn → `message`. Params: positional `?`, named `:name`, or object maps.
|
|
1142
1276
|
Multi-statement supported; `SQLExecutor` class runs batched SQL from a string/stream.
|
|
1143
1277
|
|
|
1278
|
+
#### 参数绑定:两种方式
|
|
1279
|
+
|
|
1280
|
+
```js
|
|
1281
|
+
// 1) 数组入参 —— 执行前用 applyParams 内联替换(转义安全,默认路径)
|
|
1282
|
+
await executeSQL(db, 'SELECT * FROM emp WHERE salary > ?', [5000]);
|
|
1283
|
+
|
|
1284
|
+
// 2) 原生 ? 占位符 —— 解析成 AST 节点,在执行期绑定(5.6.0+)
|
|
1285
|
+
await executeSQL(db, 'SELECT * FROM emp WHERE salary > ?', { params: [5000] });
|
|
1286
|
+
|
|
1287
|
+
// 支持顺序 ? 、编号 ?1 与标识符占位 ??
|
|
1288
|
+
await executeSQL(db, 'SELECT * FROM emp WHERE dept = ?1 OR salary > ?2', { params: ['eng', 5000] });
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
### AST access & rewrite
|
|
1292
|
+
|
|
1293
|
+
`parseSQL()` 返回普通对象树。5.6.0 起提供 `lib/ast.js`(同时从 `lib/sql.js` 导出 `AST`、`walk`、
|
|
1294
|
+
`transform`、`visit`)用于遍历、查找与改写这棵树,无需了解每种节点类型。
|
|
1295
|
+
|
|
1296
|
+
```js
|
|
1297
|
+
const { parseSQL, AST } = require('jsql-neo');
|
|
1298
|
+
|
|
1299
|
+
const ast = parseSQL('SELECT name, salary FROM emp WHERE salary > 100 ORDER BY salary DESC');
|
|
1300
|
+
|
|
1301
|
+
AST.tables(ast); // ['emp']
|
|
1302
|
+
AST.columns(ast); // ['name', 'salary']
|
|
1303
|
+
|
|
1304
|
+
// 遍历(返回 false 可跳过子树)
|
|
1305
|
+
AST.walk(ast, (node, parent, key) => { console.log(node.type); });
|
|
1306
|
+
|
|
1307
|
+
// 查找 / 收集
|
|
1308
|
+
AST.find(ast, n => n.type === 'compare');
|
|
1309
|
+
AST.collect(ast, n => n.type === 'column');
|
|
1310
|
+
|
|
1311
|
+
// 不可变改写(返回新树,原 AST 不变)
|
|
1312
|
+
const renamed = AST.transform(ast, (node) => {
|
|
1313
|
+
if (node && node.type === 'column' && node.name === 'name') return { ...node, name: 'NAME' };
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
// 原地改写
|
|
1317
|
+
AST.rewrite(ast, (node) => {
|
|
1318
|
+
if (node && node.type === 'value' && node.value === 100) return { ...node, value: 200 };
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
// 按节点类型分派的访问器(visitSelect / visitColumn / visitCompare …)
|
|
1322
|
+
class Upper extends AST.StatementVisitor {
|
|
1323
|
+
visitColumn(node) { node.name = String(node.name).toUpperCase(); }
|
|
1324
|
+
}
|
|
1325
|
+
new Upper().run(ast);
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1144
1328
|
### Events & hooks
|
|
1145
1329
|
|
|
1146
1330
|
```js
|
|
@@ -1273,7 +1457,7 @@ jsql mod --engine wasm # switch engine (restart required)
|
|
|
1273
1457
|
|
|
1274
1458
|
```bash
|
|
1275
1459
|
$ jsql version
|
|
1276
|
-
jsql-neo v5.
|
|
1460
|
+
jsql-neo v5.6.0
|
|
1277
1461
|
engine: native (napi) | wasm | js
|
|
1278
1462
|
node: v22.0.0 platform: linux x64
|
|
1279
1463
|
```
|
|
@@ -1294,7 +1478,7 @@ jsql tui --memory -q # memory mode, quiet
|
|
|
1294
1478
|
jsql tui --prompt 'db> ' --no-color
|
|
1295
1479
|
```
|
|
1296
1480
|
|
|
1297
|
-
The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=5.
|
|
1481
|
+
The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=5.6.0`.
|
|
1298
1482
|
|
|
1299
1483
|
### Keyboard shortcuts
|
|
1300
1484
|
|
|
@@ -3858,7 +4042,7 @@ Data dir: /root/.jsql-neo/data
|
|
|
3858
4042
|
|
|
3859
4043
|
```bash
|
|
3860
4044
|
$ jsql version
|
|
3861
|
-
jsql-neo v5.
|
|
4045
|
+
jsql-neo v5.6.0
|
|
3862
4046
|
engine: native (napi) | wasm | js
|
|
3863
4047
|
node: v22.0.0
|
|
3864
4048
|
platform: linux x64
|
|
@@ -4935,7 +5119,7 @@ Apache License
|
|
|
4935
5119
|
|
|
4936
5120
|
*JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
|
|
4937
5121
|
|
|
4938
|
-
*文档版本:v5.
|
|
5122
|
+
*文档版本:v5.6.0 · 最后更新:2026-08-12*
|
|
4939
5123
|
|
|
4940
5124
|
---
|
|
4941
5125
|
|
|
@@ -6705,7 +6889,7 @@ Usage: jsql version
|
|
|
6705
6889
|
|
|
6706
6890
|
输出版本与环境信息:
|
|
6707
6891
|
|
|
6708
|
-
jsql-neo v5.
|
|
6892
|
+
jsql-neo v5.6.0
|
|
6709
6893
|
engine: native (napi) | wasm | js
|
|
6710
6894
|
node: v22.0.0
|
|
6711
6895
|
platform: linux x64
|
package/index.d.ts
CHANGED
|
@@ -92,6 +92,7 @@ declare namespace JSQLNeo {
|
|
|
92
92
|
stop(): Promise<void>;
|
|
93
93
|
flush(): Promise<void>;
|
|
94
94
|
createTable(name: string, schema: Schema): Promise<unknown>;
|
|
95
|
+
addColumn(table: string, name: string, def: string | FieldDef): Promise<unknown>;
|
|
95
96
|
dropTable(name: string): Promise<unknown>;
|
|
96
97
|
insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<unknown>;
|
|
97
98
|
insertMany(table: string, data: Record<string, unknown>[]): Promise<unknown>;
|
|
@@ -260,6 +261,32 @@ declare namespace JSQLNeo {
|
|
|
260
261
|
safety?: boolean;
|
|
261
262
|
maxStatements?: number;
|
|
262
263
|
session?: Record<string, unknown>;
|
|
264
|
+
/** 5.5.0+:原生 `?` 占位符绑定值(与 applyParams 的数组入参二选一) */
|
|
265
|
+
params?: unknown[];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** AST 节点:带字符串 type 的普通对象树 */
|
|
269
|
+
export interface ASTNode {
|
|
270
|
+
type: string;
|
|
271
|
+
[key: string]: unknown;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** 5.5.0+:AST 访问与改写 */
|
|
275
|
+
export namespace AST {
|
|
276
|
+
function walk(ast: unknown, visitor: (node: ASTNode, parent?: unknown, key?: string) => void | false, opts?: { post?: (node: ASTNode) => void }): unknown;
|
|
277
|
+
function collect(ast: unknown, predicate: (node: ASTNode) => boolean): ASTNode[];
|
|
278
|
+
function find(ast: unknown, predicate: (node: ASTNode) => boolean): ASTNode | null;
|
|
279
|
+
function transform(ast: unknown, fn: (node: unknown) => unknown): unknown;
|
|
280
|
+
function rewrite(ast: unknown, fn: (node: unknown) => unknown): unknown;
|
|
281
|
+
function tables(ast: unknown): string[];
|
|
282
|
+
function columns(ast: unknown): string[];
|
|
283
|
+
function isNode(value: unknown): boolean;
|
|
284
|
+
function cloneNode(node: unknown): unknown;
|
|
285
|
+
class StatementVisitor {
|
|
286
|
+
run(ast: unknown): unknown;
|
|
287
|
+
visitDefault?(node: ASTNode): void;
|
|
288
|
+
[key: string]: unknown;
|
|
289
|
+
}
|
|
263
290
|
}
|
|
264
291
|
|
|
265
292
|
export function executeSQL(engine: unknown, sql: string, paramsOrOpts?: unknown[] | SQLOptions, opts?: SQLOptions): Promise<SQLResult>;
|
|
@@ -268,6 +295,8 @@ declare namespace JSQLNeo {
|
|
|
268
295
|
export function applyParams(sql: string, values: unknown[]): string;
|
|
269
296
|
export function escapeValue(value: unknown): string;
|
|
270
297
|
export function escapeId(value: string): string;
|
|
298
|
+
export function walk(ast: unknown, visitor: (node: ASTNode) => void | false): unknown;
|
|
299
|
+
export function transform(ast: unknown, fn: (node: unknown) => unknown): unknown;
|
|
271
300
|
|
|
272
301
|
export interface DatabaseOptions {
|
|
273
302
|
dataDir?: string;
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSQL-NEO
|
|
2
|
+
* JSQL-NEO v6.0.0-beta1 — Rust-Powered Embedded Database (WASM + HTTP)
|
|
3
3
|
*
|
|
4
4
|
* @example
|
|
5
5
|
* const jsql = require('jsql-neo');
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
const WasmClient = require('./lib/wasm_client');
|
|
15
15
|
const NativeClient = require('./lib/native_client');
|
|
16
|
-
const { Plugin } = require('./lib/plugin');
|
|
16
|
+
const { Plugin, definePlugin, HOOKS } = require('./lib/plugin');
|
|
17
|
+
const plugins = require('./lib/plugins');
|
|
17
18
|
const { ModuleManager } = require('./lib/mod');
|
|
18
19
|
const sql = require('./lib/sql');
|
|
19
20
|
const { Datastore } = require('./lib/nedb_compat');
|
|
@@ -71,6 +72,9 @@ module.exports = {
|
|
|
71
72
|
BTree: require('./lib/btree'),
|
|
72
73
|
Cache: require('./lib/cache'),
|
|
73
74
|
Plugin,
|
|
75
|
+
definePlugin,
|
|
76
|
+
HOOKS,
|
|
77
|
+
plugins,
|
|
74
78
|
ModuleManager,
|
|
75
79
|
JSQL_Error: require('./lib/errors').JSQL_Error,
|
|
76
80
|
ErrorCodes: require('./lib/errors').ErrorCodes,
|
|
@@ -80,6 +84,12 @@ module.exports = {
|
|
|
80
84
|
SQL: sql,
|
|
81
85
|
executeSQL: sql.executeSQL,
|
|
82
86
|
parseSQL: sql.parseSQL,
|
|
87
|
+
// AST 访问与改写(5.5.0+)
|
|
88
|
+
AST: sql.AST,
|
|
89
|
+
walk: sql.walk,
|
|
90
|
+
transform: sql.transform,
|
|
91
|
+
tokenize: sql.tokenize,
|
|
92
|
+
splitStatements: sql.splitStatements,
|
|
83
93
|
Datastore,
|
|
84
94
|
createConnection: mysqlCompat.createConnection,
|
|
85
95
|
createPool: mysqlCompat.createPool,
|
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 };
|