@drzl/analyzer 1.17.5 → 1.17.7
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 +113 -16
- package/dist/index.d.cts +36 -13
- package/dist/index.d.ts +36 -13
- package/dist/index.js +113 -16
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -106,14 +106,24 @@ function describeV1Column(column) {
|
|
|
106
106
|
const out = {};
|
|
107
107
|
switch (semantic) {
|
|
108
108
|
case "int8":
|
|
109
|
+
case "uint8":
|
|
109
110
|
case "int16":
|
|
110
111
|
case "int24":
|
|
111
112
|
case "int32":
|
|
112
113
|
case "int53":
|
|
113
114
|
case "uint53":
|
|
114
115
|
case "int64": {
|
|
116
|
+
if (DECIMAL_BIGINT_MODE.test(entityKind)) {
|
|
117
|
+
out.tsType = "bigint";
|
|
118
|
+
out.dbType = "NUMERIC";
|
|
119
|
+
out.integer = true;
|
|
120
|
+
const range2 = decimalModeRange(column, entityKind, "bigint");
|
|
121
|
+
if (range2) [out.min, out.max] = range2;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
115
124
|
const range = {
|
|
116
125
|
int8: ["-128", "127"],
|
|
126
|
+
uint8: ["0", "255"],
|
|
117
127
|
int16: ["-32768", "32767"],
|
|
118
128
|
int24: ["-8388608", "8388607"],
|
|
119
129
|
int32: ["-2147483648", "2147483647"],
|
|
@@ -126,7 +136,7 @@ function describeV1Column(column) {
|
|
|
126
136
|
[out.min, out.max] = range;
|
|
127
137
|
out.integer = true;
|
|
128
138
|
out.tsType = js === "bigint" ? "bigint" : "number";
|
|
129
|
-
out.dbType = semantic === "int8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
|
|
139
|
+
out.dbType = semantic === "int8" || semantic === "uint8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
|
|
130
140
|
break;
|
|
131
141
|
}
|
|
132
142
|
case "year":
|
|
@@ -175,6 +185,16 @@ function describeV1Column(column) {
|
|
|
175
185
|
out.dbType = codec?.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
|
|
176
186
|
break;
|
|
177
187
|
case "timestamp":
|
|
188
|
+
// `datetime` is the same fact under MySQL's name for it, and it had no arm, so every column
|
|
189
|
+
// stating it fell to the bare-string arm and was labelled TEXT. The columns that reach this
|
|
190
|
+
// are the string modes of `datetime` on mssql, mysql and singlestore, plus mssql's
|
|
191
|
+
// `datetime2` and `datetimeoffset`, swept over every builder the six v1 cores export; the
|
|
192
|
+
// `{ mode: 'date' }` half of the same builders states `object date` and takes the arm above.
|
|
193
|
+
// A label only, since `dbType` is read outside this file in exactly one place,
|
|
194
|
+
// `isIntegerColumn`, which the generators consult for a `tsType` of `number`. It matters
|
|
195
|
+
// because the class-name path already answers TIMESTAMP for the same 0.4x column, and the
|
|
196
|
+
// two majors disagreeing about a column is what the cross-major diff exists to catch.
|
|
197
|
+
case "datetime":
|
|
178
198
|
out.tsType = js === "string" ? "string" : "Date";
|
|
179
199
|
out.dbType = "TIMESTAMP";
|
|
180
200
|
break;
|
|
@@ -196,13 +216,25 @@ function describeV1Column(column) {
|
|
|
196
216
|
const entity = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
|
|
197
217
|
const bytes = entity.startsWith("MySql") || entity.startsWith("SingleStore");
|
|
198
218
|
out.tsType = "string";
|
|
199
|
-
out.dbType =
|
|
219
|
+
out.dbType = bytes ? "BINARY" : "BIT";
|
|
200
220
|
out.shape = bytes ? { kind: "byteString", length: declaredLength(column) } : {
|
|
201
221
|
kind: "bitstring",
|
|
202
222
|
length: declaredLength(column),
|
|
203
223
|
// A Postgres `bit(3)` holds exactly three digits; a Cockroach `varbit(16)` holds at
|
|
204
224
|
// most that many, which is why `''` is valid there and not here.
|
|
205
|
-
|
|
225
|
+
//
|
|
226
|
+
// `codec === 'bit'` alone was Postgres's answer applied to everything, and Cockroach
|
|
227
|
+
// states no codec, so both of its builders came back `exact: false` and a `bit(3)`
|
|
228
|
+
// was indistinguishable from a `varbit(3)`. Measured on CockroachDB v24.3.5: a
|
|
229
|
+
// `bit(3)` refuses '', '1', '10' and '1011' with "bit string length n does not match
|
|
230
|
+
// type BIT(3)" and takes '101'; a `varbit(8)` takes '', '1' and '10101010' and
|
|
231
|
+
// refuses nine digits with "too large for type VARBIT(8)". `drizzle-orm/zod` at
|
|
232
|
+
// 1.0.0-rc.4 answers the same for both columns.
|
|
233
|
+
//
|
|
234
|
+
// The class rather than a prefix, because `CockroachVarbit` starts with neither
|
|
235
|
+
// `CockroachBit` nor anything else this could key on without catching the varying
|
|
236
|
+
// half too.
|
|
237
|
+
exact: codec === "bit" || entity === "CockroachBit"
|
|
206
238
|
};
|
|
207
239
|
break;
|
|
208
240
|
}
|
|
@@ -254,10 +286,11 @@ function describeV1Column(column) {
|
|
|
254
286
|
out.tsType = "number";
|
|
255
287
|
out.dbType = "NUMERIC";
|
|
256
288
|
out.integer = false;
|
|
257
|
-
|
|
289
|
+
const range = decimalModeRange(column, entityKind, "number");
|
|
290
|
+
if (range) [out.min, out.max] = range;
|
|
258
291
|
if (codec === "numeric:number") {
|
|
259
292
|
out.allowsNaN = true;
|
|
260
|
-
out.allowsInfinity =
|
|
293
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
261
294
|
}
|
|
262
295
|
} else if (js === "string") {
|
|
263
296
|
out.tsType = "string";
|
|
@@ -286,6 +319,28 @@ function declaredLength(column) {
|
|
|
286
319
|
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
287
320
|
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
288
321
|
}
|
|
322
|
+
function declaredDecimalRange(column) {
|
|
323
|
+
const cfg = column?.config ?? {};
|
|
324
|
+
const precision = column?.precision ?? cfg.precision;
|
|
325
|
+
const scale = column?.scale ?? cfg.scale ?? 0;
|
|
326
|
+
if (typeof precision !== "number" || !Number.isInteger(precision) || precision < 1)
|
|
327
|
+
return void 0;
|
|
328
|
+
if (typeof scale !== "number" || !Number.isInteger(scale) || scale < 0) return void 0;
|
|
329
|
+
const nines = "9".repeat(precision);
|
|
330
|
+
const max = scale === 0 ? nines : scale < precision ? `${nines.slice(0, precision - scale)}.${nines.slice(precision - scale)}` : `0.${"0".repeat(scale - precision)}${nines}`;
|
|
331
|
+
return [`-${max}`, max];
|
|
332
|
+
}
|
|
333
|
+
var DECIMAL_NUMBER_MODE = /(?:Numeric|Decimal)Number$/;
|
|
334
|
+
var DECIMAL_BIGINT_MODE = /(?:Numeric|Decimal)BigInt$/;
|
|
335
|
+
var MYSQL_IMPLICIT_DECIMAL_RANGE = ["-9999999999", "9999999999"];
|
|
336
|
+
function decimalModeRange(column, kind, mode) {
|
|
337
|
+
const declared = declaredDecimalRange(column);
|
|
338
|
+
if (declared) return declared;
|
|
339
|
+
if (kind.startsWith("MySql") || kind.startsWith("SingleStore"))
|
|
340
|
+
return MYSQL_IMPLICIT_DECIMAL_RANGE;
|
|
341
|
+
if (kind.startsWith("SQLite")) return void 0;
|
|
342
|
+
return mode === "number" ? JS_SAFE_INTEGER_BOUNDS : void 0;
|
|
343
|
+
}
|
|
289
344
|
var VIEW_CONFIG_FIELDS = {
|
|
290
345
|
"drizzle:Columns": "selectedFields",
|
|
291
346
|
"drizzle:Name": "name",
|
|
@@ -583,6 +638,19 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
583
638
|
out.allowsNaN = nonFinite.nan;
|
|
584
639
|
out.allowsInfinity = nonFinite.infinity;
|
|
585
640
|
}
|
|
641
|
+
if (DECIMAL_NUMBER_MODE.test(ctor)) {
|
|
642
|
+
const range2 = decimalModeRange(column, ctor, "number");
|
|
643
|
+
if (range2) [out.min, out.max] = range2;
|
|
644
|
+
out.integer = false;
|
|
645
|
+
if (ctor === "PgNumericNumber") {
|
|
646
|
+
out.allowsNaN = true;
|
|
647
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
648
|
+
}
|
|
649
|
+
} else if (DECIMAL_BIGINT_MODE.test(ctor)) {
|
|
650
|
+
const range2 = decimalModeRange(column, ctor, "bigint");
|
|
651
|
+
if (range2) [out.min, out.max] = range2;
|
|
652
|
+
out.integer = true;
|
|
653
|
+
}
|
|
586
654
|
if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
|
|
587
655
|
return out;
|
|
588
656
|
}
|
|
@@ -850,7 +918,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
850
918
|
if (/^Gel/i.test(ctor)) {
|
|
851
919
|
if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
|
|
852
920
|
if (/Int53|Integer|SmallInt/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
|
|
853
|
-
if (/
|
|
921
|
+
if (/DoublePrecision/i.test(ctor)) return { tsType: "number", dbType: "DOUBLE" };
|
|
922
|
+
if (/Real/i.test(ctor)) return { tsType: "number", dbType: "REAL" };
|
|
854
923
|
if (/Decimal/i.test(ctor)) return { tsType: "string", dbType: "NUMERIC" };
|
|
855
924
|
if (/UUID/i.test(ctor)) return { tsType: "string", dbType: "UUID" };
|
|
856
925
|
if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
|
|
@@ -901,7 +970,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
901
970
|
const v1 = describeV1Column(col);
|
|
902
971
|
const constraints = this.columnConstraints(col);
|
|
903
972
|
if (v1?.shape) delete constraints.maxLength;
|
|
904
|
-
const sqlKind = String(
|
|
973
|
+
const sqlKind = String(
|
|
974
|
+
outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? ""
|
|
975
|
+
);
|
|
905
976
|
const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
|
|
906
977
|
const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
|
|
907
978
|
const ctorName = String(col?.constructor?.name ?? "");
|
|
@@ -1268,11 +1339,29 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1268
1339
|
SQLiteReal: null,
|
|
1269
1340
|
SingleStoreDouble: null,
|
|
1270
1341
|
SingleStoreReal: null,
|
|
1271
|
-
// `
|
|
1272
|
-
//
|
|
1273
|
-
//
|
|
1274
|
-
|
|
1342
|
+
// Gel, whose `real` is a `std::float32` and whose `doublePrecision` is a `std::float64`. Both
|
|
1343
|
+
// used to be answered by a `/Real|DoublePrecision/i` arm that said NUMERIC and stated nothing
|
|
1344
|
+
// else at all, so a `real` column accepted 1e300 and the server refused it.
|
|
1345
|
+
//
|
|
1346
|
+
// Measured on a live Gel 7.1 (`geldata/gel:7`, sys::get_version_as_str() -> 7.1+08db576)
|
|
1347
|
+
// through the `gel` client, casting each literal so the server parses it, and again through a
|
|
1348
|
+
// stored property on a real object type. The float32 edge is Postgres's exactly, to the double:
|
|
1349
|
+
//
|
|
1350
|
+
// 3.4028234663852886e38 accepted, returned unchanged
|
|
1351
|
+
// 3.4028235677973366e38 accepted, and stored as 3.4028234663852886e38
|
|
1352
|
+
// 3.402823567797337e38 refused, "is out of range for type std::float32"
|
|
1353
|
+
// 1e300 refused, the same way
|
|
1354
|
+
//
|
|
1355
|
+
// The same value accepted, the same next double up refused, and the same rounding down of the
|
|
1356
|
+
// midpoint, so it takes the constant already here rather than a second name for one number.
|
|
1357
|
+
// float64 took 1e300 and Number.MAX_VALUE faithfully, for the reason no 8 byte float has a
|
|
1358
|
+
// truthful finite bound.
|
|
1359
|
+
GelReal: PG_FLOAT4_RANGE,
|
|
1360
|
+
GelDoublePrecision: null
|
|
1275
1361
|
};
|
|
1362
|
+
// `numeric`/`decimal` in either of its two numeric modes is deliberately not in the table above.
|
|
1363
|
+
// Its bound is not a fixed magnitude per class but the precision each column declares for itself,
|
|
1364
|
+
// which no table keyed on a class name can hold; see `declaredDecimalRange`.
|
|
1276
1365
|
/**
|
|
1277
1366
|
* The Postgres number columns that hold a non-finite double, and which of the three each holds.
|
|
1278
1367
|
*
|
|
@@ -1283,17 +1372,25 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1283
1372
|
* table also answers for a v1 column and the two answers are identical rather than merely
|
|
1284
1373
|
* compatible. non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
1285
1374
|
*
|
|
1286
|
-
*
|
|
1287
|
-
* `
|
|
1288
|
-
*
|
|
1375
|
+
* No MySQL, SingleStore or SQLite class belongs here: MySQL refuses all three on a `float`/
|
|
1376
|
+
* `double` and stores `0.00` for a `decimal`, and SQLite returns both infinities while silently
|
|
1377
|
+
* turning `NaN` into NULL, which is a different answer that has to arrive whole.
|
|
1378
|
+
*
|
|
1379
|
+
* Gel does belong, and is the fourth and fifth entries. Measured on a live Gel 7.1 rather than
|
|
1380
|
+
* inferred from it being Postgres-backed: both `std::float32` and `std::float64` stored `nan`,
|
|
1381
|
+
* `inf` and `-inf` and handed all three back as `NaN`, `Infinity` and `-Infinity`, through a cast
|
|
1382
|
+
* and again through a stored property. Without them every row of such a column failed validation.
|
|
1289
1383
|
*
|
|
1290
1384
|
* `PgNumeric` is absent because its value is a string, and its pattern already accepts `NaN` and
|
|
1291
|
-
* `Infinity`. `PgNumericNumber`
|
|
1385
|
+
* `Infinity`. `PgNumericNumber` is absent because its answer is no longer flat: it takes `NaN` at
|
|
1386
|
+
* any width and an infinity only where no precision is declared, which is a per-column question
|
|
1387
|
+
* this table cannot ask. `columnConstraints` answers it beside the bound that decides it.
|
|
1292
1388
|
*/
|
|
1293
1389
|
_SchemaAnalyzer.PG_NON_FINITE = {
|
|
1294
1390
|
PgReal: { nan: true, infinity: true },
|
|
1295
1391
|
PgDoublePrecision: { nan: true, infinity: true },
|
|
1296
|
-
|
|
1392
|
+
GelReal: { nan: true, infinity: true },
|
|
1393
|
+
GelDoublePrecision: { nan: true, infinity: true }
|
|
1297
1394
|
};
|
|
1298
1395
|
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
1299
1396
|
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
|
@@ -65,14 +65,24 @@ function describeV1Column(column) {
|
|
|
65
65
|
const out = {};
|
|
66
66
|
switch (semantic) {
|
|
67
67
|
case "int8":
|
|
68
|
+
case "uint8":
|
|
68
69
|
case "int16":
|
|
69
70
|
case "int24":
|
|
70
71
|
case "int32":
|
|
71
72
|
case "int53":
|
|
72
73
|
case "uint53":
|
|
73
74
|
case "int64": {
|
|
75
|
+
if (DECIMAL_BIGINT_MODE.test(entityKind)) {
|
|
76
|
+
out.tsType = "bigint";
|
|
77
|
+
out.dbType = "NUMERIC";
|
|
78
|
+
out.integer = true;
|
|
79
|
+
const range2 = decimalModeRange(column, entityKind, "bigint");
|
|
80
|
+
if (range2) [out.min, out.max] = range2;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
74
83
|
const range = {
|
|
75
84
|
int8: ["-128", "127"],
|
|
85
|
+
uint8: ["0", "255"],
|
|
76
86
|
int16: ["-32768", "32767"],
|
|
77
87
|
int24: ["-8388608", "8388607"],
|
|
78
88
|
int32: ["-2147483648", "2147483647"],
|
|
@@ -85,7 +95,7 @@ function describeV1Column(column) {
|
|
|
85
95
|
[out.min, out.max] = range;
|
|
86
96
|
out.integer = true;
|
|
87
97
|
out.tsType = js === "bigint" ? "bigint" : "number";
|
|
88
|
-
out.dbType = semantic === "int8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
|
|
98
|
+
out.dbType = semantic === "int8" || semantic === "uint8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
|
|
89
99
|
break;
|
|
90
100
|
}
|
|
91
101
|
case "year":
|
|
@@ -134,6 +144,16 @@ function describeV1Column(column) {
|
|
|
134
144
|
out.dbType = codec?.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
|
|
135
145
|
break;
|
|
136
146
|
case "timestamp":
|
|
147
|
+
// `datetime` is the same fact under MySQL's name for it, and it had no arm, so every column
|
|
148
|
+
// stating it fell to the bare-string arm and was labelled TEXT. The columns that reach this
|
|
149
|
+
// are the string modes of `datetime` on mssql, mysql and singlestore, plus mssql's
|
|
150
|
+
// `datetime2` and `datetimeoffset`, swept over every builder the six v1 cores export; the
|
|
151
|
+
// `{ mode: 'date' }` half of the same builders states `object date` and takes the arm above.
|
|
152
|
+
// A label only, since `dbType` is read outside this file in exactly one place,
|
|
153
|
+
// `isIntegerColumn`, which the generators consult for a `tsType` of `number`. It matters
|
|
154
|
+
// because the class-name path already answers TIMESTAMP for the same 0.4x column, and the
|
|
155
|
+
// two majors disagreeing about a column is what the cross-major diff exists to catch.
|
|
156
|
+
case "datetime":
|
|
137
157
|
out.tsType = js === "string" ? "string" : "Date";
|
|
138
158
|
out.dbType = "TIMESTAMP";
|
|
139
159
|
break;
|
|
@@ -155,13 +175,25 @@ function describeV1Column(column) {
|
|
|
155
175
|
const entity = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
|
|
156
176
|
const bytes = entity.startsWith("MySql") || entity.startsWith("SingleStore");
|
|
157
177
|
out.tsType = "string";
|
|
158
|
-
out.dbType =
|
|
178
|
+
out.dbType = bytes ? "BINARY" : "BIT";
|
|
159
179
|
out.shape = bytes ? { kind: "byteString", length: declaredLength(column) } : {
|
|
160
180
|
kind: "bitstring",
|
|
161
181
|
length: declaredLength(column),
|
|
162
182
|
// A Postgres `bit(3)` holds exactly three digits; a Cockroach `varbit(16)` holds at
|
|
163
183
|
// most that many, which is why `''` is valid there and not here.
|
|
164
|
-
|
|
184
|
+
//
|
|
185
|
+
// `codec === 'bit'` alone was Postgres's answer applied to everything, and Cockroach
|
|
186
|
+
// states no codec, so both of its builders came back `exact: false` and a `bit(3)`
|
|
187
|
+
// was indistinguishable from a `varbit(3)`. Measured on CockroachDB v24.3.5: a
|
|
188
|
+
// `bit(3)` refuses '', '1', '10' and '1011' with "bit string length n does not match
|
|
189
|
+
// type BIT(3)" and takes '101'; a `varbit(8)` takes '', '1' and '10101010' and
|
|
190
|
+
// refuses nine digits with "too large for type VARBIT(8)". `drizzle-orm/zod` at
|
|
191
|
+
// 1.0.0-rc.4 answers the same for both columns.
|
|
192
|
+
//
|
|
193
|
+
// The class rather than a prefix, because `CockroachVarbit` starts with neither
|
|
194
|
+
// `CockroachBit` nor anything else this could key on without catching the varying
|
|
195
|
+
// half too.
|
|
196
|
+
exact: codec === "bit" || entity === "CockroachBit"
|
|
165
197
|
};
|
|
166
198
|
break;
|
|
167
199
|
}
|
|
@@ -213,10 +245,11 @@ function describeV1Column(column) {
|
|
|
213
245
|
out.tsType = "number";
|
|
214
246
|
out.dbType = "NUMERIC";
|
|
215
247
|
out.integer = false;
|
|
216
|
-
|
|
248
|
+
const range = decimalModeRange(column, entityKind, "number");
|
|
249
|
+
if (range) [out.min, out.max] = range;
|
|
217
250
|
if (codec === "numeric:number") {
|
|
218
251
|
out.allowsNaN = true;
|
|
219
|
-
out.allowsInfinity =
|
|
252
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
220
253
|
}
|
|
221
254
|
} else if (js === "string") {
|
|
222
255
|
out.tsType = "string";
|
|
@@ -245,6 +278,28 @@ function declaredLength(column) {
|
|
|
245
278
|
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
246
279
|
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
247
280
|
}
|
|
281
|
+
function declaredDecimalRange(column) {
|
|
282
|
+
const cfg = column?.config ?? {};
|
|
283
|
+
const precision = column?.precision ?? cfg.precision;
|
|
284
|
+
const scale = column?.scale ?? cfg.scale ?? 0;
|
|
285
|
+
if (typeof precision !== "number" || !Number.isInteger(precision) || precision < 1)
|
|
286
|
+
return void 0;
|
|
287
|
+
if (typeof scale !== "number" || !Number.isInteger(scale) || scale < 0) return void 0;
|
|
288
|
+
const nines = "9".repeat(precision);
|
|
289
|
+
const max = scale === 0 ? nines : scale < precision ? `${nines.slice(0, precision - scale)}.${nines.slice(precision - scale)}` : `0.${"0".repeat(scale - precision)}${nines}`;
|
|
290
|
+
return [`-${max}`, max];
|
|
291
|
+
}
|
|
292
|
+
var DECIMAL_NUMBER_MODE = /(?:Numeric|Decimal)Number$/;
|
|
293
|
+
var DECIMAL_BIGINT_MODE = /(?:Numeric|Decimal)BigInt$/;
|
|
294
|
+
var MYSQL_IMPLICIT_DECIMAL_RANGE = ["-9999999999", "9999999999"];
|
|
295
|
+
function decimalModeRange(column, kind, mode) {
|
|
296
|
+
const declared = declaredDecimalRange(column);
|
|
297
|
+
if (declared) return declared;
|
|
298
|
+
if (kind.startsWith("MySql") || kind.startsWith("SingleStore"))
|
|
299
|
+
return MYSQL_IMPLICIT_DECIMAL_RANGE;
|
|
300
|
+
if (kind.startsWith("SQLite")) return void 0;
|
|
301
|
+
return mode === "number" ? JS_SAFE_INTEGER_BOUNDS : void 0;
|
|
302
|
+
}
|
|
248
303
|
var VIEW_CONFIG_FIELDS = {
|
|
249
304
|
"drizzle:Columns": "selectedFields",
|
|
250
305
|
"drizzle:Name": "name",
|
|
@@ -542,6 +597,19 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
542
597
|
out.allowsNaN = nonFinite.nan;
|
|
543
598
|
out.allowsInfinity = nonFinite.infinity;
|
|
544
599
|
}
|
|
600
|
+
if (DECIMAL_NUMBER_MODE.test(ctor)) {
|
|
601
|
+
const range2 = decimalModeRange(column, ctor, "number");
|
|
602
|
+
if (range2) [out.min, out.max] = range2;
|
|
603
|
+
out.integer = false;
|
|
604
|
+
if (ctor === "PgNumericNumber") {
|
|
605
|
+
out.allowsNaN = true;
|
|
606
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
607
|
+
}
|
|
608
|
+
} else if (DECIMAL_BIGINT_MODE.test(ctor)) {
|
|
609
|
+
const range2 = decimalModeRange(column, ctor, "bigint");
|
|
610
|
+
if (range2) [out.min, out.max] = range2;
|
|
611
|
+
out.integer = true;
|
|
612
|
+
}
|
|
545
613
|
if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
|
|
546
614
|
return out;
|
|
547
615
|
}
|
|
@@ -809,7 +877,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
809
877
|
if (/^Gel/i.test(ctor)) {
|
|
810
878
|
if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
|
|
811
879
|
if (/Int53|Integer|SmallInt/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
|
|
812
|
-
if (/
|
|
880
|
+
if (/DoublePrecision/i.test(ctor)) return { tsType: "number", dbType: "DOUBLE" };
|
|
881
|
+
if (/Real/i.test(ctor)) return { tsType: "number", dbType: "REAL" };
|
|
813
882
|
if (/Decimal/i.test(ctor)) return { tsType: "string", dbType: "NUMERIC" };
|
|
814
883
|
if (/UUID/i.test(ctor)) return { tsType: "string", dbType: "UUID" };
|
|
815
884
|
if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
|
|
@@ -860,7 +929,9 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
860
929
|
const v1 = describeV1Column(col);
|
|
861
930
|
const constraints = this.columnConstraints(col);
|
|
862
931
|
if (v1?.shape) delete constraints.maxLength;
|
|
863
|
-
const sqlKind = String(
|
|
932
|
+
const sqlKind = String(
|
|
933
|
+
outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? ""
|
|
934
|
+
);
|
|
864
935
|
const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
|
|
865
936
|
const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
|
|
866
937
|
const ctorName = String(col?.constructor?.name ?? "");
|
|
@@ -1227,11 +1298,29 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1227
1298
|
SQLiteReal: null,
|
|
1228
1299
|
SingleStoreDouble: null,
|
|
1229
1300
|
SingleStoreReal: null,
|
|
1230
|
-
// `
|
|
1231
|
-
//
|
|
1232
|
-
//
|
|
1233
|
-
|
|
1301
|
+
// Gel, whose `real` is a `std::float32` and whose `doublePrecision` is a `std::float64`. Both
|
|
1302
|
+
// used to be answered by a `/Real|DoublePrecision/i` arm that said NUMERIC and stated nothing
|
|
1303
|
+
// else at all, so a `real` column accepted 1e300 and the server refused it.
|
|
1304
|
+
//
|
|
1305
|
+
// Measured on a live Gel 7.1 (`geldata/gel:7`, sys::get_version_as_str() -> 7.1+08db576)
|
|
1306
|
+
// through the `gel` client, casting each literal so the server parses it, and again through a
|
|
1307
|
+
// stored property on a real object type. The float32 edge is Postgres's exactly, to the double:
|
|
1308
|
+
//
|
|
1309
|
+
// 3.4028234663852886e38 accepted, returned unchanged
|
|
1310
|
+
// 3.4028235677973366e38 accepted, and stored as 3.4028234663852886e38
|
|
1311
|
+
// 3.402823567797337e38 refused, "is out of range for type std::float32"
|
|
1312
|
+
// 1e300 refused, the same way
|
|
1313
|
+
//
|
|
1314
|
+
// The same value accepted, the same next double up refused, and the same rounding down of the
|
|
1315
|
+
// midpoint, so it takes the constant already here rather than a second name for one number.
|
|
1316
|
+
// float64 took 1e300 and Number.MAX_VALUE faithfully, for the reason no 8 byte float has a
|
|
1317
|
+
// truthful finite bound.
|
|
1318
|
+
GelReal: PG_FLOAT4_RANGE,
|
|
1319
|
+
GelDoublePrecision: null
|
|
1234
1320
|
};
|
|
1321
|
+
// `numeric`/`decimal` in either of its two numeric modes is deliberately not in the table above.
|
|
1322
|
+
// Its bound is not a fixed magnitude per class but the precision each column declares for itself,
|
|
1323
|
+
// which no table keyed on a class name can hold; see `declaredDecimalRange`.
|
|
1235
1324
|
/**
|
|
1236
1325
|
* The Postgres number columns that hold a non-finite double, and which of the three each holds.
|
|
1237
1326
|
*
|
|
@@ -1242,17 +1331,25 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1242
1331
|
* table also answers for a v1 column and the two answers are identical rather than merely
|
|
1243
1332
|
* compatible. non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
1244
1333
|
*
|
|
1245
|
-
*
|
|
1246
|
-
* `
|
|
1247
|
-
*
|
|
1334
|
+
* No MySQL, SingleStore or SQLite class belongs here: MySQL refuses all three on a `float`/
|
|
1335
|
+
* `double` and stores `0.00` for a `decimal`, and SQLite returns both infinities while silently
|
|
1336
|
+
* turning `NaN` into NULL, which is a different answer that has to arrive whole.
|
|
1337
|
+
*
|
|
1338
|
+
* Gel does belong, and is the fourth and fifth entries. Measured on a live Gel 7.1 rather than
|
|
1339
|
+
* inferred from it being Postgres-backed: both `std::float32` and `std::float64` stored `nan`,
|
|
1340
|
+
* `inf` and `-inf` and handed all three back as `NaN`, `Infinity` and `-Infinity`, through a cast
|
|
1341
|
+
* and again through a stored property. Without them every row of such a column failed validation.
|
|
1248
1342
|
*
|
|
1249
1343
|
* `PgNumeric` is absent because its value is a string, and its pattern already accepts `NaN` and
|
|
1250
|
-
* `Infinity`. `PgNumericNumber`
|
|
1344
|
+
* `Infinity`. `PgNumericNumber` is absent because its answer is no longer flat: it takes `NaN` at
|
|
1345
|
+
* any width and an infinity only where no precision is declared, which is a per-column question
|
|
1346
|
+
* this table cannot ask. `columnConstraints` answers it beside the bound that decides it.
|
|
1251
1347
|
*/
|
|
1252
1348
|
_SchemaAnalyzer.PG_NON_FINITE = {
|
|
1253
1349
|
PgReal: { nan: true, infinity: true },
|
|
1254
1350
|
PgDoublePrecision: { nan: true, infinity: true },
|
|
1255
|
-
|
|
1351
|
+
GelReal: { nan: true, infinity: true },
|
|
1352
|
+
GelDoublePrecision: { nan: true, infinity: true }
|
|
1256
1353
|
};
|
|
1257
1354
|
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
1258
1355
|
var index_default = SchemaAnalyzer;
|