@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.cjs CHANGED
@@ -36,10 +36,18 @@ __export(index_exports, {
36
36
  isDrizzleView: () => isDrizzleView,
37
37
  isReadOnlyRelation: () => isReadOnlyRelation,
38
38
  isRelationsV2: () => isRelationsV2,
39
+ qualifiedForeignTable: () => qualifiedForeignTable,
40
+ qualifiedTableName: () => qualifiedTableName,
39
41
  readRelationsV2: () => readRelationsV2
40
42
  });
41
43
  module.exports = __toCommonJS(index_exports);
42
44
  var import_meta = {};
45
+ function qualifiedTableName(table) {
46
+ return table.schema ? `${table.schema}.${table.name}` : table.name;
47
+ }
48
+ function qualifiedForeignTable(fk) {
49
+ return fk.foreignSchema ? `${fk.foreignSchema}.${fk.foreignTable}` : fk.foreignTable;
50
+ }
43
51
  function renderSqlLiteral(v) {
44
52
  if (v === null || v === void 0) return "NULL";
45
53
  if (typeof v === "number" || typeof v === "bigint") return String(v);
@@ -71,15 +79,23 @@ var GEOMETRIC_CLASS_SHAPES = {
71
79
  // The object modes. `line({ mode: 'abc' })` is a `PgLineABC` and not a `PgLineObject`, and any
72
80
  // mode but `'tuple'` builds the object class: `point({ mode: 'abc' })` is a `PgPointObject` too.
73
81
  PgPointObject: { kind: "numberObject", fields: ["x", "y"] },
74
- PgLineABC: { kind: "numberObject", fields: ["a", "b", "c"] }
82
+ PgLineABC: { kind: "numberObject", fields: ["a", "b", "c"] },
83
+ // `geometry()` and `geometry({ mode: 'xy' })` are two classes, not one class with a flag, and
84
+ // the fuzzer found both unnamed on this path. Their driver mappers disagree the same way the
85
+ // point ones do: the default hands back `[1, 2]` and the xy mode hands back `{ x: 1, y: 2 }`.
86
+ PgGeometry: { kind: "tuple", length: 2 },
87
+ PgGeometryObject: { kind: "numberObject", fields: ["x", "y"] }
75
88
  };
76
89
  var V1_ONLY_ENTITY_KINDS = /^(?:MsSql|Cockroach)/;
90
+ var NUMBER_VECTOR_CLASSES = /* @__PURE__ */ new Set(["PgVector", "PgHalfVector", "SingleStoreVector"]);
91
+ var BIT_STRING_CLASSES = /* @__PURE__ */ new Set(["PgBinaryVector"]);
77
92
  var BYTE_STRING_CLASSES = /* @__PURE__ */ new Set([
78
93
  "MySqlBinary",
79
94
  "MySqlVarBinary",
80
95
  "SingleStoreBinary",
81
96
  "SingleStoreVarBinary"
82
97
  ]);
98
+ var BUFFER_CLASSES = /* @__PURE__ */ new Set(["SQLiteBlobBuffer"]);
83
99
  function describeV1Column(column) {
84
100
  const codec = column?.codec;
85
101
  const dataType = column?.dataType;
@@ -98,27 +114,63 @@ function describeV1Column(column) {
98
114
  const out = {};
99
115
  switch (semantic) {
100
116
  case "int8":
117
+ case "uint8":
101
118
  case "int16":
119
+ case "uint16":
102
120
  case "int24":
121
+ case "uint24":
103
122
  case "int32":
123
+ case "uint32":
104
124
  case "int53":
105
125
  case "uint53":
106
- case "int64": {
126
+ case "int64":
127
+ case "uint64": {
128
+ if (DECIMAL_BIGINT_MODE.test(entityKind)) {
129
+ out.tsType = "bigint";
130
+ out.dbType = "NUMERIC";
131
+ out.integer = true;
132
+ const range2 = decimalModeRange(column, entityKind, "bigint");
133
+ if (range2) [out.min, out.max] = range2;
134
+ break;
135
+ }
136
+ if ((semantic === "int64" || semantic === "uint64") && js === "string") {
137
+ out.tsType = "string";
138
+ out.dbType = "BIGINT";
139
+ if (entityKind.startsWith("Pg")) out.format = "pgBigint";
140
+ else if (entityKind.startsWith("MySql") || entityKind.startsWith("SingleStore"))
141
+ out.format = "mysqlBigint";
142
+ break;
143
+ }
107
144
  const range = {
108
145
  int8: ["-128", "127"],
146
+ uint8: ["0", "255"],
109
147
  int16: ["-32768", "32767"],
148
+ uint16: ["0", "65535"],
110
149
  int24: ["-8388608", "8388607"],
150
+ uint24: ["0", "16777215"],
111
151
  int32: ["-2147483648", "2147483647"],
152
+ uint32: ["0", "4294967295"],
112
153
  int53: ["-9007199254740991", "9007199254740991"],
113
154
  // MySQL `serial` is `bigint unsigned auto_increment`, so it starts at 0 rather than
114
- // spanning the signed range.
155
+ // spanning the signed range. An explicit `bigint({ mode: 'number', unsigned: true })`
156
+ // states the same semantic and takes the same answer.
115
157
  uint53: ["0", "9007199254740991"],
116
- int64: ["-9223372036854775808", "9223372036854775807"]
158
+ int64: ["-9223372036854775808", "9223372036854775807"],
159
+ uint64: ["0", "18446744073709551615"]
117
160
  }[semantic];
118
161
  [out.min, out.max] = range;
119
162
  out.integer = true;
120
163
  out.tsType = js === "bigint" ? "bigint" : "number";
121
- out.dbType = semantic === "int8" ? "TINYINT" : semantic === "int16" ? "SMALLINT" : semantic === "int24" ? "MEDIUMINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
164
+ out.dbType = {
165
+ int8: "TINYINT",
166
+ uint8: "TINYINT",
167
+ int16: "SMALLINT",
168
+ uint16: "SMALLINT",
169
+ int24: "MEDIUMINT",
170
+ uint24: "MEDIUMINT",
171
+ int32: "INTEGER",
172
+ uint32: "INTEGER"
173
+ }[semantic] ?? "BIGINT";
122
174
  break;
123
175
  }
124
176
  case "year":
@@ -134,6 +186,14 @@ function describeV1Column(column) {
134
186
  out.integer = false;
135
187
  out.tsType = "number";
136
188
  out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
189
+ if (codec === "float4" || codec === "float8") {
190
+ out.allowsNaN = true;
191
+ out.allowsInfinity = true;
192
+ }
193
+ if (codec === "float" || codec === "double" || codec === "real" || entityKind.startsWith("SingleStore")) {
194
+ out.allowsNaN = false;
195
+ out.allowsInfinity = false;
196
+ }
137
197
  break;
138
198
  }
139
199
  case "uuid":
@@ -163,6 +223,16 @@ function describeV1Column(column) {
163
223
  out.dbType = codec?.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
164
224
  break;
165
225
  case "timestamp":
226
+ // `datetime` is the same fact under MySQL's name for it, and it had no arm, so every column
227
+ // stating it fell to the bare-string arm and was labelled TEXT. The columns that reach this
228
+ // are the string modes of `datetime` on mssql, mysql and singlestore, plus mssql's
229
+ // `datetime2` and `datetimeoffset`, swept over every builder the six v1 cores export; the
230
+ // `{ mode: 'date' }` half of the same builders states `object date` and takes the arm above.
231
+ // A label only, since `dbType` is read outside this file in exactly one place,
232
+ // `isIntegerColumn`, which the generators consult for a `tsType` of `number`. It matters
233
+ // because the class-name path already answers TIMESTAMP for the same 0.4x column, and the
234
+ // two majors disagreeing about a column is what the cross-major diff exists to catch.
235
+ case "datetime":
166
236
  out.tsType = js === "string" ? "string" : "Date";
167
237
  out.dbType = "TIMESTAMP";
168
238
  break;
@@ -184,13 +254,25 @@ function describeV1Column(column) {
184
254
  const entity = String(column?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
185
255
  const bytes = entity.startsWith("MySql") || entity.startsWith("SingleStore");
186
256
  out.tsType = "string";
187
- out.dbType = codec === "bit" ? "BIT" : "BINARY";
257
+ out.dbType = bytes ? "BINARY" : "BIT";
188
258
  out.shape = bytes ? { kind: "byteString", length: declaredLength(column) } : {
189
259
  kind: "bitstring",
190
260
  length: declaredLength(column),
191
261
  // A Postgres `bit(3)` holds exactly three digits; a Cockroach `varbit(16)` holds at
192
262
  // most that many, which is why `''` is valid there and not here.
193
- exact: codec === "bit"
263
+ //
264
+ // `codec === 'bit'` alone was Postgres's answer applied to everything, and Cockroach
265
+ // states no codec, so both of its builders came back `exact: false` and a `bit(3)`
266
+ // was indistinguishable from a `varbit(3)`. Measured on CockroachDB v24.3.5: a
267
+ // `bit(3)` refuses '', '1', '10' and '1011' with "bit string length n does not match
268
+ // type BIT(3)" and takes '101'; a `varbit(8)` takes '', '1' and '10101010' and
269
+ // refuses nine digits with "too large for type VARBIT(8)". `drizzle-orm/zod` at
270
+ // 1.0.0-rc.4 answers the same for both columns.
271
+ //
272
+ // The class rather than a prefix, because `CockroachVarbit` starts with neither
273
+ // `CockroachBit` nor anything else this could key on without catching the varying
274
+ // half too.
275
+ exact: codec === "bit" || entity === "CockroachBit"
194
276
  };
195
277
  break;
196
278
  }
@@ -216,7 +298,16 @@ function describeV1Column(column) {
216
298
  out.dbType = "LINE";
217
299
  out.shape = js === "object" ? { kind: "numberObject", fields: ["a", "b", "c"] } : { kind: "tuple", length: 3 };
218
300
  break;
301
+ // `halfvec` beside `vector`, because they differ in storage width and in nothing a validator
302
+ // can see: `mapFromDriverValue` on both hands back `[1, 2, 3]`. It had no arm and came back
303
+ // `unknown` on this path too, which the fuzzer found.
304
+ //
305
+ // `sparsevec` is deliberately not here. Its name says vector and its value is the string
306
+ // `{1:1.5,3:2}/3`, so typing it `number[]` for symmetry would reject every row the database
307
+ // returns. Its codec already answers `string` on its own, which is the same conclusion reached
308
+ // without this arm.
219
309
  case "vector":
310
+ case "halfvec":
220
311
  out.tsType = "number[]";
221
312
  out.dbType = "VECTOR";
222
313
  out.shape = { kind: "numberVector", length: declaredLength(column) };
@@ -233,7 +324,12 @@ function describeV1Column(column) {
233
324
  out.tsType = "number";
234
325
  out.dbType = "NUMERIC";
235
326
  out.integer = false;
236
- [out.min, out.max] = JS_SAFE_INTEGER_BOUNDS;
327
+ const range = decimalModeRange(column, entityKind, "number");
328
+ if (range) [out.min, out.max] = range;
329
+ if (codec === "numeric:number") {
330
+ out.allowsNaN = true;
331
+ out.allowsInfinity = !declaredDecimalRange(column);
332
+ }
237
333
  } else if (js === "string") {
238
334
  out.tsType = "string";
239
335
  out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
@@ -261,6 +357,28 @@ function declaredLength(column) {
261
357
  const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
262
358
  return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
263
359
  }
360
+ function declaredDecimalRange(column) {
361
+ const cfg = column?.config ?? {};
362
+ const precision = column?.precision ?? cfg.precision;
363
+ const scale = column?.scale ?? cfg.scale ?? 0;
364
+ if (typeof precision !== "number" || !Number.isInteger(precision) || precision < 1)
365
+ return void 0;
366
+ if (typeof scale !== "number" || !Number.isInteger(scale) || scale < 0) return void 0;
367
+ const nines = "9".repeat(precision);
368
+ const max = scale === 0 ? nines : scale < precision ? `${nines.slice(0, precision - scale)}.${nines.slice(precision - scale)}` : `0.${"0".repeat(scale - precision)}${nines}`;
369
+ return [`-${max}`, max];
370
+ }
371
+ var DECIMAL_NUMBER_MODE = /(?:Numeric|Decimal)Number$/;
372
+ var DECIMAL_BIGINT_MODE = /(?:Numeric|Decimal)BigInt$/;
373
+ var MYSQL_IMPLICIT_DECIMAL_RANGE = ["-9999999999", "9999999999"];
374
+ function decimalModeRange(column, kind, mode) {
375
+ const declared = declaredDecimalRange(column);
376
+ if (declared) return declared;
377
+ if (kind.startsWith("MySql") || kind.startsWith("SingleStore"))
378
+ return MYSQL_IMPLICIT_DECIMAL_RANGE;
379
+ if (kind.startsWith("SQLite")) return void 0;
380
+ return mode === "number" ? JS_SAFE_INTEGER_BOUNDS : void 0;
381
+ }
264
382
  var VIEW_CONFIG_FIELDS = {
265
383
  "drizzle:Columns": "selectedFields",
266
384
  "drizzle:Name": "name",
@@ -305,28 +423,51 @@ function isRelationsV2(val) {
305
423
  )
306
424
  );
307
425
  }
426
+ function qualifiedNameOfDrizzleTable(tbl) {
427
+ const name = getSymbolOf(tbl, "drizzle:Name");
428
+ if (typeof name !== "string" || !name) return void 0;
429
+ const schema = getSymbolOf(tbl, "drizzle:Schema");
430
+ return typeof schema === "string" && schema ? `${schema}.${name}` : name;
431
+ }
308
432
  function readRelationsV2(val, issues = []) {
309
433
  const out = [];
310
434
  for (const [tableKey, entry] of Object.entries(val)) {
311
- const from = getSymbolOf(entry.table, "drizzle:Name") ?? entry.name ?? tableKey;
435
+ const from = qualifiedNameOfDrizzleTable(entry.table) ?? entry.name ?? tableKey;
312
436
  for (const [fieldName, r] of Object.entries(entry.relations ?? {})) {
313
- const to = r?.targetTableName;
437
+ const to = qualifiedNameOfDrizzleTable(r?.targetTable) ?? r?.targetTableName;
314
438
  if (typeof to !== "string" || !to) {
315
439
  issues.push({
316
440
  code: "DRZL_ANL_REL_V2",
317
441
  level: "warn",
318
- message: `Relation "${fieldName}" on "${from}" names no target table and was skipped.`
442
+ message: `Relation "${fieldName}" on "${from}" names no target table and was skipped.`,
443
+ path: from
319
444
  });
320
445
  continue;
321
446
  }
322
- const via = getSymbolOf(r.throughTable, "drizzle:Name") ?? getSymbolOf(r.through?.sourceTable, "drizzle:Name") ?? void 0;
447
+ const via = qualifiedNameOfDrizzleTable(r.throughTable) ?? qualifiedNameOfDrizzleTable(r.through?.sourceTable) ?? void 0;
323
448
  if (via) out.push({ kind: "manyToMany", from, to, via });
324
449
  else out.push({ kind: r.relationType === "many" ? "many" : "one", from, to });
325
450
  }
326
451
  }
327
452
  return out;
328
453
  }
454
+ function unknownColumnHint(reason) {
455
+ if (reason === "custom") {
456
+ return "A customType has no runtime shape to read. Declare it with .$type<T>() and turn on typedColumns to give the validator the type.";
457
+ }
458
+ if (reason === "gel-temporal") {
459
+ 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.";
460
+ }
461
+ return "Open an issue naming the column type so it can be modelled, or declare it with .$type<T>() and turn on typedColumns.";
462
+ }
329
463
  var _SchemaAnalyzer = class _SchemaAnalyzer {
464
+ /**
465
+ * One path or several. The plural exists for drizzle-kit interop: kit's `schema` key names
466
+ * files in the plural (arrays, globs), and the commonest multi-file layout is a directory of
467
+ * one file per table with no barrel, so there is no single module to point at. Entries are
468
+ * concrete files, never globs; expansion is the caller's job, so this class's contract stays
469
+ * "load exactly these modules and read their exports as one schema".
470
+ */
330
471
  constructor(schemaPath) {
331
472
  this.schemaPath = schemaPath;
332
473
  }
@@ -376,6 +517,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
376
517
  code: "DRZL_ANL_EXTRACONFIG",
377
518
  level: "warn",
378
519
  message: `Could not evaluate the extra-config callback for table "${tableName}": ${e.message}`,
520
+ path: tableName,
379
521
  hint: "Indexes, composite keys, checks and table-level foreign keys will be missing for this table."
380
522
  });
381
523
  return [];
@@ -429,9 +571,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
429
571
  };
430
572
  const foreignColumnsObj = this.getSymbol(ref.foreignTable, "drizzle:Columns") ?? {};
431
573
  const toForeignTs = this.dbToTsNames(foreignColumnsObj);
574
+ const foreignSchema = this.getSymbol(ref.foreignTable, "drizzle:Schema");
432
575
  return {
433
576
  columns: (ref.columns ?? []).map((c) => toTs(c?.name)),
434
577
  foreignTable: this.getSymbol(ref.foreignTable, "drizzle:Name") ?? "unknown",
578
+ ...foreignSchema ? { foreignSchema } : {},
435
579
  foreignColumns: (ref.foreignColumns ?? []).map((c) => toForeignTs(c?.name)),
436
580
  onDelete: action(fk?.onDelete, fk?._onDelete),
437
581
  onUpdate: action(fk?.onUpdate, fk?._onUpdate),
@@ -480,7 +624,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
480
624
  * on each returned value, so the stand-in results must carry that method or the call throws.
481
625
  */
482
626
  readRelationsObject(val, exportName, issues) {
483
- const from = this.getSymbol(val.table, "drizzle:Name") ?? exportName;
627
+ const from = qualifiedNameOfDrizzleTable(val.table) ?? exportName;
484
628
  const make = (kind) => (table, cfg) => ({
485
629
  kind,
486
630
  referencedTable: table,
@@ -494,7 +638,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
494
638
  const built = val.config({ one: make("one"), many: make("many") });
495
639
  const out = [];
496
640
  for (const rel of Object.values(built ?? {})) {
497
- const to = this.getSymbol(rel?.referencedTable, "drizzle:Name");
641
+ const to = qualifiedNameOfDrizzleTable(rel?.referencedTable);
498
642
  if (to) out.push({ kind: rel.kind, from, to });
499
643
  }
500
644
  return out;
@@ -503,6 +647,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
503
647
  code: "DRZL_ANL_RELATIONS",
504
648
  level: "warn",
505
649
  message: `Could not read the relations declared in "${exportName}": ${e.message}`,
650
+ path: from,
506
651
  hint: "Relations for this table will be missing from the analysis."
507
652
  });
508
653
  return [];
@@ -523,10 +668,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
523
668
  if (fks.length < 2) continue;
524
669
  const fkCols = new Set(fks.flatMap((f) => f.columns));
525
670
  if (!t.columns.every((c) => fkCols.has(c.name))) continue;
526
- const targets = [...new Set(fks.map((f) => f.foreignTable))];
671
+ const targets = [...new Set(fks.map(qualifiedForeignTable))];
527
672
  if (targets.length !== 2) continue;
528
- out.push({ kind: "manyToMany", from: targets[0], to: targets[1], via: t.name });
529
- out.push({ kind: "manyToMany", from: targets[1], to: targets[0], via: t.name });
673
+ const via = qualifiedTableName(t);
674
+ out.push({ kind: "manyToMany", from: targets[0], to: targets[1], via });
675
+ out.push({ kind: "manyToMany", from: targets[1], to: targets[0], via });
530
676
  }
531
677
  return out;
532
678
  }
@@ -543,7 +689,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
543
689
  if (typeof length === "number" && Number.isFinite(length) && length > 0) {
544
690
  out.maxLength = length;
545
691
  }
546
- const range = _SchemaAnalyzer.INT_RANGES[ctor];
692
+ const unsignedRange = column?.config?.unsigned === true ? _SchemaAnalyzer.UNSIGNED_INT_RANGES[ctor] : void 0;
693
+ const range = unsignedRange ?? _SchemaAnalyzer.INT_RANGES[ctor];
547
694
  if (range) {
548
695
  [out.min, out.max] = range;
549
696
  out.integer = true;
@@ -553,6 +700,24 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
553
700
  if (inexact) [out.min, out.max] = inexact;
554
701
  out.integer = false;
555
702
  }
703
+ const nonFinite = _SchemaAnalyzer.NON_FINITE_BY_CLASS[ctor];
704
+ if (nonFinite) {
705
+ out.allowsNaN = nonFinite.nan;
706
+ out.allowsInfinity = nonFinite.infinity;
707
+ }
708
+ if (DECIMAL_NUMBER_MODE.test(ctor)) {
709
+ const range2 = decimalModeRange(column, ctor, "number");
710
+ if (range2) [out.min, out.max] = range2;
711
+ out.integer = false;
712
+ if (ctor === "PgNumericNumber") {
713
+ out.allowsNaN = true;
714
+ out.allowsInfinity = !declaredDecimalRange(column);
715
+ }
716
+ } else if (DECIMAL_BIGINT_MODE.test(ctor)) {
717
+ const range2 = decimalModeRange(column, ctor, "bigint");
718
+ if (range2) [out.min, out.max] = range2;
719
+ out.integer = true;
720
+ }
556
721
  if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
557
722
  return out;
558
723
  }
@@ -564,12 +729,36 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
564
729
  tsType: column?.config?.mode === "timestamp" ? "Date" : "number",
565
730
  dbType: "INTEGER"
566
731
  };
732
+ // Both timestamp modes of `integer()`, which are one class and one type. `timestamp` and
733
+ // `timestamp_ms` differ in the scale of the number on the wire, seconds against
734
+ // milliseconds, and `mapFromDriverValue` consumes that difference and hands back a `Date`
735
+ // either way; nothing downstream of the analyzer ever sees the integer. So an arm keyed on
736
+ // the class covers both, where the mode check that used to answer this fell through the
737
+ // switch to a default arm testing `config.mode === 'timestamp'` and named only the first.
738
+ // The second came back `unknown`, and every generator emitted a schema accepting anything.
739
+ //
740
+ // `DATE` rather than the `INTEGER` that mode check returned, so the two majors describe the
741
+ // column identically. `dbType` is read in exactly one place outside this file,
742
+ // `isIntegerColumn`, which the generators consult only for a `tsType` of `number`, so the
743
+ // relabel reaches no output. Measured rather than argued: emitting a `Date` column under
744
+ // both labels, nullable and not, through all five generators gives ten byte-identical pairs.
745
+ case "SQLiteTimestamp":
746
+ return { tsType: "Date", dbType: "DATE" };
567
747
  case "SQLiteText":
568
748
  return { tsType: "string", dbType: "TEXT" };
569
749
  case "SQLiteReal":
570
750
  return { tsType: "number", dbType: "REAL" };
751
+ // No 0.4x column is a `SQLiteBlob`: `sqlite-core` builds a `SQLiteBlobBuffer`, a
752
+ // `SQLiteBlobJson` or a `SQLiteBigInt`, one per mode, and exports no class of this name at
753
+ // all. The arm answers the hand-built column in sqlite-types.spec.ts and nothing drizzle
754
+ // produces, which is why a real `blob()` reached neither it nor anything else.
571
755
  case "SQLiteBlob":
572
756
  return { tsType: "Uint8Array", dbType: "BLOB" };
757
+ // The class a real `blob()` and `blob({ mode: 'buffer' })` both build. See `BUFFER_CLASSES`
758
+ // for the measurement; the answers here are v1's own for the same column, so this is the
759
+ // two majors agreeing rather than a new opinion.
760
+ case "SQLiteBlobBuffer":
761
+ return { tsType: "Buffer", dbType: "BYTEA" };
573
762
  // SQLite spells a mode as a distinct class rather than as config, so `text({mode:'json'})`
574
763
  // is a `SQLiteTextJson` and matched no arm at all: the column came back UNKNOWN, which is
575
764
  // wider than the `any` a json column at least used to get.
@@ -608,6 +797,34 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
608
797
  case "MySqlEnumColumn":
609
798
  case "SingleStoreEnumColumn":
610
799
  return { tsType: "string", dbType: "TEXT" };
800
+ // The pgvector family, found by the analyzer fuzzer: all three came back `unknown` on this
801
+ // path, so their validators accepted anything. The answers are drizzle's own mappers rather
802
+ // than the type names, and the three do not agree with each other:
803
+ //
804
+ // vector(3) SELECT gives [1,2,3] INSERT sends "[1,2,3]"
805
+ // halfvec(3) SELECT gives [1,2,3] INSERT sends "[1,2,3]"
806
+ // sparsevec(3) SELECT gives "{1:1.5,3:2}/3" INSERT sends "{1:1.5,3:2}/3"
807
+ //
808
+ // So the two dense ones are number arrays and the sparse one is a string. Typing `sparsevec`
809
+ // as a vector for symmetry would reject every row the database returns, which is the defect
810
+ // this family was filed under to begin with. The `shape` carries the dimension count where
811
+ // one is declared, as the codec path already did for `vector`.
812
+ case "PgVector":
813
+ case "PgHalfVector":
814
+ case "SingleStoreVector":
815
+ return { tsType: "number[]", dbType: "VECTOR" };
816
+ // `BIT` rather than `TEXT`, which a first version of this arm returned. v1's codec says `BIT`
817
+ // for the same column, and the cross-major diff said so: naming the class made ten of its
818
+ // twelve entries go stale and left `c_bit.dbType` and its nullable twin standing, which is
819
+ // that check distinguishing a fix from a half fix.
820
+ case "PgBinaryVector":
821
+ return { tsType: "string", dbType: "BIT" };
822
+ case "PgGeometry":
823
+ return { tsType: "[number, number]", dbType: "GEOMETRY" };
824
+ case "PgGeometryObject":
825
+ return { tsType: "{ x: number; y: number }", dbType: "GEOMETRY" };
826
+ case "PgSparseVector":
827
+ return { tsType: "string", dbType: "TEXT" };
611
828
  case "PgInteger":
612
829
  case "PgSmallInt":
613
830
  return { tsType: "number", dbType: "INTEGER" };
@@ -728,7 +945,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
728
945
  }
729
946
  if (/Numeric|Float|Double|Real/i.test(ctor))
730
947
  return { tsType: "number", dbType: "NUMERIC" };
731
- if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
948
+ if (/Serial/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
949
+ if (/Int|TinyInt|SmallInt|MediumInt/i.test(ctor))
732
950
  return { tsType: "number", dbType: "INTEGER" };
733
951
  if (/Bool|Boolean/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
734
952
  if (/TimestampString|DateTimeString|DateString/i.test(ctor))
@@ -752,7 +970,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
752
970
  }
753
971
  if (/Numeric|Float|Double|Real/i.test(ctor))
754
972
  return { tsType: "number", dbType: "NUMERIC" };
755
- if (/Int|Serial|TinyInt|SmallInt|MediumInt/i.test(ctor))
973
+ if (/Serial/i.test(ctor)) return { tsType: "number", dbType: "BIGINT" };
974
+ if (/Int|TinyInt|SmallInt|MediumInt/i.test(ctor))
756
975
  return { tsType: "number", dbType: "INTEGER" };
757
976
  if (/Bool|Boolean/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
758
977
  if (/TimestampString|DateTimeString|DateString/i.test(ctor))
@@ -768,7 +987,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
768
987
  if (/^Gel/i.test(ctor)) {
769
988
  if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
770
989
  if (/Int53|Integer|SmallInt/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
771
- if (/Real|DoublePrecision/i.test(ctor)) return { tsType: "number", dbType: "NUMERIC" };
990
+ if (/DoublePrecision/i.test(ctor)) return { tsType: "number", dbType: "DOUBLE" };
991
+ if (/Real/i.test(ctor)) return { tsType: "number", dbType: "REAL" };
772
992
  if (/Decimal/i.test(ctor)) return { tsType: "string", dbType: "NUMERIC" };
773
993
  if (/UUID/i.test(ctor)) return { tsType: "string", dbType: "UUID" };
774
994
  if (/Json/i.test(ctor)) return { tsType: "any", dbType: "JSON" };
@@ -777,7 +997,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
777
997
  if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
778
998
  if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
779
999
  if (/Timestamp|LocalDateString|LocalTime|DateDuration|RelDuration|Duration/i.test(ctor))
780
- return { tsType: "unknown", dbType: "UNKNOWN" };
1000
+ return { tsType: "unknown", dbType: "UNKNOWN", unnameable: "gel-temporal" };
781
1001
  }
782
1002
  return { tsType: "unknown", dbType: "UNKNOWN" };
783
1003
  }
@@ -793,7 +1013,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
793
1013
  const uniqueGroups = /* @__PURE__ */ new Map();
794
1014
  for (const [colName, outerCol] of Object.entries(columnsObj)) {
795
1015
  const { element: col, dimensions: arrayDims } = unwrapArrayColumn(outerCol);
796
- let { tsType, dbType } = this.mapColumnType(col);
1016
+ const mapped = this.mapColumnType(col);
1017
+ let { tsType, dbType } = mapped;
797
1018
  if (tsType === "unknown" && /At$/.test(colName)) {
798
1019
  tsType = "Date";
799
1020
  dbType = "INTEGER";
@@ -819,11 +1040,13 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
819
1040
  const v1 = describeV1Column(col);
820
1041
  const constraints = this.columnConstraints(col);
821
1042
  if (v1?.shape) delete constraints.maxLength;
822
- const sqlKind = String(outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? "");
1043
+ const sqlKind = String(
1044
+ outerCol?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")] ?? ""
1045
+ );
823
1046
  const sqlType = sqlKind.startsWith("MySql") && typeof col?.getSQLType === "function" ? String(col.getSQLType()).toLowerCase() : void 0;
824
1047
  const byteCap = sqlType ? MYSQL_TEXT_CAPS[sqlType] : void 0;
825
1048
  const ctorName = String(col?.constructor?.name ?? "");
826
- 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];
1049
+ 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];
827
1050
  if (fallbackShape?.kind === "byteString") delete constraints.maxLength;
828
1051
  const shape = (v1?.shape ?? fallbackShape)?.kind;
829
1052
  const finalTs = v1?.tsType ?? tsType;
@@ -834,13 +1057,26 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
834
1057
  code: "DRZL_ANL_UNKNOWN_COLUMN",
835
1058
  level: "warn",
836
1059
  message: `Column "${colName}" on table "${tsName}" has no known type${sqlType2 ? ` (SQL type ${sqlType2})` : ""}, so its validator will accept any value.`,
837
- 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."
1060
+ path: `${tsName}.${colName}`,
1061
+ hint: unknownColumnHint(shape === "custom" ? "custom" : mapped.unnameable)
838
1062
  });
839
1063
  }
1064
+ const dims = arrayDims || v1?.arrayDimensions || 0;
1065
+ const declaredSqlType = (() => {
1066
+ let raw;
1067
+ try {
1068
+ raw = typeof outerCol?.getSQLType === "function" ? outerCol.getSQLType() : void 0;
1069
+ } catch {
1070
+ return void 0;
1071
+ }
1072
+ if (typeof raw !== "string" || !raw) return void 0;
1073
+ return raw.endsWith("]") ? raw : raw + "[]".repeat(dims);
1074
+ })();
840
1075
  columns.push({
841
1076
  name: colName,
842
1077
  tsType,
843
1078
  dbType,
1079
+ ...declaredSqlType ? { sqlType: declaredSqlType } : {},
844
1080
  nullable,
845
1081
  hasDefault,
846
1082
  isGenerated,
@@ -917,6 +1153,7 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
917
1153
  if (!col) continue;
918
1154
  col.references = {
919
1155
  table: fk.foreignTable,
1156
+ ...fk.foreignSchema ? { schema: fk.foreignSchema } : {},
920
1157
  column: fk.foreignColumns[0],
921
1158
  onDelete: fk.onDelete,
922
1159
  onUpdate: fk.onUpdate
@@ -945,31 +1182,85 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
945
1182
  const fs = await import("fs/promises");
946
1183
  const path = await import("path");
947
1184
  const issues = [];
948
- const full = path.resolve(process.cwd(), this.schemaPath);
949
- try {
950
- await fs.access(full);
951
- } catch (_e) {
952
- issues.push({
953
- code: "DRZL_ANL_NOFILE",
954
- level: "error",
955
- message: `Schema file not found: ${this.schemaPath}`
956
- });
957
- return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
1185
+ const listed = Array.isArray(this.schemaPath);
1186
+ const inputs = listed ? this.schemaPath : [this.schemaPath];
1187
+ const fulls = inputs.map((p) => path.resolve(process.cwd(), p));
1188
+ let missing = false;
1189
+ for (let i = 0; i < fulls.length; i++) {
1190
+ try {
1191
+ await fs.access(fulls[i]);
1192
+ } catch (_e) {
1193
+ missing = true;
1194
+ issues.push({
1195
+ code: "DRZL_ANL_NOFILE",
1196
+ level: "error",
1197
+ message: `Schema file not found: ${inputs[i]}`
1198
+ });
1199
+ }
958
1200
  }
959
- let mod;
960
- try {
961
- const { default: jiti } = await import("jiti");
962
- const jit = jiti(import_meta.url, { moduleCache: false });
963
- mod = jit(full);
964
- } catch (e) {
965
- issues.push({
966
- code: "DRZL_ANL_IMPORT",
967
- level: "error",
968
- message: `Failed to import schema: ${String(e)}`
969
- });
1201
+ if (missing) {
970
1202
  return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
971
1203
  }
972
- const exportsObj = mod?.default && typeof mod.default === "object" ? mod.default : mod;
1204
+ const { default: jiti } = await import("jiti");
1205
+ const jit = jiti(import_meta.url, { moduleCache: false });
1206
+ const exportsObj = {};
1207
+ const exportOrigin = /* @__PURE__ */ new Map();
1208
+ const duplicateDisagreement = (a, b) => {
1209
+ if (Object.is(a, b)) return null;
1210
+ const aCols = this.getSymbol(a, "drizzle:Columns");
1211
+ const bCols = this.getSymbol(b, "drizzle:Columns");
1212
+ if (aCols && bCols) {
1213
+ const aName = this.getSymbol(a, "drizzle:Name");
1214
+ const bName = this.getSymbol(b, "drizzle:Name");
1215
+ const aSchema = this.getSymbol(a, "drizzle:Schema");
1216
+ const bSchema = this.getSymbol(b, "drizzle:Schema");
1217
+ if (aName !== bName || aSchema !== bSchema) {
1218
+ return `two different tables ("${String(aName)}" and "${String(bName)}")`;
1219
+ }
1220
+ if (Object.keys(aCols).join(",") !== Object.keys(bCols).join(",")) {
1221
+ return `two declarations of table "${String(aName)}" with different columns`;
1222
+ }
1223
+ return null;
1224
+ }
1225
+ if (!!aCols !== !!bCols) return "a table and a non-table";
1226
+ const aEnum = a?.enumValues;
1227
+ const bEnum = b?.enumValues;
1228
+ if (Array.isArray(aEnum) && Array.isArray(bEnum)) {
1229
+ return JSON.stringify(aEnum) === JSON.stringify(bEnum) ? null : "two enums with different values";
1230
+ }
1231
+ return null;
1232
+ };
1233
+ for (let i = 0; i < fulls.length; i++) {
1234
+ let mod;
1235
+ try {
1236
+ mod = jit(fulls[i]);
1237
+ } catch (e) {
1238
+ issues.push({
1239
+ code: "DRZL_ANL_IMPORT",
1240
+ level: "error",
1241
+ // The single-path message keeps its historical bytes; a list names the file, since
1242
+ // "the schema" no longer identifies one.
1243
+ message: listed ? `Failed to import schema ${inputs[i]}: ${String(e)}` : `Failed to import schema: ${String(e)}`
1244
+ });
1245
+ return { dialect: "unknown", tables: [], enums: [], relations: [], issues };
1246
+ }
1247
+ const one = mod?.default && typeof mod.default === "object" ? mod.default : mod;
1248
+ for (const [name, val] of Object.entries(one)) {
1249
+ if (!(name in exportsObj)) {
1250
+ exportsObj[name] = val;
1251
+ exportOrigin.set(name, inputs[i]);
1252
+ continue;
1253
+ }
1254
+ const disagreement = duplicateDisagreement(exportsObj[name], val);
1255
+ if (!disagreement) continue;
1256
+ issues.push({
1257
+ code: "DRZL_ANL_DUP_EXPORT",
1258
+ level: "warn",
1259
+ message: `Export "${name}" is ${disagreement}: defined by both ${exportOrigin.get(name)} and ${inputs[i]}; keeping the one in ${exportOrigin.get(name)}.`,
1260
+ path: name
1261
+ });
1262
+ }
1263
+ }
973
1264
  const tables = [];
974
1265
  const relations = [];
975
1266
  const enums = [];
@@ -989,9 +1280,11 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
989
1280
  }
990
1281
  }
991
1282
  if (opts.includeRelations) {
1283
+ const self = qualifiedTableName(table);
992
1284
  for (const fk of table.foreignKeys ?? []) {
993
- relations.push({ kind: "one", from: table.name, to: fk.foreignTable });
994
- relations.push({ kind: "many", from: fk.foreignTable, to: table.name });
1285
+ const target = qualifiedForeignTable(fk);
1286
+ relations.push({ kind: "one", from: self, to: target });
1287
+ relations.push({ kind: "many", from: target, to: self });
995
1288
  }
996
1289
  }
997
1290
  } else if (this.isRelationsObject(val)) {
@@ -1019,7 +1312,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1019
1312
  issues.push({
1020
1313
  code: "DRZL_ANL_TABLE",
1021
1314
  level: "warn",
1022
- message: `Failed to analyze export ${name}: ${String(e)}`
1315
+ message: `Failed to analyze export ${name}: ${String(e)}`,
1316
+ path: name
1023
1317
  });
1024
1318
  }
1025
1319
  }
@@ -1064,11 +1358,21 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1064
1358
  relations.push(...this.inferManyToMany(tables));
1065
1359
  }
1066
1360
  if (opts.includeRelations && opts.includeHeuristicRelations) {
1067
- const tableNames = new Set(tables.map((t) => t.name));
1068
- const findTarget = (base) => {
1069
- if (tableNames.has(base)) return base;
1070
- if (tableNames.has(base + "s")) return base + "s";
1071
- if (tableNames.has(base + "es")) return base + "es";
1361
+ const byBareName = /* @__PURE__ */ new Map();
1362
+ for (const t of tables) {
1363
+ const list = byBareName.get(t.name);
1364
+ if (list) list.push(t);
1365
+ else byBareName.set(t.name, [t]);
1366
+ }
1367
+ const findTarget = (base, from) => {
1368
+ for (const candidate of [base, base + "s", base + "es"]) {
1369
+ const hits = byBareName.get(candidate);
1370
+ if (!hits?.length) continue;
1371
+ const sameSchema = hits.filter((t) => t.schema === from.schema);
1372
+ if (sameSchema.length === 1) return qualifiedTableName(sameSchema[0]);
1373
+ if (hits.length === 1) return qualifiedTableName(hits[0]);
1374
+ return void 0;
1375
+ }
1072
1376
  return void 0;
1073
1377
  };
1074
1378
  for (const t of tables) {
@@ -1076,8 +1380,8 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1076
1380
  if (c.references) continue;
1077
1381
  if (c.name.endsWith("Id")) {
1078
1382
  const base = c.name.slice(0, -2);
1079
- const target = findTarget(base);
1080
- if (target) relations.push({ kind: "one", from: t.name, to: target });
1383
+ const target = findTarget(base, t);
1384
+ if (target) relations.push({ kind: "one", from: qualifiedTableName(t), to: target });
1081
1385
  }
1082
1386
  }
1083
1387
  }
@@ -1109,6 +1413,12 @@ var _SchemaAnalyzer = class _SchemaAnalyzer {
1109
1413
  _SchemaAnalyzer.INT_RANGES = {
1110
1414
  // 8 bit
1111
1415
  MySqlTinyInt: ["-128", "127"],
1416
+ // Absent until the unsigned fix swept the family: v1 states `number int8` for the same
1417
+ // column, so the majors disagreed about every SingleStore tinyint. That is the shape the
1418
+ // cross-major diff in `scripts/verify-packed.sh` exists to catch, and its fixture carries no
1419
+ // SingleStore table, so unsigned-int-ranges.spec.ts holds these two classes across both
1420
+ // majors instead. The width is the type's, the one `MySqlTinyInt` beside it already carries.
1421
+ SingleStoreTinyInt: ["-128", "127"],
1112
1422
  SQLiteInteger: ["-9223372036854775808", "9223372036854775807"],
1113
1423
  // 16 bit
1114
1424
  PgSmallInt: ["-32768", "32767"],
@@ -1121,6 +1431,8 @@ _SchemaAnalyzer.INT_RANGES = {
1121
1431
  SingleStoreSmallInt: ["-32768", "32767"],
1122
1432
  // 24 bit
1123
1433
  MySqlMediumInt: ["-8388608", "8388607"],
1434
+ // As SingleStoreTinyInt above: v1 states `number int24` and this table said nothing.
1435
+ SingleStoreMediumInt: ["-8388608", "8388607"],
1124
1436
  // 32 bit
1125
1437
  PgInteger: ["-2147483648", "2147483647"],
1126
1438
  PgSerial: ["-2147483648", "2147483647"],
@@ -1135,7 +1447,61 @@ _SchemaAnalyzer.INT_RANGES = {
1135
1447
  PgBigInt64: ["-9223372036854775808", "9223372036854775807"],
1136
1448
  PgBigSerial64: ["-9223372036854775808", "9223372036854775807"],
1137
1449
  MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
1138
- SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
1450
+ SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"],
1451
+ // MySQL and SingleStore `serial`, which is `bigint unsigned auto_increment`: unsigned by the
1452
+ // builder's own definition, with no `config.unsigned` stating it, so the flag-keyed table
1453
+ // below cannot answer and the range lives here. The mode is number, so the safe-integer
1454
+ // ceiling rather than the column's, exactly as the 53 bit block above. The Postgres serials
1455
+ // stay signed on purpose: a Postgres serial is a plain integer defaulting from a sequence,
1456
+ // and the negative backfill note above applies to them and not to these. Before this entry
1457
+ // the class was in no table at all, so an auto-increment column accepted -1 and the majors
1458
+ // disagreed: v1 states `number uint53` for the same column and was already bounded.
1459
+ MySqlSerial: ["0", "9007199254740991"],
1460
+ SingleStoreSerial: ["0", "9007199254740991"]
1461
+ };
1462
+ /**
1463
+ * The same widths with `{ unsigned: true }` set, which is the half the table above cannot see.
1464
+ *
1465
+ * On 0.4x the flag moves no class name: `int('x', { unsigned: true })` still builds a
1466
+ * `MySqlInt`, and only `config.unsigned` and the ` unsigned` suffix on `getSQLType()` record
1467
+ * the difference, measured off real 0.45.2 columns. So the table above answered every unsigned
1468
+ * width with its signed range, and the emitted select schema refused every stored value in the
1469
+ * upper half of the column: an `int unsigned` holding 4294967295 failed validation on a row the
1470
+ * database returned, and the same one width up meant `bigint unsigned` refused
1471
+ * 18446744073709551615n.
1472
+ *
1473
+ * The ceilings are the type's, verified against a live MySQL 8.4.11: 255, 65535, 16777215 and
1474
+ * 4294967295 store and return, -1 and each ceiling plus one are refused with
1475
+ * ER_WARN_DATA_OUT_OF_RANGE. The bigint pair keeps the two modes apart for the reason the
1476
+ * signed pair above does: number mode tops out at the safe-integer bound the wire imposes,
1477
+ * bigint mode at the column's own 2^64-1, which a bigint can spell. SingleStore is MySQL wire
1478
+ * compatible, ships the same builders with the same `config.unsigned`, and v1 states the same
1479
+ * `uintN` semantics for it, measured off real rc.4 columns; the entries keep the majors in
1480
+ * agreement, which is what the cross-major diff in `scripts/verify-packed.sh` holds together.
1481
+ *
1482
+ * Keyed by class exactly like `INT_RANGES`, and consulted only when `config.unsigned` is
1483
+ * `true`, so no Postgres or SQLite column can ever reach it: neither dialect has an unsigned
1484
+ * spelling, neither builder accepts the flag, and no class of theirs is named here.
1485
+ */
1486
+ _SchemaAnalyzer.UNSIGNED_INT_RANGES = {
1487
+ // 8 bit
1488
+ MySqlTinyInt: ["0", "255"],
1489
+ SingleStoreTinyInt: ["0", "255"],
1490
+ // 16 bit
1491
+ MySqlSmallInt: ["0", "65535"],
1492
+ SingleStoreSmallInt: ["0", "65535"],
1493
+ // 24 bit
1494
+ MySqlMediumInt: ["0", "16777215"],
1495
+ SingleStoreMediumInt: ["0", "16777215"],
1496
+ // 32 bit
1497
+ MySqlInt: ["0", "4294967295"],
1498
+ SingleStoreInt: ["0", "4294967295"],
1499
+ // 53 bit, the JS safe-integer ceiling rather than the column's
1500
+ MySqlBigInt53: ["0", "9007199254740991"],
1501
+ SingleStoreBigInt53: ["0", "9007199254740991"],
1502
+ // 64 bit, representable because the value is a bigint
1503
+ MySqlBigInt64: ["0", "18446744073709551615"],
1504
+ SingleStoreBigInt64: ["0", "18446744073709551615"]
1139
1505
  };
1140
1506
  /**
1141
1507
  * The numeric column classes that are not exact, and the magnitude each one can really hold.
@@ -1186,10 +1552,85 @@ _SchemaAnalyzer.INEXACT_RANGES = {
1186
1552
  SQLiteReal: null,
1187
1553
  SingleStoreDouble: null,
1188
1554
  SingleStoreReal: null,
1189
- // `numeric({ mode: 'number' })`, which v1 reaches through the bare-number arm of
1190
- // `describeV1Column`. This one is about what a JS number can carry rather than about the
1191
- // column, which Postgres caps far lower: it refuses 2147483648 into a `numeric(10,2)`.
1192
- PgNumericNumber: JS_SAFE_INTEGER_BOUNDS
1555
+ // Gel, whose `real` is a `std::float32` and whose `doublePrecision` is a `std::float64`. Both
1556
+ // used to be answered by a `/Real|DoublePrecision/i` arm that said NUMERIC and stated nothing
1557
+ // else at all, so a `real` column accepted 1e300 and the server refused it.
1558
+ //
1559
+ // Measured on a live Gel 7.1 (`geldata/gel:7`, sys::get_version_as_str() -> 7.1+08db576)
1560
+ // through the `gel` client, casting each literal so the server parses it, and again through a
1561
+ // stored property on a real object type. The float32 edge is Postgres's exactly, to the double:
1562
+ //
1563
+ // 3.4028234663852886e38 accepted, returned unchanged
1564
+ // 3.4028235677973366e38 accepted, and stored as 3.4028234663852886e38
1565
+ // 3.402823567797337e38 refused, "is out of range for type std::float32"
1566
+ // 1e300 refused, the same way
1567
+ //
1568
+ // The same value accepted, the same next double up refused, and the same rounding down of the
1569
+ // midpoint, so it takes the constant already here rather than a second name for one number.
1570
+ // float64 took 1e300 and Number.MAX_VALUE faithfully, for the reason no 8 byte float has a
1571
+ // truthful finite bound.
1572
+ GelReal: PG_FLOAT4_RANGE,
1573
+ GelDoublePrecision: null
1574
+ };
1575
+ // `numeric`/`decimal` in either of its two numeric modes is deliberately not in the table above.
1576
+ // Its bound is not a fixed magnitude per class but the precision each column declares for itself,
1577
+ // which no table keyed on a class name can hold; see `declaredDecimalRange`.
1578
+ /**
1579
+ * The number columns whose server has an answer about a non-finite double, and what it is.
1580
+ *
1581
+ * Three states rather than two, and the third is the reason this table has a `false` half at all.
1582
+ * A column present here with `true` stores the value and hands it back, so a schema refusing it
1583
+ * refuses rows the column returns. A column present with `false` is one the server was asked
1584
+ * about and refused, so a schema accepting it promises what the server will not take. A column
1585
+ * *absent* is one nobody has measured, and the generators leave whatever their library does alone
1586
+ * rather than guessing; `nonFiniteAccepted` and `nonFiniteRefused` in `@drzl/validation-core` are
1587
+ * the two readings of that.
1588
+ *
1589
+ * The class-name half of what `describeV1Column` reads off the codec, and the two must agree: a
1590
+ * fact stated on one path and not the other is a schema that changes when the user upgrades
1591
+ * drizzle, which the cross-major diff in `verify-packed.sh` fails on. Every class name here is the
1592
+ * same on both majors, read off real columns on 0.45.2 and on 1.0.0-rc.4, so this table also
1593
+ * answers for a v1 column and the two answers are identical rather than merely compatible.
1594
+ * non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
1595
+ *
1596
+ * Postgres and Gel store all three. Gel joined on a measurement of its own rather than on being
1597
+ * Postgres-backed: a live Gel 7.1 stored `nan`, `inf` and `-inf` in both `std::float32` and
1598
+ * `std::float64` and handed all three back, through a cast and again through a stored property.
1599
+ * Without them every row of such a column failed validation.
1600
+ *
1601
+ * MySQL and SingleStore refuse all three, and that used to be left unstated on the reasoning that
1602
+ * a column stating nothing costs nothing. It cost two libraries: `v.number()` and ArkType's
1603
+ * `number` take both infinities where `z.number()` and `Type.Number()` refuse them, so an
1604
+ * unbounded `double` or `real` accepted a value the server answers `ER_WARN_DATA_OUT_OF_RANGE`
1605
+ * for. Measured on MySQL 8.4.11 in `STRICT_TRANS_TABLES`, on the binary prepared path, which is
1606
+ * the one that puts the real IEEE double on the wire: `float`, `double` and `real` refuse
1607
+ * `Infinity`, `-Infinity` and `NaN` alike, while `double` and `real` store 1e300 and
1608
+ * 3.4028235e38 unchanged. SingleStore is MySQL wire-compatible and unmeasured, and takes MySQL's
1609
+ * answer here exactly as it already takes MySQL's float32 bound in `INEXACT_RANGES`.
1610
+ *
1611
+ * No SQLite class belongs here in either direction. A real SQLite 3.53.4 stores both infinities in
1612
+ * a `real` and hands them back, and silently turns `NaN` into NULL, so it is neither the Postgres
1613
+ * answer nor the MySQL one; it is filed on its own and a column needs both halves of it or none.
1614
+ *
1615
+ * The decimal families are absent too. `PgNumeric` is a string whose pattern already accepts `NaN`
1616
+ * and `Infinity`. `PgNumericNumber` is a per-column question this table cannot ask: it takes `NaN`
1617
+ * at any width and an infinity only where no precision is declared, and `columnConstraints`
1618
+ * answers it beside the bound that decides it. MySQL's `decimal` is absent because the two client
1619
+ * paths disagree: on the binary prepared path MySQL 8.4.11 silently stored `0.00` for all three,
1620
+ * where the text path answers `Incorrect decimal value`, and "refuses" is only half true of a
1621
+ * column that accepted the row.
1622
+ */
1623
+ _SchemaAnalyzer.NON_FINITE_BY_CLASS = {
1624
+ PgReal: { nan: true, infinity: true },
1625
+ PgDoublePrecision: { nan: true, infinity: true },
1626
+ GelReal: { nan: true, infinity: true },
1627
+ GelDoublePrecision: { nan: true, infinity: true },
1628
+ MySqlFloat: { nan: false, infinity: false },
1629
+ MySqlDouble: { nan: false, infinity: false },
1630
+ MySqlReal: { nan: false, infinity: false },
1631
+ SingleStoreFloat: { nan: false, infinity: false },
1632
+ SingleStoreDouble: { nan: false, infinity: false },
1633
+ SingleStoreReal: { nan: false, infinity: false }
1193
1634
  };
1194
1635
  var SchemaAnalyzer = _SchemaAnalyzer;
1195
1636
  var index_default = SchemaAnalyzer;
@@ -1200,5 +1641,7 @@ var index_default = SchemaAnalyzer;
1200
1641
  isDrizzleView,
1201
1642
  isReadOnlyRelation,
1202
1643
  isRelationsV2,
1644
+ qualifiedForeignTable,
1645
+ qualifiedTableName,
1203
1646
  readRelationsV2
1204
1647
  });