@rebasepro/server-postgres 0.17.3-canary.gdd23447 → 0.18.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.
Files changed (149) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +31 -0
  3. package/dist/backup-argv.d.ts +19 -0
  4. package/dist/branch-argv.d.ts +28 -0
  5. package/dist/branch-prune.d.ts +77 -0
  6. package/dist/cli-collections-path.d.ts +31 -0
  7. package/dist/cli-errors.d.ts +123 -0
  8. package/dist/cli-flags.d.ts +101 -0
  9. package/dist/cli-helpers.d.ts +94 -10
  10. package/dist/collection-index-C7LopMyX.js +247 -0
  11. package/dist/collection-index-C7LopMyX.js.map +1 -0
  12. package/dist/databasePoolManager.d.ts +27 -0
  13. package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-CagOzEPS.js} +6 -6
  14. package/dist/{ensure-collection-policies-UCqgv_8c.js.map → ensure-collection-policies-CagOzEPS.js.map} +1 -1
  15. package/dist/{ensure-collection-tables-DgVixhX3.js → ensure-collection-tables-DbAhrjnG.js} +614 -52
  16. package/dist/ensure-collection-tables-DbAhrjnG.js.map +1 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.es.js +19307 -3059
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-ClJoTboT.js} +53 -189
  21. package/dist/rls-enforcement-ClJoTboT.js.map +1 -0
  22. package/dist/schema/column-type-drift.d.ts +79 -0
  23. package/dist/schema/doctor.d.ts +42 -0
  24. package/dist/schema/ensure-collection-tables.d.ts +90 -0
  25. package/dist/schema/generate-next-step.d.ts +30 -0
  26. package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
  27. package/dist/schema/generated-schema-staleness.d.ts +37 -0
  28. package/dist/schema/introspect-db-queries.d.ts +34 -0
  29. package/dist/security/rls-enforcement.d.ts +63 -2
  30. package/dist/services/BranchService.d.ts +86 -1
  31. package/dist/services/collection-helpers.d.ts +1 -1
  32. package/dist/services/realtimeService.d.ts +30 -2
  33. package/dist/services/row-pipeline.d.ts +12 -1
  34. package/dist/utils/pg-error-utils.d.ts +3 -1
  35. package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
  36. package/package.json +32 -27
  37. package/dist/backup-service-DCk7KhhL.js +0 -9044
  38. package/dist/backup-service-DCk7KhhL.js.map +0 -1
  39. package/dist/collection-index-BRUg10H5.js +0 -5690
  40. package/dist/collection-index-BRUg10H5.js.map +0 -1
  41. package/dist/connection-GOKU3Hu5.js +0 -277
  42. package/dist/connection-GOKU3Hu5.js.map +0 -1
  43. package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
  44. package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
  45. package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
  46. package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
  47. package/dist/src-DiB5RP2Z.js +0 -541
  48. package/dist/src-DiB5RP2Z.js.map +0 -1
  49. package/dist/websocket-BZ4H5wUz.js +0 -8198
  50. package/dist/websocket-BZ4H5wUz.js.map +0 -1
  51. package/src/PostgresAdapter.ts +0 -91
  52. package/src/PostgresBackendDriver.ts +0 -1857
  53. package/src/PostgresBootstrapper.ts +0 -1157
  54. package/src/auth/ensure-tables.ts +0 -896
  55. package/src/auth/schema-version.ts +0 -260
  56. package/src/auth/services.ts +0 -1610
  57. package/src/backup/backup-cli.ts +0 -520
  58. package/src/backup/backup-cron.ts +0 -212
  59. package/src/backup/backup-logic.ts +0 -93
  60. package/src/backup/backup-service.ts +0 -485
  61. package/src/backup/index.ts +0 -12
  62. package/src/backup/pg-tools.ts +0 -403
  63. package/src/backup/retention.ts +0 -86
  64. package/src/cli-errors.ts +0 -265
  65. package/src/cli-helpers.ts +0 -569
  66. package/src/cli-output.ts +0 -43
  67. package/src/cli.ts +0 -1339
  68. package/src/collections/PostgresCollectionRegistry.ts +0 -108
  69. package/src/collections/buildRegistry.ts +0 -68
  70. package/src/collections/validate-relations.ts +0 -402
  71. package/src/connection.ts +0 -333
  72. package/src/data-transformer.ts +0 -845
  73. package/src/databasePoolManager.ts +0 -95
  74. package/src/history/HistoryService.ts +0 -254
  75. package/src/history/ensure-history-table.ts +0 -53
  76. package/src/index.ts +0 -19
  77. package/src/interfaces.ts +0 -60
  78. package/src/module-dir.ts +0 -7
  79. package/src/schema/atlas-argv.ts +0 -94
  80. package/src/schema/auth-schema.ts +0 -298
  81. package/src/schema/auth-users-columns.ts +0 -131
  82. package/src/schema/carved-out-migration.ts +0 -404
  83. package/src/schema/classify-change.ts +0 -436
  84. package/src/schema/collection-index.ts +0 -427
  85. package/src/schema/collections-schema-version.ts +0 -103
  86. package/src/schema/destructive-sql.ts +0 -94
  87. package/src/schema/doctor-cli.ts +0 -66
  88. package/src/schema/doctor-policy-checks.ts +0 -105
  89. package/src/schema/doctor.ts +0 -837
  90. package/src/schema/drizzle-ddl.ts +0 -33
  91. package/src/schema/dynamic-tables.test.ts +0 -302
  92. package/src/schema/dynamic-tables.ts +0 -293
  93. package/src/schema/ensure-collection-policies.ts +0 -198
  94. package/src/schema/ensure-collection-tables.test.ts +0 -518
  95. package/src/schema/ensure-collection-tables.ts +0 -1267
  96. package/src/schema/generate-drizzle-schema-logic.ts +0 -957
  97. package/src/schema/generate-drizzle-schema.ts +0 -143
  98. package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
  99. package/src/schema/generate-postgres-ddl.ts +0 -180
  100. package/src/schema/generate-schema-commit.ts +0 -267
  101. package/src/schema/generated-schema-staleness.ts +0 -284
  102. package/src/schema/introspect-db-constraints.ts +0 -385
  103. package/src/schema/introspect-db-inference.ts +0 -255
  104. package/src/schema/introspect-db-logic.ts +0 -1387
  105. package/src/schema/introspect-db-naming.ts +0 -15
  106. package/src/schema/introspect-db-project.ts +0 -78
  107. package/src/schema/introspect-db-queries.ts +0 -326
  108. package/src/schema/introspect-db-structure.ts +0 -670
  109. package/src/schema/introspect-db-types.ts +0 -56
  110. package/src/schema/introspect-db.ts +0 -257
  111. package/src/schema/introspect-runtime.test.ts +0 -260
  112. package/src/schema/introspect-runtime.ts +0 -327
  113. package/src/schema/non-sql-collections.test.ts +0 -131
  114. package/src/schema/rls-bootstrap-sql.ts +0 -288
  115. package/src/schema/search-column.ts +0 -643
  116. package/src/schema/test-schema.ts +0 -11
  117. package/src/schema/vector-index.ts +0 -491
  118. package/src/security/anonymous-grants.test.ts +0 -73
  119. package/src/security/policy-drift.test.ts +0 -435
  120. package/src/security/policy-drift.ts +0 -473
  121. package/src/security/rls-enforcement.ts +0 -505
  122. package/src/services/BranchService.ts +0 -326
  123. package/src/services/FetchService.ts +0 -2115
  124. package/src/services/PersistService.ts +0 -557
  125. package/src/services/RelationService.ts +0 -978
  126. package/src/services/RelationWriteService.ts +0 -653
  127. package/src/services/cdc/CdcListener.ts +0 -103
  128. package/src/services/cdc/junction-tables.ts +0 -91
  129. package/src/services/cdc/trigger-cdc.ts +0 -173
  130. package/src/services/channel-bus/ChannelBus.ts +0 -44
  131. package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
  132. package/src/services/channel-bus/index.ts +0 -114
  133. package/src/services/channel-history.ts +0 -424
  134. package/src/services/channel-presence.ts +0 -185
  135. package/src/services/collection-helpers.ts +0 -420
  136. package/src/services/dataService.ts +0 -256
  137. package/src/services/index.ts +0 -14
  138. package/src/services/junction-writes.ts +0 -295
  139. package/src/services/nested-path.ts +0 -145
  140. package/src/services/pg-notify-listener.ts +0 -151
  141. package/src/services/realtimeService.ts +0 -2545
  142. package/src/services/row-pipeline.ts +0 -240
  143. package/src/services/write-denial.ts +0 -55
  144. package/src/types.ts +0 -4
  145. package/src/utils/connection-string.ts +0 -58
  146. package/src/utils/drizzle-conditions.ts +0 -2743
  147. package/src/utils/pg-array-null-patch.ts +0 -42
  148. package/src/utils/pg-error-utils.ts +0 -391
  149. package/src/websocket.ts +0 -855
