@axiosleo/orm-mysql 0.15.1 → 0.15.2
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/CHANGELOG.md +36 -0
- package/bin/orm-mysql.js +1 -1
- package/commands/skills.js +1 -1
- package/package.json +1 -1
- package/src/builder.js +48 -13
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.15.2 (2026-06-12)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Data correctness: `IN` conditions silently matched zero rows on the transaction path.**
|
|
8
|
+
|
|
9
|
+
In 0.15.0 / 0.15.1, `whereIn()` / `whereNotIn()` / `where(field, 'IN', array)` executed
|
|
10
|
+
through `TransactionHandler` silently returned an empty result set (SELECT) or affected
|
|
11
|
+
0 rows (UPDATE / DELETE), without any error. The same query worked fine through
|
|
12
|
+
`QueryHandler`.
|
|
13
|
+
|
|
14
|
+
Root cause: the SQL builder bound the whole array as a single value, producing
|
|
15
|
+
`WHERE \`id\` IN (?)` with values `[[1, 2]]`. The non-transaction path uses
|
|
16
|
+
`conn.query()` (client-side interpolation), which happens to expand array parameters
|
|
17
|
+
into `IN (1, 2)`. The transaction path uses `conn.execute()` (server-side prepared
|
|
18
|
+
statement), which never expands arrays, so the condition matched nothing.
|
|
19
|
+
|
|
20
|
+
Fix: `Builder` now emits one placeholder per array element (`IN (?,?)`) and binds flat
|
|
21
|
+
scalar values, so `query` and `execute` behave identically. The transaction path keeps
|
|
22
|
+
using prepared statements (`conn.execute`).
|
|
23
|
+
|
|
24
|
+
- The same array-as-single-bind pattern was fixed in the JSON branches: `key->'$.path'`
|
|
25
|
+
with `IN` / `NOT IN`, `CONTAIN` / `NOT CONTAIN` and `OVERLAPS` / `NOT OVERLAPS` now
|
|
26
|
+
expand arrays into `JSON_ARRAY(?,?,...)` with scalar bindings.
|
|
27
|
+
|
|
28
|
+
- Comma-separated string values for `IN` (e.g. `whereIn('id', '1,2,3')`, documented in
|
|
29
|
+
`index.d.ts`) previously threw `Value must be an array or sub-query for "IN" condition`;
|
|
30
|
+
they are now split, trimmed and expanded like arrays.
|
|
31
|
+
|
|
32
|
+
### Unchanged behavior
|
|
33
|
+
|
|
34
|
+
- Empty arrays (and now empty strings) for `IN` still throw
|
|
35
|
+
`Value must not be empty for "IN" condition`.
|
|
36
|
+
- `Query` sub-queries for `IN` still render the sub-select SQL.
|
package/bin/orm-mysql.js
CHANGED
package/commands/skills.js
CHANGED
|
@@ -118,7 +118,7 @@ class SkillsCommand extends Command {
|
|
|
118
118
|
printer.info(`Found ${PKG_NAME}@${source.version} in node_modules`);
|
|
119
119
|
} else if (source.outdated) {
|
|
120
120
|
printer.warning(`${PKG_NAME}@${source.localVersion} is installed locally but does not include skills files.`);
|
|
121
|
-
printer.warning('Skills files are available since v0.15.
|
|
121
|
+
printer.warning('Skills files are available since v0.15.2. Please update:');
|
|
122
122
|
printer.warning(` npm install ${PKG_NAME}@latest`);
|
|
123
123
|
printer.println();
|
|
124
124
|
printer.info(`Using skills from npx ${PKG_NAME}@${source.version} instead.`);
|
package/package.json
CHANGED
package/src/builder.js
CHANGED
|
@@ -334,6 +334,20 @@ class Builder {
|
|
|
334
334
|
return null;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Bind each array element as a single scalar value and return the matching
|
|
339
|
+
* placeholder list (e.g. "?,?,?"). Binding the whole array as one value only
|
|
340
|
+
* works with conn.query (client-side interpolation); conn.execute (prepared
|
|
341
|
+
* statement, used on the transaction path) does not expand arrays and would
|
|
342
|
+
* silently match nothing.
|
|
343
|
+
* @param {Array} values
|
|
344
|
+
* @returns {string}
|
|
345
|
+
*/
|
|
346
|
+
_buildArrayPlaceholders(values) {
|
|
347
|
+
values.forEach((item) => this.values.push(item));
|
|
348
|
+
return values.map(() => '?').join(',');
|
|
349
|
+
}
|
|
350
|
+
|
|
337
351
|
_buildConditionBetween(condition, isNot = false) {
|
|
338
352
|
if (!Array.isArray(condition.value) || condition.value.length !== 2) {
|
|
339
353
|
throw new Error('Value must be an array with two elements for "BETWEEN" condition');
|
|
@@ -353,22 +367,33 @@ class Builder {
|
|
|
353
367
|
}
|
|
354
368
|
|
|
355
369
|
_buildConditionIn(condition, isNot = false) {
|
|
356
|
-
|
|
370
|
+
// "1,2,3" is split into an array, then goes through the same expansion path as arrays
|
|
371
|
+
let v = is.string(condition.value)
|
|
372
|
+
? condition.value.split(',').map(s => s.trim()).filter(s => s.length)
|
|
373
|
+
: condition.value;
|
|
374
|
+
if (Array.isArray(v) && !v.length) {
|
|
357
375
|
throw new Error('Value must not be empty for "IN" condition');
|
|
358
|
-
} else if (!Array.isArray(
|
|
376
|
+
} else if (!Array.isArray(v) && !(v instanceof Query)) {
|
|
359
377
|
throw new Error('Value must be an array or sub-query for "IN" condition');
|
|
360
378
|
}
|
|
361
379
|
if (condition.key.indexOf('->') !== -1) {
|
|
362
380
|
let keys = condition.key.split('->');
|
|
363
381
|
let k = `${this._buildFieldKey(keys[0])}`;
|
|
364
|
-
let
|
|
365
|
-
|
|
366
|
-
`JSON_CONTAINS(JSON_ARRAY(
|
|
382
|
+
let sql;
|
|
383
|
+
if (Array.isArray(v)) {
|
|
384
|
+
sql = `JSON_CONTAINS(JSON_ARRAY(${this._buildArrayPlaceholders(v)}), JSON_EXTRACT(${k}, '${keys[1]}'))`;
|
|
385
|
+
} else {
|
|
386
|
+
let res = this._buildConditionValues(v);
|
|
387
|
+
sql = res ? `JSON_CONTAINS(JSON_ARRAY(${res}), JSON_EXTRACT(${k}, '${keys[1]}'))` :
|
|
388
|
+
`JSON_CONTAINS(JSON_ARRAY(?), JSON_EXTRACT(${k}, '${keys[1]}'))`;
|
|
389
|
+
}
|
|
367
390
|
return isNot ? `${sql}=0` : sql;
|
|
368
391
|
}
|
|
369
|
-
let v = is.string(condition.value) ? condition.value.split(',').map(v => v.trim()) : condition.value;
|
|
370
|
-
let res = this._buildConditionValues(v);
|
|
371
392
|
const opt = isNot ? 'NOT IN' : 'IN';
|
|
393
|
+
if (Array.isArray(v)) {
|
|
394
|
+
return `${this._buildFieldKey(condition.key)} ${opt} (${this._buildArrayPlaceholders(v)})`;
|
|
395
|
+
}
|
|
396
|
+
let res = this._buildConditionValues(v);
|
|
372
397
|
return res ? `${this._buildFieldKey(condition.key)} ${opt} (${res})` : `${this._buildFieldKey(condition.key)} ${opt} (?)`;
|
|
373
398
|
}
|
|
374
399
|
|
|
@@ -376,9 +401,14 @@ class Builder {
|
|
|
376
401
|
if (condition.key.indexOf('->') !== -1) {
|
|
377
402
|
let keys = condition.key.split('->');
|
|
378
403
|
let k = `${this._buildFieldKey(keys[0])}`;
|
|
379
|
-
let
|
|
380
|
-
|
|
381
|
-
`JSON_CONTAINS(${k}, JSON_ARRAY(
|
|
404
|
+
let sql;
|
|
405
|
+
if (Array.isArray(condition.value)) {
|
|
406
|
+
sql = `JSON_CONTAINS(${k}, JSON_ARRAY(${this._buildArrayPlaceholders(condition.value)}), '${keys[1]}')`;
|
|
407
|
+
} else {
|
|
408
|
+
let res = this._buildConditionValues(condition.value);
|
|
409
|
+
sql = res ? `JSON_CONTAINS(${k}, JSON_ARRAY(${res}), '${keys[1]}')` :
|
|
410
|
+
`JSON_CONTAINS(${k}, JSON_ARRAY(?), '${keys[1]}')`;
|
|
411
|
+
}
|
|
382
412
|
return isNot ? `${sql}=0` : sql;
|
|
383
413
|
}
|
|
384
414
|
let res = this._buildConditionValues(condition.value);
|
|
@@ -390,9 +420,14 @@ class Builder {
|
|
|
390
420
|
if (condition.key.indexOf('->') !== -1) {
|
|
391
421
|
let keys = condition.key.split('->');
|
|
392
422
|
let k = `${this._buildFieldKey(keys[0])}`;
|
|
393
|
-
let
|
|
394
|
-
|
|
395
|
-
`JSON_OVERLAPS(JSON_EXTRACT(${k}, '${keys[1]}'), JSON_ARRAY(
|
|
423
|
+
let sql;
|
|
424
|
+
if (Array.isArray(condition.value)) {
|
|
425
|
+
sql = `JSON_OVERLAPS(JSON_EXTRACT(${k}, '${keys[1]}'), JSON_ARRAY(${this._buildArrayPlaceholders(condition.value)}))`;
|
|
426
|
+
} else {
|
|
427
|
+
let res = this._buildConditionValues(condition.value);
|
|
428
|
+
sql = res ? `JSON_OVERLAPS(JSON_EXTRACT(${k}, '${keys[1]}'), JSON_ARRAY(${res}))` :
|
|
429
|
+
`JSON_OVERLAPS(JSON_EXTRACT(${k}, '${keys[1]}'), JSON_ARRAY(?))`;
|
|
430
|
+
}
|
|
396
431
|
return isNot ? `${sql}=0` : sql;
|
|
397
432
|
}
|
|
398
433
|
let res = this._buildConditionValues(condition.value);
|