jsql-neo 6.5.25 → 6.5.26
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 +1 -1
- package/index.js +1 -1
- package/lib/sql.js +25 -0
- package/package.json +3 -3
- package/test/result-raw.test.js +93 -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, **and the browser**.
|
|
5
5
|
|
|
6
|
-
> **v6.5.
|
|
6
|
+
> **v6.5.26** — 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
|

|
|
9
9
|

|
package/index.js
CHANGED
package/lib/sql.js
CHANGED
|
@@ -8263,6 +8263,31 @@ function buildResultEnvelope(stmt, r, durationMs) {
|
|
|
8263
8263
|
: (typeof out.affectedRows === 'number' ? out.affectedRows : 0);
|
|
8264
8264
|
}
|
|
8265
8265
|
if (out.warnings === undefined) out.warnings = [];
|
|
8266
|
+
/*
|
|
8267
|
+
* result.raw 的原始契约:与 columns / rows 指向**同一份**结果,只是对象形式。
|
|
8268
|
+
*
|
|
8269
|
+
* 证据在 lib/prepared.js 的 get():raw 缺失时它会用 columns + rows[0]
|
|
8270
|
+
* 现场拼出等价对象 —— 说明 raw 本就该是「columns × rows 的 zip」。
|
|
8271
|
+
*
|
|
8272
|
+
* 但 executeSelect 的多个分支直接把「投影前的源行」塞进 raw
|
|
8273
|
+
* (rows: mapped, raw: rows)。于是聚合 / 计算列 / 无 FROM 查询里,
|
|
8274
|
+
* raw 变成 undefined、{_virtual:true},或是缺了计算列的源行对象
|
|
8275
|
+
* (GROUP BY 还会泄露内部 _group)。见 issue #43。
|
|
8276
|
+
*
|
|
8277
|
+
* 与其逐个分支改,不如在统一出口兑现契约:只要结果是 SELECT 且有
|
|
8278
|
+
* columns + rows,就从投影后的元组重建对象形式。非元组行保持原样。
|
|
8279
|
+
*/
|
|
8280
|
+
if (t === 'select' && Array.isArray(out.columns) && Array.isArray(out.rows)) {
|
|
8281
|
+
const cols = out.columns;
|
|
8282
|
+
out.raw = out.rows.map((row, i) => {
|
|
8283
|
+
if (!Array.isArray(row)) {
|
|
8284
|
+
return (Array.isArray(out.raw) && out.raw[i] !== undefined) ? out.raw[i] : row;
|
|
8285
|
+
}
|
|
8286
|
+
const obj = {};
|
|
8287
|
+
for (let j = 0; j < cols.length; j++) obj[cols[j]] = row[j];
|
|
8288
|
+
return obj;
|
|
8289
|
+
});
|
|
8290
|
+
}
|
|
8266
8291
|
if (out.durationMs === undefined) out.durationMs = durationMs;
|
|
8267
8292
|
if (out.message === undefined) {
|
|
8268
8293
|
out.message = t === 'select'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsql-neo",
|
|
3
|
-
"version": "6.5.
|
|
3
|
+
"version": "6.5.26",
|
|
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",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"scripts": {
|
|
51
51
|
"postinstall": "node postinstall.js",
|
|
52
52
|
"build:native": "node scripts/build-native.js",
|
|
53
|
-
"test": "node test/smoke.js && node test/native.test.js && node test/json-functions.test.js && node test/sql-parser.test.js && node test/readme-audit.test.js && node test/query-opt.test.js && node test/wal-recovery.test.js && node test/mysql-compat-hijack.test.js && node test/dml-constraints.test.js && node test/star-nag.test.js && node test/plugin-xql.test.js && node test/bug-regression-2.test.js && node test/issue-34-35-36.test.js && node test/issue-g4.test.js && node test/issue-38.test.js && node test/issue-39.test.js && node test/issue-40.test.js",
|
|
53
|
+
"test": "node test/smoke.js && node test/result-raw.test.js && node test/native.test.js && node test/json-functions.test.js && node test/sql-parser.test.js && node test/readme-audit.test.js && node test/query-opt.test.js && node test/wal-recovery.test.js && node test/mysql-compat-hijack.test.js && node test/dml-constraints.test.js && node test/star-nag.test.js && node test/plugin-xql.test.js && node test/bug-regression-2.test.js && node test/issue-34-35-36.test.js && node test/issue-g4.test.js && node test/issue-38.test.js && node test/issue-39.test.js && node test/issue-40.test.js",
|
|
54
54
|
"test:parser": "node test/sql-parser.test.js",
|
|
55
55
|
"test:btree": "node test/btree.test.js",
|
|
56
56
|
"test:native": "node test/native.test.js",
|
|
57
57
|
"test:regress": "node test/regress-5.1.0.js",
|
|
58
|
-
"test:all": "node test/smoke.js && node test/sql-parser.test.js && node test/readme-audit.test.js && node test/native.test.js && node test/native-multi-instance.test.js && node test/wal-recovery.test.js && node test/mysql-compat-hijack.test.js && node test/wasm.test.js && node test/coverage.js && node test/regress-5.1.0.js && node test/join.test.js && node test/btree.test.js && node test/dml-constraints.test.js && node test/savepoint.test.js && node test/alter-table.test.js && node test/null-semantics.test.js && node test/identifier-keywords.test.js && node test/issue-34-35-36.test.js && node test/sql-semantics.test.js && node test/extra-coverage.test.js && node test/filter-compile.test.js && node test/recursive-cte.test.js && node test/index-ddl.test.js && node test/mongo-group.test.js && node test/multiprotocol.test.js && node test/issue-g4.test.js && node test/issue-38.test.js && node test/issue-39.test.js && node test/issue-40.test.js",
|
|
58
|
+
"test:all": "node test/smoke.js && node test/result-raw.test.js && node test/sql-parser.test.js && node test/readme-audit.test.js && node test/native.test.js && node test/native-multi-instance.test.js && node test/wal-recovery.test.js && node test/mysql-compat-hijack.test.js && node test/wasm.test.js && node test/coverage.js && node test/regress-5.1.0.js && node test/join.test.js && node test/btree.test.js && node test/dml-constraints.test.js && node test/savepoint.test.js && node test/alter-table.test.js && node test/null-semantics.test.js && node test/identifier-keywords.test.js && node test/issue-34-35-36.test.js && node test/sql-semantics.test.js && node test/extra-coverage.test.js && node test/filter-compile.test.js && node test/recursive-cte.test.js && node test/index-ddl.test.js && node test/mongo-group.test.js && node test/multiprotocol.test.js && node test/issue-g4.test.js && node test/issue-38.test.js && node test/issue-39.test.js && node test/issue-40.test.js",
|
|
59
59
|
"test:coverage": "c8 --all --include='lib/**' --exclude='lib/web_ui.js' --reporter=text --reporter=lcov node test/coverage.js",
|
|
60
60
|
"test:orms": "node examples/orms/run-all.js",
|
|
61
61
|
"test:dml": "node test/dml-constraints.test.js",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* ---- result.raw:对象形式必须与 columns / rows 指向同一份结果 ---- */
|
|
2
|
+
/*
|
|
3
|
+
* 原始契约的证据在 lib/prepared.js 的 get():raw 缺失时它会用
|
|
4
|
+
* columns + rows[0] 现场拼出等价对象。也就是说 raw 本该是
|
|
5
|
+
* 「columns × rows 的 zip」,而不是投影前的源行。
|
|
6
|
+
*
|
|
7
|
+
* 此前 executeSelect 各分支直接把源行塞进 raw(rows: mapped, raw: rows),
|
|
8
|
+
* 于是聚合 / 计算列 / 无 FROM 查询里 raw 变成 undefined、{_virtual:true},
|
|
9
|
+
* 或是缺了计算列的源行对象,GROUP BY 还会泄露内部 _group。见 issue #43。
|
|
10
|
+
*
|
|
11
|
+
* 修复在 buildResultEnvelope(统一出口)重建 raw。这里把六种场景钉死,
|
|
12
|
+
* 并额外断言「键集合恰好等于 columns」——防止内部字段再漏出来。
|
|
13
|
+
*/
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const ROOT = path.join(__dirname, '..');
|
|
16
|
+
const { Database } = require(path.join(ROOT, 'index.js'));
|
|
17
|
+
|
|
18
|
+
let pass = 0, fail = 0;
|
|
19
|
+
function ok(name, cond, got) {
|
|
20
|
+
if (cond) { pass++; console.log('[OK] ' + name); }
|
|
21
|
+
else { fail++; console.log('[FAIL] ' + name + ' -> ' + JSON.stringify(got)); }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
(async () => {
|
|
25
|
+
const db = new Database(':memory:');
|
|
26
|
+
await db.createTable('users', {
|
|
27
|
+
id: 'integer primary key auto_increment',
|
|
28
|
+
name: 'string', age: 'integer', city: 'string',
|
|
29
|
+
});
|
|
30
|
+
await db.insert('users', [
|
|
31
|
+
{ name: 'Alice', age: 30, city: 'NY' },
|
|
32
|
+
{ name: 'Bob', age: 25, city: 'LA' },
|
|
33
|
+
{ name: 'Carol', age: 40, city: 'NY' },
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
// 每个用例同时验三件事:raw 的值、与 rows 等长、键集合恰好是 columns
|
|
37
|
+
async function check(label, sql, expected) {
|
|
38
|
+
const r = await db.query(sql);
|
|
39
|
+
const got = r.raw;
|
|
40
|
+
ok(label + ' · raw 内容', JSON.stringify(got) === JSON.stringify(expected), got);
|
|
41
|
+
ok(label + ' · raw 与 rows 等长',
|
|
42
|
+
Array.isArray(got) && got.length === r.rows.length,
|
|
43
|
+
{ rawLen: got && got.length, rowsLen: r.rows.length });
|
|
44
|
+
const keysMatch = Array.isArray(got) && got.every((obj) => {
|
|
45
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
46
|
+
const keys = Object.keys(obj).sort();
|
|
47
|
+
const cols = (r.columns || []).slice().sort();
|
|
48
|
+
return JSON.stringify(keys) === JSON.stringify(cols);
|
|
49
|
+
});
|
|
50
|
+
ok(label + ' · 键集合恰好等于 columns', keysMatch,
|
|
51
|
+
{ raw: got && got[0] && Object.keys(got[0]), columns: r.columns });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await check('纯聚合',
|
|
55
|
+
'SELECT COUNT(*) AS c, SUM(age) AS s FROM users',
|
|
56
|
+
[{ c: 3, s: 95 }]);
|
|
57
|
+
await check('GROUP BY 聚合',
|
|
58
|
+
'SELECT city, COUNT(*) c FROM users GROUP BY city ORDER BY city',
|
|
59
|
+
[{ city: 'LA', c: 1 }, { city: 'NY', c: 2 }]);
|
|
60
|
+
await check('无 FROM 表达式',
|
|
61
|
+
'SELECT 1/0 AS z', [{ z: null }]);
|
|
62
|
+
await check('标量子查询',
|
|
63
|
+
'SELECT (SELECT MAX(age) FROM users) AS m', [{ m: 40 }]);
|
|
64
|
+
await check('CASE 计算列',
|
|
65
|
+
"SELECT name, CASE WHEN age>35 THEN 'senior' END AS tier FROM users WHERE name='Bob'",
|
|
66
|
+
[{ name: 'Bob', tier: null }]);
|
|
67
|
+
await check('算术计算列',
|
|
68
|
+
"SELECT name, age*2 AS dbl FROM users WHERE name='Bob'",
|
|
69
|
+
[{ name: 'Bob', dbl: 50 }]);
|
|
70
|
+
await check('普通 SELECT *',
|
|
71
|
+
"SELECT * FROM users WHERE name='Bob'",
|
|
72
|
+
[{ id: 2, name: 'Bob', age: 25, city: 'LA' }]);
|
|
73
|
+
|
|
74
|
+
// 内部字段不能漏进公开结果(issue #43 里 _group 泄露)
|
|
75
|
+
const g = await db.query('SELECT city, COUNT(*) c FROM users GROUP BY city');
|
|
76
|
+
ok('GROUP BY raw 不泄露 _group',
|
|
77
|
+
Array.isArray(g.raw) && g.raw.every((o) => !Object.keys(o).some((k) => k.startsWith('_'))),
|
|
78
|
+
g.raw);
|
|
79
|
+
|
|
80
|
+
// prepared.get() 的原始 fallback 与 raw 必须等价
|
|
81
|
+
const p = db.prepare('SELECT COUNT(*) AS c FROM users');
|
|
82
|
+
const one = await p.get();
|
|
83
|
+
const q = await db.query('SELECT COUNT(*) AS c FROM users');
|
|
84
|
+
ok('prepare().get() 与 raw[0] 一致',
|
|
85
|
+
JSON.stringify(one) === JSON.stringify(q.raw[0]),
|
|
86
|
+
{ get: one, raw0: q.raw[0] });
|
|
87
|
+
|
|
88
|
+
console.log(`\n${pass} passed, ${fail} failed`);
|
|
89
|
+
process.exit(fail === 0 ? 0 : 1);
|
|
90
|
+
})().catch((e) => {
|
|
91
|
+
console.log('[FAIL] unexpected exception: ' + e.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|