jsql-neo 6.4.0-beta.12 → 6.4.0-beta.13

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 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, **and the browser**.
5
5
 
6
- > **v6.4.0-beta.12** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo) · [jsql.vexify.top](https://jsql.vexify.top)
6
+ > **v6.4.0-beta.13** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo) · [jsql.vexify.top](https://jsql.vexify.top)
7
7
 
8
8
  ![Engines](https://img.shields.io/badge/engines-Native%20%7C%20WASM%20%7C%20Pure%20JS-7ee787)
9
9
  ![MySQL](https://img.shields.io/badge/protocol-MySQL%20compatible-1f6feb)
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * JSQL-NEO v6.4.0-beta.12 — Rust-Powered Embedded Database (WASM + HTTP)
2
+ * JSQL-NEO v6.4.0-beta.13 — Rust-Powered Embedded Database (WASM + HTTP)
3
3
  *
4
4
  * @example
5
5
  * const jsql = require('jsql-neo');
package/lib/database.js CHANGED
@@ -2406,6 +2406,15 @@ class Database {
2406
2406
  this._savepointStack = [];
2407
2407
  if (!snap) return;
2408
2408
  this._restoreTxSnapshot(snap);
2409
+ // 修复 B1:rollback 后必须落盘,否则事务期间已 flush 到磁盘的写入
2410
+ // 不会被撤销,kill -9 重启后数据不一致(ACID 原子性被破坏)。
2411
+ // dirMode 下事务写入已逐表 flush,rollback 后需重新 flush 恢复后的数据;
2412
+ // 非 dirMode 下 commitTx 会 save(),rollback 也要 save() 保持一致。
2413
+ if (this._dirMode) {
2414
+ try { this._flushDirty(); } catch (e) { this._emit('error', e); }
2415
+ } else if (!this._memoryMode) {
2416
+ this.save();
2417
+ }
2409
2418
  }
2410
2419
 
2411
2420
  // ============================================================
@@ -21,10 +21,8 @@ const { MongoServer } = require('./mongo_server');
21
21
 
22
22
  function sniffProtocol(buf) {
23
23
  if (buf.length < 1) return null;
24
- const b = buf[0];
25
- if (b === 0x00) return 'pg';
26
- if (b === 0x0a || b === 0x0d) return 'mysql';
27
- // MongoDB: header 前 4 字节 = int32LE 长度,第 13 字节 = opCode(OP_QUERY 2004 / OP_COMPRESSED 2012 / OP_MSG 2013)
24
+ // MongoDB 先检查:opCode 校验更具体,避免消息长度为 256 倍数时
25
+ // 首字节 0x00 被误判为 PG(PG 也用 0x00 开头)
28
26
  if (buf.length >= 16) {
29
27
  const opc = buf.readInt32LE(12);
30
28
  if (opc === 2012 || opc === 2013 || opc === 2004) {
@@ -32,6 +30,9 @@ function sniffProtocol(buf) {
32
30
  if (len >= 16 && len <= 48 * 1024 * 1024) return 'mongo';
33
31
  }
34
32
  }
33
+ const b = buf[0];
34
+ if (b === 0x00) return 'pg';
35
+ if (b === 0x0a || b === 0x0d) return 'mysql';
35
36
  return 'redis';
36
37
  }
37
38
 
package/lib/sql.js CHANGED
@@ -3621,6 +3621,71 @@ function applyScalarFunction(fnNode, row, ctx) {
3621
3621
  return v.toString(16);
3622
3622
  });
3623
3623
  case 'DATABASE': case 'SCHEMA': return row && row.__db !== undefined ? row.__db : 'default';
3624
+ // ===== JSON 函数(B2 修复:此前未实现,静默返回 NULL)=====
3625
+ case 'JSON_OBJECT': {
3626
+ // JSON_OBJECT(k1, v1, k2, v2, ...) → { k1: v1, k2: v2 }
3627
+ const obj = {};
3628
+ for (let i = 0; i + 1 < args.length; i += 2) {
3629
+ const k = String(args[i] == null ? '' : args[i]);
3630
+ const v = args[i + 1];
3631
+ obj[k] = v === undefined ? null : v;
3632
+ }
3633
+ return obj;
3634
+ }
3635
+ case 'JSON_ARRAY': {
3636
+ // JSON_ARRAY(v1, v2, ...) → [v1, v2]
3637
+ return args.map(v => v === undefined ? null : v);
3638
+ }
3639
+ case 'JSON_EXTRACT': {
3640
+ // JSON_EXTRACT(json, '$.key' or '$.a.b') → 值
3641
+ const json = args[0];
3642
+ const path = args[1] == null ? '' : String(args[1]);
3643
+ if (json == null || path == null) return null;
3644
+ // 解析 JSON 字符串或对象
3645
+ let data = json;
3646
+ if (typeof json === 'string') {
3647
+ try { data = JSON.parse(json); } catch (e) { return null; }
3648
+ }
3649
+ // 解析 JSONPath: $.key 或 $.a.b 或 $.a[0].b
3650
+ const m = path.match(/^\$\.?([^.\[]+)/);
3651
+ if (!m) {
3652
+ // 尝试 $ 开头后直接跟 key
3653
+ if (path === '$') return data;
3654
+ return null;
3655
+ }
3656
+ const firstKey = m[1];
3657
+ if (firstKey === '') return data;
3658
+ let current = data;
3659
+ // 逐段解析路径
3660
+ const segments = path.replace(/^\$/, '').split('.');
3661
+ for (const seg of segments) {
3662
+ if (current == null) return null;
3663
+ // 处理数组索引: key[0] 或 [0]
3664
+ const arrMatch = seg.match(/^([^\[]*)\[(\d+)\]$/);
3665
+ if (arrMatch) {
3666
+ const key = arrMatch[1];
3667
+ const idx = parseInt(arrMatch[2], 10);
3668
+ if (key) current = current[key];
3669
+ if (Array.isArray(current)) current = current[idx];
3670
+ else return null;
3671
+ } else {
3672
+ if (typeof current !== 'object' || current === null) return null;
3673
+ current = current[seg];
3674
+ }
3675
+ }
3676
+ return current === undefined ? null : current;
3677
+ }
3678
+ case 'JSON_CONTAINS': {
3679
+ // JSON_CONTAINS(json, value) → 1 or 0
3680
+ const json = args[0];
3681
+ const val = args[1];
3682
+ if (json == null) return null;
3683
+ let data = json;
3684
+ if (typeof json === 'string') {
3685
+ try { data = JSON.parse(json); } catch (e) { return null; }
3686
+ }
3687
+ return JSON.stringify(data).includes(JSON.stringify(val)) ? 1 : 0;
3688
+ }
3624
3689
  default:
3625
3690
  return null;
3626
3691
  }
@@ -6176,7 +6241,30 @@ class SQLExecutor {
6176
6241
  all = [{ _virtual: true }];
6177
6242
  } else {
6178
6243
  const items = statement.from.tables.concat(statement.from.joins.map(j => j.item));
6179
- const prefix = item => item.alias || (item.subquery ? item.alias : item.table);
6244
+ // B5 修复:自连接(同表多次出现且无别名)时,用 t1/t2 等自动别名(MySQL 惯例),
6245
+ // 而非直接用表名。不同表无别名时仍用表名,保持与 SQL 查询中的表名限定一致。
6246
+ const tableCount = new Map();
6247
+ for (const item of items) {
6248
+ if (item.table) {
6249
+ const c = (tableCount.get(item.table) || 0) + 1;
6250
+ tableCount.set(item.table, c);
6251
+ }
6252
+ }
6253
+ const autoAliasMap = new Map();
6254
+ let autoIdx = 0;
6255
+ const prefix = item => {
6256
+ if (item.alias) return item.alias;
6257
+ if (item.subquery) return item.subquery.alias || item.alias;
6258
+ // 无别名且同表出现多次时生成 t1, t2;否则用表名
6259
+ if (item.table && (tableCount.get(item.table) || 0) > 1) {
6260
+ if (!autoAliasMap.has(item)) {
6261
+ autoIdx++;
6262
+ autoAliasMap.set(item, `t${autoIdx}`);
6263
+ }
6264
+ return autoAliasMap.get(item);
6265
+ }
6266
+ return item.table;
6267
+ };
6180
6268
 
6181
6269
  // 读第一表
6182
6270
  const firstItem = statement.from.tables[0];
@@ -6207,10 +6295,10 @@ class SQLExecutor {
6207
6295
  // 递归 CTE 迭代中:自身引用绑定到已累积的行
6208
6296
  rowsAll = { rows: firstItem._virtualRows.map(r => Object.assign({}, r)), schema: null, columns: null };
6209
6297
  } else {
6210
- rowsAll = await this._readTable(firstItem.table, statement, firstItem.alias || firstItem.table);
6298
+ rowsAll = await this._readTable(firstItem.table, statement, prefix(firstItem));
6211
6299
  }
6212
6300
  schema = rowsAll.schema;
6213
- const firstPrefix = firstItem.alias || firstItem.table;
6301
+ const firstPrefix = prefix(firstItem);
6214
6302
  // 虚拟行分两种,语义不同:
6215
6303
  // - _virtualRows:递归 CTE **迭代过程内部**,FROM 是自身引用,列名不带前缀
6216
6304
  // (否则递归项里的 `n+1` 取不到值)
@@ -6238,8 +6326,8 @@ class SQLExecutor {
6238
6326
  ? { rows: (j.item._virtualRows || j.item.subquery._virtualSource).map(r => Object.assign({}, r)) }
6239
6327
  : j.item.subquery
6240
6328
  ? await this.executeSelect(j.item.subquery)
6241
- : await this._readTable(j.item.table, statement, j.item.alias || j.item.table);
6242
- const rightPrefix = j.item.alias || j.item.table;
6329
+ : await this._readTable(j.item.table, statement, prefix(j.item));
6330
+ const rightPrefix = prefix(j.item);
6243
6331
  // 迭代内部的虚拟行不加前缀;物化后的派生表要加(与普通子查询一致)
6244
6332
  const jRaw = j.item._virtualRows
6245
6333
  ? j.item._virtualRows
@@ -6515,7 +6603,16 @@ class SQLExecutor {
6515
6603
 
6516
6604
  if (statement.limit !== null) {
6517
6605
  const start = statement.offset || 0;
6518
- rows = rows.slice(start, start + statement.limit);
6606
+ // B5 修复:超大 LIMIT 时 start + limit 可能超过 Number.MAX_SAFE_INTEGER,
6607
+ // 导致 JS 精度丢失,slice 行为异常。这里加友好错误处理。
6608
+ if (statement.limit > rows.length - start) {
6609
+ // LIMIT 超过实际行数是合法的,直接返回全部剩余行
6610
+ rows = rows.slice(start);
6611
+ } else if (start + statement.limit > Number.MAX_SAFE_INTEGER) {
6612
+ throw new Error(`LIMIT value ${statement.limit} is too large; maximum safe value is ${Number.MAX_SAFE_INTEGER - start}`);
6613
+ } else {
6614
+ rows = rows.slice(start, start + statement.limit);
6615
+ }
6519
6616
  }
6520
6617
 
6521
6618
  // 无分组、无聚合的正常输出
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "6.4.0-beta.12",
3
+ "version": "6.4.0-beta.13",
4
4
  "description": "JSQL-NEO — Rust-powered embedded database with WASM, REST API, B-Tree indexes, WAL, crash recovery",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
package/postinstall.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const { execSync } = require('child_process');
2
+ const crypto = require('crypto');
2
3
  const fs = require('fs');
3
4
  const path = require('path');
4
5
  const http = require('http');
@@ -18,20 +19,16 @@ const NATIVE_PATH = path.join(ROOT, 'native', 'jsql-neo-native.node');
18
19
  // https://<host>/https://github.com/owner/repo/releases/download/vX.Y.Z/asset
19
20
  // 鉴权走 `Authorization: Bearer <token>` 请求头(Worker 从该头读取,不读 ?token=)。
20
21
  // 前缀可用 JSQL_GHPROXY 覆盖(逗号分隔多个,按序回退;'' 表示直连兜底);
21
- // token 可用 JSQL_GHPROXY_TOKEN 覆盖。
22
+ // token 仅从环境变量 JSQL_GHPROXY_TOKEN 读取,不再内嵌硬编码 token。
22
23
  // 加速站候选(按优先级):自建站优先,公共站回退,最后直连兜底。
23
24
  const DEFAULT_PROXIES = [
24
25
  'https://gh-proxyjsql-neoworkerapi.vexify.de5.net/',
25
26
  'https://gh-proxy.com/',
26
27
  ];
27
- // 内置 token:让所有安装者都自动走自建加速站,无需配置环境变量。
28
- // 加速站自身已限制只能代理 /releases/download/ 等文件下载路径,故该值公开无风险。
29
- const DEFAULT_GHPROXY_TOKEN =
30
- 'NkUbD165V_1iPvEeGRcAfNEv4cqv5WRLn1NfjgBzIMpo9gq5CBH10Rl5ZPmt7RGCh3kyaZU4Nc6pLhHHbvzc9w';
31
28
  const GHPROXY = process.env.JSQL_GHPROXY !== undefined
32
29
  ? process.env.JSQL_GHPROXY
33
30
  : DEFAULT_PROXIES[0];
34
- const GHPROXY_TOKEN = process.env.JSQL_GHPROXY_TOKEN || DEFAULT_GHPROXY_TOKEN;
31
+ const GHPROXY_TOKEN = process.env.JSQL_GHPROXY_TOKEN || '';
35
32
  const TAG = `v${pkg.version}`;
36
33
 
37
34
  /** 加速站鉴权头 */
@@ -39,6 +36,32 @@ function proxyHeaders() {
39
36
  return GHPROXY_TOKEN ? { Authorization: 'Bearer ' + GHPROXY_TOKEN } : undefined;
40
37
  }
41
38
 
39
+ /**
40
+ * 验证文件 SHA256 校验和。
41
+ * 从环境变量 JSQL_NATIVE_SHA256 读取期望值(hex 编码,64 字符)。
42
+ * 若环境变量未设置则跳过校验(向后兼容),但打印警告。
43
+ */
44
+ function verifyChecksum(filePath) {
45
+ const expected = process.env.JSQL_NATIVE_SHA256 || '';
46
+ if (!expected) {
47
+ console.warn('[jsql-neo] WARNING: JSQL_NATIVE_SHA256 not set, skipping checksum verification');
48
+ return true;
49
+ }
50
+ try {
51
+ const actual = crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
52
+ if (actual !== expected.toLowerCase()) {
53
+ console.error(`[jsql-neo] checksum mismatch: expected ${expected}, got ${actual}`);
54
+ try { fs.unlinkSync(filePath); } catch (_) {}
55
+ return false;
56
+ }
57
+ console.log('[jsql-neo] checksum verified');
58
+ return true;
59
+ } catch (e) {
60
+ console.warn('[jsql-neo] checksum verification failed:', e.message);
61
+ return false;
62
+ }
63
+ }
64
+
42
65
  /**
43
66
  * 生成候选下载地址列表(按优先级):
44
67
  * JSQL_GHPROXY 指定的代理 → 内置加速站 → 直连
@@ -143,6 +166,10 @@ async function downloadAndVerify() {
143
166
  try {
144
167
  // 加速站鉴权走 Authorization 头,直连时不带
145
168
  await download(cand.url, NATIVE_PATH, cand.viaProxy ? proxyHeaders() : undefined);
169
+ if (!verifyChecksum(NATIVE_PATH)) {
170
+ console.warn('[jsql-neo] checksum mismatch; trying next source.');
171
+ continue;
172
+ }
146
173
  if (tryLoadNative()) {
147
174
  console.log('[jsql-neo] native module downloaded: native/jsql-neo-native.node');
148
175
  return true;
@@ -156,11 +183,15 @@ async function downloadAndVerify() {
156
183
  }
157
184
 
158
185
  async function ensureNativeNode() {
159
- // 已存在且能加载 → 直接用;否则删掉并从 gh-proxy.com 下载,找不到才回退源码编译
186
+ // 已存在且能加载 → 直接用;否则删掉并重新获取
160
187
  if (tryLoadNative()) {
161
188
  console.log('[jsql-neo] native module ready: native/jsql-neo-native.node');
162
189
  return;
163
190
  }
191
+ // 下载前检查是否已存在(即使不能加载也先校验,避免重复下载)
192
+ if (fs.existsSync(NATIVE_PATH)) {
193
+ try { fs.unlinkSync(NATIVE_PATH); } catch (_) {}
194
+ }
164
195
  if (await downloadAndVerify()) return;
165
196
  buildNativeFromSource();
166
197
  }
@@ -200,4 +231,4 @@ async function main() {
200
231
  ensureServerBinary();
201
232
  }
202
233
 
203
- main();
234
+ main();