jsql-neo 6.0.0 → 6.1.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/README.md +164 -58
- package/lib/native_client.js +69 -8
- package/lib/pg_server.js +7 -1
- package/lib/redis_server.js +58 -28
- package/lib/sql.js +1018 -84
- package/lib/wasm_client.js +58 -8
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-native/Cargo.lock +2 -2
- package/package.json +4 -3
- package/test/readme-audit.test.js +353 -0
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
|
-
> **v6.0.
|
|
6
|
+
> **v6.0.1** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|

|
|
@@ -124,6 +124,7 @@ For the complete Chinese edition of every topic below, jump to the [中文版](#
|
|
|
124
124
|
- [Migration Tools](#migration-tools)
|
|
125
125
|
- [Storage & Performance](#storage--performance)
|
|
126
126
|
- [Security](#security)
|
|
127
|
+
- [Plugin System](#plugin-system)
|
|
127
128
|
- [Errors](#errors)
|
|
128
129
|
- [TypeScript](#typescript)
|
|
129
130
|
- [Architecture](#architecture)
|
|
@@ -232,7 +233,7 @@ npm install && npm run build # option 3: from source
|
|
|
232
233
|
Verify:
|
|
233
234
|
|
|
234
235
|
```bash
|
|
235
|
-
node -e "console.log(require('jsql-neo/package.json').version)" # 6.0.
|
|
236
|
+
node -e "console.log(require('jsql-neo/package.json').version)" # 6.0.1
|
|
236
237
|
```
|
|
237
238
|
|
|
238
239
|
### 30-second demo
|
|
@@ -410,7 +411,7 @@ Auth failure returns the standard `Access denied for user 'x'@'...' (using passw
|
|
|
410
411
|
|
|
411
412
|
The server answers the system probes ORMs and GUI tools run at connect time:
|
|
412
413
|
|
|
413
|
-
- `SELECT VERSION()` → `8.0.
|
|
414
|
+
- `SELECT VERSION()` → `8.0.1-jsql-neo`
|
|
414
415
|
- System variables: `@@version`, `@@version_comment`, …
|
|
415
416
|
- `SHOW DATABASES / SHOW TABLES / SHOW COLUMNS / SHOW CREATE TABLE`
|
|
416
417
|
- `information_schema.tables / columns / statistics`
|
|
@@ -1064,7 +1065,7 @@ SELECT AGE('2026-08-12', '2020-01-01'); -- '6 years 7 mons 11 days'
|
|
|
1064
1065
|
**Conditional / null** — `IF(cond, a, b)`, `IIF`, `IFNULL(a, b)`, `COALESCE(...)`, `NULLIF`,
|
|
1065
1066
|
`ISNULL`, `CASE WHEN ... THEN ... ELSE ... END` (search/simple form, works with aggregates).
|
|
1066
1067
|
|
|
1067
|
-
**System & misc** — `VERSION()` → `8.0.
|
|
1068
|
+
**System & misc** — `VERSION()` → `8.0.1-jsql-neo`, `DATABASE()`, `USER()`/`CURRENT_USER`,
|
|
1068
1069
|
`LAST_INSERT_ID()`, `ROW_COUNT()`, `CAST`/`CONVERT`, `JSON_EXTRACT(doc, '$.path')`,
|
|
1069
1070
|
`JSON_OBJECT`/`JSON_ARRAY`, `JSON_UNQUOTE`, `JSON_CONTAINS`, `JSON_SET/INSERT/REPLACE/REMOVE`,
|
|
1070
1071
|
`UUID()`, `TYPEOF(x)`, `PRINT(x)`.
|
|
@@ -1091,28 +1092,34 @@ ORDER BY avg_sal DESC;
|
|
|
1091
1092
|
|
|
1092
1093
|
### Operators
|
|
1093
1094
|
|
|
1094
|
-
**Arithmetic** — `+ - * /
|
|
1095
|
+
**Arithmetic** — `+ - * / %`,以及 `MOD` / `DIV` / `POWER()`。
|
|
1095
1096
|
|
|
1096
1097
|
**Comparison** — `= <> != < <= > >=`, `IS NULL`, `IS TRUE/FALSE` (three-valued),
|
|
1097
1098
|
`BETWEEN a AND b`, `IN (...)`, `LIKE`/`NOT LIKE`, `ILIKE` (case-insensitive),
|
|
1098
|
-
`RLIKE`/`REGEXP`,
|
|
1099
|
+
`RLIKE`/`REGEXP`, `EXISTS`, `= ANY` / `> ALL` / `SOME`.
|
|
1099
1100
|
|
|
1100
|
-
**Logic** — `AND OR NOT
|
|
1101
|
+
**Logic** — `AND OR NOT` (short-circuit).
|
|
1101
1102
|
|
|
1102
|
-
**Bitwise** — `& | ^ ~ <<
|
|
1103
|
+
**Bitwise** — `& | ^ ~ << >>`(`^` 是按位异或,`~` 是按位取反)。
|
|
1103
1104
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1105
|
+
> **运算符方言说明。** `^` 与 `~` 在 MySQL(位运算)和 PostgreSQL(`^` 为幂、
|
|
1106
|
+
> `~` 为正则匹配)中含义冲突,本实现统一采用 **MySQL 语义**:`^` = 按位异或、
|
|
1107
|
+
> `~` = 按位取反。需要幂运算请用 `POWER(x, n)`,需要正则请用 `REGEXP`。
|
|
1108
|
+
> 因此 PG 风格的 `^`(幂)与 `~ ~* !~ !~*`(正则)**暂不支持**。
|
|
1109
|
+
|
|
1110
|
+
**JSON (PG style)** — `->`(返回 JSON)、`->>`(返回文本),可链式
|
|
1111
|
+
(`meta->'addr'->>'city'`)。`#>` `#>>`(路径)、`@>` `<@`(包含)、
|
|
1112
|
+
`?` `?|` `?&`(键存在)**暂不支持**。
|
|
1106
1113
|
|
|
1107
1114
|
### LIKE / ILIKE / regex
|
|
1108
1115
|
|
|
1109
|
-
- `%` any length, `_` one char
|
|
1110
|
-
- `ILIKE` = LIKE
|
|
1111
|
-
- `REGEXP
|
|
1116
|
+
- `%` any length, `_` one char;`ESCAPE '!'` 可自定义转义符(默认 `\`)
|
|
1117
|
+
- `ILIKE` = LIKE,大小写不敏感
|
|
1118
|
+
- `REGEXP` / `RLIKE` 使用 JS RegExp 引擎;flags `i/m/s`
|
|
1112
1119
|
|
|
1113
1120
|
```sql
|
|
1114
1121
|
SELECT * FROM users
|
|
1115
|
-
WHERE email
|
|
1122
|
+
WHERE email REGEXP '^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$';
|
|
1116
1123
|
```
|
|
1117
1124
|
|
|
1118
1125
|
### Indexes & constraints
|
|
@@ -1155,7 +1162,7 @@ INSERT INTO users (id, meta) VALUES
|
|
|
1155
1162
|
(1, '{"name":"Alice","tags":["admin","ops"],"addr":{"city":"SH"}}');
|
|
1156
1163
|
|
|
1157
1164
|
SELECT meta->>'name' AS name, meta->'addr'->>'city' AS city
|
|
1158
|
-
FROM users
|
|
1165
|
+
FROM users;
|
|
1159
1166
|
```
|
|
1160
1167
|
|
|
1161
1168
|
JSON columns are fully interoperable with Mongo document views.
|
|
@@ -1263,16 +1270,17 @@ const result = await executeSQL(db, sql, params);
|
|
|
1263
1270
|
```js
|
|
1264
1271
|
{
|
|
1265
1272
|
columns: ['id', 'name', 'age'],
|
|
1266
|
-
columnTypes: ['
|
|
1273
|
+
columnTypes: ['INT', 'VARCHAR(255)', 'INT'],
|
|
1267
1274
|
rows: [[1, 'Alice', 30]],
|
|
1268
|
-
rowCount:
|
|
1269
|
-
message: '
|
|
1275
|
+
rowCount: 1, affectedRows: 0,
|
|
1276
|
+
message: '1 row selected', command: 'SELECT',
|
|
1270
1277
|
durationMs: 0.42, warnings: [],
|
|
1271
1278
|
}
|
|
1272
1279
|
```
|
|
1273
1280
|
|
|
1274
|
-
Result matrix: SELECT → `rows
|
|
1275
|
-
`
|
|
1281
|
+
Result matrix: SELECT → `rows`(另有 `columnTypes`、`rowCount`、`command`、`message`、
|
|
1282
|
+
`durationMs`、`warnings`);INSERT → `affectedRows` + `insertId`(另有 `ids`);
|
|
1283
|
+
UPDATE/DELETE → `affectedRows`;DDL/txn → `message`。 Params: positional `?`, named `:name`, or object maps.
|
|
1276
1284
|
Multi-statement supported; `SQLExecutor` class runs batched SQL from a string/stream.
|
|
1277
1285
|
|
|
1278
1286
|
#### 参数绑定:两种方式
|
|
@@ -1457,7 +1465,7 @@ jsql mod --engine wasm # switch engine (restart required)
|
|
|
1457
1465
|
|
|
1458
1466
|
```bash
|
|
1459
1467
|
$ jsql version
|
|
1460
|
-
jsql-neo v6.0.
|
|
1468
|
+
jsql-neo v6.0.1
|
|
1461
1469
|
engine: native (napi) | wasm | js
|
|
1462
1470
|
node: v22.0.0 platform: linux x64
|
|
1463
1471
|
```
|
|
@@ -1478,7 +1486,7 @@ jsql tui --memory -q # memory mode, quiet
|
|
|
1478
1486
|
jsql tui --prompt 'db> ' --no-color
|
|
1479
1487
|
```
|
|
1480
1488
|
|
|
1481
|
-
The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=6.0.
|
|
1489
|
+
The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=6.0.1`.
|
|
1482
1490
|
|
|
1483
1491
|
### Keyboard shortcuts
|
|
1484
1492
|
|
|
@@ -1753,6 +1761,105 @@ normalization on data dirs and import/export paths; Web UI db-name whitelist (no
|
|
|
1753
1761
|
}
|
|
1754
1762
|
```
|
|
1755
1763
|
|
|
1764
|
+
## Plugin System
|
|
1765
|
+
|
|
1766
|
+
JSQL-NEO ships with a unified plugin system that works across all three engine implementations:
|
|
1767
|
+
**Native** (`NativeJSQL`), **WASM** (`JSQL`), and **Pure JS** (`Database`). Full documentation
|
|
1768
|
+
is in [PLUGINS.md](./PLUGINS.md).
|
|
1769
|
+
|
|
1770
|
+
### Quick start
|
|
1771
|
+
|
|
1772
|
+
```js
|
|
1773
|
+
const { Database, Plugin } = require('jsql-neo');
|
|
1774
|
+
const db = new Database();
|
|
1775
|
+
|
|
1776
|
+
db.use({
|
|
1777
|
+
name: 'my-plugin',
|
|
1778
|
+
install(db, ctx) {
|
|
1779
|
+
db.myFlag = true;
|
|
1780
|
+
},
|
|
1781
|
+
hooks: {
|
|
1782
|
+
beforeInsert(table, data) { /* modify data */ return data; },
|
|
1783
|
+
afterInsert(table, data, ids) {},
|
|
1784
|
+
beforeUpdate(table, id, data) { return true; }, // false = abort
|
|
1785
|
+
afterUpdate(table, id, data, result) {},
|
|
1786
|
+
beforeDelete(table, id) { return true; },
|
|
1787
|
+
afterDelete(table, id, result) {},
|
|
1788
|
+
beforeFind(table, query) { return true; },
|
|
1789
|
+
afterFind(table, query, result) {},
|
|
1790
|
+
},
|
|
1791
|
+
});
|
|
1792
|
+
```
|
|
1793
|
+
|
|
1794
|
+
### Hooks reference (18 unified hooks)
|
|
1795
|
+
|
|
1796
|
+
| Hook | Parameters | Return | Description |
|
|
1797
|
+
|---|---|---|---|
|
|
1798
|
+
| `beforeInsert` | `(table, data[])` | modified data / `false` to abort | pre-insert |
|
|
1799
|
+
| `afterInsert` | `(table, data[], ids)` | — | post-insert |
|
|
1800
|
+
| `beforeUpdate` | `(table, id, data)` | `true`/`false` | pre-update |
|
|
1801
|
+
| `afterUpdate` | `(table, id, data, result)` | — | post-update |
|
|
1802
|
+
| `beforeDelete` | `(table, id)` | `true`/`false` | pre-delete |
|
|
1803
|
+
| `afterDelete` | `(table, id, result)` | — | post-delete |
|
|
1804
|
+
| `beforeFind` | `(table, query)` | `true`/`false` | pre-query |
|
|
1805
|
+
| `afterFind` | `(table, query, result)` | — | post-query |
|
|
1806
|
+
| `beforeCreateTable` | `(name, schema)` | `true`/`false` | pre-DDL |
|
|
1807
|
+
| `afterCreateTable` | `(name, schema)` | — | post-DDL |
|
|
1808
|
+
| `beforeDropTable` | `(name)` | `true`/`false` | pre-drop |
|
|
1809
|
+
| `afterDropTable` | `(name)` | — | post-drop |
|
|
1810
|
+
| `beforeFlush` | `()` | `true`/`false` | pre-flush |
|
|
1811
|
+
| `afterFlush` | `()` | — | post-flush |
|
|
1812
|
+
| `beforeCount` | `(table)` | `true`/`false` | pre-count |
|
|
1813
|
+
| `afterCount` | `(table, count)` | — | post-count |
|
|
1814
|
+
| `onStart` | `()` | — | engine start |
|
|
1815
|
+
| `onStop` | `()` | — | engine stop |
|
|
1816
|
+
|
|
1817
|
+
### Events
|
|
1818
|
+
|
|
1819
|
+
| Event | Payload |
|
|
1820
|
+
|---|---|
|
|
1821
|
+
| `insert` | `{ table, count, ids }` |
|
|
1822
|
+
| `update` | `{ table, id, data }` |
|
|
1823
|
+
| `delete` | `{ table, ids }` |
|
|
1824
|
+
| `createTable` | `{ name, schema }` |
|
|
1825
|
+
| `dropTable` | `{ name }` |
|
|
1826
|
+
| `start` / `stop` | `{}` |
|
|
1827
|
+
|
|
1828
|
+
### Plugin helper class
|
|
1829
|
+
|
|
1830
|
+
```js
|
|
1831
|
+
const timestampPlugin = new Plugin('timestamp')
|
|
1832
|
+
.on('beforeInsert', (table, data) => {
|
|
1833
|
+
data.forEach(r => r.created_at = new Date().toISOString());
|
|
1834
|
+
return data;
|
|
1835
|
+
})
|
|
1836
|
+
.onEvent((ev, payload) => console.log(ev))
|
|
1837
|
+
.install((db, ctx) => {})
|
|
1838
|
+
.build();
|
|
1839
|
+
|
|
1840
|
+
db.use(timestampPlugin);
|
|
1841
|
+
```
|
|
1842
|
+
|
|
1843
|
+
### Module system (CLI)
|
|
1844
|
+
|
|
1845
|
+
```bash
|
|
1846
|
+
jsql mod add --address /path/mod.js # register module
|
|
1847
|
+
jsql mod enable <name> # enable (auto-enables dependencies)
|
|
1848
|
+
jsql mod disable <name> # disable (cascades)
|
|
1849
|
+
jsql mod list | ls [--json] # list
|
|
1850
|
+
```
|
|
1851
|
+
|
|
1852
|
+
Modules persist to `~/.config/jsql/mod.config`. Circular dependencies are rejected;
|
|
1853
|
+
disabling cascades to dependents.
|
|
1854
|
+
|
|
1855
|
+
> **插件没有沙箱,请只装可信来源的模块。** 两条装载路径都不做隔离:
|
|
1856
|
+
> - `db.use(plugin)` —— 直接把插件对象挂进核心,与核心同权;
|
|
1857
|
+
> - `jsql mod add <dir>` —— 模块文件走普通 `require()`,同样与核心同权,
|
|
1858
|
+
> 可访问 `fs` / `child_process` 等一切 Node API。
|
|
1859
|
+
>
|
|
1860
|
+
> 早期文档曾写"模块文件在 VM 沙箱中执行",实际代码中不存在 vm 隔离,此处已更正。
|
|
1861
|
+
> 需要隔离时请在进程级解决(容器 / 子进程 / 单独的用户与权限)。
|
|
1862
|
+
|
|
1756
1863
|
---
|
|
1757
1864
|
|
|
1758
1865
|
## TypeScript
|
|
@@ -2378,7 +2485,7 @@ createMysqlServer({
|
|
|
2378
2485
|
|
|
2379
2486
|
支持常见系统查询,让 ORM 与 GUI 工具的初始化探测流程正常工作:
|
|
2380
2487
|
|
|
2381
|
-
- `SELECT VERSION()` → `8.0.
|
|
2488
|
+
- `SELECT VERSION()` → `8.0.1-jsql-neo`
|
|
2382
2489
|
- `SELECT @@version` / `@@version_comment` 等系统变量
|
|
2383
2490
|
- `SHOW DATABASES` / `SHOW TABLES` / `SHOW COLUMNS FROM t` / `SHOW CREATE TABLE t`
|
|
2384
2491
|
- `information_schema.tables` / `information_schema.columns` / `information_schema.statistics`
|
|
@@ -2410,7 +2517,7 @@ SELECT TABLE_NAME, TABLE_ROWS FROM information_schema.tables WHERE TABLE_SCHEMA
|
|
|
2410
2517
|
|
|
2411
2518
|
| 函数 | 行为 |
|
|
2412
2519
|
|---|---|
|
|
2413
|
-
| `VERSION()` | `8.0.
|
|
2520
|
+
| `VERSION()` | `8.0.1-jsql-neo` |
|
|
2414
2521
|
| `NOW()` / `CURRENT_TIMESTAMP` | `YYYY-MM-DD HH:MM:SS`(UTC) |
|
|
2415
2522
|
| `CONCAT(a, b, ...)` | 字符串拼接 |
|
|
2416
2523
|
| `GROUP_CONCAT(x)` | 聚合拼接(逗号分隔) |
|
|
@@ -3104,26 +3211,28 @@ SELECT * FROM orders LIMIT 40 OFFSET 20; -- 标准偏移语法
|
|
|
3104
3211
|
| `BEGIN` / `START TRANSACTION` | 开启事务 |
|
|
3105
3212
|
| `COMMIT` | 提交 |
|
|
3106
3213
|
| `ROLLBACK` | 回滚 |
|
|
3107
|
-
| `SAVEPOINT sp` / `
|
|
3214
|
+
| `SAVEPOINT sp` / `RELEASE SAVEPOINT sp` | 保存点(**仅登记**,无快照能力) |
|
|
3215
|
+
| `ROLLBACK TO SAVEPOINT sp` | **不支持**——会抛错,不会静默"假成功" |
|
|
3108
3216
|
| 自动事务 | 无显式事务时单语句原子 |
|
|
3109
3217
|
|
|
3110
3218
|
事务保证:
|
|
3111
3219
|
|
|
3112
3220
|
- 读已提交(read committed)隔离级别的等价语义
|
|
3113
3221
|
- 事务内修改对事务外不可见(提交前),提交后原子可见
|
|
3114
|
-
-
|
|
3222
|
+
- 回滚撤销本事务全部修改
|
|
3115
3223
|
- 支持跨表事务(引擎级提交日志)
|
|
3116
3224
|
|
|
3117
3225
|
```sql
|
|
3118
3226
|
BEGIN;
|
|
3119
3227
|
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
|
|
3120
3228
|
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
|
|
3121
|
-
SAVEPOINT sp1;
|
|
3122
|
-
UPDATE accounts SET balance = balance - 100 WHERE id = 3;
|
|
3123
|
-
ROLLBACK TO SAVEPOINT sp1; -- 撤销 id=3 的修改
|
|
3124
3229
|
COMMIT;
|
|
3125
3230
|
```
|
|
3126
3231
|
|
|
3232
|
+
> **`ROLLBACK TO SAVEPOINT` 目前不可用。** 引擎没有保存点/快照能力,无法只回退到某个
|
|
3233
|
+
> 保存点。这里刻意选择"抛错"而不是"静默成功"——假装回滚成功会让调用方误以为数据已还原。
|
|
3234
|
+
> 需要部分回滚时请用 `ROLLBACK` 放弃整个事务。
|
|
3235
|
+
|
|
3127
3236
|
#### 其他语句
|
|
3128
3237
|
|
|
3129
3238
|
| 语句 | 说明 |
|
|
@@ -3245,7 +3354,7 @@ INTERVAL 单位:`DAY` `HOUR` `MINUTE` `SECOND` `WEEK` `MONTH` `YEAR` 及其组
|
|
|
3245
3354
|
|
|
3246
3355
|
| 函数 | 说明 | 示例 |
|
|
3247
3356
|
|---|---|---|
|
|
3248
|
-
| `VERSION()` | 服务器版本 | `8.0.
|
|
3357
|
+
| `VERSION()` | 服务器版本 | `8.0.1-jsql-neo` |
|
|
3249
3358
|
| `DATABASE()` | 当前库名 | `DATABASE()` |
|
|
3250
3359
|
| `USER()` / `CURRENT_USER` | 当前用户 | `USER()` → `root` |
|
|
3251
3360
|
| `LAST_INSERT_ID()` | 最近自增 ID | 配合 AUTO_INCREMENT 使用 |
|
|
@@ -3295,7 +3404,6 @@ INTERVAL 单位:`DAY` `HOUR` `MINUTE` `SECOND` `WEEK` `MONTH` `YEAR` 及其组
|
|
|
3295
3404
|
| `+` `-` `*` `/` | 四则 | `(a + b) * 2` |
|
|
3296
3405
|
| `%` / `MOD` | 取模 | `a % 3` |
|
|
3297
3406
|
| `DIV` | 整数除法(MySQL) | `7 DIV 2` → 3 |
|
|
3298
|
-
| `^` | 幂(PG) | `2 ^ 10` → 1024 |
|
|
3299
3407
|
|
|
3300
3408
|
#### 比较运算符
|
|
3301
3409
|
|
|
@@ -3337,10 +3445,10 @@ INTERVAL 单位:`DAY` `HOUR` `MINUTE` `SECOND` `WEEK` `MONTH` `YEAR` 及其组
|
|
|
3337
3445
|
|---|---|---|
|
|
3338
3446
|
| `->` | 取 JSON 字段(返回 JSON) | `meta->'name'` |
|
|
3339
3447
|
| `->>` | 取 JSON 字段(返回文本) | `meta->>'name'` |
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3448
|
+
|
|
3449
|
+
可链式取值:`meta->'addr'->>'city'`。
|
|
3450
|
+
|
|
3451
|
+
> `#>` `#>>`(路径访问)、`@>` `<@`(包含)、`?` `?|` `?&`(键存在)**暂不支持** —— 使用时会抛出明确错误,不会静默退化成别的运算。
|
|
3344
3452
|
|
|
3345
3453
|
### LIKE / ILIKE / 正则
|
|
3346
3454
|
|
|
@@ -3358,14 +3466,12 @@ INTERVAL 单位:`DAY` `HOUR` `MINUTE` `SECOND` `WEEK` `MONTH` `YEAR` 及其组
|
|
|
3358
3466
|
|
|
3359
3467
|
- 基于 JS RegExp 引擎
|
|
3360
3468
|
- 支持 flags:`i`(忽略大小写)、`m`(多行)、`s`(点匹配换行)
|
|
3361
|
-
- PG 风格的 `~` / `~*` / `!~` / `!~*`
|
|
3362
|
-
|
|
3363
|
-
- `name ~* '^a'`(不敏感)
|
|
3364
|
-
- `name !~ '^X'`(不匹配)
|
|
3469
|
+
- PG 风格的 `~` / `~*` / `!~` / `!~*` 运算符**暂不支持**(`~` 已被用作按位取反)。
|
|
3470
|
+
正则匹配请用 `REGEXP` / `RLIKE`。
|
|
3365
3471
|
|
|
3366
3472
|
```sql
|
|
3367
3473
|
SELECT * FROM users
|
|
3368
|
-
WHERE email
|
|
3474
|
+
WHERE email REGEXP '^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$';
|
|
3369
3475
|
```
|
|
3370
3476
|
|
|
3371
3477
|
### 事务 Transactions
|
|
@@ -3464,7 +3570,7 @@ INSERT INTO users (id, meta) VALUES
|
|
|
3464
3570
|
-- 查询路径
|
|
3465
3571
|
SELECT meta->>'name' AS name,
|
|
3466
3572
|
meta->'addr'->>'city' AS city
|
|
3467
|
-
FROM users
|
|
3573
|
+
FROM users;
|
|
3468
3574
|
```
|
|
3469
3575
|
|
|
3470
3576
|
JSON 函数见 [系统与杂项函数](#系统与杂项函数)。Mongo 客户端视角下,JSON 列与 BSON 文档完全互通。
|
|
@@ -4042,7 +4148,7 @@ Data dir: /root/.jsql-neo/data
|
|
|
4042
4148
|
|
|
4043
4149
|
```bash
|
|
4044
4150
|
$ jsql version
|
|
4045
|
-
jsql-neo v6.0.
|
|
4151
|
+
jsql-neo v6.0.1
|
|
4046
4152
|
engine: native (napi) | wasm | js
|
|
4047
4153
|
node: v22.0.0
|
|
4048
4154
|
platform: linux x64
|
|
@@ -4711,7 +4817,7 @@ jsql-neo/
|
|
|
4711
4817
|
├── index.js # ★ 公共 API 出口
|
|
4712
4818
|
├── index.d.ts # TypeScript 声明
|
|
4713
4819
|
├── test/ # 测试套件
|
|
4714
|
-
├── docs/
|
|
4820
|
+
├── PLUGINS.md # 插件与模块文档(与 README 同级,无 docs/ 目录)
|
|
4715
4821
|
├── src/rust/ # Rust 引擎源码(native + wasm)
|
|
4716
4822
|
├── data/ # 默认数据目录(运行时创建)
|
|
4717
4823
|
└── package.json
|
|
@@ -4902,7 +5008,7 @@ npm run typecheck
|
|
|
4902
5008
|
- `src/rust/`:Rust 引擎(native + wasm 同源)
|
|
4903
5009
|
- `lib/`:Node 侧全部逻辑(协议/解析/TUI/兼容层)
|
|
4904
5010
|
- `test/`:单元 + 集成测试
|
|
4905
|
-
- `docs
|
|
5011
|
+
- 扩展文档位于仓库根目录:`PLUGINS.md`(插件与模块)。本仓库**没有** `docs/` 目录。
|
|
4906
5012
|
|
|
4907
5013
|
---
|
|
4908
5014
|
|
|
@@ -5119,7 +5225,7 @@ Apache License
|
|
|
5119
5225
|
|
|
5120
5226
|
*JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
|
|
5121
5227
|
|
|
5122
|
-
*文档版本:v6.0.
|
|
5228
|
+
*文档版本:v6.0.1 · 最后更新:2026-09-27*
|
|
5123
5229
|
|
|
5124
5230
|
---
|
|
5125
5231
|
|
|
@@ -5692,7 +5798,6 @@ expr := literal
|
|
|
5692
5798
|
| expr [NOT] LIKE pattern [ESCAPE char]
|
|
5693
5799
|
| expr [NOT] ILIKE pattern
|
|
5694
5800
|
| expr [NOT] RLIKE pattern | expr [NOT] REGEXP pattern
|
|
5695
|
-
| expr [NOT] ~ pattern | expr [NOT] ~* pattern | expr !~ pattern
|
|
5696
5801
|
| expr IS [NOT] NULL | expr IS [NOT] TRUE | expr IS [NOT] FALSE
|
|
5697
5802
|
| expr [NOT] IN ( ... )
|
|
5698
5803
|
| expr [NOT] ANY '(' select_statement ')'
|
|
@@ -5738,16 +5843,17 @@ function_call := func_name '(' [DISTINCT] args ')' -- 普通/聚合
|
|
|
5738
5843
|
### 运算符优先级(从高到低)
|
|
5739
5844
|
|
|
5740
5845
|
```
|
|
5741
|
-
1. () . [] -> ->>
|
|
5742
|
-
2.
|
|
5846
|
+
1. () . [] -> ->> ::
|
|
5847
|
+
2. CAST 一元 + - ~
|
|
5743
5848
|
3. ^ * / % DIV MOD
|
|
5744
5849
|
4. + -
|
|
5745
5850
|
5. << >> & | ^(位)
|
|
5746
|
-
6. = <> != < <= > >= <=> BETWEEN IN LIKE ILIKE RLIKE REGEXP
|
|
5851
|
+
6. = <> != < <= > >= <=> BETWEEN IN LIKE ILIKE RLIKE REGEXP
|
|
5747
5852
|
7. NOT
|
|
5748
|
-
8. AND
|
|
5749
|
-
9.
|
|
5750
|
-
10.
|
|
5853
|
+
8. AND &&
|
|
5854
|
+
9. XOR
|
|
5855
|
+
10. OR ||
|
|
5856
|
+
11. 三元(IF / CASE 解析为函数)
|
|
5751
5857
|
```
|
|
5752
5858
|
|
|
5753
5859
|
### 关键词语法表
|
|
@@ -6594,7 +6700,7 @@ FROM orders;
|
|
|
6594
6700
|
#### 会话信息
|
|
6595
6701
|
|
|
6596
6702
|
```sql
|
|
6597
|
-
SELECT VERSION(); -- '8.0.
|
|
6703
|
+
SELECT VERSION(); -- '8.0.1-jsql-neo'
|
|
6598
6704
|
SELECT DATABASE(); -- 'app'
|
|
6599
6705
|
SELECT USER(); -- 'root@localhost'
|
|
6600
6706
|
SELECT CURRENT_USER; -- 'root@localhost'
|
|
@@ -6889,7 +6995,7 @@ Usage: jsql version
|
|
|
6889
6995
|
|
|
6890
6996
|
输出版本与环境信息:
|
|
6891
6997
|
|
|
6892
|
-
jsql-neo v6.0.
|
|
6998
|
+
jsql-neo v6.0.1
|
|
6893
6999
|
engine: native (napi) | wasm | js
|
|
6894
7000
|
node: v22.0.0
|
|
6895
7001
|
platform: linux x64
|
|
@@ -7339,12 +7445,12 @@ jsql import ./data app.sql
|
|
|
7339
7445
|
| 冲突处理 | `ON DUPLICATE KEY UPDATE` | `ON CONFLICT ... DO ...` | 两者都支持 |
|
|
7340
7446
|
| 占位符 | `?` | `$1, $2` | 两者(分协议) |
|
|
7341
7447
|
| 模糊匹配 | `LIKE`(大小写敏感) | `LIKE` + `ILIKE` | 两者 |
|
|
7342
|
-
| 正则 | `REGEXP` |
|
|
7448
|
+
| 正则 | `REGEXP` | `REGEXP` | 两者 + `RLIKE` |
|
|
7343
7449
|
| 标识符引用 | 反引号 `` ` `` | 双引号 `"` | 两者 |
|
|
7344
7450
|
| 字符串字面量 | 单引号 | 单引号 | 单引号(双引号按标识符,PG 语义) |
|
|
7345
7451
|
| 布尔 | `TRUE/FALSE`(1/0) | `t/f`(三值逻辑) | 兼容两者 |
|
|
7346
7452
|
| 分页 | `LIMIT off, n` | `LIMIT n OFFSET off` | 两者 |
|
|
7347
|
-
| JSON 访问 | `JSON_EXTRACT` | `->` `->>` | 两者
|
|
7453
|
+
| JSON 访问 | `JSON_EXTRACT` | `->` `->>` | 两者 |
|
|
7348
7454
|
| 类型转换 | `CAST`/`CONVERT` | `::` | 三者 |
|
|
7349
7455
|
| 条件分支 | `IF()` | `CASE` | `IF` + `CASE` + `IIF` |
|
|
7350
7456
|
| 空值回退 | `IFNULL()` | `COALESCE` | 两者 |
|
|
@@ -7545,7 +7651,7 @@ CI(GitHub Actions)矩阵:`node 20/22` × `linux/macos/windows` × `native/
|
|
|
7545
7651
|
|
|
7546
7652
|
*JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
|
|
7547
7653
|
|
|
7548
|
-
*文档版本:
|
|
7654
|
+
*文档版本:v6.0.1 · 共 19 个附录 · 最后更新:2026-09-27*
|
|
7549
7655
|
|
|
7550
7656
|
---
|
|
7551
7657
|
|
|
@@ -8152,4 +8258,4 @@ npm test
|
|
|
8152
8258
|
|
|
8153
8259
|
*JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
|
|
8154
8260
|
|
|
8155
|
-
*文档版本:
|
|
8261
|
+
*文档版本:v6.0.1 · 附录 A–Z · 全文 6000+ 行 · 最后更新:2026-09-27*
|
package/lib/native_client.js
CHANGED
|
@@ -17,6 +17,33 @@ function safeParse(str, fallback) {
|
|
|
17
17
|
try { return JSON.parse(str); } catch (e) { return fallback; }
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* 序列化对象/数组字段(JSON 列)并给出可读错误。直接 JSON.stringify 遇到循环引用会抛
|
|
22
|
+
* "Converting circular structure to JSON",指向不到具体列,这里补上表/列信息。
|
|
23
|
+
* 与 lib/wasm_client.js 的 safeStringify 保持一致。
|
|
24
|
+
*/
|
|
25
|
+
function safeStringify(value, table, column) {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(value);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
const msg = (e && e.message) ? e.message : String(e);
|
|
30
|
+
throw new Error(`Cannot serialize column '${column}' of table '${table}': ${msg}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 插入行必须是普通对象;null/undefined/数组/基本类型在这里就被拦下(与 lib/wasm_client.js 一致) */
|
|
35
|
+
function assertRowObject(row, index, isBatch) {
|
|
36
|
+
const ok = row !== null && row !== undefined && typeof row === 'object' &&
|
|
37
|
+
!Array.isArray(row) && !(row instanceof Date);
|
|
38
|
+
if (ok) return;
|
|
39
|
+
const got = row === null ? 'null'
|
|
40
|
+
: Array.isArray(row) ? 'array'
|
|
41
|
+
: row instanceof Date ? 'Date'
|
|
42
|
+
: typeof row;
|
|
43
|
+
const where = isBatch ? ` at index ${index}` : '';
|
|
44
|
+
throw new TypeError(`insert(): expected a row object${where}, got ${got}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
20
47
|
const NATIVE_TYPE_MAP = {
|
|
21
48
|
text: 'string',
|
|
22
49
|
varchar: 'string',
|
|
@@ -399,8 +426,17 @@ class JSQL {
|
|
|
399
426
|
rows = rows.map(row => {
|
|
400
427
|
const out = {};
|
|
401
428
|
for (const [k, v] of Object.entries(row)) {
|
|
402
|
-
|
|
403
|
-
|
|
429
|
+
const def = schema[k];
|
|
430
|
+
let t = null;
|
|
431
|
+
if (typeof def === 'string') t = def.toLowerCase();
|
|
432
|
+
else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
|
|
433
|
+
if (v !== null && v !== undefined && ['json', 'array', 'object'].includes(t) && typeof v !== 'string') {
|
|
434
|
+
out[k] = safeStringify(v, table, k);
|
|
435
|
+
} else if (v instanceof Date) {
|
|
436
|
+
out[k] = v.toISOString();
|
|
437
|
+
} else {
|
|
438
|
+
out[k] = v;
|
|
439
|
+
}
|
|
404
440
|
}
|
|
405
441
|
return out;
|
|
406
442
|
});
|
|
@@ -450,18 +486,39 @@ class JSQL {
|
|
|
450
486
|
async _flush() {
|
|
451
487
|
this._flushOpsNow();
|
|
452
488
|
if (!this._runHooks('beforeFlush', [])) return null;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
489
|
+
// 先把整个 buffer 原子取出再写入。
|
|
490
|
+
// 旧写法是遍历 this._buffer、await 之后才清空 —— 期间其他协程继续往同一个数组
|
|
491
|
+
// push,同一批行会被多个 flush 重复写入(并发 insert 行数暴涨/丢行)。
|
|
492
|
+
// 与 lib/wasm_client.js 的修法保持一致。
|
|
493
|
+
const pending = this._buffer;
|
|
459
494
|
this._buffer = {};
|
|
460
495
|
this._bufferSize = 0;
|
|
496
|
+
const flushed = {};
|
|
497
|
+
try {
|
|
498
|
+
for (const [table, rows] of Object.entries(pending)) {
|
|
499
|
+
if (rows.length === 0) continue;
|
|
500
|
+
flushed[table] = await this._insertBatch(table, rows);
|
|
501
|
+
}
|
|
502
|
+
} catch (e) {
|
|
503
|
+
// 坏行不回写 buffer:否则 stop() 二次 flush 会再抛同一个错导致进程 exit 1
|
|
504
|
+
if (!this._flushErrors) this._flushErrors = [];
|
|
505
|
+
this._flushErrors.push({
|
|
506
|
+
at: Date.now(),
|
|
507
|
+
message: (e && e.message) ? e.message : String(e),
|
|
508
|
+
tables: Object.keys(pending),
|
|
509
|
+
rowCounts: Object.fromEntries(Object.entries(pending).map(([t, r]) => [t, r.length])),
|
|
510
|
+
});
|
|
511
|
+
throw e;
|
|
512
|
+
}
|
|
461
513
|
this._runHooks('afterFlush', []);
|
|
462
514
|
return flushed;
|
|
463
515
|
}
|
|
464
516
|
|
|
517
|
+
/** 返回历次 flush 失败的记录(坏行不会回写 buffer,只能从这里查) */
|
|
518
|
+
lastFlushErrors() {
|
|
519
|
+
return (this._flushErrors || []).slice();
|
|
520
|
+
}
|
|
521
|
+
|
|
465
522
|
_flushOpsNow() {
|
|
466
523
|
if (this._opTimer) {
|
|
467
524
|
clearTimeout(this._opTimer);
|
|
@@ -479,7 +536,11 @@ class JSQL {
|
|
|
479
536
|
}
|
|
480
537
|
|
|
481
538
|
async insert(table, data) {
|
|
539
|
+
// 入参校验:null / undefined / 非对象会在下游 Object.entries() 处炸成
|
|
540
|
+
// 难以定位的 "Cannot convert undefined or null to object",这里直接给明确 TypeError
|
|
541
|
+
// (与 lib/wasm_client.js 的 assertRowObject 保持一致)
|
|
482
542
|
const arr = Array.isArray(data) ? data : [data];
|
|
543
|
+
arr.forEach((row, i) => assertRowObject(row, i, arr.length > 1 || Array.isArray(data)));
|
|
483
544
|
var filtered = arr;
|
|
484
545
|
if (!this._runHooks('beforeInsert', [table, filtered])) return [];
|
|
485
546
|
if (arr.length > 1) {
|
package/lib/pg_server.js
CHANGED
|
@@ -861,4 +861,10 @@ class PgServer {
|
|
|
861
861
|
}
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
-
|
|
864
|
+
// README 的 createXxxServer 工厂系列里,PG 这个之前漏了实现:
|
|
865
|
+
// index.js 一直从本模块解构 createPgServer,实际拿到 undefined。
|
|
866
|
+
function createPgServer(options) {
|
|
867
|
+
return new PgServer(options || {});
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
module.exports = { PgServer, createPgServer, PgConnection, buildRowDescription, buildDataRow, buildCommandComplete, buildErrorResponse, buildReadyForQuery, buildParameterStatus, buildBackendKeyData, buildParseComplete, buildBindComplete, buildCloseComplete, buildNoData, buildParameterDesc, TYPE_OIDS, pgTypeOid, pgFormatValue };
|