@drzl/analyzer 1.18.0 → 1.21.0
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 +16 -4
- package/dist/index.cjs +506 -63
- package/dist/index.d.cts +240 -6
- package/dist/index.d.ts +240 -6
- package/dist/index.js +504 -63
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
function qualifiedTableName(table) {
|
|
3
|
+
return table.schema ? `${table.schema}.${table.name}` : table.name;
|
|
4
|
+
}
|
|
5
|
+
function qualifiedForeignTable(fk) {
|
|
6
|
+
return fk.foreignSchema ? `${fk.foreignSchema}.${fk.foreignTable}` : fk.foreignTable;
|
|
7
|
+
}
|
|
2
8
|
function renderSqlLiteral(v) {
|
|
3
9
|
if (v === null || v === void 0) return "NULL";
|
|
4
10
|
if (typeof v === "number" || typeof v === "bigint") return String(v);
|
|
@@ -30,15 +36,23 @@ var GEOMETRIC_CLASS_SHAPES = {
|
|
|
30
36
|
// The object modes. `line({ mode: 'abc' })` is a `PgLineABC` and not a `PgLineObject`, and any
|
|
31
37
|
// mode but `'tuple'` builds the object class: `point({ mode: 'abc' })` is a `PgPointObject` too.
|
|
32
38
|
PgPointObject: { kind: "numberObject", fields: ["x", "y"] },
|
|
33
|
-
PgLineABC: { kind: "numberObject", fields: ["a", "b", "c"] }
|
|
39
|
+
PgLineABC: { kind: "numberObject", fields: ["a", "b", "c"] },
|
|
40
|
+
// `geometry()` and `geometry({ mode: 'xy' })` are two classes, not one class with a flag, and
|
|
41
|
+
// the fuzzer found both unnamed on this path. Their driver mappers disagree the same way the
|
|
42
|
+
// point ones do: the default hands back `[1, 2]` and the xy mode hands back `{ x: 1, y: 2 }`.
|
|
43
|
+
PgGeometry: { kind: "tuple", length: 2 },
|
|
44
|
+
PgGeometryObject: { kind: "numberObject", fields: ["x", "y"] }
|
|
34
45
|
};
|
|
35
46
|
var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
|
|
47
|
+
var NUMBER_VECTOR_CLASSES = /* @__PURE__ */ new Set(["PgVector", "PgHalfVector", "SingleStoreVector"]);
|
|
48
|
+
var BIT_STRING_CLASSES = /* @__PURE__ */ new Set(["PgBinaryVector"]);
|
|
36
49
|
var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
|
|
37
50
|
"MySqlBinary",
|
|
38
51
|
"MySqlVarBinary",
|
|
39
52
|
"SingleStoreBinary",
|
|
40
53
|
"SingleStoreVarBinary"
|
|
41
54
|
]);
|
|
55
|
+
var BUFFER_CLASSES = /* @__PURE__ */ new Set(["SQLiteBlobBuffer"]);
|
|
42
56
|
function describeV1Column(column) {
|
|
43
57
|
const codec = column?.codec;
|
|
44
58
|
const dataType = column?.dataType;
|
|
@@ -57,27 +71,63 @@ function describeV1Column(column) {
|
|
|
57
71
|
const out = {};
|
|
58
72
|
switch (semantic) {
|
|
59
73
|
case "int8":
|
|
74
|
+
case "uint8":
|
|
60
75
|
case "int16":
|
|
76
|
+
case "uint16":
|
|
61
77
|
case "int24":
|
|
78
|
+
case "uint24":
|
|
62
79
|
case "int32":
|
|
80
|
+
case "uint32":
|
|
63
81
|
case "int53":
|
|
64
82
|
case "uint53":
|
|
65
|
-
case "int64":
|
|
83
|
+
case "int64":
|
|
84
|
+
case "uint64": {
|
|
85
|
+
if (DECIMAL_BIGINT_MODE.test(entityKind)) {
|
|
86
|
+
out.tsType = "bigint";
|
|
87
|
+
out.dbType = "NUMERIC";
|
|
88
|
+
out.integer = true;
|
|
89
|
+
const range2 = decimalModeRange(column, entityKind, "bigint");
|
|
90
|
+
if (range2) [out.min, out.max] = range2;
|
|
91
|
+
break;
|
|
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
|
+
}
|
|
66
101
|
const range = {
|
|
67
102
|
int8: ["-128", "127"],
|
|
103
|
+
uint8: ["0", "255"],
|
|
68
104
|
int16: ["-32768", "32767"],
|
|
105
|
+
uint16: ["0", "65535"],
|
|
69
106
|
int24: ["-8388608", "8388607"],
|
|
107
|
+
uint24: ["0", "16777215"],
|
|
70
108
|
int32: ["-2147483648", "2147483647"],
|
|
109
|
+
uint32: ["0", "4294967295"],
|
|
71
110
|
int53: ["-9007199254740991", "9007199254740991"],
|
|
72
111
|
// MySQL `serial` is `bigint unsigned auto_increment`, so it starts at 0 rather than
|
|
73
|
-
// 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.
|
|
74
114
|
uint53: ["0", "9007199254740991"],
|
|
75
|
-
int64: ["-9223372036854775808", "9223372036854775807"]
|
|
115
|
+
int64: ["-9223372036854775808", "9223372036854775807"],
|
|
116
|
+
uint64: ["0", "18446744073709551615"]
|
|
76
117
|
}[semantic];
|
|
77
118
|
[out.min, out.max] = range;
|
|
78
119
|
out.integer = true;
|
|
79
120
|
out.tsType = js === "bigint" ? "bigint" : "number";
|
|
80
|
-
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";
|
|
81
131
|
break;
|
|
82
132
|
}
|
|
83
133
|
case "year":
|
|
@@ -93,6 +143,14 @@ function describeV1Column(column) {
|
|
|
93
143
|
out.integer = false;
|
|
94
144
|
out.tsType = "number";
|
|
95
145
|
out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
|
|
146
|
+
if (codec === "float4" || codec === "float8") {
|
|
147
|
+
out.allowsNaN = true;
|
|
148
|
+
out.allowsInfinity = true;
|
|
149
|
+
}
|
|
150
|
+
if (codec === "float" || codec === "double" || codec === "real" || entityKind.startsWith("SingleStore")) {
|
|
151
|
+
out.allowsNaN = false;
|
|
152
|
+
out.allowsInfinity = false;
|
|
153
|
+
}
|
|
96
154
|
break;
|
|
97
155
|
}
|
|
98
156
|
case "uuid":
|
|
@@ -122,6 +180,16 @@ function describeV1Column(column) {
|
|
|
122
180
|
out.dbType = codec?.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
|
|
123
181
|
break;
|
|
124
182
|
case "timestamp":
|
|
183
|
+
// `datetime` is the same fact under MySQL's name for it, and it had no arm, so every column
|
|
184
|
+
// stating it fell to the bare-string arm and was labelled TEXT. The columns that reach this
|
|
185
|
+
// are the string modes of `datetime` on mssql, mysql and singlestore, plus mssql's
|
|
186
|
+
// `datetime2` and `datetimeoffset`, swept over every builder the six v1 cores export; the
|
|
187
|
+
// `{ mode: 'date' }` half of the same builders states `object date` and takes the arm above.
|
|
188
|
+
// A label only, since `dbType` is read outside this file in exactly one place,
|
|
189
|
+
// `isIntegerColumn`, which the generators consult for a `tsType` of `number`. It matters
|
|
190
|
+
// because the class-name path already answers TIMESTAMP for the same 0.4x column, and the
|
|
191
|
+
// two majors disagreeing about a column is what the cross-major diff exists to catch.
|
|
192
|
+
case "datetime":
|
|
125
193
|
out.tsType = js === "string" ? "string" : "Date";
|
|
126
194
|
out.dbType = "TIMESTAMP";
|
|
127
195
|
break;
|
|
@@ -143,13 +211,25 @@ function describeV1Column(column) {
|
|
|
143
211
|
const entity = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
|
|
144
212
|
const bytes = entity.startsWith("MySql") || entity.startsWith("SingleStore");
|
|
145
213
|
out.tsType = "string";
|
|
146
|
-
out.dbType =
|
|
214
|
+
out.dbType = bytes ? "BINARY" : "BIT";
|
|
147
215
|
out.shape = bytes ? { kind: "byteString", length: declaredLength(column) } : {
|
|
148
216
|
kind: "bitstring",
|
|
149
217
|
length: declaredLength(column),
|
|
150
218
|
// A Postgres `bit(3)` holds exactly three digits; a Cockroach `varbit(16)` holds at
|
|
151
219
|
// most that many, which is why `''` is valid there and not here.
|
|
152
|
-
|
|
220
|
+
//
|
|
221
|
+
// `codec === 'bit'` alone was Postgres's answer applied to everything, and Cockroach
|
|
222
|
+
// states no codec, so both of its builders came back `exact: false` and a `bit(3)`
|
|
223
|
+
// was indistinguishable from a `varbit(3)`. Measured on CockroachDB v24.3.5: a
|
|
224
|
+
// `bit(3)` refuses '', '1', '10' and '1011' with "bit string length n does not match
|
|
225
|
+
// type BIT(3)" and takes '101'; a `varbit(8)` takes '', '1' and '10101010' and
|
|
226
|
+
// refuses nine digits with "too large for type VARBIT(8)". `drizzle-orm/zod` at
|
|
227
|
+
// 1.0.0-rc.4 answers the same for both columns.
|
|
228
|
+
//
|
|
229
|
+
// The class rather than a prefix, because `CockroachVarbit` starts with neither
|
|
230
|
+
// `CockroachBit` nor anything else this could key on without catching the varying
|
|
231
|
+
// half too.
|
|
232
|
+
exact: codec === "bit" || entity === "CockroachBit"
|
|
153
233
|
};
|
|
154
234
|
break;
|
|
155
235
|
}
|
|
@@ -175,7 +255,16 @@ function describeV1Column(column) {
|
|
|
175
255
|
out.dbType = "LINE";
|
|
176
256
|
out.shape = js === "object" ? { kind: "numberObject", fields: ["a", "b", "c"] } : { kind: "tuple", length: 3 };
|
|
177
257
|
break;
|
|
258
|
+
// `halfvec` beside `vector`, because they differ in storage width and in nothing a validator
|
|
259
|
+
// can see: `mapFromDriverValue` on both hands back `[1, 2, 3]`. It had no arm and came back
|
|
260
|
+
// `unknown` on this path too, which the fuzzer found.
|
|
261
|
+
//
|
|
262
|
+
// `sparsevec` is deliberately not here. Its name says vector and its value is the string
|
|
263
|
+
// `{1:1.5,3:2}/3`, so typing it `number[]` for symmetry would reject every row the database
|
|
264
|
+
// returns. Its codec already answers `string` on its own, which is the same conclusion reached
|
|
265
|
+
// without this arm.
|
|
178
266
|
case "vector":
|
|
267
|
+
case "halfvec":
|
|
179
268
|
out.tsType = "number[]";
|
|
180
269
|
out.dbType = "VECTOR";
|
|
181
270
|
out.shape = { kind: "numberVector", length: declaredLength(column) };
|
|
@@ -192,7 +281,12 @@ function describeV1Column(column) {
|
|
|
192
281
|
out.tsType = "number";
|
|
193
282
|
out.dbType = "NUMERIC";
|
|
194
283
|
out.integer = false;
|
|
195
|
-
|
|
284
|
+
const range = decimalModeRange(column, entityKind, "number");
|
|
285
|
+
if (range) [out.min, out.max] = range;
|
|
286
|
+
if (codec === "numeric:number") {
|
|
287
|
+
out.allowsNaN = true;
|
|
288
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
289
|
+
}
|
|
196
290
|
} else if (js === "string") {
|
|
197
291
|
out.tsType = "string";
|
|
198
292
|
out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
|
|
@@ -220,6 +314,28 @@ function declaredLength(column) {
|
|
|
220
314
|
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
221
315
|
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
222
316
|
}
|
|
317
|
+
function declaredDecimalRange(column) {
|
|
318
|
+
const cfg = column?.config ?? {};
|
|
319
|
+
const precision = column?.precision ?? cfg.precision;
|
|
320
|
+
const scale = column?.scale ?? cfg.scale ?? 0;
|
|
321
|
+
if (typeof precision !== "number" || !Number.isInteger(precision) || precision < 1)
|
|
322
|
+
return void 0;
|
|
323
|
+
if (typeof scale !== "number" || !Number.isInteger(scale) || scale < 0) return void 0;
|
|
324
|
+
const nines = "9".repeat(precision);
|
|
325
|
+
const max = scale === 0 ? nines : scale < precision ? `${nines.slice(0, precision - scale)}.${nines.slice(precision - scale)}` : `0.${"0".repeat(scale - precision)}${nines}`;
|
|
326
|
+
return [`-${max}`, max];
|
|
327
|
+
}
|
|
328
|
+
var DECIMAL_NUMBER_MODE = /(?:Numeric|Decimal)Number$/;
|
|
329
|
+
var DECIMAL_BIGINT_MODE = /(?:Numeric|Decimal)BigInt$/;
|
|
330
|
+
var MYSQL_IMPLICIT_DECIMAL_RANGE = ["-9999999999", "9999999999"];
|
|
331
|
+
function decimalModeRange(column, kind, mode) {
|
|
332
|
+
const declared = declaredDecimalRange(column);
|
|
333
|
+
if (declared) return declared;
|
|
334
|
+
if (kind.startsWith("MySql") || kind.startsWith("SingleStore"))
|
|
335
|
+
return MYSQL_IMPLICIT_DECIMAL_RANGE;
|
|
336
|
+
if (kind.startsWith("SQLite")) return void 0;
|
|
337
|
+
return mode === "number" ? JS_SAFE_INTEGER_BOUNDS : void 0;
|
|
338
|
+
}
|
|
223
339
|
var VIEW_CONFIG_FIELDS = {
|
|
224
340
|
"drizzle:Columns": "selectedFields",
|
|
225
341
|
"drizzle:Name": "name",
|
|
@@ -264,28 +380,51 @@ function isRelationsV2(val) {
|
|
|
264
380
|
)
|
|
265
381
|
);
|
|
266
382
|
}
|
|
383
|
+
function qualifiedNameOfDrizzleTable(tbl) {
|
|
384
|
+
const name = getSymbolOf(tbl, "drizzle:Name");
|
|
385
|
+
if (typeof name !== "string" || !name) return void 0;
|
|
386
|
+
const schema = getSymbolOf(tbl, "drizzle:Schema");
|
|
387
|
+
return typeof schema === "string" && schema ? `${schema}.${name}` : name;
|
|
388
|
+
}
|
|
267
389
|
function readRelationsV2(val, issues = []) {
|
|
268
390
|
const out = [];
|
|
269
391
|
for (const [tableKey, entry] of Object.entries(val)) {
|
|
270
|
-
const from =
|
|
392
|
+
const from = qualifiedNameOfDrizzleTable(entry.table) ?? entry.name ?? tableKey;
|
|
271
393
|
for (const [fieldName, r] of Object.entries(entry.relations ?? {})) {
|
|
272
|
-
const to = r?.targetTableName;
|
|
394
|
+
const to = qualifiedNameOfDrizzleTable(r?.targetTable) ?? r?.targetTableName;
|
|
273
395
|
if (typeof to !== "string" || !to) {
|
|
274
396
|
issues.push({
|
|
275
397
|
code: "DRZL_ANL_REL_V2",
|
|
276
398
|
level: "warn",
|
|
277
|
-
message: `Relation "${fieldName}" on "${from}" names no target table and was skipped
|
|
399
|
+
message: `Relation "${fieldName}" on "${from}" names no target table and was skipped.`,
|
|
400
|
+
path: from
|
|
278
401
|
});
|
|
279
402
|
continue;
|
|
280
403
|
}
|
|
281
|
-
const via =
|
|
404
|
+
const via = qualifiedNameOfDrizzleTable(r.throughTable) ?? qualifiedNameOfDrizzleTable(r.through?.sourceTable) ?? void 0;
|
|
282
405
|
if (via) out.push({ kind: "manyToMany", from, to, via });
|
|
283
406
|
else out.push({ kind: r.relationType === "many" ? "many" : "one", from, to });
|
|
284
407
|
}
|
|
285
408
|
}
|
|
286
409
|
return out;
|
|
287
410
|
}
|
|
411
|
+
function unknownColumnHint(reason) {
|
|
412
|
+
if (reason === "custom") {
|
|
413
|
+
return "A customType has no runtime shape to read. Declare it with .$type<T>() and turn on typedColumns to give the validator the type.";
|
|
414
|
+
}
|
|
415
|
+
if (reason === "gel-temporal") {
|
|
416
|
+
return "A Gel temporal column holds an instance of a class from the `gel` package, which DRZL cannot import, so it is left untyped on purpose rather than guessed at. Turn on typedColumns to recover the declared type, and validate the value yourself.";
|
|
417
|
+
}
|
|
418
|
+
return "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns.";
|
|
419
|
+
}
|
|
288
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
|
+
*/
|
|
289
428
|
constructor(schemaPath) {
|
|
290
429
|
this.schemaPath = schemaPath;
|
|
291
430
|
}
|
|
@@ -335,6 +474,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
335
474
|
code: "DRZL_ANL_EXTRACONFIG",
|
|
336
475
|
level: "warn",
|
|
337
476
|
message: `Could not evaluate the extra-config callback for table "${tableName}": ${e.message}`,
|
|
477
|
+
path: tableName,
|
|
338
478
|
hint: "Indexes, composite keys, checks and table-level foreign keys will be missing for this table."
|
|
339
479
|
});
|
|
340
480
|
return [];
|
|
@@ -388,9 +528,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
388
528
|
};
|
|
389
529
|
const foreignColumnsObj = this.getSymbol(ref.foreignTable, "drizzle:Columns") ?? {};
|
|
390
530
|
const toForeignTs = this.dbToTsNames(foreignColumnsObj);
|
|
531
|
+
const foreignSchema = this.getSymbol(ref.foreignTable, "drizzle:Schema");
|
|
391
532
|
return {
|
|
392
533
|
columns: (ref.columns ?? []).map((c) => toTs(c?.name)),
|
|
393
534
|
foreignTable: this.getSymbol(ref.foreignTable, "drizzle:Name") ?? "unknown",
|
|
535
|
+
...foreignSchema ? { foreignSchema } : {},
|
|
394
536
|
foreignColumns: (ref.foreignColumns ?? []).map((c) => toForeignTs(c?.name)),
|
|
395
537
|
onDelete: action(fk?.onDelete, fk?._onDelete),
|
|
396
538
|
onUpdate: action(fk?.onUpdate, fk?._onUpdate),
|
|
@@ -439,7 +581,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
439
581
|
* on each returned value, so the stand-in results must carry that method or the call throws.
|
|
440
582
|
*/
|
|
441
583
|
readRelationsObject(val, exportName, issues) {
|
|
442
|
-
const from =
|
|
584
|
+
const from = qualifiedNameOfDrizzleTable(val.table) ?? exportName;
|
|
443
585
|
const make = (kind) => (table, cfg) => ({
|
|
444
586
|
kind,
|
|
445
587
|
referencedTable: table,
|
|
@@ -453,7 +595,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
453
595
|
const built = val.config({ one: make("one"), many: make("many") });
|
|
454
596
|
const out = [];
|
|
455
597
|
for (const rel of Object.values(built ?? {})) {
|
|
456
|
-
const to =
|
|
598
|
+
const to = qualifiedNameOfDrizzleTable(rel?.referencedTable);
|
|
457
599
|
if (to) out.push({ kind: rel.kind, from, to });
|
|
458
600
|
}
|
|
459
601
|
return out;
|
|
@@ -462,6 +604,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
462
604
|
code: "DRZL_ANL_RELATIONS",
|
|
463
605
|
level: "warn",
|
|
464
606
|
message: `Could not read the relations declared in "${exportName}": ${e.message}`,
|
|
607
|
+
path: from,
|
|
465
608
|
hint: "Relations for this table will be missing from the analysis."
|
|
466
609
|
});
|
|
467
610
|
return [];
|
|
@@ -482,10 +625,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
482
625
|
if (fks.length < 2) continue;
|
|
483
626
|
const fkCols = new Set(fks.flatMap((f) => f.columns));
|
|
484
627
|
if (!t.columns.every((c) => fkCols.has(c.name))) continue;
|
|
485
|
-
const targets = [...new Set(fks.map(
|
|
628
|
+
const targets = [...new Set(fks.map(qualifiedForeignTable))];
|
|
486
629
|
if (targets.length !== 2) continue;
|
|
487
|
-
|
|
488
|
-
out.push({ kind: "manyToMany", from: targets[
|
|
630
|
+
const via = qualifiedTableName(t);
|
|
631
|
+
out.push({ kind: "manyToMany", from: targets[0], to: targets[1], via });
|
|
632
|
+
out.push({ kind: "manyToMany", from: targets[1], to: targets[0], via });
|
|
489
633
|
}
|
|
490
634
|
return out;
|
|
491
635
|
}
|
|
@@ -502,7 +646,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
502
646
|
if (typeof length === "number" && Number.isFinite(length) && length > 0) {
|
|
503
647
|
out.maxLength = length;
|
|
504
648
|
}
|
|
505
|
-
const
|
|
649
|
+
const unsignedRange = column?.config?.unsigned === true ? _SchemaAnalyzer.UNSIGNED_INT_RANGES[ctor] : void 0;
|
|
650
|
+
const range = unsignedRange ?? _SchemaAnalyzer.INT_RANGES[ctor];
|
|
506
651
|
if (range) {
|
|
507
652
|
[out.min, out.max] = range;
|
|
508
653
|
out.integer = true;
|
|
@@ -512,6 +657,24 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
512
657
|
if (inexact) [out.min, out.max] = inexact;
|
|
513
658
|
out.integer = false;
|
|
514
659
|
}
|
|
660
|
+
const nonFinite = _SchemaAnalyzer.NON_FINITE_BY_CLASS[ctor];
|
|
661
|
+
if (nonFinite) {
|
|
662
|
+
out.allowsNaN = nonFinite.nan;
|
|
663
|
+
out.allowsInfinity = nonFinite.infinity;
|
|
664
|
+
}
|
|
665
|
+
if (DECIMAL_NUMBER_MODE.test(ctor)) {
|
|
666
|
+
const range2 = decimalModeRange(column, ctor, "number");
|
|
667
|
+
if (range2) [out.min, out.max] = range2;
|
|
668
|
+
out.integer = false;
|
|
669
|
+
if (ctor === "PgNumericNumber") {
|
|
670
|
+
out.allowsNaN = true;
|
|
671
|
+
out.allowsInfinity = !declaredDecimalRange(column);
|
|
672
|
+
}
|
|
673
|
+
} else if (DECIMAL_BIGINT_MODE.test(ctor)) {
|
|
674
|
+
const range2 = decimalModeRange(column, ctor, "bigint");
|
|
675
|
+
if (range2) [out.min, out.max] = range2;
|
|
676
|
+
out.integer = true;
|
|
677
|
+
}
|
|
515
678
|
if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
|
|
516
679
|
return out;
|
|
517
680
|
}
|
|
@@ -523,12 +686,36 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
523
686
|
tsType: column?.config?.mode === "timestamp" ? "Date" : "number",
|
|
524
687
|
dbType: "INTEGER"
|
|
525
688
|
};
|
|
689
|
+
// Both timestamp modes of `integer()`, which are one class and one type. `timestamp` and
|
|
690
|
+
// `timestamp_ms` differ in the scale of the number on the wire, seconds against
|
|
691
|
+
// milliseconds, and `mapFromDriverValue` consumes that difference and hands back a `Date`
|
|
692
|
+
// either way; nothing downstream of the analyzer ever sees the integer. So an arm keyed on
|
|
693
|
+
// the class covers both, where the mode check that used to answer this fell through the
|
|
694
|
+
// switch to a default arm testing `config.mode === 'timestamp'` and named only the first.
|
|
695
|
+
// The second came back `unknown`, and every generator emitted a schema accepting anything.
|
|
696
|
+
//
|
|
697
|
+
// `DATE` rather than the `INTEGER` that mode check returned, so the two majors describe the
|
|
698
|
+
// column identically. `dbType` is read in exactly one place outside this file,
|
|
699
|
+
// `isIntegerColumn`, which the generators consult only for a `tsType` of `number`, so the
|
|
700
|
+
// relabel reaches no output. Measured rather than argued: emitting a `Date` column under
|
|
701
|
+
// both labels, nullable and not, through all five generators gives ten byte-identical pairs.
|
|
702
|
+
case "SQLiteTimestamp":
|
|
703
|
+
return { tsType: "Date", dbType: "DATE" };
|
|
526
704
|
case "SQLiteText":
|
|
527
705
|
return { tsType: "string", dbType: "TEXT" };
|
|
528
706
|
case "SQLiteReal":
|
|
529
707
|
return { tsType: "number", dbType: "REAL" };
|
|
708
|
+
// No 0.4x column is a `SQLiteBlob`: `sqlite-core` builds a `SQLiteBlobBuffer`, a
|
|
709
|
+
// `SQLiteBlobJson` or a `SQLiteBigInt`, one per mode, and exports no class of this name at
|
|
710
|
+
// all. The arm answers the hand-built column in sqlite-types.spec.ts and nothing drizzle
|
|
711
|
+
// produces, which is why a real `blob()` reached neither it nor anything else.
|
|
530
712
|
case "SQLiteBlob":
|
|
531
713
|
return { tsType: "Uint8Array", dbType: "BLOB" };
|
|
714
|
+
// The class a real `blob()` and `blob({ mode: 'buffer' })` both build. See `BUFFER_CLASSES`
|
|
715
|
+
// for the measurement; the answers here are v1's own for the same column, so this is the
|
|
716
|
+
// two majors agreeing rather than a new opinion.
|
|
717
|
+
case "SQLiteBlobBuffer":
|
|
718
|
+
return { tsType: "Buffer", dbType: "BYTEA" };
|
|
532
719
|
// SQLite spells a mode as a distinct class rather than as config, so `text({mode:'json'})`
|
|
533
720
|
// is a `SQLiteTextJson` and matched no arm at all: the column came back UNKNOWN, which is
|
|
534
721
|
// wider than the `any` a json column at least used to get.
|
|
@@ -567,6 +754,34 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
567
754
|
case "MySqlEnumColumn":
|
|
568
755
|
case "SingleStoreEnumColumn":
|
|
569
756
|
return { tsType: "string", dbType: "TEXT" };
|
|
757
|
+
// The pgvector family, found by the analyzer fuzzer: all three came back `unknown` on this
|
|
758
|
+
// path, so their validators accepted anything. The answers are drizzle's own mappers rather
|
|
759
|
+
// than the type names, and the three do not agree with each other:
|
|
760
|
+
//
|
|
761
|
+
// vector(3) SELECT gives [1,2,3] INSERT sends "[1,2,3]"
|
|
762
|
+
// halfvec(3) SELECT gives [1,2,3] INSERT sends "[1,2,3]"
|
|
763
|
+
// sparsevec(3) SELECT gives "{1:1.5,3:2}/3" INSERT sends "{1:1.5,3:2}/3"
|
|
764
|
+
//
|
|
765
|
+
// So the two dense ones are number arrays and the sparse one is a string. Typing `sparsevec`
|
|
766
|
+
// as a vector for symmetry would reject every row the database returns, which is the defect
|
|
767
|
+
// this family was filed under to begin with. The `shape` carries the dimension count where
|
|
768
|
+
// one is declared, as the codec path already did for `vector`.
|
|
769
|
+
case "PgVector":
|
|
770
|
+
case "PgHalfVector":
|
|
771
|
+
case "SingleStoreVector":
|
|
772
|
+
return { tsType: "number[]", dbType: "VECTOR" };
|
|
773
|
+
// `BIT` rather than `TEXT`, which a first version of this arm returned. v1's codec says `BIT`
|
|
774
|
+
// for the same column, and the cross-major diff said so: naming the class made ten of its
|
|
775
|
+
// twelve entries go stale and left `c_bit.dbType` and its nullable twin standing, which is
|
|
776
|
+
// that check distinguishing a fix from a half fix.
|
|
777
|
+
case "PgBinaryVector":
|
|
778
|
+
return { tsType: "string", dbType: "BIT" };
|
|
779
|
+
case "PgGeometry":
|
|
780
|
+
return { tsType: "[number, number]", dbType: "GEOMETRY" };
|
|
781
|
+
case "PgGeometryObject":
|
|
782
|
+
return { tsType: "{ x: number; y: number }", dbType: "GEOMETRY" };
|
|
783
|
+
case "PgSparseVector":
|
|
784
|
+
return { tsType: "string", dbType: "TEXT" };
|
|
570
785
|
case "PgInteger":
|
|
571
786
|
case "PgSmallInt":
|
|
572
787
|
return { tsType: "number", dbType: "INTEGER" };
|
|
@@ -687,7 +902,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
687
902
|
}
|
|
688
903
|
if (/Numeric|Float|Double|Real/i.test(ctor))
|
|
689
904
|
return { tsType: "number", dbType: "NUMERIC" };
|
|
690
|
-
if (/
|
|
905
|
+
if (/Serial/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
|
|
906
|
+
if (/Int|TinyInt|SmallInt|MediumInt/i.test(ctor))
|
|
691
907
|
return { tsType: "number", dbType: "INTEGER" };
|
|
692
908
|
if (/Bool|Boolean/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
693
909
|
if (/TimestampString|DateTimeString|DateString/i.test(ctor))
|
|
@@ -711,7 +927,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
711
927
|
}
|
|
712
928
|
if (/Numeric|Float|Double|Real/i.test(ctor))
|
|
713
929
|
return { tsType: "number", dbType: "NUMERIC" };
|
|
714
|
-
if (/
|
|
930
|
+
if (/Serial/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
|
|
931
|
+
if (/Int|TinyInt|SmallInt|MediumInt/i.test(ctor))
|
|
715
932
|
return { tsType: "number", dbType: "INTEGER" };
|
|
716
933
|
if (/Bool|Boolean/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
717
934
|
if (/TimestampString|DateTimeString|DateString/i.test(ctor))
|
|
@@ -727,7 +944,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
727
944
|
if (/^Gel/i.test(ctor)) {
|
|
728
945
|
if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
|
|
729
946
|
if (/Int53|Integer|SmallInt/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
|
|
730
|
-
if (/
|
|
947
|
+
if (/DoublePrecision/i.test(ctor)) return { tsType: "number", dbType: "DOUBLE" };
|
|
948
|
+
if (/Real/i.test(ctor)) return { tsType: "number", dbType: "REAL" };
|
|
731
949
|
if (/Decimal/i.test(ctor)) return { tsType: "string", dbType: "NUMERIC" };
|
|
732
950
|
if (/UUID/i.test(ctor)) return { tsType: "string", dbType: "UUID" };
|
|
733
951
|
if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
|
|
@@ -736,7 +954,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
736
954
|
if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
737
955
|
if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
|
|
738
956
|
if (/Timestamp|LocalDateString|LocalTime|DateDuration|RelDuration|Duration/i.test(ctor))
|
|
739
|
-
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
957
|
+
return { tsType: "unknown", dbType: "UNKNOWN", unnameable: "gel-temporal" };
|
|
740
958
|
}
|
|
741
959
|
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
742
960
|
}
|
|
@@ -752,7 +970,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
752
970
|
const uniqueGroups = /* @__PURE__ */ new Map();
|
|
753
971
|
for (const [colName, outerCol] of Object.entries(columnsObj)) {
|
|
754
972
|
const { element: col, dimensions: arrayDims } = unwrapArrayColumn(outerCol);
|
|
755
|
-
|
|
973
|
+
const mapped = this.mapColumnType(col);
|
|
974
|
+
let { tsType, dbType } = mapped;
|
|
756
975
|
if (tsType === "unknown" && /At$/.test(colName)) {
|
|
757
976
|
tsType = "Date";
|
|
758
977
|
dbType = "INTEGER";
|
|
@@ -778,11 +997,13 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
778
997
|
const v1 = describeV1Column(col);
|
|
779
998
|
const constraints = this.columnConstraints(col);
|
|
780
999
|
if (v1?.shape) delete constraints.maxLength;
|
|
781
|
-
const sqlKind = String(
|
|
1000
|
+
const sqlKind = String(
|
|
1001
|
+
outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? ""
|
|
1002
|
+
);
|
|
782
1003
|
const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
|
|
783
1004
|
const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
|
|
784
1005
|
const ctorName = String(col?.constructor?.name ?? "");
|
|
785
|
-
const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : GEOMETRIC_CLASS_SHAPES[ctorName];
|
|
1006
|
+
const fallbackShape = dbType === "JSON" || dbType === "JSONB" || col?.config?.mode === "json" ? { kind: "json" } : BYTE_STRING_CLASSES.has(ctorName) ? { kind: "byteString", length: declaredLength(col) } : BUFFER_CLASSES.has(ctorName) ? { kind: "buffer" } : NUMBER_VECTOR_CLASSES.has(ctorName) ? { kind: "numberVector", length: declaredLength(col) } : BIT_STRING_CLASSES.has(ctorName) ? { kind: "bitstring", length: declaredLength(col), exact: true } : GEOMETRIC_CLASS_SHAPES[ctorName];
|
|
786
1007
|
if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
|
|
787
1008
|
const shape = (v1?.shape ?? fallbackShape)?.kind;
|
|
788
1009
|
const finalTs = v1?.tsType ?? tsType;
|
|
@@ -793,13 +1014,26 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
793
1014
|
code: "DRZL_ANL_UNKNOWN_COLUMN",
|
|
794
1015
|
level: "warn",
|
|
795
1016
|
message: `Column "${colName}" on table "${tsName}" has no known type${sqlType2 ? ` (SQL type ${sqlType2})` : ""}, so its validator will accept any value.`,
|
|
796
|
-
|
|
1017
|
+
path: `${tsName}.${colName}`,
|
|
1018
|
+
hint: unknownColumnHint(shape === "custom" ? "custom" : mapped.unnameable)
|
|
797
1019
|
});
|
|
798
1020
|
}
|
|
1021
|
+
const dims = arrayDims || v1?.arrayDimensions || 0;
|
|
1022
|
+
const declaredSqlType = (() => {
|
|
1023
|
+
let raw;
|
|
1024
|
+
try {
|
|
1025
|
+
raw = typeof outerCol?.getSQLType === "function" ? outerCol.getSQLType() : void 0;
|
|
1026
|
+
} catch {
|
|
1027
|
+
return void 0;
|
|
1028
|
+
}
|
|
1029
|
+
if (typeof raw !== "string" || !raw) return void 0;
|
|
1030
|
+
return raw.endsWith("]") ? raw : raw + "[]".repeat(dims);
|
|
1031
|
+
})();
|
|
799
1032
|
columns.push({
|
|
800
1033
|
name: colName,
|
|
801
1034
|
tsType,
|
|
802
1035
|
dbType,
|
|
1036
|
+
...declaredSqlType ? { sqlType: declaredSqlType } : {},
|
|
803
1037
|
nullable,
|
|
804
1038
|
hasDefault,
|
|
805
1039
|
isGenerated,
|
|
@@ -876,6 +1110,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
876
1110
|
if (!col) continue;
|
|
877
1111
|
col.references = {
|
|
878
1112
|
table: fk.foreignTable,
|
|
1113
|
+
...fk.foreignSchema ? { schema: fk.foreignSchema } : {},
|
|
879
1114
|
column: fk.foreignColumns[0],
|
|
880
1115
|
onDelete: fk.onDelete,
|
|
881
1116
|
onUpdate: fk.onUpdate
|
|
@@ -904,31 +1139,85 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
904
1139
|
const fs = await import("fs/promises");
|
|
905
1140
|
const path = await import("path");
|
|
906
1141
|
const issues = [];
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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
|
+
}
|
|
917
1157
|
}
|
|
918
|
-
|
|
919
|
-
try {
|
|
920
|
-
const { default: jiti } = await import("jiti");
|
|
921
|
-
const jit = jiti(import.meta.url, { moduleCache: false });
|
|
922
|
-
mod = jit(full);
|
|
923
|
-
} catch (e) {
|
|
924
|
-
issues.push({
|
|
925
|
-
code: "DRZL_ANL_IMPORT",
|
|
926
|
-
level: "error",
|
|
927
|
-
message: `Failed to import schema: ${String(e)}`
|
|
928
|
-
});
|
|
1158
|
+
if (missing) {
|
|
929
1159
|
return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
|
|
930
1160
|
}
|
|
931
|
-
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
|
+
}
|
|
932
1221
|
const tables = [];
|
|
933
1222
|
const relations = [];
|
|
934
1223
|
const enums = [];
|
|
@@ -948,9 +1237,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
948
1237
|
}
|
|
949
1238
|
}
|
|
950
1239
|
if (opts.includeRelations) {
|
|
1240
|
+
const self = qualifiedTableName(table);
|
|
951
1241
|
for (const fk of table.foreignKeys ?? []) {
|
|
952
|
-
|
|
953
|
-
relations.push({ kind: "
|
|
1242
|
+
const target = qualifiedForeignTable(fk);
|
|
1243
|
+
relations.push({ kind: "one", from: self, to: target });
|
|
1244
|
+
relations.push({ kind: "many", from: target, to: self });
|
|
954
1245
|
}
|
|
955
1246
|
}
|
|
956
1247
|
} else if (this.isRelationsObject(val)) {
|
|
@@ -978,7 +1269,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
978
1269
|
issues.push({
|
|
979
1270
|
code: "DRZL_ANL_TABLE",
|
|
980
1271
|
level: "warn",
|
|
981
|
-
message: `Failed to analyze export ${name}: ${String(e)}
|
|
1272
|
+
message: `Failed to analyze export ${name}: ${String(e)}`,
|
|
1273
|
+
path: name
|
|
982
1274
|
});
|
|
983
1275
|
}
|
|
984
1276
|
}
|
|
@@ -1023,11 +1315,21 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1023
1315
|
relations.push(...this.inferManyToMany(tables));
|
|
1024
1316
|
}
|
|
1025
1317
|
if (opts.includeRelations && opts.includeHeuristicRelations) {
|
|
1026
|
-
const
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
if (
|
|
1030
|
-
|
|
1318
|
+
const byBareName = /* @__PURE__ */ new Map();
|
|
1319
|
+
for (const t of tables) {
|
|
1320
|
+
const list = byBareName.get(t.name);
|
|
1321
|
+
if (list) list.push(t);
|
|
1322
|
+
else byBareName.set(t.name, [t]);
|
|
1323
|
+
}
|
|
1324
|
+
const findTarget = (base, from) => {
|
|
1325
|
+
for (const candidate of [base, base + "s", base + "es"]) {
|
|
1326
|
+
const hits = byBareName.get(candidate);
|
|
1327
|
+
if (!hits?.length) continue;
|
|
1328
|
+
const sameSchema = hits.filter((t) => t.schema === from.schema);
|
|
1329
|
+
if (sameSchema.length === 1) return qualifiedTableName(sameSchema[0]);
|
|
1330
|
+
if (hits.length === 1) return qualifiedTableName(hits[0]);
|
|
1331
|
+
return void 0;
|
|
1332
|
+
}
|
|
1031
1333
|
return void 0;
|
|
1032
1334
|
};
|
|
1033
1335
|
for (const t of tables) {
|
|
@@ -1035,8 +1337,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1035
1337
|
if (c.references) continue;
|
|
1036
1338
|
if (c.name.endsWith("Id")) {
|
|
1037
1339
|
const base = c.name.slice(0, -2);
|
|
1038
|
-
const target = findTarget(base);
|
|
1039
|
-
if (target) relations.push({ kind: "one", from: t
|
|
1340
|
+
const target = findTarget(base, t);
|
|
1341
|
+
if (target) relations.push({ kind: "one", from: qualifiedTableName(t), to: target });
|
|
1040
1342
|
}
|
|
1041
1343
|
}
|
|
1042
1344
|
}
|
|
@@ -1068,6 +1370,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
|
1068
1370
|
_SchemaAnalyzer.INT_RANGES = {
|
|
1069
1371
|
// 8 bit
|
|
1070
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"],
|
|
1071
1379
|
SQLiteInteger: ["-9223372036854775808", "9223372036854775807"],
|
|
1072
1380
|
// 16 bit
|
|
1073
1381
|
PgSmallInt: ["-32768", "32767"],
|
|
@@ -1080,6 +1388,8 @@ _SchemaAnalyzer.INT_RANGES = {
|
|
|
1080
1388
|
SingleStoreSmallInt: ["-32768", "32767"],
|
|
1081
1389
|
// 24 bit
|
|
1082
1390
|
MySqlMediumInt: ["-8388608", "8388607"],
|
|
1391
|
+
// As SingleStoreTinyInt above: v1 states `number int24` and this table said nothing.
|
|
1392
|
+
SingleStoreMediumInt: ["-8388608", "8388607"],
|
|
1083
1393
|
// 32 bit
|
|
1084
1394
|
PgInteger: ["-2147483648", "2147483647"],
|
|
1085
1395
|
PgSerial: ["-2147483648", "2147483647"],
|
|
@@ -1094,7 +1404,61 @@ _SchemaAnalyzer.INT_RANGES = {
|
|
|
1094
1404
|
PgBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
1095
1405
|
PgBigSerial64: ["-9223372036854775808", "9223372036854775807"],
|
|
1096
1406
|
MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
1097
|
-
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"]
|
|
1098
1462
|
};
|
|
1099
1463
|
/**
|
|
1100
1464
|
* The numeric column classes that are not exact, and the magnitude each one can really hold.
|
|
@@ -1145,10 +1509,85 @@ _SchemaAnalyzer.INEXACT_RANGES = {
|
|
|
1145
1509
|
SQLiteReal: null,
|
|
1146
1510
|
SingleStoreDouble: null,
|
|
1147
1511
|
SingleStoreReal: null,
|
|
1148
|
-
// `
|
|
1149
|
-
//
|
|
1150
|
-
//
|
|
1151
|
-
|
|
1512
|
+
// Gel, whose `real` is a `std::float32` and whose `doublePrecision` is a `std::float64`. Both
|
|
1513
|
+
// used to be answered by a `/Real|DoublePrecision/i` arm that said NUMERIC and stated nothing
|
|
1514
|
+
// else at all, so a `real` column accepted 1e300 and the server refused it.
|
|
1515
|
+
//
|
|
1516
|
+
// Measured on a live Gel 7.1 (`geldata/gel:7`, sys::get_version_as_str() -> 7.1+08db576)
|
|
1517
|
+
// through the `gel` client, casting each literal so the server parses it, and again through a
|
|
1518
|
+
// stored property on a real object type. The float32 edge is Postgres's exactly, to the double:
|
|
1519
|
+
//
|
|
1520
|
+
// 3.4028234663852886e38 accepted, returned unchanged
|
|
1521
|
+
// 3.4028235677973366e38 accepted, and stored as 3.4028234663852886e38
|
|
1522
|
+
// 3.402823567797337e38 refused, "is out of range for type std::float32"
|
|
1523
|
+
// 1e300 refused, the same way
|
|
1524
|
+
//
|
|
1525
|
+
// The same value accepted, the same next double up refused, and the same rounding down of the
|
|
1526
|
+
// midpoint, so it takes the constant already here rather than a second name for one number.
|
|
1527
|
+
// float64 took 1e300 and Number.MAX_VALUE faithfully, for the reason no 8 byte float has a
|
|
1528
|
+
// truthful finite bound.
|
|
1529
|
+
GelReal: PG_FLOAT4_RANGE,
|
|
1530
|
+
GelDoublePrecision: null
|
|
1531
|
+
};
|
|
1532
|
+
// `numeric`/`decimal` in either of its two numeric modes is deliberately not in the table above.
|
|
1533
|
+
// Its bound is not a fixed magnitude per class but the precision each column declares for itself,
|
|
1534
|
+
// which no table keyed on a class name can hold; see `declaredDecimalRange`.
|
|
1535
|
+
/**
|
|
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.
|
|
1545
|
+
*
|
|
1546
|
+
* The class-name half of what `describeV1Column` reads off the codec, and the two must agree: a
|
|
1547
|
+
* fact stated on one path and not the other is a schema that changes when the user upgrades
|
|
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.
|
|
1557
|
+
*
|
|
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`.
|
|
1567
|
+
*
|
|
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.
|
|
1571
|
+
*
|
|
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.
|
|
1579
|
+
*/
|
|
1580
|
+
_SchemaAnalyzer.NON_FINITE_BY_CLASS = {
|
|
1581
|
+
PgReal: { nan: true, infinity: true },
|
|
1582
|
+
PgDoublePrecision: { nan: true, infinity: true },
|
|
1583
|
+
GelReal: { 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 }
|
|
1152
1591
|
};
|
|
1153
1592
|
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
1154
1593
|
var index_default = SchemaAnalyzer;
|
|
@@ -1159,5 +1598,7 @@ export {
|
|
|
1159
1598
|
isDrizzleView,
|
|
1160
1599
|
isReadOnlyRelation,
|
|
1161
1600
|
isRelationsV2,
|
|
1601
|
+
qualifiedForeignTable,
|
|
1602
|
+
qualifiedTableName,
|
|
1162
1603
|
readRelationsV2
|
|
1163
1604
|
};
|