@@ -1,15 +0,0 @@
1
- /**
2
- * Naming helpers shared by the introspection modules. These live apart from
3
- * `introspect-db-logic.ts` because the inference pass needs them too, and
4
- * importing them from there would close a cycle back through this module.
5
- */
6
-
7
- /**
8
- * Convert a snake_case name to a human-readable Title Case label.
9
- * e.g. "created_at" -> "Created At", "customer_id" -> "Customer Id"
10
- */
11
- export function humanize(snakeName: string): string {
12
- return snakeName
13
- .replace(/_/g, " ")
14
- .replace(/\b\w/g, (c) => c.toUpperCase());
15
- }
@@ -1,78 +0,0 @@
1
- /**
2
- * What the project being introspected into can actually import.
3
- *
4
- * The generator is pure — `introspect-db-logic.ts` reads no files — so the one
5
- * question it cannot answer for itself lives here: which `defineCollection`, if
6
- * any, will resolve from the directory the collections are written to.
7
- */
8
- import fs from "fs";
9
- import path from "path";
10
-
11
- import { ADMIN_TYPES_PACKAGE, COMMON_PACKAGE, type CollectionBuilder } from "./introspect-db-logic";
12
-
13
- /** How far up to look before giving up, if no project root announces itself. */
14
- const MAX_LEVELS = 8;
15
-
16
- /** Every dependency name a manifest declares, in any of the three fields. */
17
- function declaredDependencies(manifestPath: string): Set<string> {
18
- const names = new Set<string>();
19
- let parsed: unknown;
20
- try {
21
- parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
22
- } catch {
23
- // A malformed or unreadable package.json is not this function's problem
24
- // to report — it answers "can this import resolve", and the answer is no.
25
- return names;
26
- }
27
- if (typeof parsed !== "object" || parsed === null) return names;
28
- const manifest = parsed as Record<string, unknown>;
29
- for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
30
- const block = manifest[field];
31
- if (typeof block === "object" && block !== null) {
32
- for (const name of Object.keys(block)) names.add(name);
33
- }
34
- }
35
- return names;
36
- }
37
-
38
- /**
39
- * Which `defineCollection` to generate against, for collections written to `outDir`.
40
- *
41
- * **The detection is the package manifests above the output directory**, unioned from
42
- * `outDir` up to and including the project root — the first ancestor holding a
43
- * `rebase.json`, or `MAX_LEVELS` up if there is none.
44
- *
45
- * That is the rule Node itself applies: a file in `config/collections` resolves a bare
46
- * specifier through `config/node_modules`, then `<project>/node_modules`, and so on up.
47
- * Reading the manifests along that same path answers the only question that matters —
48
- * *will this import resolve in the project I am writing into* — from the state on disk
49
- * at the moment of generation. The alternatives are all proxies for it: `rebase.json`'s
50
- * `apps` block says a CMS scaffold declared an admin app, and a `frontend/` directory
51
- * says one was scaffolded, but neither is what the compiler consults, and either can be
52
- * true of a project whose `config` package does not depend on `@rebasepro/cms-types`.
53
- *
54
- * Ambiguity resolves towards the admin panel: a project that declares both packages has
55
- * a panel, and `@rebasepro/cms-types` is the flavour that keeps the `admin` block.
56
- */
57
- export function detectCollectionBuilder(outDir: string): CollectionBuilder {
58
- let dir = path.resolve(outDir);
59
- const declared = new Set<string>();
60
-
61
- for (let level = 0; level < MAX_LEVELS; level++) {
62
- const manifest = path.join(dir, "package.json");
63
- if (fs.existsSync(manifest)) {
64
- for (const name of declaredDependencies(manifest)) declared.add(name);
65
- }
66
- // The project root, and the last level worth reading: anything above it
67
- // belongs to whatever the project happens to be checked out inside.
68
- if (fs.existsSync(path.join(dir, "rebase.json"))) break;
69
-
70
- const parent = path.dirname(dir);
71
- if (parent === dir) break;
72
- dir = parent;
73
- }
74
-
75
- if (declared.has(ADMIN_TYPES_PACKAGE)) return "admin-types";
76
- if (declared.has(COMMON_PACKAGE)) return "common";
77
- return "annotation";
78
- }
@@ -1,326 +0,0 @@
1
- /**
2
- * The catalog queries introspection runs, and the shape they produce.
3
- *
4
- * These live apart from {@link ./introspect-db.ts} (the CLI entry point) on
5
- * purpose: the fixture-capture script and the live e2e read a database through
6
- * this module too, so what the tests see is what the CLI sees. A query that
7
- * only exists inside the CLI's `main()` can only be tested by running the CLI.
8
- *
9
- * No side effects beyond `SELECT`. Introspection reads a database it does not
10
- * own — it must never `ANALYZE`, create a temp table, or otherwise write.
11
- */
12
- import type {
13
- TableRow,
14
- TableColumn,
15
- EnumValue,
16
- PrimaryKeyRow,
17
- ForeignKeyRow,
18
- UniqueConstraintRow,
19
- CheckConstraintRow,
20
- CommentRow,
21
- SchemaMetadata
22
- } from "./introspect-db-logic";
23
-
24
- /** The subset of `pg.Client` this module needs, so tests can pass a fake. */
25
- export interface QueryableClient {
26
- query<R = Record<string, unknown>>(text: string, values?: unknown[]): Promise<{ rows: R[] }>;
27
- }
28
-
29
- /**
30
- * Base tables, excluding partitions.
31
- *
32
- * `relispartition` is the reason this reads `pg_class` rather than
33
- * `information_schema.tables`, which reports every partition as a base table of
34
- * its own. Pagila partitions `payment` by month; introspected through
35
- * information_schema it yields `payment` plus 26 near-identical
36
- * `payment_p2022_*` collections, each with its own nav entry.
37
- *
38
- * `relkind = 'p'` (the partitioned parent) is included and `'r'` partitions are
39
- * dropped, which is the right way round: the parent is the queryable table.
40
- */
41
- export const TABLES_QUERY = `
42
- SELECT c.relname AS table_name,
43
- c.relkind = 'p' AS is_partitioned
44
- FROM pg_class c
45
- JOIN pg_namespace n ON n.oid = c.relnamespace
46
- WHERE n.nspname = $1
47
- AND c.relkind IN ('r', 'p')
48
- AND NOT c.relispartition
49
- AND c.relname NOT LIKE 'drizzle_%'
50
- AND c.relname NOT LIKE 'rebase_%'
51
- ORDER BY c.relname
52
- `;
53
-
54
- /**
55
- * Columns, with the catalog facts that say a column is not the user's to edit.
56
- *
57
- * `is_generated`/`is_identity` are read for that reason: a generated column
58
- * rejects any write, so a form that offers it is offering a field that always
59
- * errors. `character_maximum_length` is the declared `varchar(n)` bound —
60
- * already enforced by the database, and free to surface as validation.
61
- */
62
- export const COLUMNS_QUERY = `
63
- SELECT
64
- c.table_name,
65
- c.column_name,
66
- c.data_type,
67
- c.udt_name,
68
- c.is_nullable,
69
- c.column_default,
70
- c.ordinal_position,
71
- c.is_generated,
72
- c.is_identity,
73
- c.identity_generation,
74
- c.character_maximum_length,
75
- c.numeric_precision,
76
- c.numeric_scale,
77
- (SELECT a.atttypmod FROM pg_attribute a
78
- JOIN pg_class pc ON a.attrelid = pc.oid
79
- WHERE pc.relname = c.table_name
80
- AND a.attname = c.column_name
81
- AND pc.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema)) as atttypmod
82
- FROM information_schema.columns c
83
- WHERE c.table_schema = $1
84
- ORDER BY c.table_name, c.ordinal_position
85
- `;
86
-
87
- export const ENUMS_QUERY = `
88
- SELECT t.typname AS enum_name,
89
- e.enumlabel AS enum_value,
90
- e.enumsortorder AS sort_order
91
- FROM pg_type t
92
- JOIN pg_enum e ON t.oid = e.enumtypid
93
- JOIN pg_namespace n ON t.typnamespace = n.oid
94
- WHERE n.nspname = $1
95
- ORDER BY t.typname, e.enumsortorder
96
- `;
97
-
98
- /**
99
- * Primary key columns, in key order.
100
- *
101
- * `ORDER BY k.ord` matters for composite keys: the position of a column inside
102
- * the key is what tells a two-column PK made of two foreign keys apart from an
103
- * ordinary composite key, and `= ANY(i.indkey)` (the shape this replaced)
104
- * returns catalog order instead.
105
- */
106
- export const PRIMARY_KEYS_QUERY = `
107
- SELECT t.relname AS table_name,
108
- a.attname AS column_name
109
- FROM pg_index i
110
- JOIN pg_class t ON t.oid = i.indrelid
111
- JOIN pg_namespace n ON n.oid = t.relnamespace
112
- CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS k(attnum, ord)
113
- JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum
114
- WHERE i.indisprimary AND n.nspname = $1
115
- ORDER BY t.relname, k.ord
116
- `;
117
-
118
- /**
119
- * Foreign keys, one row per referencing column, with the delete rule.
120
- *
121
- * Read from `pg_constraint` rather than through
122
- * `information_schema.constraint_column_usage`, which does not preserve the
123
- * pairing on a composite foreign key: it reports the cross product of the
124
- * constraint's columns, so a two-column FK comes back as four rows, two of them
125
- * pairing the wrong columns. `unnest(...) WITH ORDINALITY` on both sides joined
126
- * on the ordinal keeps each referencing column with the column it references.
127
- *
128
- * `confdeltype` is carried because `ON DELETE CASCADE` is the schema author
129
- * saying, in the database, that the child cannot outlive the parent — the one
130
- * unambiguous declaration of ownership a schema contains.
131
- *
132
- * Constraints declared on a *partition* are attributed to the partition root,
133
- * because that is the table this run generates a collection for. Pagila declares
134
- * `payment`'s three foreign keys on each monthly partition and none on the
135
- * parent, so reading `pg_constraint` at face value gives the `payment`
136
- * collection no relations at all. `pick = 1` then keeps one row per logical key,
137
- * preferring the root's own constraint when it has one, so a schema that
138
- * declares the key on the parent *and* inherits it onto 26 partitions does not
139
- * yield 27 copies of the same relation.
140
- */
141
- export const FOREIGN_KEYS_QUERY = `
142
- WITH fk AS (
143
- SELECT con.oid,
144
- con.conname,
145
- con.conrelid,
146
- con.confrelid,
147
- con.conkey,
148
- con.confkey,
149
- con.confdeltype,
150
- COALESCE(pg_partition_root(con.conrelid), con.conrelid) AS root_oid,
151
- row_number() OVER (
152
- PARTITION BY COALESCE(pg_partition_root(con.conrelid), con.conrelid),
153
- con.confrelid, con.conkey, con.confkey
154
- ORDER BY (con.conrelid = COALESCE(pg_partition_root(con.conrelid), con.conrelid)) DESC,
155
- con.oid
156
- ) AS pick
157
- FROM pg_constraint con
158
- JOIN pg_namespace n ON n.oid = con.connamespace
159
- WHERE con.contype = 'f' AND n.nspname = $1
160
- )
161
- SELECT root.relname AS table_name,
162
- sa.attname AS column_name,
163
- tgt.relname AS foreign_table_name,
164
- ta.attname AS foreign_column_name,
165
- fk.conname AS constraint_name,
166
- s.ord::int AS ordinal,
167
- CASE fk.confdeltype
168
- WHEN 'a' THEN 'NO ACTION'
169
- WHEN 'r' THEN 'RESTRICT'
170
- WHEN 'c' THEN 'CASCADE'
171
- WHEN 'n' THEN 'SET NULL'
172
- WHEN 'd' THEN 'SET DEFAULT'
173
- END AS delete_rule
174
- FROM fk
175
- JOIN pg_class root ON root.oid = fk.root_oid
176
- JOIN pg_class tgt ON tgt.oid = fk.confrelid
177
- CROSS JOIN LATERAL unnest(fk.conkey) WITH ORDINALITY AS s(attnum, ord)
178
- JOIN pg_attribute sa ON sa.attrelid = fk.conrelid AND sa.attnum = s.attnum
179
- CROSS JOIN LATERAL unnest(fk.confkey) WITH ORDINALITY AS f(attnum, ord)
180
- JOIN pg_attribute ta ON ta.attrelid = fk.confrelid AND ta.attnum = f.attnum
181
- WHERE fk.pick = 1 AND s.ord = f.ord
182
- ORDER BY root.relname, fk.conname, s.ord
183
- `;
184
-
185
- /**
186
- * Unique constraints and unique indexes, as ordered column lists.
187
- *
188
- * Indexes rather than constraints alone, because `CREATE UNIQUE INDEX` and
189
- * `ADD CONSTRAINT ... UNIQUE` produce the same guarantee and schemas use both.
190
- * Partial indexes (`indpred IS NOT NULL`) are excluded: they promise uniqueness
191
- * only over the rows matching their predicate, which is not the promise a
192
- * `validation: { unique: true }` field makes.
193
- */
194
- export const UNIQUE_CONSTRAINTS_QUERY = `
195
- SELECT t.relname AS table_name,
196
- ic.relname AS constraint_name,
197
- -- ::text, because the driver has no parser for an array of \`name\`
198
- -- and hands back the raw \`{a,b}\` literal as a string. Every consumer
199
- -- here indexes and compares it as an array, and a string of that
200
- -- shape fails those silently rather than loudly.
201
- array_agg(a.attname::text ORDER BY k.ord) AS column_names
202
- FROM pg_index i
203
- JOIN pg_class t ON t.oid = i.indrelid
204
- JOIN pg_class ic ON ic.oid = i.indexrelid
205
- JOIN pg_namespace n ON n.oid = t.relnamespace
206
- CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS k(attnum, ord)
207
- JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum
208
- WHERE i.indisunique
209
- AND NOT i.indisprimary
210
- AND i.indpred IS NULL
211
- AND n.nspname = $1
212
- AND NOT t.relispartition
213
- GROUP BY t.relname, ic.relname
214
- ORDER BY t.relname, ic.relname
215
- `;
216
-
217
- /**
218
- * CHECK constraints, as the source text Postgres reproduces them from.
219
- *
220
- * `pg_get_constraintdef` output is normalized by the server — the parser in
221
- * {@link ./introspect-db-constraints.ts} reads that normalized form, not
222
- * whatever the author typed.
223
- */
224
- export const CHECK_CONSTRAINTS_QUERY = `
225
- SELECT DISTINCT
226
- root.relname AS table_name,
227
- con.conname AS constraint_name,
228
- pg_get_constraintdef(con.oid) AS definition
229
- FROM pg_constraint con
230
- JOIN pg_namespace n ON n.oid = con.connamespace
231
- JOIN pg_class root ON root.oid = COALESCE(pg_partition_root(con.conrelid), con.conrelid)
232
- WHERE con.contype = 'c'
233
- AND n.nspname = $1
234
- ORDER BY root.relname, con.conname
235
- `;
236
-
237
- /**
238
- * `COMMENT ON TABLE` / `COMMENT ON COLUMN`, which is documentation the author
239
- * already wrote and which nothing downstream has ever read.
240
- */
241
- export const COMMENTS_QUERY = `
242
- SELECT t.relname AS table_name,
243
- a.attname AS column_name,
244
- d.description AS comment
245
- FROM pg_description d
246
- JOIN pg_class t ON t.oid = d.objoid
247
- JOIN pg_namespace n ON n.oid = t.relnamespace
248
- LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.objsubid
249
- WHERE n.nspname = $1
250
- AND t.relkind IN ('r', 'p')
251
- AND (d.objsubid = 0 OR a.attname IS NOT NULL)
252
- ORDER BY t.relname, d.objsubid
253
- `;
254
-
255
- /**
256
- * Counts a table's rows, stopping once the answer can only be "more than
257
- * `limit`".
258
- *
259
- * Introspection asks this to tell a small reference table from a table that
260
- * merely looks like one (see `classifyTables`). `reltuples` would answer for
261
- * free but lies on a table that has never been analyzed — which is every table
262
- * in a database restored from a dump, i.e. exactly the case introspection meets
263
- * — and reports -1 there rather than an error, so the lie is silent.
264
- *
265
- * The subquery's LIMIT caps the work at `limit + 1` rows however large the
266
- * table is, so this stays cheap on a table with a billion rows.
267
- */
268
- export async function countRowsUpTo(
269
- client: QueryableClient,
270
- schema: string,
271
- table: string,
272
- limit: number
273
- ): Promise<number> {
274
- const { rows } = await client.query<{ count: string }>(
275
- `SELECT count(*)::text AS count FROM (SELECT 1 FROM "${schema}"."${table}" LIMIT ${limit + 1}) probe`
276
- );
277
- return Number(rows[0]?.count ?? 0);
278
- }
279
-
280
- /**
281
- * Reads everything the generator needs, in one pass.
282
- *
283
- * `rowCounts` is deliberately absent here: it costs a query per table and only
284
- * a handful of tables can possibly need it. The caller fills it in for the
285
- * candidates `lookupCandidates()` names.
286
- */
287
- export async function readSchemaMetadata(
288
- client: QueryableClient,
289
- schema: string
290
- ): Promise<SchemaMetadata> {
291
- // Sequential, not `Promise.all`. A `pg.Client` is a single connection with
292
- // one protocol stream: concurrent `query()` calls on one are queued and
293
- // deprecated (removed in pg@9), so the parallel version buys nothing and
294
- // warns while doing it.
295
- const tables = await client.query<TableRow>(TABLES_QUERY, [schema]);
296
- const columns = await client.query<TableColumn>(COLUMNS_QUERY, [schema]);
297
- const enumValues = await client.query<EnumValue>(ENUMS_QUERY, [schema]);
298
- const pks = await client.query<PrimaryKeyRow>(PRIMARY_KEYS_QUERY, [schema]);
299
- const fks = await client.query<ForeignKeyRow>(FOREIGN_KEYS_QUERY, [schema]);
300
- const uniques = await client.query<UniqueConstraintRow>(UNIQUE_CONSTRAINTS_QUERY, [schema]);
301
- const checks = await client.query<CheckConstraintRow>(CHECK_CONSTRAINTS_QUERY, [schema]);
302
- const comments = await client.query<CommentRow>(COMMENTS_QUERY, [schema]);
303
-
304
- // A column list scoped to the schema still names tables this run excludes —
305
- // views, partitions, the drizzle bookkeeping tables. Narrow every row set to
306
- // the tables actually being introspected so downstream code never has to
307
- // ask whether a table it found in `columns` really exists.
308
- const known = new Set(tables.rows.map((t) => t.table_name));
309
- const scoped = <T extends { table_name: string }>(rows: T[]) => rows.filter((r) => known.has(r.table_name));
310
-
311
- return {
312
- schema,
313
- tables: tables.rows,
314
- columns: scoped(columns.rows),
315
- enumValues: enumValues.rows,
316
- pks: scoped(pks.rows),
317
- // A foreign key pointing *out* of the introspected set (into another
318
- // schema, or at a view) cannot be turned into a relation: there is no
319
- // collection on the other end to link to.
320
- fks: scoped(fks.rows).filter((fk) => known.has(fk.foreign_table_name)),
321
- uniques: scoped(uniques.rows),
322
- checks: scoped(checks.rows),
323
- comments: scoped(comments.rows),
324
- rowCounts: {}
325
- };
326
- }