@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/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 = semantic === "int8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
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 = codec === "bit" ? "BIT" : "BINARY";
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
- exact: codec === "bit"
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
- [out.min, out.max] = JS_SAFE_INTEGER_BOUNDS;
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 = getSymbolOf(entry.table, "drizzle:Name") ?? entry.name ?? tableKey;
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 = getSymbolOf(r.throughTable, "drizzle:Name") ?? getSymbolOf(r.through?.sourceTable, "drizzle:Name") ?? void 0;
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 = this.getSymbol(val.table, "drizzle:Name") ?? exportName;
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 = this.getSymbol(rel?.referencedTable, "drizzle:Name");
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((f) => f.foreignTable))];
628
+ const targets = [...new Set(fks.map(qualifiedForeignTable))];
486
629
  if (targets.length !== 2) continue;
487
- out.push({ kind: "manyToMany", from: targets[0], to: targets[1], via: t.name });
488
- out.push({ kind: "manyToMany", from: targets[1], to: targets[0], via: t.name });
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 range = _SchemaAnalyzer.INT_RANGES[ctor];
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 (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
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 (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
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 (/Real|DoublePrecision/i.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
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
- let { tsType, dbType } = this.mapColumnType(col);
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(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
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
- hint: shape === "custom" ? "A customType has no runtime shape to read. Declare it with .$type<T>() and turn on typedColumns to give the validator the type." : "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns."
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 full = path.resolve(process.cwd(), this.schemaPath);
908
- try {
909
- await fs.access(full);
910
- } catch (_e) {
911
- issues.push({
912
- code: "DRZL_ANL_NOFILE",
913
- level: "error",
914
- message: `Schema file not found: ${this.schemaPath}`
915
- });
916
- return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
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
- let mod;
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 exportsObj = mod?.default && typeof mod.default === "object" ? mod.default : mod;
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
- relations.push({ kind: "one", from: table.name, to: fk.foreignTable });
953
- relations.push({ kind: "many", from: fk.foreignTable, to: table.name });
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 tableNames = new Set(tables.map((t) => t.name));
1027
- const findTarget = (base) => {
1028
- if (tableNames.has(base)) return base;
1029
- if (tableNames.has(base + "s")) return base + "s";
1030
- if (tableNames.has(base + "es")) return base + "es";
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.name, to: target });
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
- // `numeric({ mode: 'number' })`, which v1 reaches through the bare-number arm of
1149
- // `describeV1Column`. This one is about what a JS number can carry rather than about the
1150
- // column, which Postgres caps far lower: it refuses 2147483648 into a `numeric(10,2)`.
1151
- PgNumericNumber: JS_SAFE_INTEGER_BOUNDS
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
  };