jsql-neo 5.3.0 → 5.3.1

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. The browser. **And it fits in one npm package.**
5
5
 
6
- > **v5.3.0** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
6
+ > **v5.3.1** — official release build · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
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)
@@ -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.3.0
235
+ node -e "console.log(require('jsql-neo/package.json').version)" # 5.3.1
236
236
  ```
237
237
 
238
238
  ### 30-second demo
@@ -1273,7 +1273,7 @@ jsql mod --engine wasm # switch engine (restart required)
1273
1273
 
1274
1274
  ```bash
1275
1275
  $ jsql version
1276
- jsql-neo v5.3.0
1276
+ jsql-neo v5.3.1
1277
1277
  engine: native (napi) | wasm | js
1278
1278
  node: v22.0.0 platform: linux x64
1279
1279
  ```
@@ -1294,7 +1294,7 @@ jsql tui --memory -q # memory mode, quiet
1294
1294
  jsql tui --prompt 'db> ' --no-color
1295
1295
  ```
1296
1296
 
1297
- The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=5.3.0`.
1297
+ The status bar shows: `db=<name> dialect=<d> mode=<tui|batch> ver=5.3.1`.
1298
1298
 
1299
1299
  ### Keyboard shortcuts
1300
1300
 
@@ -3858,7 +3858,7 @@ Data dir: /root/.jsql-neo/data
3858
3858
 
3859
3859
  ```bash
3860
3860
  $ jsql version
3861
- jsql-neo v5.3.0
3861
+ jsql-neo v5.3.1
3862
3862
  engine: native (napi) | wasm | js
3863
3863
  node: v22.0.0
3864
3864
  platform: linux x64
@@ -4754,7 +4754,7 @@ SOFTWARE.
4754
4754
 
4755
4755
  *JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
4756
4756
 
4757
- *文档版本:v5.3.0 · 最后更新:2026-08-12*
4757
+ *文档版本:v5.3.1 · 最后更新:2026-08-12*
4758
4758
 
4759
4759
  ---
4760
4760
 
@@ -6524,7 +6524,7 @@ Usage: jsql version
6524
6524
 
6525
6525
  输出版本与环境信息:
6526
6526
 
6527
- jsql-neo v5.3.0
6527
+ jsql-neo v5.3.1
6528
6528
  engine: native (napi) | wasm | js
6529
6529
  node: v22.0.0
6530
6530
  platform: linux x64
