@drzl/analyzer 1.20.0 → 1.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/dist/index.cjs +232 -49
- package/dist/index.d.cts +86 -26
- package/dist/index.d.ts +86 -26
- package/dist/index.js +232 -49
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,8 @@ Drizzle schema → normalized analysis for fast, reliable codegen.
|
|
|
25
25
|
|
|
26
26
|
- Every dollar speeds up CI hardware and offsets long test runs on my aging laptop.
|
|
27
27
|
- Sponsors get roadmap input and priority responses in GitHub Issues.
|
|
28
|
-
- Prefer a quick overview?
|
|
28
|
+
- Prefer a quick overview? The current goals and thank-yous are at
|
|
29
|
+
https://use-drzl.github.io/drzl/sponsor.
|
|
29
30
|
|
|
30
31
|
## Use
|
|
31
32
|
|
|
@@ -48,10 +49,22 @@ The CLI consumes this analysis to generate validation, services, and routers.
|
|
|
48
49
|
- issues (warnings/errors) for constraints and shape
|
|
49
50
|
|
|
50
51
|
Per column, beyond the type: `nullable`, `hasDefault`, `defaultValue` (literal defaults only),
|
|
51
|
-
`isGenerated`, `enumValues`, `maxLength` (characters), `maxBytes` (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
`isGenerated`, `enumValues`, `maxLength` (characters), `maxBytes` (bytes, which is a different
|
|
53
|
+
measurement on the same kind of column), `min`/`max`, `arrayDimensions`, `format`, and `shape` for
|
|
54
|
+
values that are not scalars (json, buffer, tuple, numberObject, vector, bitstring, customType).
|
|
55
|
+
|
|
56
|
+
`maxBytes` is set by MySQL and nothing else. Its TEXT and BLOB families carry their limit in the
|
|
57
|
+
type rather than in a declared length, and count it in bytes: 255 for `tinytext` and `tinyblob`,
|
|
58
|
+
65535 for `text` and `blob`, 16777215 for the medium pair and 4294967295 for the long pair. A
|
|
59
|
+
`varchar(n)` is genuinely n characters and keeps `maxLength`. See
|
|
60
|
+
[the analyzer page](https://use-drzl.github.io/drzl/packages/analyzer) for how the generators
|
|
61
|
+
encode it.
|
|
62
|
+
|
|
63
|
+
`format` names a pattern in `COLUMN_FORMATS`, and two of its values name a dialect: a
|
|
64
|
+
`bigint({ mode: 'string' })` column is `pgBigint` on Postgres and `mysqlBigint` on MySQL and
|
|
65
|
+
SingleStore, because the two servers really do parse that text differently (`'0x1f'` is 31 on one
|
|
66
|
+
and an error on the other, `'12.5'` is 13 on one and an error on the other). SQL Server carries no
|
|
67
|
+
format for it, since none was measured.
|
|
55
68
|
|
|
56
69
|
`DRZL_ANL_UNKNOWN_COLUMN` is reported for any column whose validator would accept anything, which
|
|
57
70
|
is the shape a missing type mapping takes: nothing throws, and every row passes.
|
package/dist/index.cjs
CHANGED
|
@@ -116,11 +116,15 @@ function describeV1Column(column) {
|
|
|
116
116
|
case "int8":
|
|
117
117
|
case "uint8":
|
|
118
118
|
case "int16":
|
|
119
|
+
case "uint16":
|
|
119
120
|
case "int24":
|
|
121
|
+
case "uint24":
|
|
120
122
|
case "int32":
|
|
123
|
+
case "uint32":
|
|
121
124
|
case "int53":
|
|
122
125
|
case "uint53":
|
|
123
|
-
case "int64":
|
|
126
|
+
case "int64":
|
|
127
|
+
case "uint64": {
|
|
124
128
|
if (DECIMAL_BIGINT_MODE.test(entityKind)) {
|
|
125
129
|
out.tsType = "bigint";
|
|
126
130
|
out.dbType = "NUMERIC";
|
|
@@ -129,22 +133,44 @@ function describeV1Column(column) {
|
|
|
129
133
|
if (range2) [out.min, out.max] = range2;
|
|
130
134
|
break;
|
|
131
135
|
}
|
|
136
|
+
if ((semantic === "int64" || semantic === "uint64") && js === "string") {
|
|
137
|
+
out.tsType = "string";
|
|
138
|
+
out.dbType = "BIGINT";
|
|
139
|
+
if (entityKind.startsWith("Pg")) out.format = "pgBigint";
|
|
140
|
+
else if (entityKind.startsWith("MySql") || entityKind.startsWith("SingleStore"))
|
|
141
|
+
out.format = "mysqlBigint";
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
132
144
|
const range = {
|
|
133
145
|
int8: ["-128", "127"],
|
|
134
146
|
uint8: ["0", "255"],
|
|
135
147
|
int16: ["-32768", "32767"],
|
|
148
|
+
uint16: ["0", "65535"],
|
|
136
149
|
int24: ["-8388608", "8388607"],
|
|
150
|
+
uint24: ["0", "16777215"],
|
|
137
151
|
int32: ["-2147483648", "2147483647"],
|
|
152
|
+
uint32: ["0", "4294967295"],
|
|
138
153
|
int53: ["-9007199254740991", "9007199254740991"],
|
|
139
154
|
// MySQL `serial` is `bigint unsigned auto_increment`, so it starts at 0 rather than
|
|
140
|
-
// spanning the signed range.
|
|
155
|
+
// spanning the signed range. An explicit `bigint({ mode: 'number', unsigned: true })`
|
|
156
|
+
// states the same semantic and takes the same answer.
|
|
141
157
|
uint53: ["0", "9007199254740991"],
|
|
142
|
-
int64: ["-9223372036854775808", "9223372036854775807"]
|
|
158
|
+
int64: ["-9223372036854775808", "9223372036854775807"],
|
|
159
|
+
uint64: ["0", "18446744073709551615"]
|
|
143
160
|
}[semantic];
|
|
144
161
|
[out.min, out.max] = range;
|
|
145
162
|
out.integer = true;
|
|
146
163
|
out.tsType = js === "bigint" ? "bigint" : "number";
|
|
147
|
-
out.dbType =
|
|
164
|
+
out.dbType = {
|
|
165
|
+
int8: "TINYINT",
|
|
166
|
+
uint8: "TINYINT",
|
|
167
|
+
int16: "SMALLINT",
|
|
168
|
+
uint16: "SMALLINT",
|
|
169
|
+
int24: "MEDIUMINT",
|
|
170
|
+
uint24: "MEDIUMINT",
|
|
171
|
+
int32: "INTEGER",
|
|
172
|
+
uint32: "INTEGER"
|
|
173
|
+
}[semantic] ?? "BIGINT";
|
|
148
174
|
break;
|
|
149
175
|
}
|
|
150
176
|
case "year":
|
|
@@ -164,6 +190,10 @@ function describeV1Column(column) {
|
|
|
164
190
|
out.allowsNaN = true;
|
|
165
191
|
out.allowsInfinity = true;
|
|
166
192
|
}
|
|
193
|
+
if (codec === "float" || codec === "double" || codec === "real" || entityKind.startsWith("SingleStore")) {
|
|
194
|
+
out.allowsNaN = false;
|
|
195
|
+
out.allowsInfinity = false;
|
|
196
|
+
}
|
|
167
197
|
break;
|
|
168
198
|
}
|
|
169
199
|
case "uuid":
|
|
@@ -431,6 +461,13 @@ function unknownColumnHint(reason) {
|
|
|
431
461
|
return "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns.";
|
|
432
462
|
}
|
|
433
463
|
var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
464
|
+
/**
|
|
465
|
+
* One path or several. The plural exists for drizzle-kit interop: kit's `schema` key names
|
|
466
|
+
* files in the plural (arrays, globs), and the commonest multi-file layout is a directory of
|
|
467
|
+
* one file per table with no barrel, so there is no single module to point at. Entries are
|
|
468
|
+
* concrete files, never globs; expansion is the caller's job, so this class's contract stays
|
|
469
|
+
* "load exactly these modules and read their exports as one schema".
|
|
470
|
+
*/
|
|
434
471
|
constructor(schemaPath) {
|
|
435
472
|
this.schemaPath = schemaPath;
|
|
436
473
|
}
|
|
@@ -652,7 +689,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
652
689
|
if (typeof length === "number" && Number.isFinite(length) && length > 0) {
|
|
653
690
|
out.maxLength = length;
|
|
654
691
|
}
|
|
655
|
-
const
|
|
692
|
+
const unsignedRange = column?.config?.unsigned === true ? _SchemaAnalyzer.UNSIGNED_INT_RANGES[ctor] : void 0;
|
|
693
|
+
const range = unsignedRange ?? _SchemaAnalyzer.INT_RANGES[ctor];
|
|
656
694
|
if (range) {
|
|
657
695
|
[out.min, out.max] = range;
|
|
658
696
|
out.integer = true;
|
|
@@ -662,7 +700,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
662
700
|
if (inexact) [out.min, out.max] = inexact;
|
|
663
701
|
out.integer = false;
|
|
664
702
|
}
|
|
665
|
-
const nonFinite = _SchemaAnalyzer.
|
|
703
|
+
const nonFinite = _SchemaAnalyzer.NON_FINITE_BY_CLASS[ctor];
|
|
666
704
|
if (nonFinite) {
|
|
667
705
|
out.allowsNaN = nonFinite.nan;
|
|
668
706
|
out.allowsInfinity = nonFinite.infinity;
|
|
@@ -907,7 +945,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
907
945
|
}
|
|
908
946
|
if (/Numeric|Float|Double|Real/i.test(ctor))
|
|
909
947
|
return { tsType: "number", dbType: "NUMERIC" };
|
|
910
|
-
if (/
|
|
948
|
+
if (/Serial/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
|
|
949
|
+
if (/Int|TinyInt|SmallInt|MediumInt/i.test(ctor))
|
|
911
950
|
return { tsType: "number", dbType: "INTEGER" };
|
|
912
951
|
if (/Bool|Boolean/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
913
952
|
if (/TimestampString|DateTimeString|DateString/i.test(ctor))
|
|
@@ -931,7 +970,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
931
970
|
}
|
|
932
971
|
if (/Numeric|Float|Double|Real/i.test(ctor))
|
|
933
972
|
return { tsType: "number", dbType: "NUMERIC" };
|
|
934
|
-
if (/
|
|
973
|
+
if (/Serial/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
|
|
974
|
+
if (/Int|TinyInt|SmallInt|MediumInt/i.test(ctor))
|
|
935
975
|
return { tsType: "number", dbType: "INTEGER" };
|
|
936
976
|
if (/Bool|Boolean/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
937
977
|
if (/TimestampString|DateTimeString|DateString/i.test(ctor))
|
|
@@ -1142,31 +1182,85 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1142
1182
|
const fs = await import("fs/promises");
|
|
1143
1183
|
const path = await import("path");
|
|
1144
1184
|
const issues = [];
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1185
|
+
const listed = Array.isArray(this.schemaPath);
|
|
1186
|
+
const inputs = listed ? this.schemaPath : [this.schemaPath];
|
|
1187
|
+
const fulls = inputs.map((p) => path.resolve(process.cwd(), p));
|
|
1188
|
+
let missing = false;
|
|
1189
|
+
for (let i = 0; i < fulls.length; i++) {
|
|
1190
|
+
try {
|
|
1191
|
+
await fs.access(fulls[i]);
|
|
1192
|
+
} catch (_e) {
|
|
1193
|
+
missing = true;
|
|
1194
|
+
issues.push({
|
|
1195
|
+
code: "DRZL_ANL_NOFILE",
|
|
1196
|
+
level: "error",
|
|
1197
|
+
message: `Schema file not found: ${inputs[i]}`
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1155
1200
|
}
|
|
1156
|
-
|
|
1157
|
-
try {
|
|
1158
|
-
const { default: jiti } = await import("jiti");
|
|
1159
|
-
const jit = jiti(import_meta.url, { moduleCache: false });
|
|
1160
|
-
mod = jit(full);
|
|
1161
|
-
} catch (e) {
|
|
1162
|
-
issues.push({
|
|
1163
|
-
code: "DRZL_ANL_IMPORT",
|
|
1164
|
-
level: "error",
|
|
1165
|
-
message: `Failed to import schema: ${String(e)}`
|
|
1166
|
-
});
|
|
1201
|
+
if (missing) {
|
|
1167
1202
|
return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
|
|
1168
1203
|
}
|
|
1169
|
-
const
|
|
1204
|
+
const { default: jiti } = await import("jiti");
|
|
1205
|
+
const jit = jiti(import_meta.url, { moduleCache: false });
|
|
1206
|
+
const exportsObj = {};
|
|
1207
|
+
const exportOrigin = /* @__PURE__ */ new Map();
|
|
1208
|
+
const duplicateDisagreement = (a, b) => {
|
|
1209
|
+
if (Object.is(a, b)) return null;
|
|
1210
|
+
const aCols = this.getSymbol(a, "drizzle:Columns");
|
|
1211
|
+
const bCols = this.getSymbol(b, "drizzle:Columns");
|
|
1212
|
+
if (aCols && bCols) {
|
|
1213
|
+
const aName = this.getSymbol(a, "drizzle:Name");
|
|
1214
|
+
const bName = this.getSymbol(b, "drizzle:Name");
|
|
1215
|
+
const aSchema = this.getSymbol(a, "drizzle:Schema");
|
|
1216
|
+
const bSchema = this.getSymbol(b, "drizzle:Schema");
|
|
1217
|
+
if (aName !== bName || aSchema !== bSchema) {
|
|
1218
|
+
return `two different tables ("${String(aName)}" and "${String(bName)}")`;
|
|
1219
|
+
}
|
|
1220
|
+
if (Object.keys(aCols).join(",") !== Object.keys(bCols).join(",")) {
|
|
1221
|
+
return `two declarations of table "${String(aName)}" with different columns`;
|
|
1222
|
+
}
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
if (!!aCols !== !!bCols) return "a table and a non-table";
|
|
1226
|
+
const aEnum = a?.enumValues;
|
|
1227
|
+
const bEnum = b?.enumValues;
|
|
1228
|
+
if (Array.isArray(aEnum) && Array.isArray(bEnum)) {
|
|
1229
|
+
return JSON.stringify(aEnum) === JSON.stringify(bEnum) ? null : "two enums with different values";
|
|
1230
|
+
}
|
|
1231
|
+
return null;
|
|
1232
|
+
};
|
|
1233
|
+
for (let i = 0; i < fulls.length; i++) {
|
|
1234
|
+
let mod;
|
|
1235
|
+
try {
|
|
1236
|
+
mod = jit(fulls[i]);
|
|
1237
|
+
} catch (e) {
|
|
1238
|
+
issues.push({
|
|
1239
|
+
code: "DRZL_ANL_IMPORT",
|
|
1240
|
+
level: "error",
|
|
1241
|
+
// The single-path message keeps its historical bytes; a list names the file, since
|
|
1242
|
+
// "the schema" no longer identifies one.
|
|
1243
|
+
message: listed ? `Failed to import schema ${inputs[i]}: ${String(e)}` : `Failed to import schema: ${String(e)}`
|
|
1244
|
+
});
|
|
1245
|
+
return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
|
|
1246
|
+
}
|
|
1247
|
+
const one = mod?.default && typeof mod.default === "object" ? mod.default : mod;
|
|
1248
|
+
for (const [name, val] of Object.entries(one)) {
|
|
1249
|
+
if (!(name in exportsObj)) {
|
|
1250
|
+
exportsObj[name] = val;
|
|
1251
|
+
exportOrigin.set(name, inputs[i]);
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1254
|
+
const disagreement = duplicateDisagreement(exportsObj[name], val);
|
|
1255
|
+
if (!disagreement) continue;
|
|
1256
|
+
issues.push({
|
|
1257
|
+
code: "DRZL_ANL_DUP_EXPORT",
|
|
1258
|
+
level: "warn",
|
|
1259
|
+
message: `Export "${name}" is ${disagreement}: defined by both ${exportOrigin.get(name)} and ${inputs[i]}; keeping the one in ${exportOrigin.get(name)}.`,
|
|
1260
|
+
path: name
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1170
1264
|
const tables = [];
|
|
1171
1265
|
const relations = [];
|
|
1172
1266
|
const enums = [];
|
|
@@ -1319,6 +1413,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1319
1413
|
_SchemaAnalyzer.INT_RANGES = {
|
|
1320
1414
|
// 8 bit
|
|
1321
1415
|
MySqlTinyInt: ["-128", "127"],
|
|
1416
|
+
// Absent until the unsigned fix swept the family: v1 states `number int8` for the same
|
|
1417
|
+
// column, so the majors disagreed about every SingleStore tinyint. That is the shape the
|
|
1418
|
+
// cross-major diff in `scripts/verify-packed.sh` exists to catch, and its fixture carries no
|
|
1419
|
+
// SingleStore table, so unsigned-int-ranges.spec.ts holds these two classes across both
|
|
1420
|
+
// majors instead. The width is the type's, the one `MySqlTinyInt` beside it already carries.
|
|
1421
|
+
SingleStoreTinyInt: ["-128", "127"],
|
|
1322
1422
|
SQLiteInteger: ["-9223372036854775808", "9223372036854775807"],
|
|
1323
1423
|
// 16 bit
|
|
1324
1424
|
PgSmallInt: ["-32768", "32767"],
|
|
@@ -1331,6 +1431,8 @@ _SchemaAnalyzer.INT_RANGES = {
|
|
|
1331
1431
|
SingleStoreSmallInt: ["-32768", "32767"],
|
|
1332
1432
|
// 24 bit
|
|
1333
1433
|
MySqlMediumInt: ["-8388608", "8388607"],
|
|
1434
|
+
// As SingleStoreTinyInt above: v1 states `number int24` and this table said nothing.
|
|
1435
|
+
SingleStoreMediumInt: ["-8388608", "8388607"],
|
|
1334
1436
|
// 32 bit
|
|
1335
1437
|
PgInteger: ["-2147483648", "2147483647"],
|
|
1336
1438
|
PgSerial: ["-2147483648", "2147483647"],
|
|
@@ -1345,7 +1447,61 @@ _SchemaAnalyzer.INT_RANGES = {
|
|
|
1345
1447
|
PgBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
1346
1448
|
PgBigSerial64: ["-9223372036854775808", "9223372036854775807"],
|
|
1347
1449
|
MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
1348
|
-
SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
|
|
1450
|
+
SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
1451
|
+
// MySQL and SingleStore `serial`, which is `bigint unsigned auto_increment`: unsigned by the
|
|
1452
|
+
// builder's own definition, with no `config.unsigned` stating it, so the flag-keyed table
|
|
1453
|
+
// below cannot answer and the range lives here. The mode is number, so the safe-integer
|
|
1454
|
+
// ceiling rather than the column's, exactly as the 53 bit block above. The Postgres serials
|
|
1455
|
+
// stay signed on purpose: a Postgres serial is a plain integer defaulting from a sequence,
|
|
1456
|
+
// and the negative backfill note above applies to them and not to these. Before this entry
|
|
1457
|
+
// the class was in no table at all, so an auto-increment column accepted -1 and the majors
|
|
1458
|
+
// disagreed: v1 states `number uint53` for the same column and was already bounded.
|
|
1459
|
+
MySqlSerial: ["0", "9007199254740991"],
|
|
1460
|
+
SingleStoreSerial: ["0", "9007199254740991"]
|
|
1461
|
+
};
|
|
1462
|
+
/**
|
|
1463
|
+
* The same widths with `{ unsigned: true }` set, which is the half the table above cannot see.
|
|
1464
|
+
*
|
|
1465
|
+
* On 0.4x the flag moves no class name: `int('x', { unsigned: true })` still builds a
|
|
1466
|
+
* `MySqlInt`, and only `config.unsigned` and the ` unsigned` suffix on `getSQLType()` record
|
|
1467
|
+
* the difference, measured off real 0.45.2 columns. So the table above answered every unsigned
|
|
1468
|
+
* width with its signed range, and the emitted select schema refused every stored value in the
|
|
1469
|
+
* upper half of the column: an `int unsigned` holding 4294967295 failed validation on a row the
|
|
1470
|
+
* database returned, and the same one width up meant `bigint unsigned` refused
|
|
1471
|
+
* 18446744073709551615n.
|
|
1472
|
+
*
|
|
1473
|
+
* The ceilings are the type's, verified against a live MySQL 8.4.11: 255, 65535, 16777215 and
|
|
1474
|
+
* 4294967295 store and return, -1 and each ceiling plus one are refused with
|
|
1475
|
+
* ER_WARN_DATA_OUT_OF_RANGE. The bigint pair keeps the two modes apart for the reason the
|
|
1476
|
+
* signed pair above does: number mode tops out at the safe-integer bound the wire imposes,
|
|
1477
|
+
* bigint mode at the column's own 2^64-1, which a bigint can spell. SingleStore is MySQL wire
|
|
1478
|
+
* compatible, ships the same builders with the same `config.unsigned`, and v1 states the same
|
|
1479
|
+
* `uintN` semantics for it, measured off real rc.4 columns; the entries keep the majors in
|
|
1480
|
+
* agreement, which is what the cross-major diff in `scripts/verify-packed.sh` holds together.
|
|
1481
|
+
*
|
|
1482
|
+
* Keyed by class exactly like `INT_RANGES`, and consulted only when `config.unsigned` is
|
|
1483
|
+
* `true`, so no Postgres or SQLite column can ever reach it: neither dialect has an unsigned
|
|
1484
|
+
* spelling, neither builder accepts the flag, and no class of theirs is named here.
|
|
1485
|
+
*/
|
|
1486
|
+
_SchemaAnalyzer.UNSIGNED_INT_RANGES = {
|
|
1487
|
+
// 8 bit
|
|
1488
|
+
MySqlTinyInt: ["0", "255"],
|
|
1489
|
+
SingleStoreTinyInt: ["0", "255"],
|
|
1490
|
+
// 16 bit
|
|
1491
|
+
MySqlSmallInt: ["0", "65535"],
|
|
1492
|
+
SingleStoreSmallInt: ["0", "65535"],
|
|
1493
|
+
// 24 bit
|
|
1494
|
+
MySqlMediumInt: ["0", "16777215"],
|
|
1495
|
+
SingleStoreMediumInt: ["0", "16777215"],
|
|
1496
|
+
// 32 bit
|
|
1497
|
+
MySqlInt: ["0", "4294967295"],
|
|
1498
|
+
SingleStoreInt: ["0", "4294967295"],
|
|
1499
|
+
// 53 bit, the JS safe-integer ceiling rather than the column's
|
|
1500
|
+
MySqlBigInt53: ["0", "9007199254740991"],
|
|
1501
|
+
SingleStoreBigInt53: ["0", "9007199254740991"],
|
|
1502
|
+
// 64 bit, representable because the value is a bigint
|
|
1503
|
+
MySqlBigInt64: ["0", "18446744073709551615"],
|
|
1504
|
+
SingleStoreBigInt64: ["0", "18446744073709551615"]
|
|
1349
1505
|
};
|
|
1350
1506
|
/**
|
|
1351
1507
|
* The numeric column classes that are not exact, and the magnitude each one can really hold.
|
|
@@ -1420,34 +1576,61 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1420
1576
|
// Its bound is not a fixed magnitude per class but the precision each column declares for itself,
|
|
1421
1577
|
// which no table keyed on a class name can hold; see `declaredDecimalRange`.
|
|
1422
1578
|
/**
|
|
1423
|
-
* The
|
|
1579
|
+
* The number columns whose server has an answer about a non-finite double, and what it is.
|
|
1580
|
+
*
|
|
1581
|
+
* Three states rather than two, and the third is the reason this table has a `false` half at all.
|
|
1582
|
+
* A column present here with `true` stores the value and hands it back, so a schema refusing it
|
|
1583
|
+
* refuses rows the column returns. A column present with `false` is one the server was asked
|
|
1584
|
+
* about and refused, so a schema accepting it promises what the server will not take. A column
|
|
1585
|
+
* *absent* is one nobody has measured, and the generators leave whatever their library does alone
|
|
1586
|
+
* rather than guessing; `nonFiniteAccepted` and `nonFiniteRefused` in `@drzl/validation-core` are
|
|
1587
|
+
* the two readings of that.
|
|
1424
1588
|
*
|
|
1425
1589
|
* The class-name half of what `describeV1Column` reads off the codec, and the two must agree: a
|
|
1426
1590
|
* fact stated on one path and not the other is a schema that changes when the user upgrades
|
|
1427
|
-
* drizzle, which the cross-major diff in `verify-packed.sh` fails on.
|
|
1428
|
-
*
|
|
1429
|
-
*
|
|
1430
|
-
*
|
|
1591
|
+
* drizzle, which the cross-major diff in `verify-packed.sh` fails on. Every class name here is the
|
|
1592
|
+
* same on both majors, read off real columns on 0.45.2 and on 1.0.0-rc.4, so this table also
|
|
1593
|
+
* answers for a v1 column and the two answers are identical rather than merely compatible.
|
|
1594
|
+
* non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
1595
|
+
*
|
|
1596
|
+
* Postgres and Gel store all three. Gel joined on a measurement of its own rather than on being
|
|
1597
|
+
* Postgres-backed: a live Gel 7.1 stored `nan`, `inf` and `-inf` in both `std::float32` and
|
|
1598
|
+
* `std::float64` and handed all three back, through a cast and again through a stored property.
|
|
1599
|
+
* Without them every row of such a column failed validation.
|
|
1431
1600
|
*
|
|
1432
|
-
*
|
|
1433
|
-
*
|
|
1434
|
-
*
|
|
1601
|
+
* MySQL and SingleStore refuse all three, and that used to be left unstated on the reasoning that
|
|
1602
|
+
* a column stating nothing costs nothing. It cost two libraries: `v.number()` and ArkType's
|
|
1603
|
+
* `number` take both infinities where `z.number()` and `Type.Number()` refuse them, so an
|
|
1604
|
+
* unbounded `double` or `real` accepted a value the server answers `ER_WARN_DATA_OUT_OF_RANGE`
|
|
1605
|
+
* for. Measured on MySQL 8.4.11 in `STRICT_TRANS_TABLES`, on the binary prepared path, which is
|
|
1606
|
+
* the one that puts the real IEEE double on the wire: `float`, `double` and `real` refuse
|
|
1607
|
+
* `Infinity`, `-Infinity` and `NaN` alike, while `double` and `real` store 1e300 and
|
|
1608
|
+
* 3.4028235e38 unchanged. SingleStore is MySQL wire-compatible and unmeasured, and takes MySQL's
|
|
1609
|
+
* answer here exactly as it already takes MySQL's float32 bound in `INEXACT_RANGES`.
|
|
1435
1610
|
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1439
|
-
* and again through a stored property. Without them every row of such a column failed validation.
|
|
1611
|
+
* No SQLite class belongs here in either direction. A real SQLite 3.53.4 stores both infinities in
|
|
1612
|
+
* a `real` and hands them back, and silently turns `NaN` into NULL, so it is neither the Postgres
|
|
1613
|
+
* answer nor the MySQL one; it is filed on its own and a column needs both halves of it or none.
|
|
1440
1614
|
*
|
|
1441
|
-
*
|
|
1442
|
-
* `Infinity`. `PgNumericNumber` is
|
|
1443
|
-
* any width and an infinity only where no precision is declared,
|
|
1444
|
-
*
|
|
1615
|
+
* The decimal families are absent too. `PgNumeric` is a string whose pattern already accepts `NaN`
|
|
1616
|
+
* and `Infinity`. `PgNumericNumber` is a per-column question this table cannot ask: it takes `NaN`
|
|
1617
|
+
* at any width and an infinity only where no precision is declared, and `columnConstraints`
|
|
1618
|
+
* answers it beside the bound that decides it. MySQL's `decimal` is absent because the two client
|
|
1619
|
+
* paths disagree: on the binary prepared path MySQL 8.4.11 silently stored `0.00` for all three,
|
|
1620
|
+
* where the text path answers `Incorrect decimal value`, and "refuses" is only half true of a
|
|
1621
|
+
* column that accepted the row.
|
|
1445
1622
|
*/
|
|
1446
|
-
_SchemaAnalyzer.
|
|
1623
|
+
_SchemaAnalyzer.NON_FINITE_BY_CLASS = {
|
|
1447
1624
|
PgReal: { nan: true, infinity: true },
|
|
1448
1625
|
PgDoublePrecision: { nan: true, infinity: true },
|
|
1449
1626
|
GelReal: { nan: true, infinity: true },
|
|
1450
|
-
GelDoublePrecision: { nan: true, infinity: true }
|
|
1627
|
+
GelDoublePrecision: { nan: true, infinity: true },
|
|
1628
|
+
MySqlFloat: { nan: false, infinity: false },
|
|
1629
|
+
MySqlDouble: { nan: false, infinity: false },
|
|
1630
|
+
MySqlReal: { nan: false, infinity: false },
|
|
1631
|
+
SingleStoreFloat: { nan: false, infinity: false },
|
|
1632
|
+
SingleStoreDouble: { nan: false, infinity: false },
|
|
1633
|
+
SingleStoreReal: { nan: false, infinity: false }
|
|
1451
1634
|
};
|
|
1452
1635
|
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
1453
1636
|
var index_default = SchemaAnalyzer;
|
package/dist/index.d.cts
CHANGED
|
@@ -59,9 +59,11 @@ interface Column {
|
|
|
59
59
|
/**
|
|
60
60
|
* A coarse label for the column's kind, not its type.
|
|
61
61
|
*
|
|
62
|
-
* `varchar`, `char` and `text` are all `TEXT` here, deliberately:
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* `varchar`, `char` and `text` are all `TEXT` here, deliberately: two consumers read this and
|
|
63
|
+
* both ask coarse questions. `isIntegerColumn` asks whether a number is whole, and
|
|
64
|
+
* `comparisonWire` in `@drzl/validation-core` asks whether a string wire carries decimal text
|
|
65
|
+
* the database compares numerically, which is `NUMERIC` (every decimal family) and `BIGINT`
|
|
66
|
+
* (the v1 string mode). Use `sqlType` for the question this name suggests it answers.
|
|
65
67
|
*/
|
|
66
68
|
dbType: string;
|
|
67
69
|
/**
|
|
@@ -172,13 +174,18 @@ interface Column {
|
|
|
172
174
|
/**
|
|
173
175
|
* A string column whose contents have a shape the database enforces.
|
|
174
176
|
*
|
|
175
|
-
* Only formats checked against
|
|
177
|
+
* Only formats checked against a real server appear here, and the list is short because most
|
|
176
178
|
* candidates failed: Postgres reads `'today'` and `'January 8, 1999'` as dates, pads
|
|
177
179
|
* `'2020-01-01'` into a macaddr, and accepts `'10.1/16'` as an inet. A check for any of those
|
|
178
180
|
* would reject input the database accepts, and turning away valid data is worse than not
|
|
179
181
|
* checking at all. See `COLUMN_FORMATS` in `@drzl/validation-core`.
|
|
182
|
+
*
|
|
183
|
+
* Two of the keys name a dialect, because the same column has two different answers: a
|
|
184
|
+
* `bigint({ mode: 'string' })` is parsed by Postgres as an integer literal, `'0x1f'` and
|
|
185
|
+
* `'1_000'` included, and by MySQL as a decimal number it then rounds, so `'12.5'` is a row on
|
|
186
|
+
* one server and an error on the other.
|
|
180
187
|
*/
|
|
181
|
-
format?: 'uuid' | 'numeric';
|
|
188
|
+
format?: 'uuid' | 'numeric' | 'pgBigint' | 'mysqlBigint';
|
|
182
189
|
/**
|
|
183
190
|
* The column's default, when it is a literal a schema can reproduce.
|
|
184
191
|
*
|
|
@@ -474,7 +481,14 @@ declare function isRelationsV2(val: any): boolean;
|
|
|
474
481
|
declare function readRelationsV2(val: any, issues?: Issue[]): Relation[];
|
|
475
482
|
declare class SchemaAnalyzer {
|
|
476
483
|
private readonly schemaPath;
|
|
477
|
-
|
|
484
|
+
/**
|
|
485
|
+
* One path or several. The plural exists for drizzle-kit interop: kit's `schema` key names
|
|
486
|
+
* files in the plural (arrays, globs), and the commonest multi-file layout is a directory of
|
|
487
|
+
* one file per table with no barrel, so there is no single module to point at. Entries are
|
|
488
|
+
* concrete files, never globs; expansion is the caller's job, so this class's contract stays
|
|
489
|
+
* "load exactly these modules and read their exports as one schema".
|
|
490
|
+
*/
|
|
491
|
+
constructor(schemaPath: string | readonly string[]);
|
|
478
492
|
private getSymbol;
|
|
479
493
|
/**
|
|
480
494
|
* Drizzle keys the Columns object by TypeScript property name, but every other piece of
|
|
@@ -564,6 +578,31 @@ declare class SchemaAnalyzer {
|
|
|
564
578
|
* promise a precision that cannot survive the round trip.
|
|
565
579
|
*/
|
|
566
580
|
private static readonly INT_RANGES;
|
|
581
|
+
/**
|
|
582
|
+
* The same widths with `{ unsigned: true }` set, which is the half the table above cannot see.
|
|
583
|
+
*
|
|
584
|
+
* On 0.4x the flag moves no class name: `int('x', { unsigned: true })` still builds a
|
|
585
|
+
* `MySqlInt`, and only `config.unsigned` and the ` unsigned` suffix on `getSQLType()` record
|
|
586
|
+
* the difference, measured off real 0.45.2 columns. So the table above answered every unsigned
|
|
587
|
+
* width with its signed range, and the emitted select schema refused every stored value in the
|
|
588
|
+
* upper half of the column: an `int unsigned` holding 4294967295 failed validation on a row the
|
|
589
|
+
* database returned, and the same one width up meant `bigint unsigned` refused
|
|
590
|
+
* 18446744073709551615n.
|
|
591
|
+
*
|
|
592
|
+
* The ceilings are the type's, verified against a live MySQL 8.4.11: 255, 65535, 16777215 and
|
|
593
|
+
* 4294967295 store and return, -1 and each ceiling plus one are refused with
|
|
594
|
+
* ER_WARN_DATA_OUT_OF_RANGE. The bigint pair keeps the two modes apart for the reason the
|
|
595
|
+
* signed pair above does: number mode tops out at the safe-integer bound the wire imposes,
|
|
596
|
+
* bigint mode at the column's own 2^64-1, which a bigint can spell. SingleStore is MySQL wire
|
|
597
|
+
* compatible, ships the same builders with the same `config.unsigned`, and v1 states the same
|
|
598
|
+
* `uintN` semantics for it, measured off real rc.4 columns; the entries keep the majors in
|
|
599
|
+
* agreement, which is what the cross-major diff in `scripts/verify-packed.sh` holds together.
|
|
600
|
+
*
|
|
601
|
+
* Keyed by class exactly like `INT_RANGES`, and consulted only when `config.unsigned` is
|
|
602
|
+
* `true`, so no Postgres or SQLite column can ever reach it: neither dialect has an unsigned
|
|
603
|
+
* spelling, neither builder accepts the flag, and no class of theirs is named here.
|
|
604
|
+
*/
|
|
605
|
+
private static readonly UNSIGNED_INT_RANGES;
|
|
567
606
|
/**
|
|
568
607
|
* The numeric column classes that are not exact, and the magnitude each one can really hold.
|
|
569
608
|
*
|
|
@@ -601,30 +640,51 @@ declare class SchemaAnalyzer {
|
|
|
601
640
|
*/
|
|
602
641
|
private static readonly INEXACT_RANGES;
|
|
603
642
|
/**
|
|
604
|
-
* The
|
|
643
|
+
* The number columns whose server has an answer about a non-finite double, and what it is.
|
|
644
|
+
*
|
|
645
|
+
* Three states rather than two, and the third is the reason this table has a `false` half at all.
|
|
646
|
+
* A column present here with `true` stores the value and hands it back, so a schema refusing it
|
|
647
|
+
* refuses rows the column returns. A column present with `false` is one the server was asked
|
|
648
|
+
* about and refused, so a schema accepting it promises what the server will not take. A column
|
|
649
|
+
* *absent* is one nobody has measured, and the generators leave whatever their library does alone
|
|
650
|
+
* rather than guessing; `nonFiniteAccepted` and `nonFiniteRefused` in `@drzl/validation-core` are
|
|
651
|
+
* the two readings of that.
|
|
605
652
|
*
|
|
606
653
|
* The class-name half of what `describeV1Column` reads off the codec, and the two must agree: a
|
|
607
654
|
* fact stated on one path and not the other is a schema that changes when the user upgrades
|
|
608
|
-
* drizzle, which the cross-major diff in `verify-packed.sh` fails on.
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
655
|
+
* drizzle, which the cross-major diff in `verify-packed.sh` fails on. Every class name here is the
|
|
656
|
+
* same on both majors, read off real columns on 0.45.2 and on 1.0.0-rc.4, so this table also
|
|
657
|
+
* answers for a v1 column and the two answers are identical rather than merely compatible.
|
|
658
|
+
* non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
659
|
+
*
|
|
660
|
+
* Postgres and Gel store all three. Gel joined on a measurement of its own rather than on being
|
|
661
|
+
* Postgres-backed: a live Gel 7.1 stored `nan`, `inf` and `-inf` in both `std::float32` and
|
|
662
|
+
* `std::float64` and handed all three back, through a cast and again through a stored property.
|
|
663
|
+
* Without them every row of such a column failed validation.
|
|
664
|
+
*
|
|
665
|
+
* MySQL and SingleStore refuse all three, and that used to be left unstated on the reasoning that
|
|
666
|
+
* a column stating nothing costs nothing. It cost two libraries: `v.number()` and ArkType's
|
|
667
|
+
* `number` take both infinities where `z.number()` and `Type.Number()` refuse them, so an
|
|
668
|
+
* unbounded `double` or `real` accepted a value the server answers `ER_WARN_DATA_OUT_OF_RANGE`
|
|
669
|
+
* for. Measured on MySQL 8.4.11 in `STRICT_TRANS_TABLES`, on the binary prepared path, which is
|
|
670
|
+
* the one that puts the real IEEE double on the wire: `float`, `double` and `real` refuse
|
|
671
|
+
* `Infinity`, `-Infinity` and `NaN` alike, while `double` and `real` store 1e300 and
|
|
672
|
+
* 3.4028235e38 unchanged. SingleStore is MySQL wire-compatible and unmeasured, and takes MySQL's
|
|
673
|
+
* answer here exactly as it already takes MySQL's float32 bound in `INEXACT_RANGES`.
|
|
674
|
+
*
|
|
675
|
+
* No SQLite class belongs here in either direction. A real SQLite 3.53.4 stores both infinities in
|
|
676
|
+
* a `real` and hands them back, and silently turns `NaN` into NULL, so it is neither the Postgres
|
|
677
|
+
* answer nor the MySQL one; it is filed on its own and a column needs both halves of it or none.
|
|
678
|
+
*
|
|
679
|
+
* The decimal families are absent too. `PgNumeric` is a string whose pattern already accepts `NaN`
|
|
680
|
+
* and `Infinity`. `PgNumericNumber` is a per-column question this table cannot ask: it takes `NaN`
|
|
681
|
+
* at any width and an infinity only where no precision is declared, and `columnConstraints`
|
|
682
|
+
* answers it beside the bound that decides it. MySQL's `decimal` is absent because the two client
|
|
683
|
+
* paths disagree: on the binary prepared path MySQL 8.4.11 silently stored `0.00` for all three,
|
|
684
|
+
* where the text path answers `Incorrect decimal value`, and "refuses" is only half true of a
|
|
685
|
+
* column that accepted the row.
|
|
626
686
|
*/
|
|
627
|
-
private static readonly
|
|
687
|
+
private static readonly NON_FINITE_BY_CLASS;
|
|
628
688
|
/**
|
|
629
689
|
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
630
690
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -59,9 +59,11 @@ interface Column {
|
|
|
59
59
|
/**
|
|
60
60
|
* A coarse label for the column's kind, not its type.
|
|
61
61
|
*
|
|
62
|
-
* `varchar`, `char` and `text` are all `TEXT` here, deliberately:
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* `varchar`, `char` and `text` are all `TEXT` here, deliberately: two consumers read this and
|
|
63
|
+
* both ask coarse questions. `isIntegerColumn` asks whether a number is whole, and
|
|
64
|
+
* `comparisonWire` in `@drzl/validation-core` asks whether a string wire carries decimal text
|
|
65
|
+
* the database compares numerically, which is `NUMERIC` (every decimal family) and `BIGINT`
|
|
66
|
+
* (the v1 string mode). Use `sqlType` for the question this name suggests it answers.
|
|
65
67
|
*/
|
|
66
68
|
dbType: string;
|
|
67
69
|
/**
|
|
@@ -172,13 +174,18 @@ interface Column {
|
|
|
172
174
|
/**
|
|
173
175
|
* A string column whose contents have a shape the database enforces.
|
|
174
176
|
*
|
|
175
|
-
* Only formats checked against
|
|
177
|
+
* Only formats checked against a real server appear here, and the list is short because most
|
|
176
178
|
* candidates failed: Postgres reads `'today'` and `'January 8, 1999'` as dates, pads
|
|
177
179
|
* `'2020-01-01'` into a macaddr, and accepts `'10.1/16'` as an inet. A check for any of those
|
|
178
180
|
* would reject input the database accepts, and turning away valid data is worse than not
|
|
179
181
|
* checking at all. See `COLUMN_FORMATS` in `@drzl/validation-core`.
|
|
182
|
+
*
|
|
183
|
+
* Two of the keys name a dialect, because the same column has two different answers: a
|
|
184
|
+
* `bigint({ mode: 'string' })` is parsed by Postgres as an integer literal, `'0x1f'` and
|
|
185
|
+
* `'1_000'` included, and by MySQL as a decimal number it then rounds, so `'12.5'` is a row on
|
|
186
|
+
* one server and an error on the other.
|
|
180
187
|
*/
|
|
181
|
-
format?: 'uuid' | 'numeric';
|
|
188
|
+
format?: 'uuid' | 'numeric' | 'pgBigint' | 'mysqlBigint';
|
|
182
189
|
/**
|
|
183
190
|
* The column's default, when it is a literal a schema can reproduce.
|
|
184
191
|
*
|
|
@@ -474,7 +481,14 @@ declare function isRelationsV2(val: any): boolean;
|
|
|
474
481
|
declare function readRelationsV2(val: any, issues?: Issue[]): Relation[];
|
|
475
482
|
declare class SchemaAnalyzer {
|
|
476
483
|
private readonly schemaPath;
|
|
477
|
-
|
|
484
|
+
/**
|
|
485
|
+
* One path or several. The plural exists for drizzle-kit interop: kit's `schema` key names
|
|
486
|
+
* files in the plural (arrays, globs), and the commonest multi-file layout is a directory of
|
|
487
|
+
* one file per table with no barrel, so there is no single module to point at. Entries are
|
|
488
|
+
* concrete files, never globs; expansion is the caller's job, so this class's contract stays
|
|
489
|
+
* "load exactly these modules and read their exports as one schema".
|
|
490
|
+
*/
|
|
491
|
+
constructor(schemaPath: string | readonly string[]);
|
|
478
492
|
private getSymbol;
|
|
479
493
|
/**
|
|
480
494
|
* Drizzle keys the Columns object by TypeScript property name, but every other piece of
|
|
@@ -564,6 +578,31 @@ declare class SchemaAnalyzer {
|
|
|
564
578
|
* promise a precision that cannot survive the round trip.
|
|
565
579
|
*/
|
|
566
580
|
private static readonly INT_RANGES;
|
|
581
|
+
/**
|
|
582
|
+
* The same widths with `{ unsigned: true }` set, which is the half the table above cannot see.
|
|
583
|
+
*
|
|
584
|
+
* On 0.4x the flag moves no class name: `int('x', { unsigned: true })` still builds a
|
|
585
|
+
* `MySqlInt`, and only `config.unsigned` and the ` unsigned` suffix on `getSQLType()` record
|
|
586
|
+
* the difference, measured off real 0.45.2 columns. So the table above answered every unsigned
|
|
587
|
+
* width with its signed range, and the emitted select schema refused every stored value in the
|
|
588
|
+
* upper half of the column: an `int unsigned` holding 4294967295 failed validation on a row the
|
|
589
|
+
* database returned, and the same one width up meant `bigint unsigned` refused
|
|
590
|
+
* 18446744073709551615n.
|
|
591
|
+
*
|
|
592
|
+
* The ceilings are the type's, verified against a live MySQL 8.4.11: 255, 65535, 16777215 and
|
|
593
|
+
* 4294967295 store and return, -1 and each ceiling plus one are refused with
|
|
594
|
+
* ER_WARN_DATA_OUT_OF_RANGE. The bigint pair keeps the two modes apart for the reason the
|
|
595
|
+
* signed pair above does: number mode tops out at the safe-integer bound the wire imposes,
|
|
596
|
+
* bigint mode at the column's own 2^64-1, which a bigint can spell. SingleStore is MySQL wire
|
|
597
|
+
* compatible, ships the same builders with the same `config.unsigned`, and v1 states the same
|
|
598
|
+
* `uintN` semantics for it, measured off real rc.4 columns; the entries keep the majors in
|
|
599
|
+
* agreement, which is what the cross-major diff in `scripts/verify-packed.sh` holds together.
|
|
600
|
+
*
|
|
601
|
+
* Keyed by class exactly like `INT_RANGES`, and consulted only when `config.unsigned` is
|
|
602
|
+
* `true`, so no Postgres or SQLite column can ever reach it: neither dialect has an unsigned
|
|
603
|
+
* spelling, neither builder accepts the flag, and no class of theirs is named here.
|
|
604
|
+
*/
|
|
605
|
+
private static readonly UNSIGNED_INT_RANGES;
|
|
567
606
|
/**
|
|
568
607
|
* The numeric column classes that are not exact, and the magnitude each one can really hold.
|
|
569
608
|
*
|
|
@@ -601,30 +640,51 @@ declare class SchemaAnalyzer {
|
|
|
601
640
|
*/
|
|
602
641
|
private static readonly INEXACT_RANGES;
|
|
603
642
|
/**
|
|
604
|
-
* The
|
|
643
|
+
* The number columns whose server has an answer about a non-finite double, and what it is.
|
|
644
|
+
*
|
|
645
|
+
* Three states rather than two, and the third is the reason this table has a `false` half at all.
|
|
646
|
+
* A column present here with `true` stores the value and hands it back, so a schema refusing it
|
|
647
|
+
* refuses rows the column returns. A column present with `false` is one the server was asked
|
|
648
|
+
* about and refused, so a schema accepting it promises what the server will not take. A column
|
|
649
|
+
* *absent* is one nobody has measured, and the generators leave whatever their library does alone
|
|
650
|
+
* rather than guessing; `nonFiniteAccepted` and `nonFiniteRefused` in `@drzl/validation-core` are
|
|
651
|
+
* the two readings of that.
|
|
605
652
|
*
|
|
606
653
|
* The class-name half of what `describeV1Column` reads off the codec, and the two must agree: a
|
|
607
654
|
* fact stated on one path and not the other is a schema that changes when the user upgrades
|
|
608
|
-
* drizzle, which the cross-major diff in `verify-packed.sh` fails on.
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
655
|
+
* drizzle, which the cross-major diff in `verify-packed.sh` fails on. Every class name here is the
|
|
656
|
+
* same on both majors, read off real columns on 0.45.2 and on 1.0.0-rc.4, so this table also
|
|
657
|
+
* answers for a v1 column and the two answers are identical rather than merely compatible.
|
|
658
|
+
* non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
659
|
+
*
|
|
660
|
+
* Postgres and Gel store all three. Gel joined on a measurement of its own rather than on being
|
|
661
|
+
* Postgres-backed: a live Gel 7.1 stored `nan`, `inf` and `-inf` in both `std::float32` and
|
|
662
|
+
* `std::float64` and handed all three back, through a cast and again through a stored property.
|
|
663
|
+
* Without them every row of such a column failed validation.
|
|
664
|
+
*
|
|
665
|
+
* MySQL and SingleStore refuse all three, and that used to be left unstated on the reasoning that
|
|
666
|
+
* a column stating nothing costs nothing. It cost two libraries: `v.number()` and ArkType's
|
|
667
|
+
* `number` take both infinities where `z.number()` and `Type.Number()` refuse them, so an
|
|
668
|
+
* unbounded `double` or `real` accepted a value the server answers `ER_WARN_DATA_OUT_OF_RANGE`
|
|
669
|
+
* for. Measured on MySQL 8.4.11 in `STRICT_TRANS_TABLES`, on the binary prepared path, which is
|
|
670
|
+
* the one that puts the real IEEE double on the wire: `float`, `double` and `real` refuse
|
|
671
|
+
* `Infinity`, `-Infinity` and `NaN` alike, while `double` and `real` store 1e300 and
|
|
672
|
+
* 3.4028235e38 unchanged. SingleStore is MySQL wire-compatible and unmeasured, and takes MySQL's
|
|
673
|
+
* answer here exactly as it already takes MySQL's float32 bound in `INEXACT_RANGES`.
|
|
674
|
+
*
|
|
675
|
+
* No SQLite class belongs here in either direction. A real SQLite 3.53.4 stores both infinities in
|
|
676
|
+
* a `real` and hands them back, and silently turns `NaN` into NULL, so it is neither the Postgres
|
|
677
|
+
* answer nor the MySQL one; it is filed on its own and a column needs both halves of it or none.
|
|
678
|
+
*
|
|
679
|
+
* The decimal families are absent too. `PgNumeric` is a string whose pattern already accepts `NaN`
|
|
680
|
+
* and `Infinity`. `PgNumericNumber` is a per-column question this table cannot ask: it takes `NaN`
|
|
681
|
+
* at any width and an infinity only where no precision is declared, and `columnConstraints`
|
|
682
|
+
* answers it beside the bound that decides it. MySQL's `decimal` is absent because the two client
|
|
683
|
+
* paths disagree: on the binary prepared path MySQL 8.4.11 silently stored `0.00` for all three,
|
|
684
|
+
* where the text path answers `Incorrect decimal value`, and "refuses" is only half true of a
|
|
685
|
+
* column that accepted the row.
|
|
626
686
|
*/
|
|
627
|
-
private static readonly
|
|
687
|
+
private static readonly NON_FINITE_BY_CLASS;
|
|
628
688
|
/**
|
|
629
689
|
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
630
690
|
*
|
package/dist/index.js
CHANGED
|
@@ -73,11 +73,15 @@ function describeV1Column(column) {
|
|
|
73
73
|
case "int8":
|
|
74
74
|
case "uint8":
|
|
75
75
|
case "int16":
|
|
76
|
+
case "uint16":
|
|
76
77
|
case "int24":
|
|
78
|
+
case "uint24":
|
|
77
79
|
case "int32":
|
|
80
|
+
case "uint32":
|
|
78
81
|
case "int53":
|
|
79
82
|
case "uint53":
|
|
80
|
-
case "int64":
|
|
83
|
+
case "int64":
|
|
84
|
+
case "uint64": {
|
|
81
85
|
if (DECIMAL_BIGINT_MODE.test(entityKind)) {
|
|
82
86
|
out.tsType = "bigint";
|
|
83
87
|
out.dbType = "NUMERIC";
|
|
@@ -86,22 +90,44 @@ function describeV1Column(column) {
|
|
|
86
90
|
if (range2) [out.min, out.max] = range2;
|
|
87
91
|
break;
|
|
88
92
|
}
|
|
93
|
+
if ((semantic === "int64" || semantic === "uint64") && js === "string") {
|
|
94
|
+
out.tsType = "string";
|
|
95
|
+
out.dbType = "BIGINT";
|
|
96
|
+
if (entityKind.startsWith("Pg")) out.format = "pgBigint";
|
|
97
|
+
else if (entityKind.startsWith("MySql") || entityKind.startsWith("SingleStore"))
|
|
98
|
+
out.format = "mysqlBigint";
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
89
101
|
const range = {
|
|
90
102
|
int8: ["-128", "127"],
|
|
91
103
|
uint8: ["0", "255"],
|
|
92
104
|
int16: ["-32768", "32767"],
|
|
105
|
+
uint16: ["0", "65535"],
|
|
93
106
|
int24: ["-8388608", "8388607"],
|
|
107
|
+
uint24: ["0", "16777215"],
|
|
94
108
|
int32: ["-2147483648", "2147483647"],
|
|
109
|
+
uint32: ["0", "4294967295"],
|
|
95
110
|
int53: ["-9007199254740991", "9007199254740991"],
|
|
96
111
|
// MySQL `serial` is `bigint unsigned auto_increment`, so it starts at 0 rather than
|
|
97
|
-
// spanning the signed range.
|
|
112
|
+
// spanning the signed range. An explicit `bigint({ mode: 'number', unsigned: true })`
|
|
113
|
+
// states the same semantic and takes the same answer.
|
|
98
114
|
uint53: ["0", "9007199254740991"],
|
|
99
|
-
int64: ["-9223372036854775808", "9223372036854775807"]
|
|
115
|
+
int64: ["-9223372036854775808", "9223372036854775807"],
|
|
116
|
+
uint64: ["0", "18446744073709551615"]
|
|
100
117
|
}[semantic];
|
|
101
118
|
[out.min, out.max] = range;
|
|
102
119
|
out.integer = true;
|
|
103
120
|
out.tsType = js === "bigint" ? "bigint" : "number";
|
|
104
|
-
out.dbType =
|
|
121
|
+
out.dbType = {
|
|
122
|
+
int8: "TINYINT",
|
|
123
|
+
uint8: "TINYINT",
|
|
124
|
+
int16: "SMALLINT",
|
|
125
|
+
uint16: "SMALLINT",
|
|
126
|
+
int24: "MEDIUMINT",
|
|
127
|
+
uint24: "MEDIUMINT",
|
|
128
|
+
int32: "INTEGER",
|
|
129
|
+
uint32: "INTEGER"
|
|
130
|
+
}[semantic] ?? "BIGINT";
|
|
105
131
|
break;
|
|
106
132
|
}
|
|
107
133
|
case "year":
|
|
@@ -121,6 +147,10 @@ function describeV1Column(column) {
|
|
|
121
147
|
out.allowsNaN = true;
|
|
122
148
|
out.allowsInfinity = true;
|
|
123
149
|
}
|
|
150
|
+
if (codec === "float" || codec === "double" || codec === "real" || entityKind.startsWith("SingleStore")) {
|
|
151
|
+
out.allowsNaN = false;
|
|
152
|
+
out.allowsInfinity = false;
|
|
153
|
+
}
|
|
124
154
|
break;
|
|
125
155
|
}
|
|
126
156
|
case "uuid":
|
|
@@ -388,6 +418,13 @@ function unknownColumnHint(reason) {
|
|
|
388
418
|
return "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns.";
|
|
389
419
|
}
|
|
390
420
|
var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
421
|
+
/**
|
|
422
|
+
* One path or several. The plural exists for drizzle-kit interop: kit's `schema` key names
|
|
423
|
+
* files in the plural (arrays, globs), and the commonest multi-file layout is a directory of
|
|
424
|
+
* one file per table with no barrel, so there is no single module to point at. Entries are
|
|
425
|
+
* concrete files, never globs; expansion is the caller's job, so this class's contract stays
|
|
426
|
+
* "load exactly these modules and read their exports as one schema".
|
|
427
|
+
*/
|
|
391
428
|
constructor(schemaPath) {
|
|
392
429
|
this.schemaPath = schemaPath;
|
|
393
430
|
}
|
|
@@ -609,7 +646,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
609
646
|
if (typeof length === "number" && Number.isFinite(length) && length > 0) {
|
|
610
647
|
out.maxLength = length;
|
|
611
648
|
}
|
|
612
|
-
const
|
|
649
|
+
const unsignedRange = column?.config?.unsigned === true ? _SchemaAnalyzer.UNSIGNED_INT_RANGES[ctor] : void 0;
|
|
650
|
+
const range = unsignedRange ?? _SchemaAnalyzer.INT_RANGES[ctor];
|
|
613
651
|
if (range) {
|
|
614
652
|
[out.min, out.max] = range;
|
|
615
653
|
out.integer = true;
|
|
@@ -619,7 +657,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
619
657
|
if (inexact) [out.min, out.max] = inexact;
|
|
620
658
|
out.integer = false;
|
|
621
659
|
}
|
|
622
|
-
const nonFinite = _SchemaAnalyzer.
|
|
660
|
+
const nonFinite = _SchemaAnalyzer.NON_FINITE_BY_CLASS[ctor];
|
|
623
661
|
if (nonFinite) {
|
|
624
662
|
out.allowsNaN = nonFinite.nan;
|
|
625
663
|
out.allowsInfinity = nonFinite.infinity;
|
|
@@ -864,7 +902,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
864
902
|
}
|
|
865
903
|
if (/Numeric|Float|Double|Real/i.test(ctor))
|
|
866
904
|
return { tsType: "number", dbType: "NUMERIC" };
|
|
867
|
-
if (/
|
|
905
|
+
if (/Serial/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
|
|
906
|
+
if (/Int|TinyInt|SmallInt|MediumInt/i.test(ctor))
|
|
868
907
|
return { tsType: "number", dbType: "INTEGER" };
|
|
869
908
|
if (/Bool|Boolean/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
870
909
|
if (/TimestampString|DateTimeString|DateString/i.test(ctor))
|
|
@@ -888,7 +927,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
888
927
|
}
|
|
889
928
|
if (/Numeric|Float|Double|Real/i.test(ctor))
|
|
890
929
|
return { tsType: "number", dbType: "NUMERIC" };
|
|
891
|
-
if (/
|
|
930
|
+
if (/Serial/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
|
|
931
|
+
if (/Int|TinyInt|SmallInt|MediumInt/i.test(ctor))
|
|
892
932
|
return { tsType: "number", dbType: "INTEGER" };
|
|
893
933
|
if (/Bool|Boolean/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
894
934
|
if (/TimestampString|DateTimeString|DateString/i.test(ctor))
|
|
@@ -1099,31 +1139,85 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1099
1139
|
const fs = await import("fs/promises");
|
|
1100
1140
|
const path = await import("path");
|
|
1101
1141
|
const issues = [];
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1142
|
+
const listed = Array.isArray(this.schemaPath);
|
|
1143
|
+
const inputs = listed ? this.schemaPath : [this.schemaPath];
|
|
1144
|
+
const fulls = inputs.map((p) => path.resolve(process.cwd(), p));
|
|
1145
|
+
let missing = false;
|
|
1146
|
+
for (let i = 0; i < fulls.length; i++) {
|
|
1147
|
+
try {
|
|
1148
|
+
await fs.access(fulls[i]);
|
|
1149
|
+
} catch (_e) {
|
|
1150
|
+
missing = true;
|
|
1151
|
+
issues.push({
|
|
1152
|
+
code: "DRZL_ANL_NOFILE",
|
|
1153
|
+
level: "error",
|
|
1154
|
+
message: `Schema file not found: ${inputs[i]}`
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1112
1157
|
}
|
|
1113
|
-
|
|
1114
|
-
try {
|
|
1115
|
-
const { default: jiti } = await import("jiti");
|
|
1116
|
-
const jit = jiti(import.meta.url, { moduleCache: false });
|
|
1117
|
-
mod = jit(full);
|
|
1118
|
-
} catch (e) {
|
|
1119
|
-
issues.push({
|
|
1120
|
-
code: "DRZL_ANL_IMPORT",
|
|
1121
|
-
level: "error",
|
|
1122
|
-
message: `Failed to import schema: ${String(e)}`
|
|
1123
|
-
});
|
|
1158
|
+
if (missing) {
|
|
1124
1159
|
return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
|
|
1125
1160
|
}
|
|
1126
|
-
const
|
|
1161
|
+
const { default: jiti } = await import("jiti");
|
|
1162
|
+
const jit = jiti(import.meta.url, { moduleCache: false });
|
|
1163
|
+
const exportsObj = {};
|
|
1164
|
+
const exportOrigin = /* @__PURE__ */ new Map();
|
|
1165
|
+
const duplicateDisagreement = (a, b) => {
|
|
1166
|
+
if (Object.is(a, b)) return null;
|
|
1167
|
+
const aCols = this.getSymbol(a, "drizzle:Columns");
|
|
1168
|
+
const bCols = this.getSymbol(b, "drizzle:Columns");
|
|
1169
|
+
if (aCols && bCols) {
|
|
1170
|
+
const aName = this.getSymbol(a, "drizzle:Name");
|
|
1171
|
+
const bName = this.getSymbol(b, "drizzle:Name");
|
|
1172
|
+
const aSchema = this.getSymbol(a, "drizzle:Schema");
|
|
1173
|
+
const bSchema = this.getSymbol(b, "drizzle:Schema");
|
|
1174
|
+
if (aName !== bName || aSchema !== bSchema) {
|
|
1175
|
+
return `two different tables ("${String(aName)}" and "${String(bName)}")`;
|
|
1176
|
+
}
|
|
1177
|
+
if (Object.keys(aCols).join(",") !== Object.keys(bCols).join(",")) {
|
|
1178
|
+
return `two declarations of table "${String(aName)}" with different columns`;
|
|
1179
|
+
}
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
if (!!aCols !== !!bCols) return "a table and a non-table";
|
|
1183
|
+
const aEnum = a?.enumValues;
|
|
1184
|
+
const bEnum = b?.enumValues;
|
|
1185
|
+
if (Array.isArray(aEnum) && Array.isArray(bEnum)) {
|
|
1186
|
+
return JSON.stringify(aEnum) === JSON.stringify(bEnum) ? null : "two enums with different values";
|
|
1187
|
+
}
|
|
1188
|
+
return null;
|
|
1189
|
+
};
|
|
1190
|
+
for (let i = 0; i < fulls.length; i++) {
|
|
1191
|
+
let mod;
|
|
1192
|
+
try {
|
|
1193
|
+
mod = jit(fulls[i]);
|
|
1194
|
+
} catch (e) {
|
|
1195
|
+
issues.push({
|
|
1196
|
+
code: "DRZL_ANL_IMPORT",
|
|
1197
|
+
level: "error",
|
|
1198
|
+
// The single-path message keeps its historical bytes; a list names the file, since
|
|
1199
|
+
// "the schema" no longer identifies one.
|
|
1200
|
+
message: listed ? `Failed to import schema ${inputs[i]}: ${String(e)}` : `Failed to import schema: ${String(e)}`
|
|
1201
|
+
});
|
|
1202
|
+
return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
|
|
1203
|
+
}
|
|
1204
|
+
const one = mod?.default && typeof mod.default === "object" ? mod.default : mod;
|
|
1205
|
+
for (const [name, val] of Object.entries(one)) {
|
|
1206
|
+
if (!(name in exportsObj)) {
|
|
1207
|
+
exportsObj[name] = val;
|
|
1208
|
+
exportOrigin.set(name, inputs[i]);
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
const disagreement = duplicateDisagreement(exportsObj[name], val);
|
|
1212
|
+
if (!disagreement) continue;
|
|
1213
|
+
issues.push({
|
|
1214
|
+
code: "DRZL_ANL_DUP_EXPORT",
|
|
1215
|
+
level: "warn",
|
|
1216
|
+
message: `Export "${name}" is ${disagreement}: defined by both ${exportOrigin.get(name)} and ${inputs[i]}; keeping the one in ${exportOrigin.get(name)}.`,
|
|
1217
|
+
path: name
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1127
1221
|
const tables = [];
|
|
1128
1222
|
const relations = [];
|
|
1129
1223
|
const enums = [];
|
|
@@ -1276,6 +1370,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1276
1370
|
_SchemaAnalyzer.INT_RANGES = {
|
|
1277
1371
|
// 8 bit
|
|
1278
1372
|
MySqlTinyInt: ["-128", "127"],
|
|
1373
|
+
// Absent until the unsigned fix swept the family: v1 states `number int8` for the same
|
|
1374
|
+
// column, so the majors disagreed about every SingleStore tinyint. That is the shape the
|
|
1375
|
+
// cross-major diff in `scripts/verify-packed.sh` exists to catch, and its fixture carries no
|
|
1376
|
+
// SingleStore table, so unsigned-int-ranges.spec.ts holds these two classes across both
|
|
1377
|
+
// majors instead. The width is the type's, the one `MySqlTinyInt` beside it already carries.
|
|
1378
|
+
SingleStoreTinyInt: ["-128", "127"],
|
|
1279
1379
|
SQLiteInteger: ["-9223372036854775808", "9223372036854775807"],
|
|
1280
1380
|
// 16 bit
|
|
1281
1381
|
PgSmallInt: ["-32768", "32767"],
|
|
@@ -1288,6 +1388,8 @@ _SchemaAnalyzer.INT_RANGES = {
|
|
|
1288
1388
|
SingleStoreSmallInt: ["-32768", "32767"],
|
|
1289
1389
|
// 24 bit
|
|
1290
1390
|
MySqlMediumInt: ["-8388608", "8388607"],
|
|
1391
|
+
// As SingleStoreTinyInt above: v1 states `number int24` and this table said nothing.
|
|
1392
|
+
SingleStoreMediumInt: ["-8388608", "8388607"],
|
|
1291
1393
|
// 32 bit
|
|
1292
1394
|
PgInteger: ["-2147483648", "2147483647"],
|
|
1293
1395
|
PgSerial: ["-2147483648", "2147483647"],
|
|
@@ -1302,7 +1404,61 @@ _SchemaAnalyzer.INT_RANGES = {
|
|
|
1302
1404
|
PgBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
1303
1405
|
PgBigSerial64: ["-9223372036854775808", "9223372036854775807"],
|
|
1304
1406
|
MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
1305
|
-
SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
|
|
1407
|
+
SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
1408
|
+
// MySQL and SingleStore `serial`, which is `bigint unsigned auto_increment`: unsigned by the
|
|
1409
|
+
// builder's own definition, with no `config.unsigned` stating it, so the flag-keyed table
|
|
1410
|
+
// below cannot answer and the range lives here. The mode is number, so the safe-integer
|
|
1411
|
+
// ceiling rather than the column's, exactly as the 53 bit block above. The Postgres serials
|
|
1412
|
+
// stay signed on purpose: a Postgres serial is a plain integer defaulting from a sequence,
|
|
1413
|
+
// and the negative backfill note above applies to them and not to these. Before this entry
|
|
1414
|
+
// the class was in no table at all, so an auto-increment column accepted -1 and the majors
|
|
1415
|
+
// disagreed: v1 states `number uint53` for the same column and was already bounded.
|
|
1416
|
+
MySqlSerial: ["0", "9007199254740991"],
|
|
1417
|
+
SingleStoreSerial: ["0", "9007199254740991"]
|
|
1418
|
+
};
|
|
1419
|
+
/**
|
|
1420
|
+
* The same widths with `{ unsigned: true }` set, which is the half the table above cannot see.
|
|
1421
|
+
*
|
|
1422
|
+
* On 0.4x the flag moves no class name: `int('x', { unsigned: true })` still builds a
|
|
1423
|
+
* `MySqlInt`, and only `config.unsigned` and the ` unsigned` suffix on `getSQLType()` record
|
|
1424
|
+
* the difference, measured off real 0.45.2 columns. So the table above answered every unsigned
|
|
1425
|
+
* width with its signed range, and the emitted select schema refused every stored value in the
|
|
1426
|
+
* upper half of the column: an `int unsigned` holding 4294967295 failed validation on a row the
|
|
1427
|
+
* database returned, and the same one width up meant `bigint unsigned` refused
|
|
1428
|
+
* 18446744073709551615n.
|
|
1429
|
+
*
|
|
1430
|
+
* The ceilings are the type's, verified against a live MySQL 8.4.11: 255, 65535, 16777215 and
|
|
1431
|
+
* 4294967295 store and return, -1 and each ceiling plus one are refused with
|
|
1432
|
+
* ER_WARN_DATA_OUT_OF_RANGE. The bigint pair keeps the two modes apart for the reason the
|
|
1433
|
+
* signed pair above does: number mode tops out at the safe-integer bound the wire imposes,
|
|
1434
|
+
* bigint mode at the column's own 2^64-1, which a bigint can spell. SingleStore is MySQL wire
|
|
1435
|
+
* compatible, ships the same builders with the same `config.unsigned`, and v1 states the same
|
|
1436
|
+
* `uintN` semantics for it, measured off real rc.4 columns; the entries keep the majors in
|
|
1437
|
+
* agreement, which is what the cross-major diff in `scripts/verify-packed.sh` holds together.
|
|
1438
|
+
*
|
|
1439
|
+
* Keyed by class exactly like `INT_RANGES`, and consulted only when `config.unsigned` is
|
|
1440
|
+
* `true`, so no Postgres or SQLite column can ever reach it: neither dialect has an unsigned
|
|
1441
|
+
* spelling, neither builder accepts the flag, and no class of theirs is named here.
|
|
1442
|
+
*/
|
|
1443
|
+
_SchemaAnalyzer.UNSIGNED_INT_RANGES = {
|
|
1444
|
+
// 8 bit
|
|
1445
|
+
MySqlTinyInt: ["0", "255"],
|
|
1446
|
+
SingleStoreTinyInt: ["0", "255"],
|
|
1447
|
+
// 16 bit
|
|
1448
|
+
MySqlSmallInt: ["0", "65535"],
|
|
1449
|
+
SingleStoreSmallInt: ["0", "65535"],
|
|
1450
|
+
// 24 bit
|
|
1451
|
+
MySqlMediumInt: ["0", "16777215"],
|
|
1452
|
+
SingleStoreMediumInt: ["0", "16777215"],
|
|
1453
|
+
// 32 bit
|
|
1454
|
+
MySqlInt: ["0", "4294967295"],
|
|
1455
|
+
SingleStoreInt: ["0", "4294967295"],
|
|
1456
|
+
// 53 bit, the JS safe-integer ceiling rather than the column's
|
|
1457
|
+
MySqlBigInt53: ["0", "9007199254740991"],
|
|
1458
|
+
SingleStoreBigInt53: ["0", "9007199254740991"],
|
|
1459
|
+
// 64 bit, representable because the value is a bigint
|
|
1460
|
+
MySqlBigInt64: ["0", "18446744073709551615"],
|
|
1461
|
+
SingleStoreBigInt64: ["0", "18446744073709551615"]
|
|
1306
1462
|
};
|
|
1307
1463
|
/**
|
|
1308
1464
|
* The numeric column classes that are not exact, and the magnitude each one can really hold.
|
|
@@ -1377,34 +1533,61 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1377
1533
|
// Its bound is not a fixed magnitude per class but the precision each column declares for itself,
|
|
1378
1534
|
// which no table keyed on a class name can hold; see `declaredDecimalRange`.
|
|
1379
1535
|
/**
|
|
1380
|
-
* The
|
|
1536
|
+
* The number columns whose server has an answer about a non-finite double, and what it is.
|
|
1537
|
+
*
|
|
1538
|
+
* Three states rather than two, and the third is the reason this table has a `false` half at all.
|
|
1539
|
+
* A column present here with `true` stores the value and hands it back, so a schema refusing it
|
|
1540
|
+
* refuses rows the column returns. A column present with `false` is one the server was asked
|
|
1541
|
+
* about and refused, so a schema accepting it promises what the server will not take. A column
|
|
1542
|
+
* *absent* is one nobody has measured, and the generators leave whatever their library does alone
|
|
1543
|
+
* rather than guessing; `nonFiniteAccepted` and `nonFiniteRefused` in `@drzl/validation-core` are
|
|
1544
|
+
* the two readings of that.
|
|
1381
1545
|
*
|
|
1382
1546
|
* The class-name half of what `describeV1Column` reads off the codec, and the two must agree: a
|
|
1383
1547
|
* fact stated on one path and not the other is a schema that changes when the user upgrades
|
|
1384
|
-
* drizzle, which the cross-major diff in `verify-packed.sh` fails on.
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1548
|
+
* drizzle, which the cross-major diff in `verify-packed.sh` fails on. Every class name here is the
|
|
1549
|
+
* same on both majors, read off real columns on 0.45.2 and on 1.0.0-rc.4, so this table also
|
|
1550
|
+
* answers for a v1 column and the two answers are identical rather than merely compatible.
|
|
1551
|
+
* non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
1552
|
+
*
|
|
1553
|
+
* Postgres and Gel store all three. Gel joined on a measurement of its own rather than on being
|
|
1554
|
+
* Postgres-backed: a live Gel 7.1 stored `nan`, `inf` and `-inf` in both `std::float32` and
|
|
1555
|
+
* `std::float64` and handed all three back, through a cast and again through a stored property.
|
|
1556
|
+
* Without them every row of such a column failed validation.
|
|
1388
1557
|
*
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1558
|
+
* MySQL and SingleStore refuse all three, and that used to be left unstated on the reasoning that
|
|
1559
|
+
* a column stating nothing costs nothing. It cost two libraries: `v.number()` and ArkType's
|
|
1560
|
+
* `number` take both infinities where `z.number()` and `Type.Number()` refuse them, so an
|
|
1561
|
+
* unbounded `double` or `real` accepted a value the server answers `ER_WARN_DATA_OUT_OF_RANGE`
|
|
1562
|
+
* for. Measured on MySQL 8.4.11 in `STRICT_TRANS_TABLES`, on the binary prepared path, which is
|
|
1563
|
+
* the one that puts the real IEEE double on the wire: `float`, `double` and `real` refuse
|
|
1564
|
+
* `Infinity`, `-Infinity` and `NaN` alike, while `double` and `real` store 1e300 and
|
|
1565
|
+
* 3.4028235e38 unchanged. SingleStore is MySQL wire-compatible and unmeasured, and takes MySQL's
|
|
1566
|
+
* answer here exactly as it already takes MySQL's float32 bound in `INEXACT_RANGES`.
|
|
1392
1567
|
*
|
|
1393
|
-
*
|
|
1394
|
-
*
|
|
1395
|
-
*
|
|
1396
|
-
* and again through a stored property. Without them every row of such a column failed validation.
|
|
1568
|
+
* No SQLite class belongs here in either direction. A real SQLite 3.53.4 stores both infinities in
|
|
1569
|
+
* a `real` and hands them back, and silently turns `NaN` into NULL, so it is neither the Postgres
|
|
1570
|
+
* answer nor the MySQL one; it is filed on its own and a column needs both halves of it or none.
|
|
1397
1571
|
*
|
|
1398
|
-
*
|
|
1399
|
-
* `Infinity`. `PgNumericNumber` is
|
|
1400
|
-
* any width and an infinity only where no precision is declared,
|
|
1401
|
-
*
|
|
1572
|
+
* The decimal families are absent too. `PgNumeric` is a string whose pattern already accepts `NaN`
|
|
1573
|
+
* and `Infinity`. `PgNumericNumber` is a per-column question this table cannot ask: it takes `NaN`
|
|
1574
|
+
* at any width and an infinity only where no precision is declared, and `columnConstraints`
|
|
1575
|
+
* answers it beside the bound that decides it. MySQL's `decimal` is absent because the two client
|
|
1576
|
+
* paths disagree: on the binary prepared path MySQL 8.4.11 silently stored `0.00` for all three,
|
|
1577
|
+
* where the text path answers `Incorrect decimal value`, and "refuses" is only half true of a
|
|
1578
|
+
* column that accepted the row.
|
|
1402
1579
|
*/
|
|
1403
|
-
_SchemaAnalyzer.
|
|
1580
|
+
_SchemaAnalyzer.NON_FINITE_BY_CLASS = {
|
|
1404
1581
|
PgReal: { nan: true, infinity: true },
|
|
1405
1582
|
PgDoublePrecision: { nan: true, infinity: true },
|
|
1406
1583
|
GelReal: { nan: true, infinity: true },
|
|
1407
|
-
GelDoublePrecision: { nan: true, infinity: true }
|
|
1584
|
+
GelDoublePrecision: { nan: true, infinity: true },
|
|
1585
|
+
MySqlFloat: { nan: false, infinity: false },
|
|
1586
|
+
MySqlDouble: { nan: false, infinity: false },
|
|
1587
|
+
MySqlReal: { nan: false, infinity: false },
|
|
1588
|
+
SingleStoreFloat: { nan: false, infinity: false },
|
|
1589
|
+
SingleStoreDouble: { nan: false, infinity: false },
|
|
1590
|
+
SingleStoreReal: { nan: false, infinity: false }
|
|
1408
1591
|
};
|
|
1409
1592
|
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
1410
1593
|
var index_default = SchemaAnalyzer;
|