jsql-neo 6.4.0-beta.8 → 6.4.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 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.8** — 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** — 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.8 — Rust-Powered Embedded Database (WASM + HTTP)
2
+ * JSQL-NEO v6.4.0 — Rust-Powered Embedded Database (WASM + HTTP)
3
3
  *
4
4
  * @example
5
5
  * const jsql = require('jsql-neo');
package/lib/btree.js CHANGED
@@ -171,9 +171,10 @@ class BTree {
171
171
  for (let i = 0; i < node.keys.length; i++) {
172
172
  const k = node.keys[i];
173
173
  const s = k === undefined || k === null ? '' : String(k);
174
- if (s < p) continue;
175
- if (!s.startsWith(p)) return result;
176
- result.push(...node.values[i]);
174
+ // 不能假设键按字典序排序(数字键按数值序),必须检查所有键
175
+ if (s.startsWith(p)) {
176
+ result.push(...node.values[i]);
177
+ }
177
178
  }
178
179
  node = node.next;
179
180
  }
@@ -326,13 +327,14 @@ class BTree {
326
327
  const left = parent.children[i - 1];
327
328
  const right = parent.children[i];
328
329
  if (right.leaf) {
329
- // 叶子节点:分隔键是叶子的副本,用被移动的键而非旧分隔键,避免重复
330
+ // 叶子节点:移动左叶最大键到右叶最小位置
330
331
  const k = left.keys.pop();
331
332
  const v = left.values.pop();
332
333
  right.keys.unshift(k);
333
334
  right.values.unshift(v);
334
- parent.keys[i - 1] = k;
335
- parent.values[i - 1] = v;
335
+ // 父节点分隔键应为左叶新的最大值(pop 后的最后一个键)
336
+ parent.keys[i - 1] = left.keys[left.keys.length - 1];
337
+ parent.values[i - 1] = left.values[left.values.length - 1];
336
338
  } else {
337
339
  right.keys.unshift(parent.keys[i - 1]);
338
340
  right.values.unshift(parent.values[i - 1]);
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/query.js CHANGED
@@ -675,6 +675,12 @@ class Query {
675
675
  for (const { field, dir } of this._orderBy) {
676
676
  const va = a[field];
677
677
  const vb = b[field];
678
+ // MySQL 语义:NULL 在 ASC 时排在最前,DESC 时排在最后
679
+ const aNull = va === null || va === undefined;
680
+ const bNull = vb === null || vb === undefined;
681
+ if (aNull && bNull) continue;
682
+ if (aNull) return dir === 'asc' ? -1 : 1;
683
+ if (bNull) return dir === 'asc' ? 1 : -1;
678
684
  if (va < vb) return dir === 'asc' ? -1 : 1;
679
685
  if (va > vb) return dir === 'asc' ? 1 : -1;
680
686
  }