@@ -6788,6 +6788,14 @@ new TUIShell({
6788
6788
 
6789
6789
  ## 附录 N:版本历史 CHANGELOG
6790
6790
 
6791
+ ### v5.3.1 (2026-08-12)
6792
+
6793
+ **修复**
6794
+ - `MysqlConnection` 未导出导致多协议服务器 MySQL 路由崩溃(`lib/mysql_server.js` 补导出)
6795
+
6796
+ **变更**
6797
+ - 版本号升至 5.3.1(package.json / Mongo `buildInfo` 同步)
6798
+
6791
6799
  ### v5.3.0 (2026-08-12)
6792
6800
 
6793
6801
  **新增**
@@ -7146,7 +7154,7 @@ CI(GitHub Actions)矩阵:`node 20/22` × `linux/macos/windows` × `native/
7146
7154
 
7147
7155
  *JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
7148
7156
 
7149
- *文档版本:v5.3.0 · 共 19 个附录 · 最后更新:2026-08-12*
7157
+ *文档版本:v5.3.1 · 共 19 个附录 · 最后更新:2026-08-12*
7150
7158
 
7151
7159
  ---
7152
7160
 
@@ -7753,4 +7761,4 @@ npm test
7753
7761
 
7754
7762
  *JSQL-NEO — One engine to rule them all. MySQL. PostgreSQL. MongoDB. Redis. SQL. TypeScript. The browser.*
7755
7763
 
7756
- *文档版本:v5.3.0 · 附录 A–Z · 全文 6000+ 行 · 最后更新:2026-08-12*
7764
+ *文档版本:v5.3.1 · 附录 A–Z · 全文 6000+ 行 · 最后更新:2026-08-12*
@@ -322,7 +322,7 @@ class MongoServer {
322
322
  localTime: new Date(), logicalSessionTimeoutMinutes: 30, connectionId: 1,
323
323
  };
324
324
  case 'ping': return { ok: 1 };
325
- case 'buildInfo': return { ok: 1, version: '5.3.0-jsql-neo', gitVersion: 'jsql-neo', versionArray: [5, 3, 0, 0] };
325
+ case 'buildInfo': return { ok: 1, version: '5.3.1-jsql-neo', gitVersion: 'jsql-neo', versionArray: [5, 3, 1, 0] };
326
326
  case 'getParameter': return { ok: 1 };
327
327
  case 'endSessions': return { ok: 1 };
328
328
  case 'listDatabases':
package/lib/query.js CHANGED
@@ -537,7 +537,7 @@ class Query {
537
537
  const key = fr[join.foreignField];
538
538
  const matches = localRows.filter(lr => lr[join.localField] === key);
539
539
  if (matches.length === 0) {
540
- result.push({ ...fr, ...this._nullRow(join, true) });
540
+ result.push(this._rightNullRow(localRows, fr, join));
541
541
  } else {
542
542
  for (const lr of matches) {
543
543
  result.push(this._mergeJoinRow(lr, fr, join));
@@ -582,7 +582,7 @@ class Query {
582
582
  for (const fr of foreignRows) {
583
583
  const matches = localRows.filter(lr => lr[join.localField] === fr[join.foreignField]);
584
584
  if (matches.length === 0) {
585
- result.push({ ...fr, ...this._nullRow(join, true) });
585
+ result.push(this._rightNullRow(localRows, fr, join));
586
586
  } else {
587
587
  for (const lr of matches) {
588
588
  result.push(this._mergeJoinRow(lr, fr, join));
@@ -621,6 +621,24 @@ class Query {
621
621
  return nulls;
622
622
  }
623
623
 
624
+ /**
625
+ * RIGHT JOIN 未匹配右表行:保留右表数据,本地表字段填 null。
626
+ * 返回 { localNulls, merged },其中 merged = { ...nulls, ...fr }
627
+ */
628
+ _rightNullRow(localRows, foreignRow, join) {
629
+ const localSchema = this._table._schema || {};
630
+ const localNulls = {};
631
+ for (const field of Object.keys(localSchema)) {
632
+ if (field !== '_softDelete') localNulls[field] = null;
633
+ }
634
+ const prefix = join.as ? join.as + '_' : '';
635
+ const foreignPrefixed = {};
636
+ for (const [key, value] of Object.entries(foreignRow)) {
637
+ foreignPrefixed[prefix + key] = value;
638
+ }
639
+ return { ...localNulls, ...foreignPrefixed };
640
+ }
641
+
624
642
  // ============================================================
625
643
  // 内部: 排序
626
644
  // ============================================================
package/lib/sql.js CHANGED
@@ -261,12 +261,18 @@ class Parser {
261
261
  if (this.peek().type === 'op' && this.peek().value === '.') {
262
262
  this.next();
263
263
  const t2 = this.next();
264
- if (t2.type !== 'ident') throw new Error(`Expected table name after '.', got '${t2.value}'`);
264
+ if (t2.type !== 'ident' && !(t2.type === 'keyword' && this._isSchemaView(t2.value))) {
265
+ throw new Error(`Expected table name after '.', got '${t2.value}'`);
266
+ }
265
267
  return t.value + '.' + t2.value;
266
268
  }
267
269
  return t.value;
268
270
  }
269
271
 
272
+ _isSchemaView(v) {
273
+ return ['TABLES', 'COLUMNS', 'SCHEMATA', 'STATISTICS', 'KEY_COLUMN_USAGE', 'REFERENTIAL_CONSTRAINTS', 'TABLE_CONSTRAINTS', 'VIEWS'].includes(String(v).toUpperCase());
274
+ }
275
+
270
276
  parseCreateTable() {
271
277
  this.expectKeyword('CREATE');
272
278
  this.expectKeyword('TABLE');
@@ -1870,11 +1876,12 @@ class SQLExecutor {
1870
1876
  const schema = this.engine.getTableSchema
1871
1877
  ? await this.engine.getTableSchema(statement.table)
1872
1878
  : (this.engine._schemas ? this.engine._schemas[statement.table] : null);
1879
+ const pkCols = schema ? Object.keys(schema).filter(k => schema[k].primaryKey) : [];
1873
1880
  const all = (await this.engine.find(statement.table, {}, { limit: 1e9, offset: 0 })).map(r => normalizeRow(r, schema));
1874
1881
  let count = 0;
1875
1882
  for (const row of all) {
1876
1883
  if (!statement.where || evaluateExpr(statement.where, row, this.ctx)) {
1877
- const id = row._rid !== undefined ? row._rid : row.id;
1884
+ const id = this._rowPkId(row, pkCols);
1878
1885
  if (id !== undefined) {
1879
1886
  const data = {};
1880
1887
  for (const [col, val] of statement.assignments) {
@@ -1892,11 +1899,12 @@ class SQLExecutor {
1892
1899
  const schema = this.engine.getTableSchema
1893
1900
  ? await this.engine.getTableSchema(statement.table)
1894
1901
  : (this.engine._schemas ? this.engine._schemas[statement.table] : null);
1902
+ const pkCols = schema ? Object.keys(schema).filter(k => schema[k].primaryKey) : [];
1895
1903
  const all = (await this.engine.find(statement.table, {}, { limit: 1e9, offset: 0 })).map(r => normalizeRow(r, schema));
1896
1904
  const ids = [];
1897
1905
  for (const row of all) {
1898
1906
  if (!statement.where || evaluateExpr(statement.where, row, this.ctx)) {
1899
- const id = row._rid !== undefined ? row._rid : row.id;
1907
+ const id = this._rowPkId(row, pkCols);
1900
1908
  if (id !== undefined) ids.push(id);
1901
1909
  }
1902
1910
  }
@@ -2166,6 +2174,19 @@ class SQLExecutor {
2166
2174
  return schema;
2167
2175
  }
2168
2176
 
2177
+ /**
2178
+ * 取行的行 ID:优先内部 _rid,否则用实际主键字段值(不再硬编码 id)。
2179
+ */
2180
+ _rowPkId(row, pkCols) {
2181
+ if (row && row._rid !== undefined) return row._rid;
2182
+ if (pkCols.length > 0) {
2183
+ for (const c of pkCols) {
2184
+ if (row[c] !== undefined && row[c] !== null) return row[c];
2185
+ }
2186
+ }
2187
+ return row ? row.id : undefined;
2188
+ }
2189
+
2169
2190
  async _readTable(table) {
2170
2191
  const schema = await this._getSchema(table);
2171
2192
  const rows = (await this.engine.find(table, {}, { limit: 1e9, offset: 0 })).map(r => normalizeRow(r, schema));
@@ -2366,6 +2387,19 @@ class SQLExecutor {
2366
2387
  return out;
2367
2388
  }
2368
2389
 
2390
+ /**
2391
+ * 生成对端表的前缀 null 行:仅含 `prefix.col` 键(值为 null),
2392
+ * 用于 JOIN 未匹配行补齐限定列,避免回退到未前缀副本拿错值。
2393
+ */
2394
+ _nullPrefixedRow(schema, prefix) {
2395
+ const out = {};
2396
+ for (const k of Object.keys(schema)) {
2397
+ if (k === '_softDelete') continue;
2398
+ out[prefix + '.' + k] = null;
2399
+ }
2400
+ return out;
2401
+ }
2402
+
2369
2403
  _aggValue(rows, fn, column) {
2370
2404
  if (fn === 'COUNT') return rows.length;
2371
2405
  const op = typeof column === 'string' ? { type: 'column', name: column } : column;
@@ -2445,6 +2479,7 @@ class SQLExecutor {
2445
2479
 
2446
2480
  let schema = null;
2447
2481
  let all;
2482
+ let rowsAll;
2448
2483
  if (!statement.from) {
2449
2484
  // 无 FROM:虚拟行(SELECT 1, 'a')
2450
2485
  all = [{ _virtual: true }];
@@ -2469,7 +2504,6 @@ class SQLExecutor {
2469
2504
  const rows = filtered.map(r => outCols.map(c => (c in r ? r[c] : null)));
2470
2505
  return { ok: true, type: 'select', table: firstItem.table, columns: outCols, rows, raw: filtered };
2471
2506
  }
2472
- let rowsAll;
2473
2507
  if (firstItem.subquery) {
2474
2508
  const res = await this.executeSelect(firstItem.subquery);
2475
2509
  rowsAll = { rows: this._subQueryRows(res), schema: null, columns: res.columns };
@@ -2495,6 +2529,10 @@ class SQLExecutor {
2495
2529
  }
2496
2530
  const matched = [];
2497
2531
  const unmatchedRight = new Set(rightRows.map((r, i) => i));
2532
+ // 未匹配行补对端表的前缀 null 列:限定列名(如 a.id / b.id)按前缀解析,
2533
+ // 避免回退到未前缀副本拿到错误值。
2534
+ const rightNulls = (rightRes.schema) ? this._nullPrefixedRow(rightRes.schema, rightPrefix) : null;
2535
+ const leftNulls = (rowsAll && rowsAll.schema) ? this._nullPrefixedRow(rowsAll.schema, firstPrefix) : null;
2498
2536
  rows.forEach(l => {
2499
2537
  let m = null;
2500
2538
  for (let ri = 0; ri < rightRows.length; ri++) {
@@ -2507,11 +2545,11 @@ class SQLExecutor {
2507
2545
  matched.push({ ...l, ...rightRows[m] });
2508
2546
  unmatchedRight.delete(m);
2509
2547
  } else if (j.type === 'left') {
2510
- matched.push({ ...l });
2548
+ matched.push(rightNulls ? { ...rightNulls, ...l } : { ...l });
2511
2549
  }
2512
2550
  });
2513
2551
  if (j.type === 'right') {
2514
- for (const ri of unmatchedRight) matched.push({ ...rightRows[ri] });
2552
+ for (const ri of unmatchedRight) matched.push(leftNulls ? { ...leftNulls, ...rightRows[ri] } : { ...rightRows[ri] });
2515
2553
  }
2516
2554
  rows = matched;
2517
2555
  }
package/lib/table.js CHANGED
@@ -242,6 +242,23 @@ class Table {
242
242
  const schema = this._schema;
243
243
  const checkConstraints = this._checkConstraints;
244
244
  const foreignKeys = this._foreignKeys;
245
+ // 批量预分配自增 ID 范围:先扫描显式提供的最大值,一次性推进计数器,
246
+ // 再在循环内用本地序号递增,避免批量内重复读取/写入同一计数器字段。
247
+ const baseAutoInc = this._autoIncrement;
248
+ if (autoIncField) {
249
+ let maxExplicit = 0;
250
+ for (const it of items) {
251
+ const v = it[autoIncField];
252
+ if (v !== undefined && v !== null) {
253
+ const n = Number(v);
254
+ if (!isNaN(n) && n > maxExplicit) maxExplicit = n;
255
+ }
256
+ }
257
+ if (maxExplicit > baseAutoInc) {
258
+ this._autoIncrement = maxExplicit;
259
+ }
260
+ }
261
+ let autoIncSeq = 0;
245
262
  for (let i = 0; i < N; i++) {
246
263
  let data = items[i];
247
264
  for (const [f, dv] of Object.entries(defaults)) {
@@ -279,8 +296,9 @@ class Table {
279
296
  }
280
297
  }
281
298
  if (autoIncField && data[autoIncField] === undefined) {
282
- this._autoIncrement++;
283
- data[autoIncField] = this._autoIncrement;
299
+ autoIncSeq++;
300
+ data[autoIncField] = baseAutoInc + autoIncSeq;
301
+ this._autoIncrement = data[autoIncField];
284
302
  } else if (autoIncField && data[autoIncField] > this._autoIncrement) {
285
303
  this._autoIncrement = data[autoIncField];
286
304
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "5.3.0",
3
+ "version": "5.3.1",
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",