jsql-neo 6.5.10 → 6.5.11

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.5.10** — stable release · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo) · [jsql.vexify.top](https://jsql.vexify.top)
6
+ > **v6.5.11** — stable release · [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.5.10 — Rust-Powered Embedded Database (WASM + HTTP)
2
+ * JSQL-NEO v6.5.11 — Rust-Powered Embedded Database (WASM + HTTP)
3
3
  *
4
4
  * @example
5
5
  * const jsql = require('jsql-neo');
package/lib/sql.js CHANGED
@@ -708,6 +708,11 @@ class Parser {
708
708
  break;
709
709
  } else {
710
710
  const col = this.parseTableName();
711
+ // 重复列名必须报错:对象键覆盖会静默丢列(schema[col] = def 直接覆盖),
712
+ // 用户 CREATE TABLE d(a INT, a INT) 会悄悄变成单列 a。
713
+ if (schema[col] !== undefined) {
714
+ throw new Error(`Duplicate column name '${col}' in table '${name}'`);
715
+ }
711
716
  const def = this.parseColumnDef();
712
717
  schema[col] = def;
713
718
  if (def.primaryKey) hasPk = true;
@@ -755,6 +760,8 @@ class Parser {
755
760
  const mapped = typeMap[type];
756
761
  if (!mapped) throw new Error(`Unsupported column type: ${typeTok.value}`);
757
762
  def.type = mapped;
763
+ // JSON / JSONB 列保留原始标记,供数据层校验非法 JSON({bad json 这类字符串会被 JSON.parse 拒绝)
764
+ if (type === 'json' || type === 'jsonb') def.jsonType = true;
758
765
  if (/^(BIG)?SERIAL$/i.test(typeRaw)) { def.autoIncrement = true; def.required = true; }
759
766
 
760
767
  // 长度限制: TEXT(255) / VARCHAR(100) / INTEGER(11) ...
@@ -3110,10 +3117,6 @@ function resolveOperand(operand, row, ctx) {
3110
3117
  // 布尔类节点出现在"取值"位置时(如 IF(status = 'paid', ...)、SUM(IF(a > b,1,0))),
3111
3118
  // 求值成 true/false 供上层按真值判断
3112
3119
  case 'compare':
3113
- case 'and':
3114
- case 'or':
3115
- case 'xor':
3116
- case 'not':
3117
3120
  case 'in':
3118
3121
  case 'isNull':
3119
3122
  case 'isTruth':
@@ -3122,6 +3125,25 @@ function resolveOperand(operand, row, ctx) {
3122
3125
  case 'like':
3123
3126
  case 'between':
3124
3127
  return evaluateExpr(operand, row, ctx);
3128
+ case 'and': {
3129
+ const a = sqlTruthy(resolveOperand(operand.left, row, ctx));
3130
+ const b = sqlTruthy(resolveOperand(operand.right, row, ctx));
3131
+ return sqlAnd(a, b);
3132
+ }
3133
+ case 'or': {
3134
+ const a = sqlTruthy(resolveOperand(operand.left, row, ctx));
3135
+ const b = sqlTruthy(resolveOperand(operand.right, row, ctx));
3136
+ return sqlOr(a, b);
3137
+ }
3138
+ case 'xor': {
3139
+ const a = sqlTruthy(resolveOperand(operand.left, row, ctx));
3140
+ const b = sqlTruthy(resolveOperand(operand.right, row, ctx));
3141
+ return sqlXor(a, b);
3142
+ }
3143
+ case 'not': {
3144
+ const v = sqlTruthy(resolveOperand(operand.expr, row, ctx));
3145
+ return v === null ? null : (v ? 0 : 1);
3146
+ }
3125
3147
  case 'unit': {
3126
3148
  // 裸时间单位(MONTH / DAY …):优先当列取,取不到就返回单位名本身
3127
3149
  const lower = String(operand.name).toLowerCase();
@@ -4422,6 +4444,33 @@ function ciCompare(a, b, cs) {
4422
4444
  return as === bs ? 0 : (as < bs ? -1 : 1);
4423
4445
  }
4424
4446
 
4447
+ // ── 三值逻辑(SQL 标准:TRUE / FALSE / UNKNOWN)──────────────────────
4448
+ // NULL 参与布尔运算时结果不是 0/1,而是 UNKNOWN(用 null 表示)。
4449
+ // 此前 resolveOperand 没有 and/or/xor/not 分支,SELECT TRUE AND TRUE 落到
4450
+ // default 返回 undefined → 数值化成 0,所有布尔表达式全部失效。
4451
+ function sqlTruthy(v) {
4452
+ if (v === null || v === undefined) return null;
4453
+ if (typeof v === 'boolean') return v;
4454
+ if (typeof v === 'number') return v !== 0;
4455
+ if (typeof v === 'string') return v !== '' && !(Number(v) === 0 && String(v).trim() !== '');
4456
+ return Boolean(v);
4457
+ }
4458
+ // 三值真值表,入参已归一化为 true / false / null,返回 1 / 0 / null
4459
+ function sqlAnd(a, b) {
4460
+ if (a === false || b === false) return 0;
4461
+ if (a === null || b === null) return null;
4462
+ return 1;
4463
+ }
4464
+ function sqlOr(a, b) {
4465
+ if (a === true || b === true) return 1;
4466
+ if (a === null || b === null) return null;
4467
+ return 0;
4468
+ }
4469
+ function sqlXor(a, b) {
4470
+ if (a === null || b === null) return null;
4471
+ return a !== b ? 1 : 0;
4472
+ }
4473
+
4425
4474
  function evaluateExpr(expr, row, ctx) {
4426
4475
  if (expr === null || expr === undefined) return false;
4427
4476
  switch (expr.type) {
@@ -6347,6 +6396,18 @@ class SQLExecutor {
6347
6396
  walk(statement.where);
6348
6397
  walk(statement.having);
6349
6398
  walk(statement.orderBy);
6399
+ // ORDER BY 的列在解析器里是裸字符串(非 {type:'column'} 对象),walk 检不到。
6400
+ // 字符串可能是输出别名/位置序号(已在 known 里豁免),也可能是真实列;
6401
+ // 两者都不是 → 必须报错(此前静默忽略,用户 ORDER BY nonexistent_col 拿到原序)。
6402
+ if (Array.isArray(statement.orderBy)) {
6403
+ for (const o of statement.orderBy) {
6404
+ const c = o && o.column;
6405
+ if (typeof c !== 'string') continue; // 位置序号(数字)跳过
6406
+ const n = c.toLowerCase();
6407
+ const base = n.indexOf('.') >= 0 ? n.slice(n.indexOf('.') + 1) : n;
6408
+ if (!known.has(n) && !known.has(base)) throw new Error(`no such column: ${c}`);
6409
+ }
6410
+ }
6350
6411
  if (Array.isArray(statement.groupBy)) {
6351
6412
  // groupBy 是列名字符串数组
6352
6413
  for (const g of statement.groupBy) {
@@ -7876,6 +7937,30 @@ async function inferColumnTypes(engine, stmt, r) {
7876
7937
  });
7877
7938
  }
7878
7939
 
7940
+ // 结果里的大整数(> MAX_SAFE_INTEGER 的 BigInt)在 JSON.stringify / wasm 绑定下
7941
+ // 无法序列化("Do not know how to serialize a BigInt")。输出前递归转成字符串,
7942
+ // 保留精度且可序列化;内部运算仍走 BigInt,精度不丢。
7943
+ function finalizeBigInts(value, seen) {
7944
+ if (typeof value === 'bigint') return value.toString();
7945
+ if (Array.isArray(value)) {
7946
+ if (!seen) seen = new Set();
7947
+ if (seen.has(value)) return value;
7948
+ seen.add(value);
7949
+ for (let i = 0; i < value.length; i++) value[i] = finalizeBigInts(value[i], seen);
7950
+ return value;
7951
+ }
7952
+ if (value && typeof value === 'object') {
7953
+ // Buffer / Date / RegExp 等特殊对象不深入(Buffer 是 Uint8Array,逐字节递归会爆栈)
7954
+ if (value instanceof Buffer || value instanceof Date || value instanceof RegExp) return value;
7955
+ if (!seen) seen = new Set();
7956
+ if (seen.has(value)) return value;
7957
+ seen.add(value);
7958
+ for (const k of Object.keys(value)) value[k] = finalizeBigInts(value[k], seen);
7959
+ return value;
7960
+ }
7961
+ return value;
7962
+ }
7963
+
7879
7964
  async function executeSQL(engine, sql, paramsOrOpts, opts = {}) {
7880
7965
  // 参数守卫:engine 必须是引擎对象,sql 必须是字符串。误用时报清晰错误,
7881
7966
  // 而不是在后续像 `reading 'length'` 那样抛出令人费解的 TypeError。
@@ -7934,6 +8019,7 @@ async function executeSQL(engine, sql, paramsOrOpts, opts = {}) {
7934
8019
  let r = await executor.execute(stmt);
7935
8020
  const durationMs = Number(process.hrtime.bigint() - t0) / 1e6;
7936
8021
  r = buildResultEnvelope(stmt, r, Math.round(durationMs * 1000) / 1000);
8022
+ r = finalizeBigInts(r);
7937
8023
  if (r && r.columns && r.columnTypes === undefined) {
7938
8024
  r.columnTypes = await inferColumnTypes(engine, stmt, r);
7939
8025
  }
package/lib/table.js CHANGED
@@ -1543,6 +1543,11 @@ class Table {
1543
1543
  else if (typeof v === 'number' || typeof v === 'boolean' || v instanceof Date) result[field] = String(v);
1544
1544
  else if (Buffer.isBuffer(v)) result[field] = v.toString('utf8');
1545
1545
  else throw createError('ER_TRUNCATED_WRONG_VALUE', t, v);
1546
+ } else if ((t === 'object' || t === 'array' || t === 'json') && (def.jsonType || t === 'json')) {
1547
+ // JSON / JSONB 列:字符串形式必须是合法 JSON({bad json 这类会被 JSON.parse 拒绝)
1548
+ if (typeof v === 'string') {
1549
+ try { JSON.parse(v); } catch (e) { throw createError('ER_TRUNCATED_WRONG_VALUE', t, v); }
1550
+ }
1546
1551
  }
1547
1552
  }
1548
1553
  return result;
@@ -561,6 +561,18 @@ class JSQL {
561
561
  // 难以定位的 "Cannot convert undefined or null to object",这里直接给明确 TypeError
562
562
  const arr = Array.isArray(data) ? data : [data];
563
563
  arr.forEach((row, i) => assertRowObject(row, i, arr.length > 1 || Array.isArray(data)));
564
+ // JSON / JSONB 列:字符串形式必须是合法 JSON({bad json 这类会被 JSON.parse 拒绝)
565
+ const schema = this._schemas[table];
566
+ if (schema) {
567
+ for (const row of arr) {
568
+ for (const [f, def] of Object.entries(schema)) {
569
+ if (def && def.jsonType && typeof row[f] === 'string') {
570
+ try { JSON.parse(row[f]); }
571
+ catch (e) { throw new Error(`Truncated incorrect object value: '${row[f]}'`); }
572
+ }
573
+ }
574
+ }
575
+ }
564
576
  if (!(await this._runHooks('beforeInsert', [table, arr]))) return [];
565
577
  if (arr.length > 1) {
566
578
  await this._flush();
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "6.5.10",
3
+ "version": "6.5.11",
4
4
  "description": "JSQL-NEO \u2014 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",
@@ -0,0 +1,87 @@
1
+ /*
2
+ * Bug 回归测试:此前发现的 5 个缺陷,逐一固化,防止回退。
3
+ *
4
+ * 1. 布尔求值器整体失效:TRUE AND TRUE → 0、NULL AND TRUE → 0、NULL OR FALSE → 0。
5
+ * 正确三值逻辑:1 / NULL / NULL。根因是 resolveOperand 没有 and/or/xor/not 分支,
6
+ * 落到 default 返回 undefined → 数值化成 0。
7
+ * 2. CREATE TABLE d(a INT, a INT) 不拒绝重复列名(对象键覆盖静默丢列)。
8
+ * 3. JSONB 列能塞进 {bad json 这种非法 JSON(两引擎:Database _validateDataTypes + wasm insert)。
9
+ * 4. ORDER BY 不存在的列静默忽略(解析产物是裸字符串,_validateColumnRefs 的 walk 检不到)。
10
+ * 5. SELECT 超大整数抛 "Do not know how to serialize a BigInt"(输出前应转字符串)。
11
+ */
12
+ const path = require('path');
13
+
14
+ const ROOT = path.join(__dirname, '..');
15
+ const { JSQL } = require(path.join(ROOT, 'index.js'));
16
+ const { executeSQL } = require(path.join(ROOT, 'lib/sql.js'));
17
+
18
+ const OPTS = { safety: false };
19
+ let pass = 0, fail = 0;
20
+
21
+ function ok(name, cond, extra) {
22
+ if (cond) { pass++; console.log('[OK] ', name); }
23
+ else { fail++; console.log('[FAIL]', name, extra !== undefined ? '-> ' + JSON.stringify(extra).slice(0, 150) : ''); }
24
+ }
25
+ const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
26
+
27
+ (async () => {
28
+ const db = new JSQL(':memory:');
29
+ await db.start();
30
+ const q = (sql) => executeSQL(db, sql, OPTS);
31
+ const rows = async (sql) => (await q(sql)).rows;
32
+ const one = async (sql) => (await q(sql)).rows[0][0];
33
+ const fails = async (sql) => { try { await q(sql); return null; } catch (e) { return e.message; } };
34
+
35
+ // ── 1. 三值布尔逻辑 ────────────────────────────────────────────
36
+ console.log('--- 1. 布尔三值逻辑 ---');
37
+ ok('TRUE AND TRUE = 1', (await one('SELECT TRUE AND TRUE')) === 1);
38
+ ok('TRUE AND FALSE = 0', (await one('SELECT TRUE AND FALSE')) === 0);
39
+ ok('NULL AND TRUE = NULL', (await one('SELECT NULL AND TRUE')) === null);
40
+ ok('NULL AND FALSE = 0', (await one('SELECT NULL AND FALSE')) === 0);
41
+ ok('NULL OR FALSE = NULL', (await one('SELECT NULL OR FALSE')) === null);
42
+ ok('NULL OR TRUE = 1', (await one('SELECT NULL OR TRUE')) === 1);
43
+ ok('FALSE OR FALSE = 0', (await one('SELECT FALSE OR FALSE')) === 0);
44
+ ok('NOT TRUE = 0', (await one('SELECT NOT TRUE')) === 0);
45
+ ok('NOT NULL = NULL', (await one('SELECT NOT NULL')) === null);
46
+ ok('1 AND 0 = 0', (await one('SELECT 1 AND 0')) === 0);
47
+ ok('1 XOR 0 = 1', (await one('SELECT 1 XOR 0')) === 1);
48
+ ok('TRUE OR NULL = 1', (await one('SELECT TRUE OR NULL')) === 1);
49
+
50
+ // ── 2. CREATE TABLE 重复列名 ───────────────────────────────────
51
+ console.log('--- 2. 重复列名 ---');
52
+ ok('d(a INT, a INT) 报错', /Duplicate column/i.test(await fails('CREATE TABLE r1(a INT, a INT)')));
53
+ ok('d(x,y,x) 报错', /Duplicate column/i.test(await fails('CREATE TABLE r2(x INT, y INT, x INT)')));
54
+ ok('正常列不报错', (await fails('CREATE TABLE r3(a INT, b TEXT)')) === null);
55
+ ok('id PRIMARY KEY + id 重复报错', /Duplicate column/i.test(await fails('CREATE TABLE r4(id INT PRIMARY KEY, id INT)')));
56
+
57
+ // ── 3. JSONB 非法 JSON ─────────────────────────────────────────
58
+ console.log('--- 3. JSONB 校验 ---');
59
+ await q('CREATE TABLE rj(c JSONB)');
60
+ ok('{bad json 拒绝', /Truncated incorrect object value/.test(await fails(`INSERT INTO rj VALUES ('{bad json')`)));
61
+ ok('[1,2 拒绝', /Truncated incorrect object value/.test(await fails(`INSERT INTO rj VALUES ('[1,2')`)));
62
+ ok('合法 {"a":1} 通过', (await fails(`INSERT INTO rj VALUES ('{"a":1}')`)) === null);
63
+ await q('CREATE TABLE rj2(c JSON)');
64
+ ok('JSON 列 nope 拒绝', /Truncated incorrect object value/.test(await fails(`INSERT INTO rj2 VALUES ('nope')`)));
65
+
66
+ // ── 4. ORDER BY 不存在列 ───────────────────────────────────────
67
+ console.log('--- 4. ORDER BY 校验 ---');
68
+ await q('CREATE TABLE ro (id INT PRIMARY KEY, v INT, s TEXT)');
69
+ await q('INSERT INTO ro VALUES (1,2,\'x\'),(2,1,\'y\')');
70
+ ok('ORDER BY nonexistent_col 报错', /no such column: nonexistent_col/.test(await fails('SELECT id FROM ro ORDER BY nonexistent_col')));
71
+ ok('WHERE 不存在列报错', /no such column: nosuch/.test(await fails('SELECT id FROM ro WHERE nosuch > 1')));
72
+ ok('ORDER BY 真列 v 正常', eq(await rows('SELECT id FROM ro ORDER BY v'), [[2], [1]]));
73
+ ok('ORDER BY 位置序号 1 正常', eq(await rows('SELECT id FROM ro ORDER BY 1'), [[1], [2]]));
74
+ ok('ORDER BY 输出别名 vv 正常', eq(await rows('SELECT v AS vv FROM ro ORDER BY vv'), [[1], [2]]));
75
+
76
+ // ── 5. 超大整数序列化 ──────────────────────────────────────────
77
+ console.log('--- 5. 超大整数 ---');
78
+ const big = await one('SELECT 1000000000000000000000');
79
+ ok('超大整数可序列化(字符串)', typeof big === 'string' && big === '1000000000000000000000');
80
+ ok('超界精确值 9007199254740993', (await one('SELECT 9007199254740993')) === '9007199254740993');
81
+ const bigRows = await rows('SELECT 1000000000000000000000');
82
+ ok('JSON.stringify 不炸', (() => { try { JSON.stringify(bigRows); return true; } catch (e) { return false; } })());
83
+
84
+ await db.stop();
85
+ console.log(`\nRESULT: ${pass} passed, ${fail} failed`);
86
+ process.exit(fail ? 1 : 0);
87
+ })().catch((e) => { console.error('FATAL', e); process.exit(1); });