@drzl/analyzer 1.18.0 → 1.20.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 +294 -34
- package/dist/index.d.cts +177 -3
- package/dist/index.d.ts +177 -3
- package/dist/index.js +292 -34
- package/package.json +5 -2
package/dist/index.d.cts
CHANGED
|
@@ -8,28 +8,91 @@ interface Issue {
|
|
|
8
8
|
level: 'info' | 'warn' | 'error';
|
|
9
9
|
message: string;
|
|
10
10
|
hint?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Where the issue is, as `table` or `table.column`.
|
|
13
|
+
*
|
|
14
|
+
* Declared since this interface existed and set by nothing, so every consumer wanting to group
|
|
15
|
+
* warnings by table had to read the names back out of the English in `message`. `drzl doctor` is
|
|
16
|
+
* the first such consumer and a report built by regex over prose breaks the first time a message
|
|
17
|
+
* is reworded, so the names are stated here instead.
|
|
18
|
+
*
|
|
19
|
+
* Still optional: an issue about the schema as a whole, such as an unidentifiable dialect, is
|
|
20
|
+
* about no table and says so by omitting this.
|
|
21
|
+
*/
|
|
11
22
|
path?: string;
|
|
12
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Why a column has no type, where "nobody has modelled it" is not the answer.
|
|
26
|
+
*
|
|
27
|
+
* The distinction exists because it changes the advice. A column class this file has no arm for is
|
|
28
|
+
* a gap someone can close, and the warning tells its author to say so. A Gel temporal column is
|
|
29
|
+
* not: the value is an instance of a class from the `gel` package, DRZL cannot import that package,
|
|
30
|
+
* and no generator could emit a check for it even knowing the name. Leaving it `unknown` is the
|
|
31
|
+
* measured answer rather than an omission, and its warning should say that instead of asking for a
|
|
32
|
+
* bug report that is already closed.
|
|
33
|
+
*/
|
|
34
|
+
type UnnameableReason = 'gel-temporal';
|
|
13
35
|
interface ColumnRef {
|
|
14
36
|
table: string;
|
|
15
37
|
column: string;
|
|
16
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A link between two tables, each end named by `qualifiedTableName`.
|
|
41
|
+
*
|
|
42
|
+
* Qualified rather than bare, because a bare database name identifies a table only while no two
|
|
43
|
+
* SQL schemas hold it: `to: 'users'` cannot say whether it means `public.users` or
|
|
44
|
+
* `reporting.users`, and every consumer resolves these strings back to a table object. A table in
|
|
45
|
+
* the default schema has no prefix, so nothing about a single-schema analysis changes.
|
|
46
|
+
*/
|
|
17
47
|
interface Relation {
|
|
18
48
|
kind: 'one' | 'many' | 'manyToMany';
|
|
49
|
+
/** Qualified table name: `users`, or `reporting.users`. */
|
|
19
50
|
from: string;
|
|
51
|
+
/** Qualified table name: `users`, or `reporting.users`. */
|
|
20
52
|
to: string;
|
|
53
|
+
/** Qualified name of the join table, for m2m. */
|
|
21
54
|
via?: string;
|
|
22
55
|
}
|
|
23
56
|
interface Column {
|
|
24
57
|
name: string;
|
|
25
58
|
tsType: string;
|
|
59
|
+
/**
|
|
60
|
+
* A coarse label for the column's kind, not its type.
|
|
61
|
+
*
|
|
62
|
+
* `varchar`, `char` and `text` are all `TEXT` here, deliberately: exactly one consumer reads
|
|
63
|
+
* this, `isIntegerColumn`, and it only asks whether a number is whole. Use `sqlType` for the
|
|
64
|
+
* question this name suggests it answers.
|
|
65
|
+
*/
|
|
26
66
|
dbType: string;
|
|
67
|
+
/**
|
|
68
|
+
* The column's type as the database declares it, from Drizzle's own `getSQLType()`:
|
|
69
|
+
* `varchar(255)`, `numeric(10, 2)`, `timestamp with time zone`, `text[]`, or an enum's type
|
|
70
|
+
* name.
|
|
71
|
+
*
|
|
72
|
+
* The one fact about a column that no validator schema can carry, and the one every other fact
|
|
73
|
+
* here is a consequence of. A generator that emits metadata beside its schemas has nothing else
|
|
74
|
+
* to put in it: `dbType` is a label rather than a type, and the declared width lives in
|
|
75
|
+
* `maxLength`, the precision in `min`/`max`, and neither of those says which type produced it.
|
|
76
|
+
*
|
|
77
|
+
* The two Drizzle majors disagree about an array and are reconciled here, measured rather than
|
|
78
|
+
* assumed: 0.4x wraps the column in a `PgArray` whose own answer is already `text[]`, while v1
|
|
79
|
+
* leaves the class alone and raises `dimensions`, so its answer is the bare `text`. The suffix
|
|
80
|
+
* is added from `arrayDimensions` when the type does not already carry one, so a consumer
|
|
81
|
+
* cannot tell which major produced its metadata.
|
|
82
|
+
*
|
|
83
|
+
* Absent where the builder has no `getSQLType` or it throws. Nothing is guessed from the class
|
|
84
|
+
* name: an invented type string reads exactly like a real one, and there is no way for a
|
|
85
|
+
* consumer to tell them apart.
|
|
86
|
+
*/
|
|
87
|
+
sqlType?: string;
|
|
27
88
|
nullable: boolean;
|
|
28
89
|
hasDefault: boolean;
|
|
29
90
|
isGenerated: boolean;
|
|
30
91
|
defaultExpression?: string;
|
|
31
92
|
references?: {
|
|
32
93
|
table: string;
|
|
94
|
+
/** SQL schema of the referenced table, absent for the default one. See `ForeignKey`. */
|
|
95
|
+
schema?: string;
|
|
33
96
|
column: string;
|
|
34
97
|
onDelete?: string;
|
|
35
98
|
onUpdate?: string;
|
|
@@ -48,8 +111,16 @@ interface Column {
|
|
|
48
111
|
*
|
|
49
112
|
* Strings rather than numbers because a 64 bit bound is not representable as a JS number:
|
|
50
113
|
* `9223372036854775807` rounds to `9223372036854775808` the moment it becomes one, so a
|
|
51
|
-
* numeric field here would silently emit a wrong bound.
|
|
52
|
-
*
|
|
114
|
+
* numeric field here would silently emit a wrong bound. A 20 digit `numeric(20,0)` bound is
|
|
115
|
+
* further past that again.
|
|
116
|
+
*
|
|
117
|
+
* Not an integer range, despite the name this field used to be described by. An inexact column
|
|
118
|
+
* carries one too: a `real` is bounded by the magnitude the database refuses past, and a
|
|
119
|
+
* `numeric(10,2)` by the width its own declaration states. `integer` says which kind it is, and
|
|
120
|
+
* saying it is what stops a bounded float schema refusing 1.5.
|
|
121
|
+
*
|
|
122
|
+
* Absent where nothing declares a bound: an 8 byte float holds every finite JS number, and a
|
|
123
|
+
* `numeric` with no precision holds arbitrary precision.
|
|
53
124
|
*/
|
|
54
125
|
min?: string;
|
|
55
126
|
max?: string;
|
|
@@ -62,6 +133,42 @@ interface Column {
|
|
|
62
133
|
* generators fall back to the old inference there.
|
|
63
134
|
*/
|
|
64
135
|
integer?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Whether the column stores and returns `NaN`, and whether it does the same for an infinity.
|
|
138
|
+
*
|
|
139
|
+
* A range cannot say this. `>=`/`<=` refuses `Infinity` whatever the two numbers are, and `NaN`
|
|
140
|
+
* compares false against both ends, so a bounded float column described by its range alone
|
|
141
|
+
* refused three values Postgres stores in it and hands back on SELECT. That is a read-path
|
|
142
|
+
* defect: every read of such a row fails validation on a column behaving exactly as documented.
|
|
143
|
+
* The generators render these as a union beside the range rather than as a wider range.
|
|
144
|
+
*
|
|
145
|
+
* Measured against PostgreSQL 18.3 through PGlite, on the bound-parameter path a validator
|
|
146
|
+
* guards. `real` and `double precision` return all three unchanged. An unconstrained `numeric`
|
|
147
|
+
* does too, but a `numeric(10,2)` refuses either infinity with `22003 numeric field overflow`
|
|
148
|
+
* while still taking `NaN`, and `integer`/`bigint` refuse all three.
|
|
149
|
+
*
|
|
150
|
+
* So `numeric` in `{ mode: 'number' }` answers each of the two separately: `allowsNaN` at any
|
|
151
|
+
* width, and `allowsInfinity` only where the declaration carries no precision. That used to be a
|
|
152
|
+
* flat `false`, and the recorded reason was that nothing here read a column's precision or scale,
|
|
153
|
+
* so the two declarations were indistinguishable and the narrower answer was the safer one.
|
|
154
|
+
* `declaredDecimalRange` reads both numbers now, the two are distinguishable, and each says what
|
|
155
|
+
* its own server does.
|
|
156
|
+
*
|
|
157
|
+
* Postgres and Gel. MySQL refuses all three on a `float`/`double`, and on a `decimal` refuses
|
|
158
|
+
* them outright rather than storing `0.00`: measured on MySQL 8.4.11 in `STRICT_TRANS_TABLES`,
|
|
159
|
+
* all three answer `Incorrect decimal value`. SQLite returns both infinities and silently turns
|
|
160
|
+
* `NaN` into NULL, which is real and is filed on its own: a column needs both halves of that
|
|
161
|
+
* answer or none.
|
|
162
|
+
*
|
|
163
|
+
* Gel joined on a measurement of its own rather than on being Postgres-backed: a live Gel 7.1
|
|
164
|
+
* stored `nan`, `inf` and `-inf` in both `std::float32` and `std::float64` and handed all three
|
|
165
|
+
* back unchanged, through a cast and through a stored property.
|
|
166
|
+
*
|
|
167
|
+
* Absent on every other column, including the string mode of `numeric`, which already carries the
|
|
168
|
+
* same fact as a pattern; see `COLUMN_FORMATS.numeric` in `@drzl/validation-core`.
|
|
169
|
+
*/
|
|
170
|
+
allowsNaN?: boolean;
|
|
171
|
+
allowsInfinity?: boolean;
|
|
65
172
|
/**
|
|
66
173
|
* A string column whose contents have a shape the database enforces.
|
|
67
174
|
*
|
|
@@ -228,6 +335,17 @@ interface ForeignKey {
|
|
|
228
335
|
name?: string;
|
|
229
336
|
columns: string[];
|
|
230
337
|
foreignTable: string;
|
|
338
|
+
/**
|
|
339
|
+
* The SQL schema the referenced table lives in, absent for the default one, exactly as
|
|
340
|
+
* `Table.schema` is.
|
|
341
|
+
*
|
|
342
|
+
* `foreignTable` is a bare database name and Postgres lets two schemas hold the same one, so a
|
|
343
|
+
* key pointing at `reporting.users` recorded the identical string a key pointing at
|
|
344
|
+
* `public.users` records. Every consumer that resolves a key back to a table object did so by
|
|
345
|
+
* that string, and therefore resolved to whichever of the two it saw first. Use
|
|
346
|
+
* `qualifiedForeignTable` rather than reading the two fields apart.
|
|
347
|
+
*/
|
|
348
|
+
foreignSchema?: string;
|
|
231
349
|
foreignColumns: string[];
|
|
232
350
|
onDelete?: string;
|
|
233
351
|
onUpdate?: string;
|
|
@@ -235,6 +353,15 @@ interface ForeignKey {
|
|
|
235
353
|
interface Table {
|
|
236
354
|
name: string;
|
|
237
355
|
tsName: string;
|
|
356
|
+
/**
|
|
357
|
+
* The SQL schema the table was declared in, from `pgSchema('reporting').table(...)` and the
|
|
358
|
+
* MySQL and SingleStore equivalents. Absent for a table declared with plain `pgTable`, which is
|
|
359
|
+
* the only spelling of the default schema there is: Drizzle refuses `pgSchema('public')`
|
|
360
|
+
* outright, with "Postgres is using public schema by default".
|
|
361
|
+
*
|
|
362
|
+
* `name` stays bare, so two tables in two schemas share one. `qualifiedTableName` is what tells
|
|
363
|
+
* them apart, and is what every name-addressed surface in DRZL matches against.
|
|
364
|
+
*/
|
|
238
365
|
schema?: string;
|
|
239
366
|
columns: Column[];
|
|
240
367
|
primaryKey?: Key;
|
|
@@ -254,6 +381,28 @@ interface Enum {
|
|
|
254
381
|
name: string;
|
|
255
382
|
values: string[];
|
|
256
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* The one name that identifies a table across every SQL schema in an analysis.
|
|
386
|
+
*
|
|
387
|
+
* `reporting.users` where the table names a schema, and the bare `users` where it does not. The
|
|
388
|
+
* bare form for the default schema is deliberate and is what makes this safe to reach for
|
|
389
|
+
* everywhere: on a schema module that never calls `pgSchema`, and that is nearly all of them, this
|
|
390
|
+
* returns exactly `table.name`, so every file name, every export, every config pattern and every
|
|
391
|
+
* emitted path is byte for byte what it was.
|
|
392
|
+
*
|
|
393
|
+
* `public.users` is not produced here. Drizzle refuses `pgSchema('public')`, so no analysis can
|
|
394
|
+
* ever carry `schema: 'public'`, and a table with no schema *is* the public one. `public.` exists
|
|
395
|
+
* only as a spelling a config may use, resolved by `@drzl/cli`.
|
|
396
|
+
*/
|
|
397
|
+
declare function qualifiedTableName(table: {
|
|
398
|
+
name: string;
|
|
399
|
+
schema?: string;
|
|
400
|
+
}): string;
|
|
401
|
+
/** The same name, for the far end of a foreign key. */
|
|
402
|
+
declare function qualifiedForeignTable(fk: {
|
|
403
|
+
foreignTable: string;
|
|
404
|
+
foreignSchema?: string;
|
|
405
|
+
}): string;
|
|
257
406
|
interface Analysis {
|
|
258
407
|
drizzleVersion?: string;
|
|
259
408
|
dialect: Dialect;
|
|
@@ -451,6 +600,31 @@ declare class SchemaAnalyzer {
|
|
|
451
600
|
* `number double` on drizzle v1, which is where these pairings come from.
|
|
452
601
|
*/
|
|
453
602
|
private static readonly INEXACT_RANGES;
|
|
603
|
+
/**
|
|
604
|
+
* The Postgres number columns that hold a non-finite double, and which of the three each holds.
|
|
605
|
+
*
|
|
606
|
+
* The class-name half of what `describeV1Column` reads off the codec, and the two must agree: a
|
|
607
|
+
* fact stated on one path and not the other is a schema that changes when the user upgrades
|
|
608
|
+
* drizzle, which the cross-major diff in `verify-packed.sh` fails on. These three class names are
|
|
609
|
+
* the same on both majors, read off real `pgTable` columns on 0.45.2 and on 1.0.0-rc.4, so this
|
|
610
|
+
* table also answers for a v1 column and the two answers are identical rather than merely
|
|
611
|
+
* compatible. non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
612
|
+
*
|
|
613
|
+
* No MySQL, SingleStore or SQLite class belongs here: MySQL refuses all three on a `float`/
|
|
614
|
+
* `double` and stores `0.00` for a `decimal`, and SQLite returns both infinities while silently
|
|
615
|
+
* turning `NaN` into NULL, which is a different answer that has to arrive whole.
|
|
616
|
+
*
|
|
617
|
+
* Gel does belong, and is the fourth and fifth entries. Measured on a live Gel 7.1 rather than
|
|
618
|
+
* inferred from it being Postgres-backed: both `std::float32` and `std::float64` stored `nan`,
|
|
619
|
+
* `inf` and `-inf` and handed all three back as `NaN`, `Infinity` and `-Infinity`, through a cast
|
|
620
|
+
* and again through a stored property. Without them every row of such a column failed validation.
|
|
621
|
+
*
|
|
622
|
+
* `PgNumeric` is absent because its value is a string, and its pattern already accepts `NaN` and
|
|
623
|
+
* `Infinity`. `PgNumericNumber` is absent because its answer is no longer flat: it takes `NaN` at
|
|
624
|
+
* any width and an infinity only where no precision is declared, which is a per-column question
|
|
625
|
+
* this table cannot ask. `columnConstraints` answers it beside the bound that decides it.
|
|
626
|
+
*/
|
|
627
|
+
private static readonly PG_NON_FINITE;
|
|
454
628
|
/**
|
|
455
629
|
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
456
630
|
*
|
|
@@ -463,4 +637,4 @@ declare class SchemaAnalyzer {
|
|
|
463
637
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
464
638
|
}
|
|
465
639
|
|
|
466
|
-
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, isDrizzleView, isReadOnlyRelation, isRelationsV2, readRelationsV2 };
|
|
640
|
+
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, type UnnameableReason, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, qualifiedForeignTable, qualifiedTableName, readRelationsV2 };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,28 +8,91 @@ interface Issue {
|
|
|
8
8
|
level: 'info' | 'warn' | 'error';
|
|
9
9
|
message: string;
|
|
10
10
|
hint?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Where the issue is, as `table` or `table.column`.
|
|
13
|
+
*
|
|
14
|
+
* Declared since this interface existed and set by nothing, so every consumer wanting to group
|
|
15
|
+
* warnings by table had to read the names back out of the English in `message`. `drzl doctor` is
|
|
16
|
+
* the first such consumer and a report built by regex over prose breaks the first time a message
|
|
17
|
+
* is reworded, so the names are stated here instead.
|
|
18
|
+
*
|
|
19
|
+
* Still optional: an issue about the schema as a whole, such as an unidentifiable dialect, is
|
|
20
|
+
* about no table and says so by omitting this.
|
|
21
|
+
*/
|
|
11
22
|
path?: string;
|
|
12
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Why a column has no type, where "nobody has modelled it" is not the answer.
|
|
26
|
+
*
|
|
27
|
+
* The distinction exists because it changes the advice. A column class this file has no arm for is
|
|
28
|
+
* a gap someone can close, and the warning tells its author to say so. A Gel temporal column is
|
|
29
|
+
* not: the value is an instance of a class from the `gel` package, DRZL cannot import that package,
|
|
30
|
+
* and no generator could emit a check for it even knowing the name. Leaving it `unknown` is the
|
|
31
|
+
* measured answer rather than an omission, and its warning should say that instead of asking for a
|
|
32
|
+
* bug report that is already closed.
|
|
33
|
+
*/
|
|
34
|
+
type UnnameableReason = 'gel-temporal';
|
|
13
35
|
interface ColumnRef {
|
|
14
36
|
table: string;
|
|
15
37
|
column: string;
|
|
16
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A link between two tables, each end named by `qualifiedTableName`.
|
|
41
|
+
*
|
|
42
|
+
* Qualified rather than bare, because a bare database name identifies a table only while no two
|
|
43
|
+
* SQL schemas hold it: `to: 'users'` cannot say whether it means `public.users` or
|
|
44
|
+
* `reporting.users`, and every consumer resolves these strings back to a table object. A table in
|
|
45
|
+
* the default schema has no prefix, so nothing about a single-schema analysis changes.
|
|
46
|
+
*/
|
|
17
47
|
interface Relation {
|
|
18
48
|
kind: 'one' | 'many' | 'manyToMany';
|
|
49
|
+
/** Qualified table name: `users`, or `reporting.users`. */
|
|
19
50
|
from: string;
|
|
51
|
+
/** Qualified table name: `users`, or `reporting.users`. */
|
|
20
52
|
to: string;
|
|
53
|
+
/** Qualified name of the join table, for m2m. */
|
|
21
54
|
via?: string;
|
|
22
55
|
}
|
|
23
56
|
interface Column {
|
|
24
57
|
name: string;
|
|
25
58
|
tsType: string;
|
|
59
|
+
/**
|
|
60
|
+
* A coarse label for the column's kind, not its type.
|
|
61
|
+
*
|
|
62
|
+
* `varchar`, `char` and `text` are all `TEXT` here, deliberately: exactly one consumer reads
|
|
63
|
+
* this, `isIntegerColumn`, and it only asks whether a number is whole. Use `sqlType` for the
|
|
64
|
+
* question this name suggests it answers.
|
|
65
|
+
*/
|
|
26
66
|
dbType: string;
|
|
67
|
+
/**
|
|
68
|
+
* The column's type as the database declares it, from Drizzle's own `getSQLType()`:
|
|
69
|
+
* `varchar(255)`, `numeric(10, 2)`, `timestamp with time zone`, `text[]`, or an enum's type
|
|
70
|
+
* name.
|
|
71
|
+
*
|
|
72
|
+
* The one fact about a column that no validator schema can carry, and the one every other fact
|
|
73
|
+
* here is a consequence of. A generator that emits metadata beside its schemas has nothing else
|
|
74
|
+
* to put in it: `dbType` is a label rather than a type, and the declared width lives in
|
|
75
|
+
* `maxLength`, the precision in `min`/`max`, and neither of those says which type produced it.
|
|
76
|
+
*
|
|
77
|
+
* The two Drizzle majors disagree about an array and are reconciled here, measured rather than
|
|
78
|
+
* assumed: 0.4x wraps the column in a `PgArray` whose own answer is already `text[]`, while v1
|
|
79
|
+
* leaves the class alone and raises `dimensions`, so its answer is the bare `text`. The suffix
|
|
80
|
+
* is added from `arrayDimensions` when the type does not already carry one, so a consumer
|
|
81
|
+
* cannot tell which major produced its metadata.
|
|
82
|
+
*
|
|
83
|
+
* Absent where the builder has no `getSQLType` or it throws. Nothing is guessed from the class
|
|
84
|
+
* name: an invented type string reads exactly like a real one, and there is no way for a
|
|
85
|
+
* consumer to tell them apart.
|
|
86
|
+
*/
|
|
87
|
+
sqlType?: string;
|
|
27
88
|
nullable: boolean;
|
|
28
89
|
hasDefault: boolean;
|
|
29
90
|
isGenerated: boolean;
|
|
30
91
|
defaultExpression?: string;
|
|
31
92
|
references?: {
|
|
32
93
|
table: string;
|
|
94
|
+
/** SQL schema of the referenced table, absent for the default one. See `ForeignKey`. */
|
|
95
|
+
schema?: string;
|
|
33
96
|
column: string;
|
|
34
97
|
onDelete?: string;
|
|
35
98
|
onUpdate?: string;
|
|
@@ -48,8 +111,16 @@ interface Column {
|
|
|
48
111
|
*
|
|
49
112
|
* Strings rather than numbers because a 64 bit bound is not representable as a JS number:
|
|
50
113
|
* `9223372036854775807` rounds to `9223372036854775808` the moment it becomes one, so a
|
|
51
|
-
* numeric field here would silently emit a wrong bound.
|
|
52
|
-
*
|
|
114
|
+
* numeric field here would silently emit a wrong bound. A 20 digit `numeric(20,0)` bound is
|
|
115
|
+
* further past that again.
|
|
116
|
+
*
|
|
117
|
+
* Not an integer range, despite the name this field used to be described by. An inexact column
|
|
118
|
+
* carries one too: a `real` is bounded by the magnitude the database refuses past, and a
|
|
119
|
+
* `numeric(10,2)` by the width its own declaration states. `integer` says which kind it is, and
|
|
120
|
+
* saying it is what stops a bounded float schema refusing 1.5.
|
|
121
|
+
*
|
|
122
|
+
* Absent where nothing declares a bound: an 8 byte float holds every finite JS number, and a
|
|
123
|
+
* `numeric` with no precision holds arbitrary precision.
|
|
53
124
|
*/
|
|
54
125
|
min?: string;
|
|
55
126
|
max?: string;
|
|
@@ -62,6 +133,42 @@ interface Column {
|
|
|
62
133
|
* generators fall back to the old inference there.
|
|
63
134
|
*/
|
|
64
135
|
integer?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Whether the column stores and returns `NaN`, and whether it does the same for an infinity.
|
|
138
|
+
*
|
|
139
|
+
* A range cannot say this. `>=`/`<=` refuses `Infinity` whatever the two numbers are, and `NaN`
|
|
140
|
+
* compares false against both ends, so a bounded float column described by its range alone
|
|
141
|
+
* refused three values Postgres stores in it and hands back on SELECT. That is a read-path
|
|
142
|
+
* defect: every read of such a row fails validation on a column behaving exactly as documented.
|
|
143
|
+
* The generators render these as a union beside the range rather than as a wider range.
|
|
144
|
+
*
|
|
145
|
+
* Measured against PostgreSQL 18.3 through PGlite, on the bound-parameter path a validator
|
|
146
|
+
* guards. `real` and `double precision` return all three unchanged. An unconstrained `numeric`
|
|
147
|
+
* does too, but a `numeric(10,2)` refuses either infinity with `22003 numeric field overflow`
|
|
148
|
+
* while still taking `NaN`, and `integer`/`bigint` refuse all three.
|
|
149
|
+
*
|
|
150
|
+
* So `numeric` in `{ mode: 'number' }` answers each of the two separately: `allowsNaN` at any
|
|
151
|
+
* width, and `allowsInfinity` only where the declaration carries no precision. That used to be a
|
|
152
|
+
* flat `false`, and the recorded reason was that nothing here read a column's precision or scale,
|
|
153
|
+
* so the two declarations were indistinguishable and the narrower answer was the safer one.
|
|
154
|
+
* `declaredDecimalRange` reads both numbers now, the two are distinguishable, and each says what
|
|
155
|
+
* its own server does.
|
|
156
|
+
*
|
|
157
|
+
* Postgres and Gel. MySQL refuses all three on a `float`/`double`, and on a `decimal` refuses
|
|
158
|
+
* them outright rather than storing `0.00`: measured on MySQL 8.4.11 in `STRICT_TRANS_TABLES`,
|
|
159
|
+
* all three answer `Incorrect decimal value`. SQLite returns both infinities and silently turns
|
|
160
|
+
* `NaN` into NULL, which is real and is filed on its own: a column needs both halves of that
|
|
161
|
+
* answer or none.
|
|
162
|
+
*
|
|
163
|
+
* Gel joined on a measurement of its own rather than on being Postgres-backed: a live Gel 7.1
|
|
164
|
+
* stored `nan`, `inf` and `-inf` in both `std::float32` and `std::float64` and handed all three
|
|
165
|
+
* back unchanged, through a cast and through a stored property.
|
|
166
|
+
*
|
|
167
|
+
* Absent on every other column, including the string mode of `numeric`, which already carries the
|
|
168
|
+
* same fact as a pattern; see `COLUMN_FORMATS.numeric` in `@drzl/validation-core`.
|
|
169
|
+
*/
|
|
170
|
+
allowsNaN?: boolean;
|
|
171
|
+
allowsInfinity?: boolean;
|
|
65
172
|
/**
|
|
66
173
|
* A string column whose contents have a shape the database enforces.
|
|
67
174
|
*
|
|
@@ -228,6 +335,17 @@ interface ForeignKey {
|
|
|
228
335
|
name?: string;
|
|
229
336
|
columns: string[];
|
|
230
337
|
foreignTable: string;
|
|
338
|
+
/**
|
|
339
|
+
* The SQL schema the referenced table lives in, absent for the default one, exactly as
|
|
340
|
+
* `Table.schema` is.
|
|
341
|
+
*
|
|
342
|
+
* `foreignTable` is a bare database name and Postgres lets two schemas hold the same one, so a
|
|
343
|
+
* key pointing at `reporting.users` recorded the identical string a key pointing at
|
|
344
|
+
* `public.users` records. Every consumer that resolves a key back to a table object did so by
|
|
345
|
+
* that string, and therefore resolved to whichever of the two it saw first. Use
|
|
346
|
+
* `qualifiedForeignTable` rather than reading the two fields apart.
|
|
347
|
+
*/
|
|
348
|
+
foreignSchema?: string;
|
|
231
349
|
foreignColumns: string[];
|
|
232
350
|
onDelete?: string;
|
|
233
351
|
onUpdate?: string;
|
|
@@ -235,6 +353,15 @@ interface ForeignKey {
|
|
|
235
353
|
interface Table {
|
|
236
354
|
name: string;
|
|
237
355
|
tsName: string;
|
|
356
|
+
/**
|
|
357
|
+
* The SQL schema the table was declared in, from `pgSchema('reporting').table(...)` and the
|
|
358
|
+
* MySQL and SingleStore equivalents. Absent for a table declared with plain `pgTable`, which is
|
|
359
|
+
* the only spelling of the default schema there is: Drizzle refuses `pgSchema('public')`
|
|
360
|
+
* outright, with "Postgres is using public schema by default".
|
|
361
|
+
*
|
|
362
|
+
* `name` stays bare, so two tables in two schemas share one. `qualifiedTableName` is what tells
|
|
363
|
+
* them apart, and is what every name-addressed surface in DRZL matches against.
|
|
364
|
+
*/
|
|
238
365
|
schema?: string;
|
|
239
366
|
columns: Column[];
|
|
240
367
|
primaryKey?: Key;
|
|
@@ -254,6 +381,28 @@ interface Enum {
|
|
|
254
381
|
name: string;
|
|
255
382
|
values: string[];
|
|
256
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* The one name that identifies a table across every SQL schema in an analysis.
|
|
386
|
+
*
|
|
387
|
+
* `reporting.users` where the table names a schema, and the bare `users` where it does not. The
|
|
388
|
+
* bare form for the default schema is deliberate and is what makes this safe to reach for
|
|
389
|
+
* everywhere: on a schema module that never calls `pgSchema`, and that is nearly all of them, this
|
|
390
|
+
* returns exactly `table.name`, so every file name, every export, every config pattern and every
|
|
391
|
+
* emitted path is byte for byte what it was.
|
|
392
|
+
*
|
|
393
|
+
* `public.users` is not produced here. Drizzle refuses `pgSchema('public')`, so no analysis can
|
|
394
|
+
* ever carry `schema: 'public'`, and a table with no schema *is* the public one. `public.` exists
|
|
395
|
+
* only as a spelling a config may use, resolved by `@drzl/cli`.
|
|
396
|
+
*/
|
|
397
|
+
declare function qualifiedTableName(table: {
|
|
398
|
+
name: string;
|
|
399
|
+
schema?: string;
|
|
400
|
+
}): string;
|
|
401
|
+
/** The same name, for the far end of a foreign key. */
|
|
402
|
+
declare function qualifiedForeignTable(fk: {
|
|
403
|
+
foreignTable: string;
|
|
404
|
+
foreignSchema?: string;
|
|
405
|
+
}): string;
|
|
257
406
|
interface Analysis {
|
|
258
407
|
drizzleVersion?: string;
|
|
259
408
|
dialect: Dialect;
|
|
@@ -451,6 +600,31 @@ declare class SchemaAnalyzer {
|
|
|
451
600
|
* `number double` on drizzle v1, which is where these pairings come from.
|
|
452
601
|
*/
|
|
453
602
|
private static readonly INEXACT_RANGES;
|
|
603
|
+
/**
|
|
604
|
+
* The Postgres number columns that hold a non-finite double, and which of the three each holds.
|
|
605
|
+
*
|
|
606
|
+
* The class-name half of what `describeV1Column` reads off the codec, and the two must agree: a
|
|
607
|
+
* fact stated on one path and not the other is a schema that changes when the user upgrades
|
|
608
|
+
* drizzle, which the cross-major diff in `verify-packed.sh` fails on. These three class names are
|
|
609
|
+
* the same on both majors, read off real `pgTable` columns on 0.45.2 and on 1.0.0-rc.4, so this
|
|
610
|
+
* table also answers for a v1 column and the two answers are identical rather than merely
|
|
611
|
+
* compatible. non-finite-numbers.spec.ts asserts that agreement through the real analyzer.
|
|
612
|
+
*
|
|
613
|
+
* No MySQL, SingleStore or SQLite class belongs here: MySQL refuses all three on a `float`/
|
|
614
|
+
* `double` and stores `0.00` for a `decimal`, and SQLite returns both infinities while silently
|
|
615
|
+
* turning `NaN` into NULL, which is a different answer that has to arrive whole.
|
|
616
|
+
*
|
|
617
|
+
* Gel does belong, and is the fourth and fifth entries. Measured on a live Gel 7.1 rather than
|
|
618
|
+
* inferred from it being Postgres-backed: both `std::float32` and `std::float64` stored `nan`,
|
|
619
|
+
* `inf` and `-inf` and handed all three back as `NaN`, `Infinity` and `-Infinity`, through a cast
|
|
620
|
+
* and again through a stored property. Without them every row of such a column failed validation.
|
|
621
|
+
*
|
|
622
|
+
* `PgNumeric` is absent because its value is a string, and its pattern already accepts `NaN` and
|
|
623
|
+
* `Infinity`. `PgNumericNumber` is absent because its answer is no longer flat: it takes `NaN` at
|
|
624
|
+
* any width and an infinity only where no precision is declared, which is a per-column question
|
|
625
|
+
* this table cannot ask. `columnConstraints` answers it beside the bound that decides it.
|
|
626
|
+
*/
|
|
627
|
+
private static readonly PG_NON_FINITE;
|
|
454
628
|
/**
|
|
455
629
|
* Constraints the column definition already carries, which the analysis used to throw away.
|
|
456
630
|
*
|
|
@@ -463,4 +637,4 @@ declare class SchemaAnalyzer {
|
|
|
463
637
|
analyze(opts?: AnalyzeOptions): Promise<Analysis>;
|
|
464
638
|
}
|
|
465
639
|
|
|
466
|
-
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, isDrizzleView, isReadOnlyRelation, isRelationsV2, readRelationsV2 };
|
|
640
|
+
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, type UnnameableReason, SchemaAnalyzer as default, describeV1Column, isDrizzleView, isReadOnlyRelation, isRelationsV2, qualifiedForeignTable, qualifiedTableName, readRelationsV2 };
|