@drzl/validation-core 3.22.3 → 3.22.4
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/dist/index.cjs +12 -5
- package/dist/index.d.cts +13 -14
- package/dist/index.d.ts +13 -14
- package/dist/index.js +12 -5
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -153,7 +153,7 @@ function measureExpression(measure, variable) {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
function lengthCheckLabel(check) {
|
|
156
|
-
const fn = check.unit === "bytes" ? "octet_length" : "length";
|
|
156
|
+
const fn = check.fn ? check.fn === "char_length" ? "length" : check.fn : check.unit === "bytes" ? "octet_length" : "length";
|
|
157
157
|
const rule = `${fn}(${check.column}) ${check.operator} ${check.value}`;
|
|
158
158
|
return check.name ? `${check.name}: ${rule}` : rule;
|
|
159
159
|
}
|
|
@@ -404,7 +404,13 @@ function foldDisjunctionToSet(branches, name) {
|
|
|
404
404
|
sets: [{ column, values, kind, name }]
|
|
405
405
|
};
|
|
406
406
|
}
|
|
407
|
-
function
|
|
407
|
+
function lengthUnit(fn, dialect) {
|
|
408
|
+
const name = fn.toLowerCase();
|
|
409
|
+
if (name === "octet_length") return "bytes";
|
|
410
|
+
if (name === "char_length") return "characters";
|
|
411
|
+
return dialect === "mysql" ? "bytes" : "characters";
|
|
412
|
+
}
|
|
413
|
+
function parseCheck(expression, name, dialect) {
|
|
408
414
|
const expr = unwrap((expression ?? "").trim());
|
|
409
415
|
if (!expr) return { ok: false, reason: "empty expression" };
|
|
410
416
|
if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
|
|
@@ -481,11 +487,12 @@ function parseCheck(expression, name) {
|
|
|
481
487
|
const lengthOf = expr.match(LENGTH_OF);
|
|
482
488
|
if (lengthOf) {
|
|
483
489
|
const op2 = lengthOf[3] === "!=" ? "<>" : lengthOf[3];
|
|
484
|
-
const
|
|
490
|
+
const fn = lengthOf[1].toLowerCase();
|
|
491
|
+
const unit = lengthUnit(fn, dialect);
|
|
485
492
|
return {
|
|
486
493
|
ok: true,
|
|
487
494
|
checks: [],
|
|
488
|
-
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, name }]
|
|
495
|
+
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, fn, name }]
|
|
489
496
|
};
|
|
490
497
|
}
|
|
491
498
|
const cardinalityOf = expr.match(CARDINALITY_OF);
|
|
@@ -992,7 +999,7 @@ function classifyTableChecks(table) {
|
|
|
992
999
|
const out = [];
|
|
993
1000
|
for (const k of table.checks ?? []) {
|
|
994
1001
|
const expression = (k.expression ?? "").trim();
|
|
995
|
-
const parsed = parseCheck(k.expression, k.name);
|
|
1002
|
+
const parsed = parseCheck(k.expression, k.name, table.dialect);
|
|
996
1003
|
if (!parsed.ok) {
|
|
997
1004
|
out.push({
|
|
998
1005
|
...k.name ? { name: k.name } : {},
|
package/dist/index.d.cts
CHANGED
|
@@ -452,6 +452,15 @@ interface LengthCheck {
|
|
|
452
452
|
* a given column.
|
|
453
453
|
*/
|
|
454
454
|
unit?: 'characters' | 'bytes';
|
|
455
|
+
/**
|
|
456
|
+
* The function as written, before the engine decided what it counts.
|
|
457
|
+
*
|
|
458
|
+
* `unit` used to imply this, because the two were the same question: `octet_length` meant bytes
|
|
459
|
+
* and everything else meant characters. On MySQL `length()` also means bytes, so deriving the
|
|
460
|
+
* name back from the unit relabelled a user's `length(name) <= 5` as `octet_length(name) <= 5`,
|
|
461
|
+
* which is a constraint they did not write. The label the ledger matches on has to be theirs.
|
|
462
|
+
*/
|
|
463
|
+
fn?: 'length' | 'char_length' | 'octet_length';
|
|
455
464
|
name?: string;
|
|
456
465
|
}
|
|
457
466
|
/**
|
|
@@ -587,21 +596,11 @@ type ParsedCheck = {
|
|
|
587
596
|
reason: string;
|
|
588
597
|
};
|
|
589
598
|
/**
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* inclusive bounds; `AND` of arbitrary predicates is not, because getting its scope wrong would
|
|
594
|
-
* silently change what is enforced.
|
|
595
|
-
*
|
|
596
|
-
* **The order below is load bearing, in both directions.** `BETWEEN` is matched before the `AND`
|
|
597
|
-
* split because it *holds* an `AND`; splitting first turned every `BETWEEN` into an unparseable
|
|
598
|
-
* pair and dropped a constraint that had been enforced. The unary `IS` predicates are matched
|
|
599
|
-
* *after* the splits for the mirror-image reason: none of them holds an `AND` or an `OR`, and
|
|
600
|
-
* their trailing operand is greedy, so matching first would let `a IS DISTINCT FROM 5 AND b > 0`
|
|
601
|
-
* swallow the second predicate. `OR` is split before `AND` because SQL binds `AND` tighter, and
|
|
602
|
-
* no SQL operator spells an `OR` inside itself the way `BETWEEN` spells an `AND`.
|
|
599
|
+
* `dialect` decides what `length()` counts, and nothing else in this parser. Optional, and absent
|
|
600
|
+
* means the Postgres reading, which is what every caller got before it existed: a caller that does
|
|
601
|
+
* not know its dialect keeps the answer it already had rather than being given a new one.
|
|
603
602
|
*/
|
|
604
|
-
declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
|
|
603
|
+
declare function parseCheck(expression: string | undefined, name?: string, dialect?: string): ParsedCheck;
|
|
605
604
|
/**
|
|
606
605
|
* A number-kind literal, spelled for the wire type of the column it constrains.
|
|
607
606
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -452,6 +452,15 @@ interface LengthCheck {
|
|
|
452
452
|
* a given column.
|
|
453
453
|
*/
|
|
454
454
|
unit?: 'characters' | 'bytes';
|
|
455
|
+
/**
|
|
456
|
+
* The function as written, before the engine decided what it counts.
|
|
457
|
+
*
|
|
458
|
+
* `unit` used to imply this, because the two were the same question: `octet_length` meant bytes
|
|
459
|
+
* and everything else meant characters. On MySQL `length()` also means bytes, so deriving the
|
|
460
|
+
* name back from the unit relabelled a user's `length(name) <= 5` as `octet_length(name) <= 5`,
|
|
461
|
+
* which is a constraint they did not write. The label the ledger matches on has to be theirs.
|
|
462
|
+
*/
|
|
463
|
+
fn?: 'length' | 'char_length' | 'octet_length';
|
|
455
464
|
name?: string;
|
|
456
465
|
}
|
|
457
466
|
/**
|
|
@@ -587,21 +596,11 @@ type ParsedCheck = {
|
|
|
587
596
|
reason: string;
|
|
588
597
|
};
|
|
589
598
|
/**
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* inclusive bounds; `AND` of arbitrary predicates is not, because getting its scope wrong would
|
|
594
|
-
* silently change what is enforced.
|
|
595
|
-
*
|
|
596
|
-
* **The order below is load bearing, in both directions.** `BETWEEN` is matched before the `AND`
|
|
597
|
-
* split because it *holds* an `AND`; splitting first turned every `BETWEEN` into an unparseable
|
|
598
|
-
* pair and dropped a constraint that had been enforced. The unary `IS` predicates are matched
|
|
599
|
-
* *after* the splits for the mirror-image reason: none of them holds an `AND` or an `OR`, and
|
|
600
|
-
* their trailing operand is greedy, so matching first would let `a IS DISTINCT FROM 5 AND b > 0`
|
|
601
|
-
* swallow the second predicate. `OR` is split before `AND` because SQL binds `AND` tighter, and
|
|
602
|
-
* no SQL operator spells an `OR` inside itself the way `BETWEEN` spells an `AND`.
|
|
599
|
+
* `dialect` decides what `length()` counts, and nothing else in this parser. Optional, and absent
|
|
600
|
+
* means the Postgres reading, which is what every caller got before it existed: a caller that does
|
|
601
|
+
* not know its dialect keeps the answer it already had rather than being given a new one.
|
|
603
602
|
*/
|
|
604
|
-
declare function parseCheck(expression: string | undefined, name?: string): ParsedCheck;
|
|
603
|
+
declare function parseCheck(expression: string | undefined, name?: string, dialect?: string): ParsedCheck;
|
|
605
604
|
/**
|
|
606
605
|
* A number-kind literal, spelled for the wire type of the column it constrains.
|
|
607
606
|
*
|
package/dist/index.js
CHANGED
|
@@ -52,7 +52,7 @@ function measureExpression(measure, variable) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
function lengthCheckLabel(check) {
|
|
55
|
-
const fn = check.unit === "bytes" ? "octet_length" : "length";
|
|
55
|
+
const fn = check.fn ? check.fn === "char_length" ? "length" : check.fn : check.unit === "bytes" ? "octet_length" : "length";
|
|
56
56
|
const rule = `${fn}(${check.column}) ${check.operator} ${check.value}`;
|
|
57
57
|
return check.name ? `${check.name}: ${rule}` : rule;
|
|
58
58
|
}
|
|
@@ -303,7 +303,13 @@ function foldDisjunctionToSet(branches, name) {
|
|
|
303
303
|
sets: [{ column, values, kind, name }]
|
|
304
304
|
};
|
|
305
305
|
}
|
|
306
|
-
function
|
|
306
|
+
function lengthUnit(fn, dialect) {
|
|
307
|
+
const name = fn.toLowerCase();
|
|
308
|
+
if (name === "octet_length") return "bytes";
|
|
309
|
+
if (name === "char_length") return "characters";
|
|
310
|
+
return dialect === "mysql" ? "bytes" : "characters";
|
|
311
|
+
}
|
|
312
|
+
function parseCheck(expression, name, dialect) {
|
|
307
313
|
const expr = unwrap((expression ?? "").trim());
|
|
308
314
|
if (!expr) return { ok: false, reason: "empty expression" };
|
|
309
315
|
if (expr.includes("?")) return { ok: false, reason: "expression contains an unresolved value" };
|
|
@@ -380,11 +386,12 @@ function parseCheck(expression, name) {
|
|
|
380
386
|
const lengthOf = expr.match(LENGTH_OF);
|
|
381
387
|
if (lengthOf) {
|
|
382
388
|
const op2 = lengthOf[3] === "!=" ? "<>" : lengthOf[3];
|
|
383
|
-
const
|
|
389
|
+
const fn = lengthOf[1].toLowerCase();
|
|
390
|
+
const unit = lengthUnit(fn, dialect);
|
|
384
391
|
return {
|
|
385
392
|
ok: true,
|
|
386
393
|
checks: [],
|
|
387
|
-
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, name }]
|
|
394
|
+
lengths: [{ column: lengthOf[2], operator: op2, value: lengthOf[4], unit, fn, name }]
|
|
388
395
|
};
|
|
389
396
|
}
|
|
390
397
|
const cardinalityOf = expr.match(CARDINALITY_OF);
|
|
@@ -891,7 +898,7 @@ function classifyTableChecks(table) {
|
|
|
891
898
|
const out = [];
|
|
892
899
|
for (const k of table.checks ?? []) {
|
|
893
900
|
const expression = (k.expression ?? "").trim();
|
|
894
|
-
const parsed = parseCheck(k.expression, k.name);
|
|
901
|
+
const parsed = parseCheck(k.expression, k.name, table.dialect);
|
|
895
902
|
if (!parsed.ok) {
|
|
896
903
|
out.push({
|
|
897
904
|
...k.name ? { name: k.name } : {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drzl/validation-core",
|
|
3
|
-
"version": "3.22.
|
|
3
|
+
"version": "3.22.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"sideEffects": false,
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@drzl/analyzer": "^1.21.
|
|
27
|
+
"@drzl/analyzer": "^1.21.4"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"prettier": ">=3"
|