@drzl/analyzer 1.5.2 → 1.7.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 +251 -22
- package/dist/index.d.cts +109 -2
- package/dist/index.d.ts +109 -2
- package/dist/index.js +249 -21
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -31,11 +31,147 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
SchemaAnalyzer: () => SchemaAnalyzer,
|
|
34
|
-
default: () => index_default
|
|
34
|
+
default: () => index_default,
|
|
35
|
+
describeV1Column: () => describeV1Column
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(index_exports);
|
|
37
38
|
var import_meta = {};
|
|
38
|
-
|
|
39
|
+
function renderSqlLiteral(v) {
|
|
40
|
+
if (v === null || v === void 0) return "NULL";
|
|
41
|
+
if (typeof v === "number" || typeof v === "bigint") return String(v);
|
|
42
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
43
|
+
if (v instanceof Date) return `'${v.toISOString()}'`;
|
|
44
|
+
if (Array.isArray(v)) return `(${v.map(renderSqlLiteral).join(", ")})`;
|
|
45
|
+
return `'${String(v).replace(/'/g, "''")}'`;
|
|
46
|
+
}
|
|
47
|
+
var V1_FLOAT_BOUNDS = {
|
|
48
|
+
float: ["-8388608", "8388607"],
|
|
49
|
+
// real / float4, 2^23
|
|
50
|
+
double: ["-140737488355328", "140737488355327"]
|
|
51
|
+
// double precision / float8, 2^47
|
|
52
|
+
};
|
|
53
|
+
function describeV1Column(column) {
|
|
54
|
+
const codec = column?.codec;
|
|
55
|
+
const dataType = column?.dataType;
|
|
56
|
+
if (typeof codec !== "string" || typeof dataType !== "string") return null;
|
|
57
|
+
const [js, semantic = ""] = dataType.split(" ");
|
|
58
|
+
const out = {};
|
|
59
|
+
switch (semantic) {
|
|
60
|
+
case "int16":
|
|
61
|
+
case "int32":
|
|
62
|
+
case "int53":
|
|
63
|
+
case "int64": {
|
|
64
|
+
const range = {
|
|
65
|
+
int16: ["-32768", "32767"],
|
|
66
|
+
int32: ["-2147483648", "2147483647"],
|
|
67
|
+
int53: ["-9007199254740991", "9007199254740991"],
|
|
68
|
+
int64: ["-9223372036854775808", "9223372036854775807"]
|
|
69
|
+
}[semantic];
|
|
70
|
+
[out.min, out.max] = range;
|
|
71
|
+
out.integer = true;
|
|
72
|
+
out.tsType = js === "bigint" ? "bigint" : "number";
|
|
73
|
+
out.dbType = semantic === "int16" ? "SMALLINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "float":
|
|
77
|
+
case "double": {
|
|
78
|
+
[out.min, out.max] = V1_FLOAT_BOUNDS[semantic];
|
|
79
|
+
out.integer = false;
|
|
80
|
+
out.tsType = "number";
|
|
81
|
+
out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "uuid":
|
|
85
|
+
out.tsType = "string";
|
|
86
|
+
out.dbType = "UUID";
|
|
87
|
+
out.format = "uuid";
|
|
88
|
+
break;
|
|
89
|
+
case "numeric":
|
|
90
|
+
out.tsType = "string";
|
|
91
|
+
out.dbType = "NUMERIC";
|
|
92
|
+
break;
|
|
93
|
+
case "json":
|
|
94
|
+
out.tsType = "any";
|
|
95
|
+
out.dbType = codec === "jsonb" ? "JSONB" : "JSON";
|
|
96
|
+
out.shape = { kind: "json" };
|
|
97
|
+
break;
|
|
98
|
+
case "buffer":
|
|
99
|
+
out.tsType = "Buffer";
|
|
100
|
+
out.dbType = "BYTEA";
|
|
101
|
+
out.shape = { kind: "buffer" };
|
|
102
|
+
break;
|
|
103
|
+
case "date":
|
|
104
|
+
out.tsType = js === "string" ? "string" : "Date";
|
|
105
|
+
out.dbType = codec.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
|
|
106
|
+
break;
|
|
107
|
+
case "timestamp":
|
|
108
|
+
out.tsType = js === "string" ? "string" : "Date";
|
|
109
|
+
out.dbType = "TIMESTAMP";
|
|
110
|
+
break;
|
|
111
|
+
case "time":
|
|
112
|
+
out.tsType = "string";
|
|
113
|
+
out.dbType = "TIME";
|
|
114
|
+
break;
|
|
115
|
+
case "interval":
|
|
116
|
+
out.tsType = "string";
|
|
117
|
+
out.dbType = "INTERVAL";
|
|
118
|
+
break;
|
|
119
|
+
case "inet":
|
|
120
|
+
case "cidr":
|
|
121
|
+
case "macaddr":
|
|
122
|
+
out.tsType = "string";
|
|
123
|
+
out.dbType = semantic.toUpperCase();
|
|
124
|
+
break;
|
|
125
|
+
case "binary":
|
|
126
|
+
out.tsType = "string";
|
|
127
|
+
out.dbType = "BIT";
|
|
128
|
+
out.shape = { kind: "bitstring", length: declaredLength(column) };
|
|
129
|
+
break;
|
|
130
|
+
case "point":
|
|
131
|
+
case "geometry":
|
|
132
|
+
out.tsType = "[number, number]";
|
|
133
|
+
out.dbType = semantic.toUpperCase();
|
|
134
|
+
out.shape = { kind: "tuple", length: 2 };
|
|
135
|
+
break;
|
|
136
|
+
case "line":
|
|
137
|
+
out.tsType = "[number, number, number]";
|
|
138
|
+
out.dbType = "LINE";
|
|
139
|
+
out.shape = { kind: "tuple", length: 3 };
|
|
140
|
+
break;
|
|
141
|
+
case "vector":
|
|
142
|
+
out.tsType = "number[]";
|
|
143
|
+
out.dbType = "VECTOR";
|
|
144
|
+
out.shape = { kind: "numberVector", length: declaredLength(column) };
|
|
145
|
+
break;
|
|
146
|
+
case "enum":
|
|
147
|
+
out.tsType = "string";
|
|
148
|
+
out.dbType = "TEXT";
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
if (js === "boolean") {
|
|
152
|
+
out.tsType = "boolean";
|
|
153
|
+
out.dbType = "BOOLEAN";
|
|
154
|
+
} else if (js === "number") {
|
|
155
|
+
out.tsType = "number";
|
|
156
|
+
out.dbType = "NUMERIC";
|
|
157
|
+
out.integer = false;
|
|
158
|
+
[out.min, out.max] = ["-9007199254740991", "9007199254740991"];
|
|
159
|
+
} else if (js === "string") {
|
|
160
|
+
out.tsType = "string";
|
|
161
|
+
out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
|
|
162
|
+
} else {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const dims = column?.dimensions;
|
|
167
|
+
if (typeof dims === "number" && dims >= 1) out.arrayDimensions = dims;
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
function declaredLength(column) {
|
|
171
|
+
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
172
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
173
|
+
}
|
|
174
|
+
var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
39
175
|
constructor(schemaPath) {
|
|
40
176
|
this.schemaPath = schemaPath;
|
|
41
177
|
}
|
|
@@ -172,6 +308,12 @@ var SchemaAnalyzer = class {
|
|
|
172
308
|
if (Array.isArray(c?.value)) return c.value.join("");
|
|
173
309
|
if (typeof c?.name === "string") return toTs(c.name);
|
|
174
310
|
if (c?.queryChunks) return this.renderSql(c, toTs);
|
|
311
|
+
if (c === null || ["number", "string", "boolean", "bigint"].includes(typeof c)) {
|
|
312
|
+
return renderSqlLiteral(c);
|
|
313
|
+
}
|
|
314
|
+
if (typeof c === "object" && "value" in c) {
|
|
315
|
+
return renderSqlLiteral(c.value);
|
|
316
|
+
}
|
|
175
317
|
return "?";
|
|
176
318
|
}).join("").trim();
|
|
177
319
|
}
|
|
@@ -243,6 +385,27 @@ var SchemaAnalyzer = class {
|
|
|
243
385
|
}
|
|
244
386
|
return out;
|
|
245
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
390
|
+
*
|
|
391
|
+
* Everything here is read off Drizzle's own column instance, so it states what the schema
|
|
392
|
+
* states. Nothing is inferred from a name or guessed from a type.
|
|
393
|
+
*/
|
|
394
|
+
columnConstraints(column) {
|
|
395
|
+
const ctor = column?.constructor?.name ?? "";
|
|
396
|
+
const out = {};
|
|
397
|
+
const length = column?.length ?? column?.config?.length;
|
|
398
|
+
if (typeof length === "number" && Number.isFinite(length) && length > 0) {
|
|
399
|
+
out.maxLength = length;
|
|
400
|
+
}
|
|
401
|
+
const range = _SchemaAnalyzer.INT_RANGES[ctor];
|
|
402
|
+
if (range) {
|
|
403
|
+
[out.min, out.max] = range;
|
|
404
|
+
out.integer = true;
|
|
405
|
+
}
|
|
406
|
+
if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
|
|
407
|
+
return out;
|
|
408
|
+
}
|
|
246
409
|
mapColumnType(column) {
|
|
247
410
|
const ctor = column?.constructor?.name ?? "";
|
|
248
411
|
switch (ctor) {
|
|
@@ -264,6 +427,14 @@ var SchemaAnalyzer = class {
|
|
|
264
427
|
case "PgInteger":
|
|
265
428
|
case "PgSmallInt":
|
|
266
429
|
return { tsType: "number", dbType: "INTEGER" };
|
|
430
|
+
// Drizzle names these by their mode: `PgBigInt53` for `{ mode: 'number' }` and
|
|
431
|
+
// `PgBigInt64` for `{ mode: 'bigint' }`. `PgBigInt` matched neither, so both fell through
|
|
432
|
+
// to the regex arm and came back as `bigint`, which is wrong for the number mode: the
|
|
433
|
+
// value really is a JS number there, and a schema demanding a bigint rejects every row.
|
|
434
|
+
case "PgBigInt53":
|
|
435
|
+
return { tsType: "number", dbType: "BIGINT" };
|
|
436
|
+
case "PgBigInt64":
|
|
437
|
+
return { tsType: "bigint", dbType: "BIGINT" };
|
|
267
438
|
case "PgBigInt":
|
|
268
439
|
return {
|
|
269
440
|
tsType: column?.config?.mode === "number" ? "number" : "bigint",
|
|
@@ -277,6 +448,9 @@ var SchemaAnalyzer = class {
|
|
|
277
448
|
case "PgVarchar":
|
|
278
449
|
case "PgChar":
|
|
279
450
|
return { tsType: "string", dbType: "TEXT" };
|
|
451
|
+
// Drizzle spells it `PgUUID`. `PgUuid` matched nothing, so every uuid column fell through
|
|
452
|
+
// to the regex arm below and came back as plain TEXT, losing the format.
|
|
453
|
+
case "PgUUID":
|
|
280
454
|
case "PgUuid":
|
|
281
455
|
return { tsType: "string", dbType: "UUID" };
|
|
282
456
|
case "PgBoolean":
|
|
@@ -301,7 +475,8 @@ var SchemaAnalyzer = class {
|
|
|
301
475
|
if (/Text|Varchar|Char|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
302
476
|
if (/Inet|Cidr|Macaddr8?|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
303
477
|
if (/Point|Line/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
304
|
-
if (/TimestampString|DateString/i.test(ctor))
|
|
478
|
+
if (/TimestampString|DateString/i.test(ctor))
|
|
479
|
+
return { tsType: "string", dbType: "TIMESTAMP" };
|
|
305
480
|
if (/Timestamptz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
|
|
306
481
|
if (/Timestamp/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMP" };
|
|
307
482
|
if (/Date/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMP" };
|
|
@@ -310,8 +485,10 @@ var SchemaAnalyzer = class {
|
|
|
310
485
|
if (/\bInt(eger)?\b|Serial/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
|
|
311
486
|
if (/BigInt/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
|
|
312
487
|
if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
313
|
-
if (/Jsonb?/i.test(ctor))
|
|
314
|
-
|
|
488
|
+
if (/Jsonb?/i.test(ctor))
|
|
489
|
+
return { tsType: "any", dbType: /Jsonb/i.test(ctor) ? "JSONB" : "JSON" };
|
|
490
|
+
if (/Numeric|Float|Double|Real/i.test(ctor))
|
|
491
|
+
return { tsType: "number", dbType: "NUMERIC" };
|
|
315
492
|
}
|
|
316
493
|
if (/^MySql/i.test(ctor)) {
|
|
317
494
|
if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
|
|
@@ -363,7 +540,8 @@ var SchemaAnalyzer = class {
|
|
|
363
540
|
if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
|
|
364
541
|
if (/Timestamp/i.test(ctor)) return { tsType: "string", dbType: "TIMESTAMP" };
|
|
365
542
|
if (/LocalDateString|LocalTime/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
366
|
-
if (/DateDuration|RelDuration|Duration/i.test(ctor))
|
|
543
|
+
if (/DateDuration|RelDuration|Duration/i.test(ctor))
|
|
544
|
+
return { tsType: "string", dbType: "TEXT" };
|
|
367
545
|
}
|
|
368
546
|
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
369
547
|
}
|
|
@@ -398,6 +576,9 @@ var SchemaAnalyzer = class {
|
|
|
398
576
|
arr.push(colName);
|
|
399
577
|
uniqueGroups.set(uName, arr);
|
|
400
578
|
}
|
|
579
|
+
const v1 = describeV1Column(col);
|
|
580
|
+
const constraints = this.columnConstraints(col);
|
|
581
|
+
if (v1?.shape) delete constraints.maxLength;
|
|
401
582
|
columns.push({
|
|
402
583
|
name: colName,
|
|
403
584
|
tsType,
|
|
@@ -407,7 +588,9 @@ var SchemaAnalyzer = class {
|
|
|
407
588
|
isGenerated,
|
|
408
589
|
defaultExpression: void 0,
|
|
409
590
|
references,
|
|
410
|
-
enumValues: Array.isArray(ev) ? ev : void 0
|
|
591
|
+
enumValues: Array.isArray(ev) ? ev : void 0,
|
|
592
|
+
...constraints,
|
|
593
|
+
...v1 ?? {}
|
|
411
594
|
});
|
|
412
595
|
}
|
|
413
596
|
const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
|
|
@@ -568,27 +751,32 @@ var SchemaAnalyzer = class {
|
|
|
568
751
|
enums.push(candidate);
|
|
569
752
|
}
|
|
570
753
|
let dialect = "unknown";
|
|
571
|
-
const
|
|
754
|
+
const marks = /* @__PURE__ */ new Set();
|
|
572
755
|
for (const [_, val] of Object.entries(exportsObj)) {
|
|
573
756
|
const cols = val?.[/* @__PURE__ */ Symbol.for("drizzle:Columns")];
|
|
574
|
-
if (cols)
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
757
|
+
if (!cols) continue;
|
|
758
|
+
for (const c of Object.values(cols)) {
|
|
759
|
+
const kind = c?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")];
|
|
760
|
+
if (typeof kind === "string") marks.add(kind);
|
|
761
|
+
const n = c?.constructor?.name;
|
|
762
|
+
if (n) marks.add(n);
|
|
579
763
|
}
|
|
580
764
|
}
|
|
581
|
-
const names = Array.from(
|
|
765
|
+
const names = Array.from(marks).join(",");
|
|
582
766
|
if (/SQLite/i.test(names)) dialect = "sqlite";
|
|
583
|
-
else if (/Pg|Postgres/i.test(names)) dialect = "postgres";
|
|
584
|
-
else if (/MySql|Mysql/i.test(names)) dialect = "mysql";
|
|
585
767
|
else if (/SingleStore/i.test(names)) dialect = "singlestore";
|
|
768
|
+
else if (/Cockroach/i.test(names)) dialect = "cockroach";
|
|
769
|
+
else if (/MsSql/i.test(names)) dialect = "mssql";
|
|
770
|
+
else if (/MySql|Mysql/i.test(names)) dialect = "mysql";
|
|
771
|
+
else if (/Pg|Postgres/i.test(names)) dialect = "postgres";
|
|
586
772
|
else if (/Gel/i.test(names)) dialect = "gel";
|
|
587
|
-
if (dialect === "unknown") {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
773
|
+
if (dialect === "unknown" && tables.length) {
|
|
774
|
+
issues.push({
|
|
775
|
+
code: "DRZL_ANL_DIALECT",
|
|
776
|
+
level: "warn",
|
|
777
|
+
message: `Could not identify the Drizzle dialect for this schema${names ? `; saw column kinds: ${Array.from(marks).slice(0, 6).join(", ")}` : ""}.`,
|
|
778
|
+
hint: "Column types will fall back to their coarse defaults. If this is a dialect DRZL does not know yet, please open an issue."
|
|
779
|
+
});
|
|
592
780
|
}
|
|
593
781
|
if (opts.includeRelations) {
|
|
594
782
|
relations.push(...this.inferManyToMany(tables));
|
|
@@ -628,8 +816,49 @@ var SchemaAnalyzer = class {
|
|
|
628
816
|
};
|
|
629
817
|
}
|
|
630
818
|
};
|
|
819
|
+
/**
|
|
820
|
+
* The range an integer column can actually hold, keyed off the Drizzle column class.
|
|
821
|
+
*
|
|
822
|
+
* Matches what `drizzle-orm/zod` emits, measured at 1.0.0-rc.4. The two bigint modes differ on
|
|
823
|
+
* purpose: in `{ mode: 'number' }` the value arrives as a JS number, so the real ceiling is
|
|
824
|
+
* `Number.MAX_SAFE_INTEGER` rather than the column's, and bounding at the column's would
|
|
825
|
+
* promise a precision that cannot survive the round trip.
|
|
826
|
+
*/
|
|
827
|
+
_SchemaAnalyzer.INT_RANGES = {
|
|
828
|
+
// 8 bit
|
|
829
|
+
MySqlTinyInt: ["-128", "127"],
|
|
830
|
+
SQLiteInteger: ["-9223372036854775808", "9223372036854775807"],
|
|
831
|
+
// 16 bit
|
|
832
|
+
PgSmallInt: ["-32768", "32767"],
|
|
833
|
+
// A serial is an ordinary integer column that happens to default from a sequence. The
|
|
834
|
+
// sequence starts at 1, the column does not: `INSERT ... (id) VALUES (-5)` is accepted by
|
|
835
|
+
// Postgres and is how backfills and sentinel rows get written. Lower-bounding these at 1
|
|
836
|
+
// rejected valid rows, and `drizzle-orm/zod` bounds them by the integer width too.
|
|
837
|
+
PgSmallSerial: ["-32768", "32767"],
|
|
838
|
+
MySqlSmallInt: ["-32768", "32767"],
|
|
839
|
+
SingleStoreSmallInt: ["-32768", "32767"],
|
|
840
|
+
// 24 bit
|
|
841
|
+
MySqlMediumInt: ["-8388608", "8388607"],
|
|
842
|
+
// 32 bit
|
|
843
|
+
PgInteger: ["-2147483648", "2147483647"],
|
|
844
|
+
PgSerial: ["-2147483648", "2147483647"],
|
|
845
|
+
MySqlInt: ["-2147483648", "2147483647"],
|
|
846
|
+
SingleStoreInt: ["-2147483648", "2147483647"],
|
|
847
|
+
// 53 bit, the JS safe-integer ceiling rather than the column's
|
|
848
|
+
PgBigInt53: ["-9007199254740991", "9007199254740991"],
|
|
849
|
+
PgBigSerial53: ["-9007199254740991", "9007199254740991"],
|
|
850
|
+
MySqlBigInt53: ["-9007199254740991", "9007199254740991"],
|
|
851
|
+
SingleStoreBigInt53: ["-9007199254740991", "9007199254740991"],
|
|
852
|
+
// 64 bit, representable because the value is a bigint
|
|
853
|
+
PgBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
854
|
+
PgBigSerial64: ["-9223372036854775808", "9223372036854775807"],
|
|
855
|
+
MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
856
|
+
SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
|
|
857
|
+
};
|
|
858
|
+
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
631
859
|
var index_default = SchemaAnalyzer;
|
|
632
860
|
// Annotate the CommonJS export names for ESM import in node:
|
|
633
861
|
0 && (module.exports = {
|
|
634
|
-
SchemaAnalyzer
|
|
862
|
+
SchemaAnalyzer,
|
|
863
|
+
describeV1Column
|
|
635
864
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `mssql` and `cockroach` arrived with Drizzle v1. `gel` was removed in the same release, but
|
|
3
|
+
* stays here so an analysis of a 0.4x schema still names what it found.
|
|
4
|
+
*/
|
|
5
|
+
type Dialect = 'sqlite' | 'postgres' | 'mysql' | 'singlestore' | 'mssql' | 'cockroach' | 'gel' | 'unknown';
|
|
2
6
|
interface Issue {
|
|
3
7
|
code: string;
|
|
4
8
|
level: 'info' | 'warn' | 'error';
|
|
@@ -31,7 +35,80 @@ interface Column {
|
|
|
31
35
|
onUpdate?: string;
|
|
32
36
|
};
|
|
33
37
|
enumValues?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Declared character limit, from `varchar('x', { length: 255 })` and friends.
|
|
40
|
+
*
|
|
41
|
+
* The column has always known this and the analysis discarded it, so generated schemas
|
|
42
|
+
* accepted a 300 character value that the database then rejected. Absent where the type has
|
|
43
|
+
* no limit, since claiming one would invent a constraint the schema never stated.
|
|
44
|
+
*/
|
|
45
|
+
maxLength?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Inclusive bounds for an integer column, as decimal strings.
|
|
48
|
+
*
|
|
49
|
+
* Strings rather than numbers because a 64 bit bound is not representable as a JS number:
|
|
50
|
+
* `9223372036854775807` rounds to `9223372036854775808` the moment it becomes one, so a
|
|
51
|
+
* numeric field here would silently emit a wrong bound. Absent for floats and for `numeric`,
|
|
52
|
+
* which have no integer range.
|
|
53
|
+
*/
|
|
54
|
+
min?: string;
|
|
55
|
+
max?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Whether a numeric column holds whole numbers only.
|
|
58
|
+
*
|
|
59
|
+
* Stated rather than inferred. Generators used to read "has both bounds" as "is an integer",
|
|
60
|
+
* which held only while integers were the sole bounded type; bounding `real` and `double
|
|
61
|
+
* precision` promptly made every float schema reject `1.5`. Absent on a pre-1.7 analysis, and
|
|
62
|
+
* generators fall back to the old inference there.
|
|
63
|
+
*/
|
|
64
|
+
integer?: boolean;
|
|
65
|
+
/** A string column with a known shape, currently only `uuid`. */
|
|
66
|
+
format?: 'uuid';
|
|
67
|
+
/**
|
|
68
|
+
* Array depth for a column declared with `.array()`, absent when the column is a scalar.
|
|
69
|
+
*
|
|
70
|
+
* Drizzle does not give an array its own column class: `text().array()` is still a `PgText`,
|
|
71
|
+
* distinguished only by `dimensions`. Reading the class alone therefore produced a schema for
|
|
72
|
+
* the *element*, which rejected every row the database returned and accepted a bare string in
|
|
73
|
+
* its place.
|
|
74
|
+
*
|
|
75
|
+
* `.array(3)` sets a size rather than a dimension and Drizzle itself treats the result as a
|
|
76
|
+
* scalar, so it is deliberately not an array here either.
|
|
77
|
+
*/
|
|
78
|
+
arrayDimensions?: number;
|
|
79
|
+
/**
|
|
80
|
+
* A structured value that cannot be expressed as a scalar plus constraints.
|
|
81
|
+
*
|
|
82
|
+
* These all used to fall through to `any`/`unknown` or, worse, to `string`. A `point` really
|
|
83
|
+
* arrives as `[number, number]`, so a string schema rejected every row; a `bytea` typed as
|
|
84
|
+
* `unknown` accepted `null` on a NOT NULL column.
|
|
85
|
+
*/
|
|
86
|
+
shape?: ColumnShape;
|
|
87
|
+
}
|
|
88
|
+
type ColumnShape =
|
|
89
|
+
/** `bytea`, `blob`: a binary payload, carried as a Buffer/Uint8Array. */
|
|
90
|
+
{
|
|
91
|
+
kind: 'buffer';
|
|
92
|
+
}
|
|
93
|
+
/** `json`, `jsonb`: any value that survives a JSON round trip, checked recursively. */
|
|
94
|
+
| {
|
|
95
|
+
kind: 'json';
|
|
34
96
|
}
|
|
97
|
+
/** `point`, `line`, `geometry`: a fixed-length tuple of numbers. */
|
|
98
|
+
| {
|
|
99
|
+
kind: 'tuple';
|
|
100
|
+
length: number;
|
|
101
|
+
}
|
|
102
|
+
/** `vector`, `halfvec`: a numeric vector, with a fixed length where one is declared. */
|
|
103
|
+
| {
|
|
104
|
+
kind: 'numberVector';
|
|
105
|
+
length?: number;
|
|
106
|
+
}
|
|
107
|
+
/** `bit`: a string of `0`/`1`, with a fixed length where one is declared. */
|
|
108
|
+
| {
|
|
109
|
+
kind: 'bitstring';
|
|
110
|
+
length?: number;
|
|
111
|
+
};
|
|
35
112
|
interface Key {
|
|
36
113
|
name?: string;
|
|
37
114
|
columns: string[];
|
|
@@ -88,6 +165,20 @@ interface AnalyzeOptions {
|
|
|
88
165
|
validateConstraints?: boolean;
|
|
89
166
|
includeHeuristicRelations?: boolean;
|
|
90
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Everything Drizzle v1 states about a column outright, or `null` on an older Drizzle.
|
|
170
|
+
*
|
|
171
|
+
* v1 stamps each column with a `dataType` of the form `"<js type> <semantic>"` (`"number
|
|
172
|
+
* int32"`, `"object buffer"`, `"array point"`) alongside a `codec` naming the SQL side. That
|
|
173
|
+
* is a far better key than the constructor name the analyzer used to match on: the class list
|
|
174
|
+
* ran to dozens of names per dialect, drifted between releases, and a miss fell through to a
|
|
175
|
+
* regex that guessed from the name. `PgBinaryVector`, for one, is a bit string and not a
|
|
176
|
+
* vector at all.
|
|
177
|
+
*
|
|
178
|
+
* Gated on `codec`, which 0.4x columns do not carry, so an older schema keeps the class-name
|
|
179
|
+
* path below untouched.
|
|
180
|
+
*/
|
|
181
|
+
declare function describeV1Column(column: any): Partial<Column> | null;
|
|
91
182
|
declare class SchemaAnalyzer {
|
|
92
183
|
private readonly schemaPath;
|
|
93
184
|
constructor(schemaPath: string);
|
|
@@ -171,9 +262,25 @@ declare class SchemaAnalyzer {
|
|
|
171
262
|
* it a join table would invent a relation the author never declared.
|
|
172
263
|
*/
|
|
173
264
|
private inferManyToMany;
|
|
265
|
+
/**
|
|
266
|
+
* The range an integer column can actually hold, keyed off the Drizzle column class.
|
|
267
|
+
*
|
|
268
|
+
* Matches what `drizzle-orm/zod` emits, measured at 1.0.0-rc.4. The two bigint modes differ on
|
|
269
|
+
* purpose: in `{ mode: 'number' }` the value arrives as a JS number, so the real ceiling is
|
|
270
|
+
* `Number.MAX_SAFE_INTEGER` rather than the column's, and bounding at the column's would
|
|
271
|
+
* promise a precision that cannot survive the round trip.
|
|
272
|
+
*/
|
|
273
|
+
private static readonly INT_RANGES;
|
|
274
|
+
/**
|
|
275
|
+
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
276
|
+
*
|
|
277
|
+
* Everything here is read off Drizzle's own column instance, so it states what the schema
|
|
278
|
+
* states. Nothing is inferred from a name or guessed from a type.
|
|
279
|
+
*/
|
|
280
|
+
private columnConstraints;
|
|
174
281
|
private mapColumnType;
|
|
175
282
|
private analyzeTable;
|
|
176
283
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
177
284
|
}
|
|
178
285
|
|
|
179
|
-
export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default };
|
|
286
|
+
export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default, describeV1Column };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `mssql` and `cockroach` arrived with Drizzle v1. `gel` was removed in the same release, but
|
|
3
|
+
* stays here so an analysis of a 0.4x schema still names what it found.
|
|
4
|
+
*/
|
|
5
|
+
type Dialect = 'sqlite' | 'postgres' | 'mysql' | 'singlestore' | 'mssql' | 'cockroach' | 'gel' | 'unknown';
|
|
2
6
|
interface Issue {
|
|
3
7
|
code: string;
|
|
4
8
|
level: 'info' | 'warn' | 'error';
|
|
@@ -31,7 +35,80 @@ interface Column {
|
|
|
31
35
|
onUpdate?: string;
|
|
32
36
|
};
|
|
33
37
|
enumValues?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Declared character limit, from `varchar('x', { length: 255 })` and friends.
|
|
40
|
+
*
|
|
41
|
+
* The column has always known this and the analysis discarded it, so generated schemas
|
|
42
|
+
* accepted a 300 character value that the database then rejected. Absent where the type has
|
|
43
|
+
* no limit, since claiming one would invent a constraint the schema never stated.
|
|
44
|
+
*/
|
|
45
|
+
maxLength?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Inclusive bounds for an integer column, as decimal strings.
|
|
48
|
+
*
|
|
49
|
+
* Strings rather than numbers because a 64 bit bound is not representable as a JS number:
|
|
50
|
+
* `9223372036854775807` rounds to `9223372036854775808` the moment it becomes one, so a
|
|
51
|
+
* numeric field here would silently emit a wrong bound. Absent for floats and for `numeric`,
|
|
52
|
+
* which have no integer range.
|
|
53
|
+
*/
|
|
54
|
+
min?: string;
|
|
55
|
+
max?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Whether a numeric column holds whole numbers only.
|
|
58
|
+
*
|
|
59
|
+
* Stated rather than inferred. Generators used to read "has both bounds" as "is an integer",
|
|
60
|
+
* which held only while integers were the sole bounded type; bounding `real` and `double
|
|
61
|
+
* precision` promptly made every float schema reject `1.5`. Absent on a pre-1.7 analysis, and
|
|
62
|
+
* generators fall back to the old inference there.
|
|
63
|
+
*/
|
|
64
|
+
integer?: boolean;
|
|
65
|
+
/** A string column with a known shape, currently only `uuid`. */
|
|
66
|
+
format?: 'uuid';
|
|
67
|
+
/**
|
|
68
|
+
* Array depth for a column declared with `.array()`, absent when the column is a scalar.
|
|
69
|
+
*
|
|
70
|
+
* Drizzle does not give an array its own column class: `text().array()` is still a `PgText`,
|
|
71
|
+
* distinguished only by `dimensions`. Reading the class alone therefore produced a schema for
|
|
72
|
+
* the *element*, which rejected every row the database returned and accepted a bare string in
|
|
73
|
+
* its place.
|
|
74
|
+
*
|
|
75
|
+
* `.array(3)` sets a size rather than a dimension and Drizzle itself treats the result as a
|
|
76
|
+
* scalar, so it is deliberately not an array here either.
|
|
77
|
+
*/
|
|
78
|
+
arrayDimensions?: number;
|
|
79
|
+
/**
|
|
80
|
+
* A structured value that cannot be expressed as a scalar plus constraints.
|
|
81
|
+
*
|
|
82
|
+
* These all used to fall through to `any`/`unknown` or, worse, to `string`. A `point` really
|
|
83
|
+
* arrives as `[number, number]`, so a string schema rejected every row; a `bytea` typed as
|
|
84
|
+
* `unknown` accepted `null` on a NOT NULL column.
|
|
85
|
+
*/
|
|
86
|
+
shape?: ColumnShape;
|
|
87
|
+
}
|
|
88
|
+
type ColumnShape =
|
|
89
|
+
/** `bytea`, `blob`: a binary payload, carried as a Buffer/Uint8Array. */
|
|
90
|
+
{
|
|
91
|
+
kind: 'buffer';
|
|
92
|
+
}
|
|
93
|
+
/** `json`, `jsonb`: any value that survives a JSON round trip, checked recursively. */
|
|
94
|
+
| {
|
|
95
|
+
kind: 'json';
|
|
34
96
|
}
|
|
97
|
+
/** `point`, `line`, `geometry`: a fixed-length tuple of numbers. */
|
|
98
|
+
| {
|
|
99
|
+
kind: 'tuple';
|
|
100
|
+
length: number;
|
|
101
|
+
}
|
|
102
|
+
/** `vector`, `halfvec`: a numeric vector, with a fixed length where one is declared. */
|
|
103
|
+
| {
|
|
104
|
+
kind: 'numberVector';
|
|
105
|
+
length?: number;
|
|
106
|
+
}
|
|
107
|
+
/** `bit`: a string of `0`/`1`, with a fixed length where one is declared. */
|
|
108
|
+
| {
|
|
109
|
+
kind: 'bitstring';
|
|
110
|
+
length?: number;
|
|
111
|
+
};
|
|
35
112
|
interface Key {
|
|
36
113
|
name?: string;
|
|
37
114
|
columns: string[];
|
|
@@ -88,6 +165,20 @@ interface AnalyzeOptions {
|
|
|
88
165
|
validateConstraints?: boolean;
|
|
89
166
|
includeHeuristicRelations?: boolean;
|
|
90
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Everything Drizzle v1 states about a column outright, or `null` on an older Drizzle.
|
|
170
|
+
*
|
|
171
|
+
* v1 stamps each column with a `dataType` of the form `"<js type> <semantic>"` (`"number
|
|
172
|
+
* int32"`, `"object buffer"`, `"array point"`) alongside a `codec` naming the SQL side. That
|
|
173
|
+
* is a far better key than the constructor name the analyzer used to match on: the class list
|
|
174
|
+
* ran to dozens of names per dialect, drifted between releases, and a miss fell through to a
|
|
175
|
+
* regex that guessed from the name. `PgBinaryVector`, for one, is a bit string and not a
|
|
176
|
+
* vector at all.
|
|
177
|
+
*
|
|
178
|
+
* Gated on `codec`, which 0.4x columns do not carry, so an older schema keeps the class-name
|
|
179
|
+
* path below untouched.
|
|
180
|
+
*/
|
|
181
|
+
declare function describeV1Column(column: any): Partial<Column> | null;
|
|
91
182
|
declare class SchemaAnalyzer {
|
|
92
183
|
private readonly schemaPath;
|
|
93
184
|
constructor(schemaPath: string);
|
|
@@ -171,9 +262,25 @@ declare class SchemaAnalyzer {
|
|
|
171
262
|
* it a join table would invent a relation the author never declared.
|
|
172
263
|
*/
|
|
173
264
|
private inferManyToMany;
|
|
265
|
+
/**
|
|
266
|
+
* The range an integer column can actually hold, keyed off the Drizzle column class.
|
|
267
|
+
*
|
|
268
|
+
* Matches what `drizzle-orm/zod` emits, measured at 1.0.0-rc.4. The two bigint modes differ on
|
|
269
|
+
* purpose: in `{ mode: 'number' }` the value arrives as a JS number, so the real ceiling is
|
|
270
|
+
* `Number.MAX_SAFE_INTEGER` rather than the column's, and bounding at the column's would
|
|
271
|
+
* promise a precision that cannot survive the round trip.
|
|
272
|
+
*/
|
|
273
|
+
private static readonly INT_RANGES;
|
|
274
|
+
/**
|
|
275
|
+
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
276
|
+
*
|
|
277
|
+
* Everything here is read off Drizzle's own column instance, so it states what the schema
|
|
278
|
+
* states. Nothing is inferred from a name or guessed from a type.
|
|
279
|
+
*/
|
|
280
|
+
private columnConstraints;
|
|
174
281
|
private mapColumnType;
|
|
175
282
|
private analyzeTable;
|
|
176
283
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
177
284
|
}
|
|
178
285
|
|
|
179
|
-
export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default };
|
|
286
|
+
export { type Analysis, type AnalyzeOptions, type Check, type Column, type ColumnRef, type ColumnShape, type Dialect, type Enum, type ForeignKey, type Index, type Issue, type Key, type Relation, SchemaAnalyzer, type Table, SchemaAnalyzer as default, describeV1Column };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,140 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
|
|
2
|
+
function renderSqlLiteral(v) {
|
|
3
|
+
if (v === null || v === void 0) return "NULL";
|
|
4
|
+
if (typeof v === "number" || typeof v === "bigint") return String(v);
|
|
5
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
6
|
+
if (v instanceof Date) return `'${v.toISOString()}'`;
|
|
7
|
+
if (Array.isArray(v)) return `(${v.map(renderSqlLiteral).join(", ")})`;
|
|
8
|
+
return `'${String(v).replace(/'/g, "''")}'`;
|
|
9
|
+
}
|
|
10
|
+
var V1_FLOAT_BOUNDS = {
|
|
11
|
+
float: ["-8388608", "8388607"],
|
|
12
|
+
// real / float4, 2^23
|
|
13
|
+
double: ["-140737488355328", "140737488355327"]
|
|
14
|
+
// double precision / float8, 2^47
|
|
15
|
+
};
|
|
16
|
+
function describeV1Column(column) {
|
|
17
|
+
const codec = column?.codec;
|
|
18
|
+
const dataType = column?.dataType;
|
|
19
|
+
if (typeof codec !== "string" || typeof dataType !== "string") return null;
|
|
20
|
+
const [js, semantic = ""] = dataType.split(" ");
|
|
21
|
+
const out = {};
|
|
22
|
+
switch (semantic) {
|
|
23
|
+
case "int16":
|
|
24
|
+
case "int32":
|
|
25
|
+
case "int53":
|
|
26
|
+
case "int64": {
|
|
27
|
+
const range = {
|
|
28
|
+
int16: ["-32768", "32767"],
|
|
29
|
+
int32: ["-2147483648", "2147483647"],
|
|
30
|
+
int53: ["-9007199254740991", "9007199254740991"],
|
|
31
|
+
int64: ["-9223372036854775808", "9223372036854775807"]
|
|
32
|
+
}[semantic];
|
|
33
|
+
[out.min, out.max] = range;
|
|
34
|
+
out.integer = true;
|
|
35
|
+
out.tsType = js === "bigint" ? "bigint" : "number";
|
|
36
|
+
out.dbType = semantic === "int16" ? "SMALLINT" : semantic === "int32" ? "INTEGER" : "BIGINT";
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "float":
|
|
40
|
+
case "double": {
|
|
41
|
+
[out.min, out.max] = V1_FLOAT_BOUNDS[semantic];
|
|
42
|
+
out.integer = false;
|
|
43
|
+
out.tsType = "number";
|
|
44
|
+
out.dbType = semantic === "float" ? "REAL" : "DOUBLE";
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case "uuid":
|
|
48
|
+
out.tsType = "string";
|
|
49
|
+
out.dbType = "UUID";
|
|
50
|
+
out.format = "uuid";
|
|
51
|
+
break;
|
|
52
|
+
case "numeric":
|
|
53
|
+
out.tsType = "string";
|
|
54
|
+
out.dbType = "NUMERIC";
|
|
55
|
+
break;
|
|
56
|
+
case "json":
|
|
57
|
+
out.tsType = "any";
|
|
58
|
+
out.dbType = codec === "jsonb" ? "JSONB" : "JSON";
|
|
59
|
+
out.shape = { kind: "json" };
|
|
60
|
+
break;
|
|
61
|
+
case "buffer":
|
|
62
|
+
out.tsType = "Buffer";
|
|
63
|
+
out.dbType = "BYTEA";
|
|
64
|
+
out.shape = { kind: "buffer" };
|
|
65
|
+
break;
|
|
66
|
+
case "date":
|
|
67
|
+
out.tsType = js === "string" ? "string" : "Date";
|
|
68
|
+
out.dbType = codec.startsWith("timestamp") ? "TIMESTAMP" : "DATE";
|
|
69
|
+
break;
|
|
70
|
+
case "timestamp":
|
|
71
|
+
out.tsType = js === "string" ? "string" : "Date";
|
|
72
|
+
out.dbType = "TIMESTAMP";
|
|
73
|
+
break;
|
|
74
|
+
case "time":
|
|
75
|
+
out.tsType = "string";
|
|
76
|
+
out.dbType = "TIME";
|
|
77
|
+
break;
|
|
78
|
+
case "interval":
|
|
79
|
+
out.tsType = "string";
|
|
80
|
+
out.dbType = "INTERVAL";
|
|
81
|
+
break;
|
|
82
|
+
case "inet":
|
|
83
|
+
case "cidr":
|
|
84
|
+
case "macaddr":
|
|
85
|
+
out.tsType = "string";
|
|
86
|
+
out.dbType = semantic.toUpperCase();
|
|
87
|
+
break;
|
|
88
|
+
case "binary":
|
|
89
|
+
out.tsType = "string";
|
|
90
|
+
out.dbType = "BIT";
|
|
91
|
+
out.shape = { kind: "bitstring", length: declaredLength(column) };
|
|
92
|
+
break;
|
|
93
|
+
case "point":
|
|
94
|
+
case "geometry":
|
|
95
|
+
out.tsType = "[number, number]";
|
|
96
|
+
out.dbType = semantic.toUpperCase();
|
|
97
|
+
out.shape = { kind: "tuple", length: 2 };
|
|
98
|
+
break;
|
|
99
|
+
case "line":
|
|
100
|
+
out.tsType = "[number, number, number]";
|
|
101
|
+
out.dbType = "LINE";
|
|
102
|
+
out.shape = { kind: "tuple", length: 3 };
|
|
103
|
+
break;
|
|
104
|
+
case "vector":
|
|
105
|
+
out.tsType = "number[]";
|
|
106
|
+
out.dbType = "VECTOR";
|
|
107
|
+
out.shape = { kind: "numberVector", length: declaredLength(column) };
|
|
108
|
+
break;
|
|
109
|
+
case "enum":
|
|
110
|
+
out.tsType = "string";
|
|
111
|
+
out.dbType = "TEXT";
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
if (js === "boolean") {
|
|
115
|
+
out.tsType = "boolean";
|
|
116
|
+
out.dbType = "BOOLEAN";
|
|
117
|
+
} else if (js === "number") {
|
|
118
|
+
out.tsType = "number";
|
|
119
|
+
out.dbType = "NUMERIC";
|
|
120
|
+
out.integer = false;
|
|
121
|
+
[out.min, out.max] = ["-9007199254740991", "9007199254740991"];
|
|
122
|
+
} else if (js === "string") {
|
|
123
|
+
out.tsType = "string";
|
|
124
|
+
out.dbType = codec === "varchar" ? "VARCHAR" : codec === "char" ? "CHAR" : "TEXT";
|
|
125
|
+
} else {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const dims = column?.dimensions;
|
|
130
|
+
if (typeof dims === "number" && dims >= 1) out.arrayDimensions = dims;
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
function declaredLength(column) {
|
|
134
|
+
const n = column?.length ?? column?.config?.length ?? column?.config?.dimensions;
|
|
135
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
136
|
+
}
|
|
137
|
+
var _SchemaAnalyzer = class _SchemaAnalyzer {
|
|
3
138
|
constructor(schemaPath) {
|
|
4
139
|
this.schemaPath = schemaPath;
|
|
5
140
|
}
|
|
@@ -136,6 +271,12 @@ var SchemaAnalyzer = class {
|
|
|
136
271
|
if (Array.isArray(c?.value)) return c.value.join("");
|
|
137
272
|
if (typeof c?.name === "string") return toTs(c.name);
|
|
138
273
|
if (c?.queryChunks) return this.renderSql(c, toTs);
|
|
274
|
+
if (c === null || ["number", "string", "boolean", "bigint"].includes(typeof c)) {
|
|
275
|
+
return renderSqlLiteral(c);
|
|
276
|
+
}
|
|
277
|
+
if (typeof c === "object" && "value" in c) {
|
|
278
|
+
return renderSqlLiteral(c.value);
|
|
279
|
+
}
|
|
139
280
|
return "?";
|
|
140
281
|
}).join("").trim();
|
|
141
282
|
}
|
|
@@ -207,6 +348,27 @@ var SchemaAnalyzer = class {
|
|
|
207
348
|
}
|
|
208
349
|
return out;
|
|
209
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
353
|
+
*
|
|
354
|
+
* Everything here is read off Drizzle's own column instance, so it states what the schema
|
|
355
|
+
* states. Nothing is inferred from a name or guessed from a type.
|
|
356
|
+
*/
|
|
357
|
+
columnConstraints(column) {
|
|
358
|
+
const ctor = column?.constructor?.name ?? "";
|
|
359
|
+
const out = {};
|
|
360
|
+
const length = column?.length ?? column?.config?.length;
|
|
361
|
+
if (typeof length === "number" && Number.isFinite(length) && length > 0) {
|
|
362
|
+
out.maxLength = length;
|
|
363
|
+
}
|
|
364
|
+
const range = _SchemaAnalyzer.INT_RANGES[ctor];
|
|
365
|
+
if (range) {
|
|
366
|
+
[out.min, out.max] = range;
|
|
367
|
+
out.integer = true;
|
|
368
|
+
}
|
|
369
|
+
if (/^(Pg)?UUID$/i.test(ctor) || /Uuid$/i.test(ctor)) out.format = "uuid";
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
210
372
|
mapColumnType(column) {
|
|
211
373
|
const ctor = column?.constructor?.name ?? "";
|
|
212
374
|
switch (ctor) {
|
|
@@ -228,6 +390,14 @@ var SchemaAnalyzer = class {
|
|
|
228
390
|
case "PgInteger":
|
|
229
391
|
case "PgSmallInt":
|
|
230
392
|
return { tsType: "number", dbType: "INTEGER" };
|
|
393
|
+
// Drizzle names these by their mode: `PgBigInt53` for `{ mode: 'number' }` and
|
|
394
|
+
// `PgBigInt64` for `{ mode: 'bigint' }`. `PgBigInt` matched neither, so both fell through
|
|
395
|
+
// to the regex arm and came back as `bigint`, which is wrong for the number mode: the
|
|
396
|
+
// value really is a JS number there, and a schema demanding a bigint rejects every row.
|
|
397
|
+
case "PgBigInt53":
|
|
398
|
+
return { tsType: "number", dbType: "BIGINT" };
|
|
399
|
+
case "PgBigInt64":
|
|
400
|
+
return { tsType: "bigint", dbType: "BIGINT" };
|
|
231
401
|
case "PgBigInt":
|
|
232
402
|
return {
|
|
233
403
|
tsType: column?.config?.mode === "number" ? "number" : "bigint",
|
|
@@ -241,6 +411,9 @@ var SchemaAnalyzer = class {
|
|
|
241
411
|
case "PgVarchar":
|
|
242
412
|
case "PgChar":
|
|
243
413
|
return { tsType: "string", dbType: "TEXT" };
|
|
414
|
+
// Drizzle spells it `PgUUID`. `PgUuid` matched nothing, so every uuid column fell through
|
|
415
|
+
// to the regex arm below and came back as plain TEXT, losing the format.
|
|
416
|
+
case "PgUUID":
|
|
244
417
|
case "PgUuid":
|
|
245
418
|
return { tsType: "string", dbType: "UUID" };
|
|
246
419
|
case "PgBoolean":
|
|
@@ -265,7 +438,8 @@ var SchemaAnalyzer = class {
|
|
|
265
438
|
if (/Text|Varchar|Char|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
266
439
|
if (/Inet|Cidr|Macaddr8?|Uuid/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
267
440
|
if (/Point|Line/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
268
|
-
if (/TimestampString|DateString/i.test(ctor))
|
|
441
|
+
if (/TimestampString|DateString/i.test(ctor))
|
|
442
|
+
return { tsType: "string", dbType: "TIMESTAMP" };
|
|
269
443
|
if (/Timestamptz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
|
|
270
444
|
if (/Timestamp/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMP" };
|
|
271
445
|
if (/Date/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMP" };
|
|
@@ -274,8 +448,10 @@ var SchemaAnalyzer = class {
|
|
|
274
448
|
if (/\bInt(eger)?\b|Serial/i.test(ctor)) return { tsType: "number", dbType: "INTEGER" };
|
|
275
449
|
if (/BigInt/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
|
|
276
450
|
if (/Bool/i.test(ctor)) return { tsType: "boolean", dbType: "BOOLEAN" };
|
|
277
|
-
if (/Jsonb?/i.test(ctor))
|
|
278
|
-
|
|
451
|
+
if (/Jsonb?/i.test(ctor))
|
|
452
|
+
return { tsType: "any", dbType: /Jsonb/i.test(ctor) ? "JSONB" : "JSON" };
|
|
453
|
+
if (/Numeric|Float|Double|Real/i.test(ctor))
|
|
454
|
+
return { tsType: "number", dbType: "NUMERIC" };
|
|
279
455
|
}
|
|
280
456
|
if (/^MySql/i.test(ctor)) {
|
|
281
457
|
if (/BigInt64/i.test(ctor)) return { tsType: "bigint", dbType: "BIGINT" };
|
|
@@ -327,7 +503,8 @@ var SchemaAnalyzer = class {
|
|
|
327
503
|
if (/TimestampTz/i.test(ctor)) return { tsType: "Date", dbType: "TIMESTAMPTZ" };
|
|
328
504
|
if (/Timestamp/i.test(ctor)) return { tsType: "string", dbType: "TIMESTAMP" };
|
|
329
505
|
if (/LocalDateString|LocalTime/i.test(ctor)) return { tsType: "string", dbType: "TEXT" };
|
|
330
|
-
if (/DateDuration|RelDuration|Duration/i.test(ctor))
|
|
506
|
+
if (/DateDuration|RelDuration|Duration/i.test(ctor))
|
|
507
|
+
return { tsType: "string", dbType: "TEXT" };
|
|
331
508
|
}
|
|
332
509
|
return { tsType: "unknown", dbType: "UNKNOWN" };
|
|
333
510
|
}
|
|
@@ -362,6 +539,9 @@ var SchemaAnalyzer = class {
|
|
|
362
539
|
arr.push(colName);
|
|
363
540
|
uniqueGroups.set(uName, arr);
|
|
364
541
|
}
|
|
542
|
+
const v1 = describeV1Column(col);
|
|
543
|
+
const constraints = this.columnConstraints(col);
|
|
544
|
+
if (v1?.shape) delete constraints.maxLength;
|
|
365
545
|
columns.push({
|
|
366
546
|
name: colName,
|
|
367
547
|
tsType,
|
|
@@ -371,7 +551,9 @@ var SchemaAnalyzer = class {
|
|
|
371
551
|
isGenerated,
|
|
372
552
|
defaultExpression: void 0,
|
|
373
553
|
references,
|
|
374
|
-
enumValues: Array.isArray(ev) ? ev : void 0
|
|
554
|
+
enumValues: Array.isArray(ev) ? ev : void 0,
|
|
555
|
+
...constraints,
|
|
556
|
+
...v1 ?? {}
|
|
375
557
|
});
|
|
376
558
|
}
|
|
377
559
|
const name = this.getSymbol(tbl, "drizzle:Name") || tsName;
|
|
@@ -532,27 +714,32 @@ var SchemaAnalyzer = class {
|
|
|
532
714
|
enums.push(candidate);
|
|
533
715
|
}
|
|
534
716
|
let dialect = "unknown";
|
|
535
|
-
const
|
|
717
|
+
const marks = /* @__PURE__ */ new Set();
|
|
536
718
|
for (const [_, val] of Object.entries(exportsObj)) {
|
|
537
719
|
const cols = val?.[/* @__PURE__ */ Symbol.for("drizzle:Columns")];
|
|
538
|
-
if (cols)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
720
|
+
if (!cols) continue;
|
|
721
|
+
for (const c of Object.values(cols)) {
|
|
722
|
+
const kind = c?.constructor?.[/* @__PURE__ */ Symbol.for("drizzle:entityKind")];
|
|
723
|
+
if (typeof kind === "string") marks.add(kind);
|
|
724
|
+
const n = c?.constructor?.name;
|
|
725
|
+
if (n) marks.add(n);
|
|
543
726
|
}
|
|
544
727
|
}
|
|
545
|
-
const names = Array.from(
|
|
728
|
+
const names = Array.from(marks).join(",");
|
|
546
729
|
if (/SQLite/i.test(names)) dialect = "sqlite";
|
|
547
|
-
else if (/Pg|Postgres/i.test(names)) dialect = "postgres";
|
|
548
|
-
else if (/MySql|Mysql/i.test(names)) dialect = "mysql";
|
|
549
730
|
else if (/SingleStore/i.test(names)) dialect = "singlestore";
|
|
731
|
+
else if (/Cockroach/i.test(names)) dialect = "cockroach";
|
|
732
|
+
else if (/MsSql/i.test(names)) dialect = "mssql";
|
|
733
|
+
else if (/MySql|Mysql/i.test(names)) dialect = "mysql";
|
|
734
|
+
else if (/Pg|Postgres/i.test(names)) dialect = "postgres";
|
|
550
735
|
else if (/Gel/i.test(names)) dialect = "gel";
|
|
551
|
-
if (dialect === "unknown") {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
736
|
+
if (dialect === "unknown" && tables.length) {
|
|
737
|
+
issues.push({
|
|
738
|
+
code: "DRZL_ANL_DIALECT",
|
|
739
|
+
level: "warn",
|
|
740
|
+
message: `Could not identify the Drizzle dialect for this schema${names ? `; saw column kinds: ${Array.from(marks).slice(0, 6).join(", ")}` : ""}.`,
|
|
741
|
+
hint: "Column types will fall back to their coarse defaults. If this is a dialect DRZL does not know yet, please open an issue."
|
|
742
|
+
});
|
|
556
743
|
}
|
|
557
744
|
if (opts.includeRelations) {
|
|
558
745
|
relations.push(...this.inferManyToMany(tables));
|
|
@@ -592,8 +779,49 @@ var SchemaAnalyzer = class {
|
|
|
592
779
|
};
|
|
593
780
|
}
|
|
594
781
|
};
|
|
782
|
+
/**
|
|
783
|
+
* The range an integer column can actually hold, keyed off the Drizzle column class.
|
|
784
|
+
*
|
|
785
|
+
* Matches what `drizzle-orm/zod` emits, measured at 1.0.0-rc.4. The two bigint modes differ on
|
|
786
|
+
* purpose: in `{ mode: 'number' }` the value arrives as a JS number, so the real ceiling is
|
|
787
|
+
* `Number.MAX_SAFE_INTEGER` rather than the column's, and bounding at the column's would
|
|
788
|
+
* promise a precision that cannot survive the round trip.
|
|
789
|
+
*/
|
|
790
|
+
_SchemaAnalyzer.INT_RANGES = {
|
|
791
|
+
// 8 bit
|
|
792
|
+
MySqlTinyInt: ["-128", "127"],
|
|
793
|
+
SQLiteInteger: ["-9223372036854775808", "9223372036854775807"],
|
|
794
|
+
// 16 bit
|
|
795
|
+
PgSmallInt: ["-32768", "32767"],
|
|
796
|
+
// A serial is an ordinary integer column that happens to default from a sequence. The
|
|
797
|
+
// sequence starts at 1, the column does not: `INSERT ... (id) VALUES (-5)` is accepted by
|
|
798
|
+
// Postgres and is how backfills and sentinel rows get written. Lower-bounding these at 1
|
|
799
|
+
// rejected valid rows, and `drizzle-orm/zod` bounds them by the integer width too.
|
|
800
|
+
PgSmallSerial: ["-32768", "32767"],
|
|
801
|
+
MySqlSmallInt: ["-32768", "32767"],
|
|
802
|
+
SingleStoreSmallInt: ["-32768", "32767"],
|
|
803
|
+
// 24 bit
|
|
804
|
+
MySqlMediumInt: ["-8388608", "8388607"],
|
|
805
|
+
// 32 bit
|
|
806
|
+
PgInteger: ["-2147483648", "2147483647"],
|
|
807
|
+
PgSerial: ["-2147483648", "2147483647"],
|
|
808
|
+
MySqlInt: ["-2147483648", "2147483647"],
|
|
809
|
+
SingleStoreInt: ["-2147483648", "2147483647"],
|
|
810
|
+
// 53 bit, the JS safe-integer ceiling rather than the column's
|
|
811
|
+
PgBigInt53: ["-9007199254740991", "9007199254740991"],
|
|
812
|
+
PgBigSerial53: ["-9007199254740991", "9007199254740991"],
|
|
813
|
+
MySqlBigInt53: ["-9007199254740991", "9007199254740991"],
|
|
814
|
+
SingleStoreBigInt53: ["-9007199254740991", "9007199254740991"],
|
|
815
|
+
// 64 bit, representable because the value is a bigint
|
|
816
|
+
PgBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
817
|
+
PgBigSerial64: ["-9223372036854775808", "9223372036854775807"],
|
|
818
|
+
MySqlBigInt64: ["-9223372036854775808", "9223372036854775807"],
|
|
819
|
+
SingleStoreBigInt64: ["-9223372036854775808", "9223372036854775807"]
|
|
820
|
+
};
|
|
821
|
+
var SchemaAnalyzer = _SchemaAnalyzer;
|
|
595
822
|
var index_default = SchemaAnalyzer;
|
|
596
823
|
export {
|
|
597
824
|
SchemaAnalyzer,
|
|
598
|
-
index_default as default
|
|
825
|
+
index_default as default,
|
|
826
|
+
describeV1Column
|
|
599
827
|
};
|