@drzl/analyzer 1.17.4 → 1.17.6
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 +112 -14
- package/dist/index.d.cts +36 -13
- package/dist/index.d.ts +36 -13
- package/dist/index.js +112 -14
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -87,6 +87,7 @@ var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
|
|
|
87
87
|
"SingleStoreBinary",
|
|
88
88
|
"SingleStoreVarBinary"
|
|
89
89
|
]);
|
|
90
|
+
var BUFFER_CLASSES = /* @__PURE__ */ new Set(["SQLiteBlobBuffer"]);
|
|
90
91
|
function describeV1Column(column) {
|
|
91
92
|
const codec = column?.codec;
|
|
92
93
|
const dataType = column?.dataType;
|
|
@@ -111,6 +112,14 @@ function describeV1Column(column) {
|
|
|
111
112
|
case "int53":
|
|
112
113
|
case "uint53":
|
|
113
114
|
case "int64": {
|
|
115
|
+
if (DECIMAL_BIGINT_MODE.test(entityKind)) {
|
|
116
|
+
out.tsType = "bigint";
|
|
117
|
+
out.dbType = "NUMERIC";
|
|
118
|
+
out.integer = true;
|
|
119
|
+
const range2 = decimalModeRange(column, entityKind, "bigint");
|
|
120
|
+
if (range2) [out.min, out.max] = range2;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
114
123
|
const range = {
|
|
115
124
|
int8: ["-128", "127"],
|
|
116
125
|
int16: ["-32768", "32767"],
|
|
@@ -253,10 +262,11 @@ function describeV1Column(column) {
|
|
|
253
262
|
out.tsType = "number";
|
|
254
263
|
out.dbType = "NUMERIC";
|
|
255
264
|
out.integer = false;
|
|
256
|
-
|
|
265
|
+
const range = decimalModeRange(column, entityKind, "number");
|
|
266
|
+
if (range) [out.min, out.max] = range;
|
|
257
267
|
if (codec === "numeric:number") {
|
|
258
268
|
out.allowsNaN = true;
|
|
259
|
-
out.allowsInfinity =
|
|
269
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
260
270
|
}
|
|
261
271
|
} else if (js === "string") {
|
|
262
272
|
out.tsType = "string";
|
|
@@ -285,6 +295,28 @@ function declaredLength(column) {
|
|
|
285
295
|
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
286
296
|
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
287
297
|
}
|
|
298
|
+
function declaredDecimalRange(column) {
|
|
299
|
+
const cfg = column?.config ?? {};
|
|
300
|
+
const precision = column?.precision ?? cfg.precision;
|
|
301
|
+
const scale = column?.scale ?? cfg.scale ?? 0;
|
|
302
|
+
if (typeof precision !== "number" || !Number.isInteger(precision) || precision < 1)
|
|
303
|
+
return void 0;
|
|
304
|
+
if (typeof scale !== "number" || !Number.isInteger(scale) || scale < 0) return void 0;
|
|
305
|
+
const nines = "9".repeat(precision);
|
|
306
|
+
const max = scale === 0 ? nines : scale < precision ? `${nines.slice(0, precision - scale)}.${nines.slice(precision - scale)}` : `0.${"0".repeat(scale - precision)}${nines}`;
|
|
307
|
+
return [`-${max}`, max];
|
|
308
|
+
}
|
|
309
|
+
var DECIMAL_NUMBER_MODE = /(?:Numeric|Decimal)Number$/;
|
|
310
|
+
var DECIMAL_BIGINT_MODE = /(?:Numeric|Decimal)BigInt$/;
|
|
311
|
+
var MYSQL_IMPLICIT_DECIMAL_RANGE = ["-9999999999", "9999999999"];
|
|
312
|
+
function decimalModeRange(column, kind, mode) {
|
|
313
|
+
const declared = declaredDecimalRange(column);
|
|
314
|
+
if (declared) return declared;
|
|
315
|
+
if (kind.startsWith("MySql") || kind.startsWith("SingleStore"))
|
|
316
|
+
return MYSQL_IMPLICIT_DECIMAL_RANGE;
|
|
317
|
+
if (kind.startsWith("SQLite")) return void 0;
|
|
318
|
+
return mode === "number" ? JS_SAFE_INTEGER_BOUNDS : void 0;
|
|
319
|
+
}
|
|
288
320
|
var VIEW_CONFIG_FIELDS = {
|
|
289
321
|
"drizzle:Columns": "selectedFields",
|
|
290
322
|
"drizzle:Name": "name",
|
|
@@ -582,6 +614,19 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
582
614
|
out.allowsNaN = nonFinite.nan;
|
|
583
615
|
out.allowsInfinity = nonFinite.infinity;
|
|
584
616
|
}
|
|
617
|
+
if (DECIMAL_NUMBER_MODE.test(ctor)) {
|
|
618
|
+
const range2 = decimalModeRange(column, ctor, "number");
|
|
619
|
+
if (range2) [out.min, out.max] = range2;
|
|
620
|
+
out.integer = false;
|
|
621
|
+
if (ctor === "PgNumericNumber") {
|
|
622
|
+
out.allowsNaN = true;
|
|
623
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
624
|
+
}
|
|
625
|
+
} else if (DECIMAL_BIGINT_MODE.test(ctor)) {
|
|
626
|
+
const range2 = decimalModeRange(column, ctor, "bigint");
|
|
627
|
+
if (range2) [out.min, out.max] = range2;
|
|
628
|
+
out.integer = true;
|
|
629
|
+
}
|
|
585
630
|
if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
|
|
586
631
|
return out;
|
|
587
632
|
}
|
|
@@ -593,12 +638,36 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
593
638
|
tsType: column?.config?.mode === "timestamp" ? "Date" : "number",
|
|
594
639
|
dbType: "INTEGER"
|
|
595
640
|
};
|
|
641
|
+
// Both timestamp modes of `integer()`, which are one class and one type. `timestamp` and
|
|
642
|
+
// `timestamp_ms` differ in the scale of the number on the wire, seconds against
|
|
643
|
+
// milliseconds, and `mapFromDriverValue` consumes that difference and hands back a `Date`
|
|
644
|
+
// either way; nothing downstream of the analyzer ever sees the integer. So an arm keyed on
|
|
645
|
+
// the class covers both, where the mode check that used to answer this fell through the
|
|
646
|
+
// switch to a default arm testing `config.mode === 'timestamp'` and named only the first.
|
|
647
|
+
// The second came back `unknown`, and every generator emitted a schema accepting anything.
|
|
648
|
+
//
|
|
649
|
+
// `DATE` rather than the `INTEGER` that mode check returned, so the two majors describe the
|
|
650
|
+
// column identically. `dbType` is read in exactly one place outside this file,
|
|
651
|
+
// `isIntegerColumn`, which the generators consult only for a `tsType` of `number`, so the
|
|
652
|
+
// relabel reaches no output. Measured rather than argued: emitting a `Date` column under
|
|
653
|
+
// both labels, nullable and not, through all five generators gives ten byte-identical pairs.
|
|
654
|
+
case "SQLiteTimestamp":
|
|
655
|
+
return { tsType: "Date", dbType: "DATE" };
|
|
596
656
|
case "SQLiteText":
|
|
597
657
|
return { tsType: "string", dbType: "TEXT" };
|
|
598
658
|
case "SQLiteReal":
|
|
599
659
|
return { tsType: "number", dbType: "REAL" };
|
|
660
|
+
// No 0.4x column is a `SQLiteBlob`: `sqlite-core` builds a `SQLiteBlobBuffer`, a
|
|
661
|
+
// `SQLiteBlobJson` or a `SQLiteBigInt`, one per mode, and exports no class of this name at
|
|
662
|
+
// all. The arm answers the hand-built column in sqlite-types.spec.ts and nothing drizzle
|
|
663
|
+
// produces, which is why a real `blob()` reached neither it nor anything else.
|
|
600
664
|
case "SQLiteBlob":
|
|
601
665
|
return { tsType: "Uint8Array", dbType: "BLOB" };
|
|
666
|
+
// The class a real `blob()` and `blob({ mode: 'buffer' })` both build. See `BUFFER_CLASSES`
|
|
667
|
+
// for the measurement; the answers here are v1's own for the same column, so this is the
|
|
668
|
+
// two majors agreeing rather than a new opinion.
|
|
669
|
+
case "SQLiteBlobBuffer":
|
|
670
|
+
return { tsType: "Buffer", dbType: "BYTEA" };
|
|
602
671
|
// SQLite spells a mode as a distinct class rather than as config, so `text({mode:'json'})`
|
|
603
672
|
// is a `SQLiteTextJson` and matched no arm at all: the column came back UNKNOWN, which is
|
|
604
673
|
// wider than the `any` a json column at least used to get.
|
|
@@ -825,7 +894,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
825
894
|
if (/^Gel/i.test(ctor)) {
|
|
826
895
|
if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
|
|
827
896
|
if (/Int53|Integer|SmallInt/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
|
|
828
|
-
if (/
|
|
897
|
+
if (/DoublePrecision/i.test(ctor)) return { tsType: "number", dbType: "DOUBLE" };
|
|
898
|
+
if (/Real/i.test(ctor)) return { tsType: "number", dbType: "REAL" };
|
|
829
899
|
if (/Decimal/i.test(ctor)) return { tsType: "string", dbType: "NUMERIC" };
|
|
830
900
|
if (/UUID/i.test(ctor)) return { tsType: "string", dbType: "UUID" };
|
|
831
901
|
if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
|
|
@@ -876,11 +946,13 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
876
946
|
const v1 = describeV1Column(col);
|
|
877
947
|
const constraints = this.columnConstraints(col);
|
|
878
948
|
if (v1?.shape) delete constraints.maxLength;
|
|
879
|
-
const sqlKind = String(
|
|
949
|
+
const sqlKind = String(
|
|
950
|
+
outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? ""
|
|
951
|
+
);
|
|
880
952
|
const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
|
|
881
953
|
const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
|
|
882
954
|
const ctorName = String(col?.constructor?.name ?? "");
|
|
883
|
-
const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : NUMBER_VECTOR_CLASSES.has(ctorName) ? { kind: "numberVector", length: declaredLength(col) } : BIT_STRING_CLASSES.has(ctorName) ? { kind: "bitstring", length: declaredLength(col), exact: true } : GEOMETRIC_CLASS_SHAPES[ctorName];
|
|
955
|
+
const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : BUFFER_CLASSES.has(ctorName) ? { kind: "buffer" } : NUMBER_VECTOR_CLASSES.has(ctorName) ? { kind: "numberVector", length: declaredLength(col) } : BIT_STRING_CLASSES.has(ctorName) ? { kind: "bitstring", length: declaredLength(col), exact: true } : GEOMETRIC_CLASS_SHAPES[ctorName];
|
|
884
956
|
if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
|
|
885
957
|
const shape = (v1?.shape ?? fallbackShape)?.kind;
|
|
886
958
|
const finalTs = v1?.tsType ?? tsType;
|
|
@@ -1243,11 +1315,29 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1243
1315
|
SQLiteReal: null,
|
|
1244
1316
|
SingleStoreDouble: null,
|
|
1245
1317
|
SingleStoreReal: null,
|
|
1246
|
-
// `
|
|
1247
|
-
//
|
|
1248
|
-
//
|
|
1249
|
-
|
|
1318
|
+
// Gel, whose `real` is a `std::float32` and whose `doublePrecision` is a `std::float64`. Both
|
|
1319
|
+
// used to be answered by a `/Real|DoublePrecision/i` arm that said NUMERIC and stated nothing
|
|
1320
|
+
// else at all, so a `real` column accepted 1e300 and the server refused it.
|
|
1321
|
+
//
|
|
1322
|
+
// Measured on a live Gel 7.1 (`geldata/gel:7`, sys::get_version_as_str() -> 7.1+08db576)
|
|
1323
|
+
// through the `gel` client, casting each literal so the server parses it, and again through a
|
|
1324
|
+
// stored property on a real object type. The float32 edge is Postgres's exactly, to the double:
|
|
1325
|
+
//
|
|
1326
|
+
// 3.4028234663852886e38 accepted, returned unchanged
|
|
1327
|
+
// 3.4028235677973366e38 accepted, and stored as 3.4028234663852886e38
|
|
1328
|
+
// 3.402823567797337e38 refused, "is out of range for type std::float32"
|
|
1329
|
+
// 1e300 refused, the same way
|
|
1330
|
+
//
|
|
1331
|
+
// The same value accepted, the same next double up refused, and the same rounding down of the
|
|
1332
|
+
// midpoint, so it takes the constant already here rather than a second name for one number.
|
|
1333
|
+
// float64 took 1e300 and Number.MAX_VALUE faithfully, for the reason no 8 byte float has a
|
|
1334
|
+
// truthful finite bound.
|
|
1335
|
+
GelReal: PG_FLOAT4_RANGE,
|
|
1336
|
+
GelDoublePrecision: null
|
|
1250
1337
|
};
|
|
1338
|
+
// `numeric`/`decimal` in either of its two numeric modes is deliberately not in the table above.
|
|
1339
|
+
// Its bound is not a fixed magnitude per class but the precision each column declares for itself,
|
|
1340
|
+
// which no table keyed on a class name can hold; see `declaredDecimalRange`.
|
|
1251
1341
|
/**
|
|
1252
1342
|
* The Postgres number columns that hold a non-finite double, and which of the three each holds.
|
|
1253
1343
|
*
|
|
@@ -1258,17 +1348,25 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1258
1348
|
* table also answers for a v1 column and the two answers are identical rather than merely
|
|
1259
1349
|
* compatible. non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
1260
1350
|
*
|
|
1261
|
-
*
|
|
1262
|
-
* `
|
|
1263
|
-
*
|
|
1351
|
+
* No MySQL, SingleStore or SQLite class belongs here: MySQL refuses all three on a `float`/
|
|
1352
|
+
* `double` and stores `0.00` for a `decimal`, and SQLite returns both infinities while silently
|
|
1353
|
+
* turning `NaN` into NULL, which is a different answer that has to arrive whole.
|
|
1354
|
+
*
|
|
1355
|
+
* Gel does belong, and is the fourth and fifth entries. Measured on a live Gel 7.1 rather than
|
|
1356
|
+
* inferred from it being Postgres-backed: both `std::float32` and `std::float64` stored `nan`,
|
|
1357
|
+
* `inf` and `-inf` and handed all three back as `NaN`, `Infinity` and `-Infinity`, through a cast
|
|
1358
|
+
* and again through a stored property. Without them every row of such a column failed validation.
|
|
1264
1359
|
*
|
|
1265
1360
|
* `PgNumeric` is absent because its value is a string, and its pattern already accepts `NaN` and
|
|
1266
|
-
* `Infinity`. `PgNumericNumber`
|
|
1361
|
+
* `Infinity`. `PgNumericNumber` is absent because its answer is no longer flat: it takes `NaN` at
|
|
1362
|
+
* any width and an infinity only where no precision is declared, which is a per-column question
|
|
1363
|
+
* this table cannot ask. `columnConstraints` answers it beside the bound that decides it.
|
|
1267
1364
|
*/
|
|
1268
1365
|
_SchemaAnalyzer.PG_NON_FINITE = {
|
|
1269
1366
|
PgReal: { nan: true, infinity: true },
|
|
1270
1367
|
PgDoublePrecision: { nan: true, infinity: true },
|
|
1271
|
-
|
|
1368
|
+
GelReal: { nan: true, infinity: true },
|
|
1369
|
+
GelDoublePrecision: { nan: true, infinity: true }
|
|
1272
1370
|
};
|
|
1273
1371
|
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
1274
1372
|
var index_default = SchemaAnalyzer;
|
package/dist/index.d.cts
CHANGED
|
@@ -48,8 +48,16 @@ interface Column {
|
|
|
48
48
|
*
|
|
49
49
|
* Strings rather than numbers because a 64 bit bound is not representable as a JS number:
|
|
50
50
|
* `9223372036854775807` rounds to `9223372036854775808` the moment it becomes one, so a
|
|
51
|
-
* numeric field here would silently emit a wrong bound.
|
|
52
|
-
*
|
|
51
|
+
* numeric field here would silently emit a wrong bound. A 20 digit `numeric(20,0)` bound is
|
|
52
|
+
* further past that again.
|
|
53
|
+
*
|
|
54
|
+
* Not an integer range, despite the name this field used to be described by. An inexact column
|
|
55
|
+
* carries one too: a `real` is bounded by the magnitude the database refuses past, and a
|
|
56
|
+
* `numeric(10,2)` by the width its own declaration states. `integer` says which kind it is, and
|
|
57
|
+
* saying it is what stops a bounded float schema refusing 1.5.
|
|
58
|
+
*
|
|
59
|
+
* Absent where nothing declares a bound: an 8 byte float holds every finite JS number, and a
|
|
60
|
+
* `numeric` with no precision holds arbitrary precision.
|
|
53
61
|
*/
|
|
54
62
|
min?: string;
|
|
55
63
|
max?: string;
|
|
@@ -76,14 +84,22 @@ interface Column {
|
|
|
76
84
|
* does too, but a `numeric(10,2)` refuses either infinity with `22003 numeric field overflow`
|
|
77
85
|
* while still taking `NaN`, and `integer`/`bigint` refuse all three.
|
|
78
86
|
*
|
|
79
|
-
* So `numeric` in `{ mode: 'number' }`
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
87
|
+
* So `numeric` in `{ mode: 'number' }` answers each of the two separately: `allowsNaN` at any
|
|
88
|
+
* width, and `allowsInfinity` only where the declaration carries no precision. That used to be a
|
|
89
|
+
* flat `false`, and the recorded reason was that nothing here read a column's precision or scale,
|
|
90
|
+
* so the two declarations were indistinguishable and the narrower answer was the safer one.
|
|
91
|
+
* `declaredDecimalRange` reads both numbers now, the two are distinguishable, and each says what
|
|
92
|
+
* its own server does.
|
|
83
93
|
*
|
|
84
|
-
* Postgres
|
|
85
|
-
*
|
|
86
|
-
*
|
|
94
|
+
* Postgres and Gel. MySQL refuses all three on a `float`/`double`, and on a `decimal` refuses
|
|
95
|
+
* them outright rather than storing `0.00`: measured on MySQL 8.4.11 in `STRICT_TRANS_TABLES`,
|
|
96
|
+
* all three answer `Incorrect decimal value`. SQLite returns both infinities and silently turns
|
|
97
|
+
* `NaN` into NULL, which is real and is filed on its own: a column needs both halves of that
|
|
98
|
+
* answer or none.
|
|
99
|
+
*
|
|
100
|
+
* Gel joined on a measurement of its own rather than on being Postgres-backed: a live Gel 7.1
|
|
101
|
+
* stored `nan`, `inf` and `-inf` in both `std::float32` and `std::float64` and handed all three
|
|
102
|
+
* back unchanged, through a cast and through a stored property.
|
|
87
103
|
*
|
|
88
104
|
* Absent on every other column, including the string mode of `numeric`, which already carries the
|
|
89
105
|
* same fact as a pattern; see `COLUMN_FORMATS.numeric` in `@drzl/validation-core`.
|
|
@@ -489,12 +505,19 @@ declare class SchemaAnalyzer {
|
|
|
489
505
|
* table also answers for a v1 column and the two answers are identical rather than merely
|
|
490
506
|
* compatible. non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
491
507
|
*
|
|
492
|
-
*
|
|
493
|
-
* `
|
|
494
|
-
*
|
|
508
|
+
* No MySQL, SingleStore or SQLite class belongs here: MySQL refuses all three on a `float`/
|
|
509
|
+
* `double` and stores `0.00` for a `decimal`, and SQLite returns both infinities while silently
|
|
510
|
+
* turning `NaN` into NULL, which is a different answer that has to arrive whole.
|
|
511
|
+
*
|
|
512
|
+
* Gel does belong, and is the fourth and fifth entries. Measured on a live Gel 7.1 rather than
|
|
513
|
+
* inferred from it being Postgres-backed: both `std::float32` and `std::float64` stored `nan`,
|
|
514
|
+
* `inf` and `-inf` and handed all three back as `NaN`, `Infinity` and `-Infinity`, through a cast
|
|
515
|
+
* and again through a stored property. Without them every row of such a column failed validation.
|
|
495
516
|
*
|
|
496
517
|
* `PgNumeric` is absent because its value is a string, and its pattern already accepts `NaN` and
|
|
497
|
-
* `Infinity`. `PgNumericNumber`
|
|
518
|
+
* `Infinity`. `PgNumericNumber` is absent because its answer is no longer flat: it takes `NaN` at
|
|
519
|
+
* any width and an infinity only where no precision is declared, which is a per-column question
|
|
520
|
+
* this table cannot ask. `columnConstraints` answers it beside the bound that decides it.
|
|
498
521
|
*/
|
|
499
522
|
private static readonly PG_NON_FINITE;
|
|
500
523
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -48,8 +48,16 @@ interface Column {
|
|
|
48
48
|
*
|
|
49
49
|
* Strings rather than numbers because a 64 bit bound is not representable as a JS number:
|
|
50
50
|
* `9223372036854775807` rounds to `9223372036854775808` the moment it becomes one, so a
|
|
51
|
-
* numeric field here would silently emit a wrong bound.
|
|
52
|
-
*
|
|
51
|
+
* numeric field here would silently emit a wrong bound. A 20 digit `numeric(20,0)` bound is
|
|
52
|
+
* further past that again.
|
|
53
|
+
*
|
|
54
|
+
* Not an integer range, despite the name this field used to be described by. An inexact column
|
|
55
|
+
* carries one too: a `real` is bounded by the magnitude the database refuses past, and a
|
|
56
|
+
* `numeric(10,2)` by the width its own declaration states. `integer` says which kind it is, and
|
|
57
|
+
* saying it is what stops a bounded float schema refusing 1.5.
|
|
58
|
+
*
|
|
59
|
+
* Absent where nothing declares a bound: an 8 byte float holds every finite JS number, and a
|
|
60
|
+
* `numeric` with no precision holds arbitrary precision.
|
|
53
61
|
*/
|
|
54
62
|
min?: string;
|
|
55
63
|
max?: string;
|
|
@@ -76,14 +84,22 @@ interface Column {
|
|
|
76
84
|
* does too, but a `numeric(10,2)` refuses either infinity with `22003 numeric field overflow`
|
|
77
85
|
* while still taking `NaN`, and `integer`/`bigint` refuse all three.
|
|
78
86
|
*
|
|
79
|
-
* So `numeric` in `{ mode: 'number' }`
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
87
|
+
* So `numeric` in `{ mode: 'number' }` answers each of the two separately: `allowsNaN` at any
|
|
88
|
+
* width, and `allowsInfinity` only where the declaration carries no precision. That used to be a
|
|
89
|
+
* flat `false`, and the recorded reason was that nothing here read a column's precision or scale,
|
|
90
|
+
* so the two declarations were indistinguishable and the narrower answer was the safer one.
|
|
91
|
+
* `declaredDecimalRange` reads both numbers now, the two are distinguishable, and each says what
|
|
92
|
+
* its own server does.
|
|
83
93
|
*
|
|
84
|
-
* Postgres
|
|
85
|
-
*
|
|
86
|
-
*
|
|
94
|
+
* Postgres and Gel. MySQL refuses all three on a `float`/`double`, and on a `decimal` refuses
|
|
95
|
+
* them outright rather than storing `0.00`: measured on MySQL 8.4.11 in `STRICT_TRANS_TABLES`,
|
|
96
|
+
* all three answer `Incorrect decimal value`. SQLite returns both infinities and silently turns
|
|
97
|
+
* `NaN` into NULL, which is real and is filed on its own: a column needs both halves of that
|
|
98
|
+
* answer or none.
|
|
99
|
+
*
|
|
100
|
+
* Gel joined on a measurement of its own rather than on being Postgres-backed: a live Gel 7.1
|
|
101
|
+
* stored `nan`, `inf` and `-inf` in both `std::float32` and `std::float64` and handed all three
|
|
102
|
+
* back unchanged, through a cast and through a stored property.
|
|
87
103
|
*
|
|
88
104
|
* Absent on every other column, including the string mode of `numeric`, which already carries the
|
|
89
105
|
* same fact as a pattern; see `COLUMN_FORMATS.numeric` in `@drzl/validation-core`.
|
|
@@ -489,12 +505,19 @@ declare class SchemaAnalyzer {
|
|
|
489
505
|
* table also answers for a v1 column and the two answers are identical rather than merely
|
|
490
506
|
* compatible. non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
491
507
|
*
|
|
492
|
-
*
|
|
493
|
-
* `
|
|
494
|
-
*
|
|
508
|
+
* No MySQL, SingleStore or SQLite class belongs here: MySQL refuses all three on a `float`/
|
|
509
|
+
* `double` and stores `0.00` for a `decimal`, and SQLite returns both infinities while silently
|
|
510
|
+
* turning `NaN` into NULL, which is a different answer that has to arrive whole.
|
|
511
|
+
*
|
|
512
|
+
* Gel does belong, and is the fourth and fifth entries. Measured on a live Gel 7.1 rather than
|
|
513
|
+
* inferred from it being Postgres-backed: both `std::float32` and `std::float64` stored `nan`,
|
|
514
|
+
* `inf` and `-inf` and handed all three back as `NaN`, `Infinity` and `-Infinity`, through a cast
|
|
515
|
+
* and again through a stored property. Without them every row of such a column failed validation.
|
|
495
516
|
*
|
|
496
517
|
* `PgNumeric` is absent because its value is a string, and its pattern already accepts `NaN` and
|
|
497
|
-
* `Infinity`. `PgNumericNumber`
|
|
518
|
+
* `Infinity`. `PgNumericNumber` is absent because its answer is no longer flat: it takes `NaN` at
|
|
519
|
+
* any width and an infinity only where no precision is declared, which is a per-column question
|
|
520
|
+
* this table cannot ask. `columnConstraints` answers it beside the bound that decides it.
|
|
498
521
|
*/
|
|
499
522
|
private static readonly PG_NON_FINITE;
|
|
500
523
|
/**
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,7 @@ var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
|
|
|
46
46
|
"SingleStoreBinary",
|
|
47
47
|
"SingleStoreVarBinary"
|
|
48
48
|
]);
|
|
49
|
+
var BUFFER_CLASSES = /* @__PURE__ */ new Set(["SQLiteBlobBuffer"]);
|
|
49
50
|
function describeV1Column(column) {
|
|
50
51
|
const codec = column?.codec;
|
|
51
52
|
const dataType = column?.dataType;
|
|
@@ -70,6 +71,14 @@ function describeV1Column(column) {
|
|
|
70
71
|
case "int53":
|
|
71
72
|
case "uint53":
|
|
72
73
|
case "int64": {
|
|
74
|
+
if (DECIMAL_BIGINT_MODE.test(entityKind)) {
|
|
75
|
+
out.tsType = "bigint";
|
|
76
|
+
out.dbType = "NUMERIC";
|
|
77
|
+
out.integer = true;
|
|
78
|
+
const range2 = decimalModeRange(column, entityKind, "bigint");
|
|
79
|
+
if (range2) [out.min, out.max] = range2;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
73
82
|
const range = {
|
|
74
83
|
int8: ["-128", "127"],
|
|
75
84
|
int16: ["-32768", "32767"],
|
|
@@ -212,10 +221,11 @@ function describeV1Column(column) {
|
|
|
212
221
|
out.tsType = "number";
|
|
213
222
|
out.dbType = "NUMERIC";
|
|
214
223
|
out.integer = false;
|
|
215
|
-
|
|
224
|
+
const range = decimalModeRange(column, entityKind, "number");
|
|
225
|
+
if (range) [out.min, out.max] = range;
|
|
216
226
|
if (codec === "numeric:number") {
|
|
217
227
|
out.allowsNaN = true;
|
|
218
|
-
out.allowsInfinity =
|
|
228
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
219
229
|
}
|
|
220
230
|
} else if (js === "string") {
|
|
221
231
|
out.tsType = "string";
|
|
@@ -244,6 +254,28 @@ function declaredLength(column) {
|
|
|
244
254
|
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
245
255
|
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
246
256
|
}
|
|
257
|
+
function declaredDecimalRange(column) {
|
|
258
|
+
const cfg = column?.config ?? {};
|
|
259
|
+
const precision = column?.precision ?? cfg.precision;
|
|
260
|
+
const scale = column?.scale ?? cfg.scale ?? 0;
|
|
261
|
+
if (typeof precision !== "number" || !Number.isInteger(precision) || precision < 1)
|
|
262
|
+
return void 0;
|
|
263
|
+
if (typeof scale !== "number" || !Number.isInteger(scale) || scale < 0) return void 0;
|
|
264
|
+
const nines = "9".repeat(precision);
|
|
265
|
+
const max = scale === 0 ? nines : scale < precision ? `${nines.slice(0, precision - scale)}.${nines.slice(precision - scale)}` : `0.${"0".repeat(scale - precision)}${nines}`;
|
|
266
|
+
return [`-${max}`, max];
|
|
267
|
+
}
|
|
268
|
+
var DECIMAL_NUMBER_MODE = /(?:Numeric|Decimal)Number$/;
|
|
269
|
+
var DECIMAL_BIGINT_MODE = /(?:Numeric|Decimal)BigInt$/;
|
|
270
|
+
var MYSQL_IMPLICIT_DECIMAL_RANGE = ["-9999999999", "9999999999"];
|
|
271
|
+
function decimalModeRange(column, kind, mode) {
|
|
272
|
+
const declared = declaredDecimalRange(column);
|
|
273
|
+
if (declared) return declared;
|
|
274
|
+
if (kind.startsWith("MySql") || kind.startsWith("SingleStore"))
|
|
275
|
+
return MYSQL_IMPLICIT_DECIMAL_RANGE;
|
|
276
|
+
if (kind.startsWith("SQLite")) return void 0;
|
|
277
|
+
return mode === "number" ? JS_SAFE_INTEGER_BOUNDS : void 0;
|
|
278
|
+
}
|
|
247
279
|
var VIEW_CONFIG_FIELDS = {
|
|
248
280
|
"drizzle:Columns": "selectedFields",
|
|
249
281
|
"drizzle:Name": "name",
|
|
@@ -541,6 +573,19 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
541
573
|
out.allowsNaN = nonFinite.nan;
|
|
542
574
|
out.allowsInfinity = nonFinite.infinity;
|
|
543
575
|
}
|
|
576
|
+
if (DECIMAL_NUMBER_MODE.test(ctor)) {
|
|
577
|
+
const range2 = decimalModeRange(column, ctor, "number");
|
|
578
|
+
if (range2) [out.min, out.max] = range2;
|
|
579
|
+
out.integer = false;
|
|
580
|
+
if (ctor === "PgNumericNumber") {
|
|
581
|
+
out.allowsNaN = true;
|
|
582
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
583
|
+
}
|
|
584
|
+
} else if (DECIMAL_BIGINT_MODE.test(ctor)) {
|
|
585
|
+
const range2 = decimalModeRange(column, ctor, "bigint");
|
|
586
|
+
if (range2) [out.min, out.max] = range2;
|
|
587
|
+
out.integer = true;
|
|
588
|
+
}
|
|
544
589
|
if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
|
|
545
590
|
return out;
|
|
546
591
|
}
|
|
@@ -552,12 +597,36 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
552
597
|
tsType: column?.config?.mode === "timestamp" ? "Date" : "number",
|
|
553
598
|
dbType: "INTEGER"
|
|
554
599
|
};
|
|
600
|
+
// Both timestamp modes of `integer()`, which are one class and one type. `timestamp` and
|
|
601
|
+
// `timestamp_ms` differ in the scale of the number on the wire, seconds against
|
|
602
|
+
// milliseconds, and `mapFromDriverValue` consumes that difference and hands back a `Date`
|
|
603
|
+
// either way; nothing downstream of the analyzer ever sees the integer. So an arm keyed on
|
|
604
|
+
// the class covers both, where the mode check that used to answer this fell through the
|
|
605
|
+
// switch to a default arm testing `config.mode === 'timestamp'` and named only the first.
|
|
606
|
+
// The second came back `unknown`, and every generator emitted a schema accepting anything.
|
|
607
|
+
//
|
|
608
|
+
// `DATE` rather than the `INTEGER` that mode check returned, so the two majors describe the
|
|
609
|
+
// column identically. `dbType` is read in exactly one place outside this file,
|
|
610
|
+
// `isIntegerColumn`, which the generators consult only for a `tsType` of `number`, so the
|
|
611
|
+
// relabel reaches no output. Measured rather than argued: emitting a `Date` column under
|
|
612
|
+
// both labels, nullable and not, through all five generators gives ten byte-identical pairs.
|
|
613
|
+
case "SQLiteTimestamp":
|
|
614
|
+
return { tsType: "Date", dbType: "DATE" };
|
|
555
615
|
case "SQLiteText":
|
|
556
616
|
return { tsType: "string", dbType: "TEXT" };
|
|
557
617
|
case "SQLiteReal":
|
|
558
618
|
return { tsType: "number", dbType: "REAL" };
|
|
619
|
+
// No 0.4x column is a `SQLiteBlob`: `sqlite-core` builds a `SQLiteBlobBuffer`, a
|
|
620
|
+
// `SQLiteBlobJson` or a `SQLiteBigInt`, one per mode, and exports no class of this name at
|
|
621
|
+
// all. The arm answers the hand-built column in sqlite-types.spec.ts and nothing drizzle
|
|
622
|
+
// produces, which is why a real `blob()` reached neither it nor anything else.
|
|
559
623
|
case "SQLiteBlob":
|
|
560
624
|
return { tsType: "Uint8Array", dbType: "BLOB" };
|
|
625
|
+
// The class a real `blob()` and `blob({ mode: 'buffer' })` both build. See `BUFFER_CLASSES`
|
|
626
|
+
// for the measurement; the answers here are v1's own for the same column, so this is the
|
|
627
|
+
// two majors agreeing rather than a new opinion.
|
|
628
|
+
case "SQLiteBlobBuffer":
|
|
629
|
+
return { tsType: "Buffer", dbType: "BYTEA" };
|
|
561
630
|
// SQLite spells a mode as a distinct class rather than as config, so `text({mode:'json'})`
|
|
562
631
|
// is a `SQLiteTextJson` and matched no arm at all: the column came back UNKNOWN, which is
|
|
563
632
|
// wider than the `any` a json column at least used to get.
|
|
@@ -784,7 +853,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
784
853
|
if (/^Gel/i.test(ctor)) {
|
|
785
854
|
if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
|
|
786
855
|
if (/Int53|Integer|SmallInt/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
|
|
787
|
-
if (/
|
|
856
|
+
if (/DoublePrecision/i.test(ctor)) return { tsType: "number", dbType: "DOUBLE" };
|
|
857
|
+
if (/Real/i.test(ctor)) return { tsType: "number", dbType: "REAL" };
|
|
788
858
|
if (/Decimal/i.test(ctor)) return { tsType: "string", dbType: "NUMERIC" };
|
|
789
859
|
if (/UUID/i.test(ctor)) return { tsType: "string", dbType: "UUID" };
|
|
790
860
|
if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
|
|
@@ -835,11 +905,13 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
835
905
|
const v1 = describeV1Column(col);
|
|
836
906
|
const constraints = this.columnConstraints(col);
|
|
837
907
|
if (v1?.shape) delete constraints.maxLength;
|
|
838
|
-
const sqlKind = String(
|
|
908
|
+
const sqlKind = String(
|
|
909
|
+
outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? ""
|
|
910
|
+
);
|
|
839
911
|
const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
|
|
840
912
|
const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
|
|
841
913
|
const ctorName = String(col?.constructor?.name ?? "");
|
|
842
|
-
const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : NUMBER_VECTOR_CLASSES.has(ctorName) ? { kind: "numberVector", length: declaredLength(col) } : BIT_STRING_CLASSES.has(ctorName) ? { kind: "bitstring", length: declaredLength(col), exact: true } : GEOMETRIC_CLASS_SHAPES[ctorName];
|
|
914
|
+
const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : BUFFER_CLASSES.has(ctorName) ? { kind: "buffer" } : NUMBER_VECTOR_CLASSES.has(ctorName) ? { kind: "numberVector", length: declaredLength(col) } : BIT_STRING_CLASSES.has(ctorName) ? { kind: "bitstring", length: declaredLength(col), exact: true } : GEOMETRIC_CLASS_SHAPES[ctorName];
|
|
843
915
|
if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
|
|
844
916
|
const shape = (v1?.shape ?? fallbackShape)?.kind;
|
|
845
917
|
const finalTs = v1?.tsType ?? tsType;
|
|
@@ -1202,11 +1274,29 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1202
1274
|
SQLiteReal: null,
|
|
1203
1275
|
SingleStoreDouble: null,
|
|
1204
1276
|
SingleStoreReal: null,
|
|
1205
|
-
// `
|
|
1206
|
-
//
|
|
1207
|
-
//
|
|
1208
|
-
|
|
1277
|
+
// Gel, whose `real` is a `std::float32` and whose `doublePrecision` is a `std::float64`. Both
|
|
1278
|
+
// used to be answered by a `/Real|DoublePrecision/i` arm that said NUMERIC and stated nothing
|
|
1279
|
+
// else at all, so a `real` column accepted 1e300 and the server refused it.
|
|
1280
|
+
//
|
|
1281
|
+
// Measured on a live Gel 7.1 (`geldata/gel:7`, sys::get_version_as_str() -> 7.1+08db576)
|
|
1282
|
+
// through the `gel` client, casting each literal so the server parses it, and again through a
|
|
1283
|
+
// stored property on a real object type. The float32 edge is Postgres's exactly, to the double:
|
|
1284
|
+
//
|
|
1285
|
+
// 3.4028234663852886e38 accepted, returned unchanged
|
|
1286
|
+
// 3.4028235677973366e38 accepted, and stored as 3.4028234663852886e38
|
|
1287
|
+
// 3.402823567797337e38 refused, "is out of range for type std::float32"
|
|
1288
|
+
// 1e300 refused, the same way
|
|
1289
|
+
//
|
|
1290
|
+
// The same value accepted, the same next double up refused, and the same rounding down of the
|
|
1291
|
+
// midpoint, so it takes the constant already here rather than a second name for one number.
|
|
1292
|
+
// float64 took 1e300 and Number.MAX_VALUE faithfully, for the reason no 8 byte float has a
|
|
1293
|
+
// truthful finite bound.
|
|
1294
|
+
GelReal: PG_FLOAT4_RANGE,
|
|
1295
|
+
GelDoublePrecision: null
|
|
1209
1296
|
};
|
|
1297
|
+
// `numeric`/`decimal` in either of its two numeric modes is deliberately not in the table above.
|
|
1298
|
+
// Its bound is not a fixed magnitude per class but the precision each column declares for itself,
|
|
1299
|
+
// which no table keyed on a class name can hold; see `declaredDecimalRange`.
|
|
1210
1300
|
/**
|
|
1211
1301
|
* The Postgres number columns that hold a non-finite double, and which of the three each holds.
|
|
1212
1302
|
*
|
|
@@ -1217,17 +1307,25 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1217
1307
|
* table also answers for a v1 column and the two answers are identical rather than merely
|
|
1218
1308
|
* compatible. non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
1219
1309
|
*
|
|
1220
|
-
*
|
|
1221
|
-
* `
|
|
1222
|
-
*
|
|
1310
|
+
* No MySQL, SingleStore or SQLite class belongs here: MySQL refuses all three on a `float`/
|
|
1311
|
+
* `double` and stores `0.00` for a `decimal`, and SQLite returns both infinities while silently
|
|
1312
|
+
* turning `NaN` into NULL, which is a different answer that has to arrive whole.
|
|
1313
|
+
*
|
|
1314
|
+
* Gel does belong, and is the fourth and fifth entries. Measured on a live Gel 7.1 rather than
|
|
1315
|
+
* inferred from it being Postgres-backed: both `std::float32` and `std::float64` stored `nan`,
|
|
1316
|
+
* `inf` and `-inf` and handed all three back as `NaN`, `Infinity` and `-Infinity`, through a cast
|
|
1317
|
+
* and again through a stored property. Without them every row of such a column failed validation.
|
|
1223
1318
|
*
|
|
1224
1319
|
* `PgNumeric` is absent because its value is a string, and its pattern already accepts `NaN` and
|
|
1225
|
-
* `Infinity`. `PgNumericNumber`
|
|
1320
|
+
* `Infinity`. `PgNumericNumber` is absent because its answer is no longer flat: it takes `NaN` at
|
|
1321
|
+
* any width and an infinity only where no precision is declared, which is a per-column question
|
|
1322
|
+
* this table cannot ask. `columnConstraints` answers it beside the bound that decides it.
|
|
1226
1323
|
*/
|
|
1227
1324
|
_SchemaAnalyzer.PG_NON_FINITE = {
|
|
1228
1325
|
PgReal: { nan: true, infinity: true },
|
|
1229
1326
|
PgDoublePrecision: { nan: true, infinity: true },
|
|
1230
|
-
|
|
1327
|
+
GelReal: { nan: true, infinity: true },
|
|
1328
|
+
GelDoublePrecision: { nan: true, infinity: true }
|
|
1231
1329
|
};
|
|
1232
1330
|
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
1233
1331
|
var index_default = SchemaAnalyzer;
|