@prisma-next/adapter-postgres 0.12.0-dev.31 → 0.12.0-dev.33

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/control.mjs CHANGED
@@ -1,726 +1,10 @@
1
- import { n as renderLoweredDdl, r as createPostgresBuiltinCodecLookup, t as renderLoweredSql } from "./sql-renderer-DqVeL4hP.mjs";
1
+ import { t as PostgresControlAdapter } from "./control-adapter-CykaRJU9.mjs";
2
2
  import { t as postgresAdapterDescriptorMeta } from "./descriptor-meta-C1wNCHkd.mjs";
3
- import { APP_SPACE_ID } from "@prisma-next/framework-components/control";
4
- import { isDdlNode } from "@prisma-next/sql-relational-core/ast";
3
+ import { parsePostgresDefault } from "@prisma-next/target-postgres/default-normalizer";
4
+ import { normalizeSchemaNativeType } from "@prisma-next/target-postgres/native-type-normalizer";
5
5
  import { SqlEscapeError, escapeLiteral, qualifyName, quoteIdentifier } from "@prisma-next/target-postgres/sql-utils";
6
- import { ifDefined } from "@prisma-next/utils/defined";
7
- import { PG_ENUM_CODEC_ID } from "@prisma-next/target-postgres/codec-ids";
8
- import { parseMarkerRowSafely, withMarkerReadErrorHandling } from "@prisma-next/errors/execution";
9
- import { parseContractMarkerRow } from "@prisma-next/family-sql/verify";
10
- import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
11
- import { postgresColumnsCompatible } from "@prisma-next/target-postgres/column-type-compatibility";
12
- import { buildControlTableBootstrapQueries, buildSignMarkerBootstrapQueries } from "@prisma-next/target-postgres/contract-free";
13
- import { parsePostgresDefault, parsePostgresDefault as parsePostgresDefault$1 } from "@prisma-next/target-postgres/default-normalizer";
14
- import { createResolveExistingEnumValues, enumStorageCompoundKey, readExistingEnumValues, readPostgresSchemaIrAnnotations } from "@prisma-next/target-postgres/enum-planning";
15
- import { normalizeSchemaNativeType, normalizeSchemaNativeType as normalizeSchemaNativeType$1 } from "@prisma-next/target-postgres/native-type-normalizer";
16
- import { blindCast } from "@prisma-next/utils/casts";
17
6
  import { timestampNowControlDescriptor } from "@prisma-next/family-sql/control";
18
7
  import { builtinGeneratorRegistryMetadata, resolveBuiltinGeneratedColumnDescriptor } from "@prisma-next/ids";
19
- //#region ../../../1-framework/3-tooling/migration/dist/exports/ledger-origin.mjs
20
- function ledgerOriginFromStored(originCoreHash) {
21
- if (originCoreHash === null || originCoreHash === "" || originCoreHash === "sha256:empty") return null;
22
- return originCoreHash;
23
- }
24
- //#endregion
25
- //#region src/core/enum-control-hooks.ts
26
- const ENUM_INTROSPECT_QUERY = `
27
- SELECT
28
- n.nspname AS schema_name,
29
- t.typname AS type_name,
30
- array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
31
- FROM pg_type t
32
- JOIN pg_namespace n ON t.typnamespace = n.oid
33
- JOIN pg_enum e ON t.oid = e.enumtypid
34
- WHERE n.nspname = $1
35
- GROUP BY n.nspname, t.typname
36
- ORDER BY n.nspname, t.typname
37
- `;
38
- function isStringArray(value) {
39
- return Array.isArray(value) && value.every((entry) => typeof entry === "string");
40
- }
41
- /**
42
- * Parses a PostgreSQL array value into a JavaScript string array.
43
- *
44
- * The `pg` library returns `array_agg` results either as a JS array
45
- * (when type parsers are configured) or as a string in PostgreSQL array
46
- * literal format (`{value1,value2,...}`). Handles PG's quoting rules:
47
- * - Elements containing commas, quotes, backslashes, or whitespace are
48
- * double-quoted.
49
- * - Inside quoted elements, `\"` represents `"` and `\\` represents `\`.
50
- *
51
- * Returns `null` when the input cannot be parsed as a PG array.
52
- */
53
- function parsePostgresArray(value) {
54
- if (isStringArray(value)) return value;
55
- if (typeof value === "string" && value.startsWith("{") && value.endsWith("}")) {
56
- const inner = value.slice(1, -1);
57
- if (inner === "") return [];
58
- return parseArrayElements(inner);
59
- }
60
- return null;
61
- }
62
- function parseArrayElements(input) {
63
- const result = [];
64
- let i = 0;
65
- while (i < input.length) {
66
- if (input[i] === ",") {
67
- i++;
68
- continue;
69
- }
70
- if (input[i] === "\"") {
71
- i++;
72
- let element = "";
73
- while (i < input.length && input[i] !== "\"") {
74
- if (input[i] === "\\" && i + 1 < input.length) {
75
- i++;
76
- element += input[i];
77
- } else element += input[i];
78
- i++;
79
- }
80
- i++;
81
- result.push(element);
82
- } else {
83
- const nextComma = input.indexOf(",", i);
84
- if (nextComma === -1) {
85
- result.push(input.slice(i).trim());
86
- i = input.length;
87
- } else {
88
- result.push(input.slice(i, nextComma).trim());
89
- i = nextComma;
90
- }
91
- }
92
- }
93
- return result;
94
- }
95
- /**
96
- * Reads enum types from the live Postgres schema and returns them in
97
- * the codec-typed annotation shape consumed by `control-adapter.ts`
98
- * (which writes them under `schema.annotations.pg.storageTypes`).
99
- */
100
- async function introspectPostgresEnumTypes(options) {
101
- const namespace = options.schemaName ?? "public";
102
- const result = await options.driver.query(ENUM_INTROSPECT_QUERY, [namespace]);
103
- const types = {};
104
- for (const row of result.rows) {
105
- const values = parsePostgresArray(row.values);
106
- if (!values) throw new Error(`Failed to parse enum values for type "${row.type_name}": unexpected format: ${JSON.stringify(row.values)}`);
107
- types[row.type_name] = {
108
- codecId: PG_ENUM_CODEC_ID,
109
- nativeType: row.type_name,
110
- typeParams: { values }
111
- };
112
- }
113
- return types;
114
- }
115
- //#endregion
116
- //#region src/core/control-adapter.ts
117
- const POSTGRES_MARKER_TABLE = "prisma_contract.marker";
118
- const POSTGRES_LEDGER_TABLE = "prisma_contract.ledger";
119
- /**
120
- * Postgres control plane adapter for control-plane operations like introspection.
121
- * Provides target-specific implementations for control-plane domain actions.
122
- */
123
- var PostgresControlAdapter = class {
124
- familyId = "sql";
125
- targetId = "postgres";
126
- codecLookup;
127
- /**
128
- * @param codecLookup - Codec lookup used by the SQL renderer to resolve
129
- * per-codec metadata at lower-time. Defaults to a Postgres-builtins-only
130
- * lookup when omitted. Stack-aware callers
131
- * (`SqlControlAdapterDescriptor.create(stack)`) supply
132
- * `stack.codecLookup` so extension codecs are visible to the renderer.
133
- */
134
- constructor(codecLookup) {
135
- this.codecLookup = codecLookup ?? createPostgresBuiltinCodecLookup();
136
- }
137
- /**
138
- * Target-specific normalizer for raw Postgres default expressions.
139
- * Used by schema verification to normalize raw defaults before comparison.
140
- */
141
- normalizeDefault = parsePostgresDefault$1;
142
- /**
143
- * Target-specific normalizer for Postgres schema native type names.
144
- * Used by schema verification to normalize introspected type names
145
- * before comparison with contract native types.
146
- */
147
- normalizeNativeType = normalizeSchemaNativeType$1;
148
- /**
149
- * Target-supplied compatible-shape relation used under the `external`
150
- * control policy. Threading the same relation the migration planner/runner
151
- * use keeps runtime verify and migration verify in agreement.
152
- */
153
- columnsCompatible = postgresColumnsCompatible;
154
- /**
155
- * Bridges native `PostgresEnumStorageEntry` IR walks against the Postgres
156
- * introspection shape (`schema.annotations.pg.storageTypes`). Lets
157
- * the family-level schema verifier walk enum types without reaching
158
- * into target-specific annotation layouts itself.
159
- */
160
- resolveExistingEnumValues = (schema, enumType, namespaceId) => {
161
- return readExistingEnumValues(schema, namespaceId === UNBOUND_NAMESPACE_ID ? readPostgresSchemaIrAnnotations(schema).schema ?? "public" : namespaceId, enumType.nativeType);
162
- };
163
- resolveExistingEnumValuesForContract = (contract) => createResolveExistingEnumValues(contract.storage);
164
- bootstrapControlTableQueries() {
165
- return buildControlTableBootstrapQueries();
166
- }
167
- bootstrapSignMarkerQueries() {
168
- return buildSignMarkerBootstrapQueries();
169
- }
170
- /**
171
- * Lower a SQL query AST into a Postgres-flavored `{ sql, params }` payload.
172
- *
173
- * Delegates to the shared `renderLoweredSql` renderer so the control adapter
174
- * emits byte-identical SQL to `PostgresAdapterImpl.lower()` for the same AST
175
- * and contract. Used at migration plan/emit time (e.g. by `dataTransform`)
176
- * without instantiating the runtime adapter.
177
- */
178
- lower(ast, context) {
179
- if (isDdlNode(ast)) return renderLoweredDdl(ast);
180
- return renderLoweredSql(ast, context.contract, this.codecLookup);
181
- }
182
- /**
183
- * Reads the contract marker from `prisma_contract.marker`. Probes
184
- * `information_schema.tables` first so a fresh database (where the
185
- * `prisma_contract` schema doesn't yet exist) returns `null` instead of a
186
- * "relation does not exist" error — some Postgres wire-protocol clients
187
- * (e.g. PGlite's TCP proxy) don't fully recover from extended-protocol
188
- * parse errors, so we probe before reading.
189
- */
190
- async readMarker(driver, space) {
191
- const markerContext = {
192
- space,
193
- markerLocation: POSTGRES_MARKER_TABLE
194
- };
195
- if ((await withMarkerReadErrorHandling(() => driver.query(`select 1
196
- from information_schema.tables
197
- where table_schema = $1 and table_name = $2`, ["prisma_contract", "marker"]), markerContext)).rows.length === 0) return null;
198
- const row = (await withMarkerReadErrorHandling(() => driver.query(`select
199
- core_hash,
200
- profile_hash,
201
- contract_json,
202
- canonical_version,
203
- updated_at,
204
- app_tag,
205
- meta,
206
- invariants
207
- from prisma_contract.marker
208
- where space = $1`, [space]), markerContext)).rows[0];
209
- if (!row) return null;
210
- return parseMarkerRowSafely(row, parseContractMarkerRow, markerContext);
211
- }
212
- /**
213
- * Reads every row from `prisma_contract.marker` and returns them keyed
214
- * by `space`. Mirrors the existence probe in {@link readMarker}: a
215
- * fresh database without the `prisma_contract` schema returns an empty
216
- * map rather than raising "relation does not exist".
217
- */
218
- async readAllMarkers(driver) {
219
- const markerContext = {
220
- space: APP_SPACE_ID,
221
- markerLocation: POSTGRES_MARKER_TABLE
222
- };
223
- if ((await withMarkerReadErrorHandling(() => driver.query(`select 1
224
- from information_schema.tables
225
- where table_schema = $1 and table_name = $2`, ["prisma_contract", "marker"]), markerContext)).rows.length === 0) return /* @__PURE__ */ new Map();
226
- const result = await withMarkerReadErrorHandling(() => driver.query(`select
227
- space,
228
- core_hash,
229
- profile_hash,
230
- contract_json,
231
- canonical_version,
232
- updated_at,
233
- app_tag,
234
- meta,
235
- invariants
236
- from prisma_contract.marker`), markerContext);
237
- const rows = /* @__PURE__ */ new Map();
238
- for (const row of result.rows) rows.set(row.space, parseMarkerRowSafely(row, parseContractMarkerRow, {
239
- space: row.space,
240
- markerLocation: POSTGRES_MARKER_TABLE
241
- }));
242
- return rows;
243
- }
244
- /**
245
- * Reads per-migration ledger rows from `prisma_contract.ledger` in apply
246
- * order. Probes `information_schema.tables` first so a fresh database
247
- * without the ledger table returns `[]` instead of raising "relation does
248
- * not exist".
249
- */
250
- async readLedger(driver, space) {
251
- const ledgerContext = {
252
- space: space ?? "*",
253
- markerLocation: POSTGRES_LEDGER_TABLE
254
- };
255
- if ((await withMarkerReadErrorHandling(() => driver.query(`select 1
256
- from information_schema.tables
257
- where table_schema = $1 and table_name = $2`, ["prisma_contract", "ledger"]), ledgerContext)).rows.length === 0) return [];
258
- let sql = `select
259
- space,
260
- migration_name,
261
- migration_hash,
262
- origin_core_hash,
263
- destination_core_hash,
264
- operations,
265
- created_at
266
- from prisma_contract.ledger`;
267
- if (space !== void 0) sql += `
268
- where space = $1`;
269
- sql += `
270
- order by id`;
271
- return (await withMarkerReadErrorHandling(() => driver.query(sql, space === void 0 ? void 0 : [space]), ledgerContext)).rows.map((row) => ({
272
- space: row.space,
273
- migrationName: row.migration_name,
274
- migrationHash: row.migration_hash,
275
- from: ledgerOriginFromStored(row.origin_core_hash),
276
- to: row.destination_core_hash,
277
- appliedAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at),
278
- operationCount: Array.isArray(row.operations) ? row.operations.length : 0
279
- }));
280
- }
281
- /**
282
- * Introspects a Postgres database schema and returns a raw SqlSchemaIR.
283
- *
284
- * This is a pure schema discovery operation that queries the Postgres catalog
285
- * and returns the schema structure without type mapping or contract enrichment.
286
- * Type mapping and enrichment are handled separately by enrichment helpers.
287
- *
288
- * When `contract` is provided and its storage declares more than one
289
- * namespace (or any explicit bound namespace), the adapter walks every
290
- * declared namespace and merges the per-schema introspection results
291
- * into a single `SqlSchemaIR`. `UNBOUND_NAMESPACE_ID` resolves to the
292
- * connection's `current_schema()` so late-bound tables follow the
293
- * runtime `search_path`. When no contract is passed, the adapter falls
294
- * back to introspecting the single `schema` argument (defaulting to
295
- * `'public'`).
296
- *
297
- * Uses batched queries to minimize database round trips (6 queries per
298
- * schema walked).
299
- *
300
- * @param driver - ControlDriverInstance<'sql', 'postgres'> instance for executing queries
301
- * @param contract - Optional contract for contract-guided introspection (multi-namespace walk, filtering)
302
- * @param schema - Schema name to introspect when no contract is provided (defaults to 'public')
303
- * @returns Promise resolving to SqlSchemaIR representing the live database schema
304
- */
305
- async introspect(driver, contract, schema = "public") {
306
- const declaredNamespaces = extractContractNamespaceIds(contract);
307
- const ir = declaredNamespaces.length > 0 ? await this.introspectNamespaces(driver, declaredNamespaces) : await this.introspectSchema(driver, schema);
308
- const existingSchemas = await this.listExistingSchemas(driver);
309
- const annotations = ir.annotations ?? {};
310
- const pg = annotations.pg ?? {};
311
- return {
312
- ...ir,
313
- annotations: {
314
- ...annotations,
315
- pg: {
316
- ...pg,
317
- existingSchemas
318
- }
319
- }
320
- };
321
- }
322
- /**
323
- * Lists every non-system schema present in the connected database.
324
- * The introspection consumer (`verifyPostgresNamespacePresence`)
325
- * treats the result as the authoritative ground truth — declared
326
- * namespaces whose `ddlSchemaName` is missing from this list become
327
- * `missing_schema` issues, and the planner emits the matching
328
- * `CREATE SCHEMA` before table DDL.
329
- */
330
- async listExistingSchemas(driver) {
331
- return (await driver.query(`SELECT nspname
332
- FROM pg_catalog.pg_namespace
333
- WHERE nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
334
- AND nspname NOT LIKE 'pg_temp_%'
335
- AND nspname NOT LIKE 'pg_toast_temp_%'
336
- ORDER BY nspname`)).rows.map((row) => row.nspname);
337
- }
338
- /**
339
- * Walks every declared namespace, resolving `UNBOUND_NAMESPACE_ID` to
340
- * the connection's `current_schema()`, and merges the per-schema results
341
- * into a single `SqlSchemaIR`. The merged `tables` map is flat (keyed by
342
- * table name) so callers that look up by `tableName` see every contract
343
- * table regardless of which namespace it lives in.
344
- */
345
- async introspectNamespaces(driver, namespaceIds) {
346
- const resolvedSchemas = await Promise.all(namespaceIds.map(async (id) => {
347
- if (id === UNBOUND_NAMESPACE_ID) {
348
- const { rows } = await driver.query("SELECT current_schema() AS current_schema");
349
- return rows[0]?.current_schema ?? "public";
350
- }
351
- return id;
352
- }));
353
- const uniqueSchemas = Array.from(new Set(resolvedSchemas));
354
- const perSchema = await Promise.all(uniqueSchemas.map((s) => this.introspectSchema(driver, s)));
355
- const mergedTables = {};
356
- for (const ir of perSchema) for (const [tableName, table] of Object.entries(ir.tables)) mergedTables[tableName] = table;
357
- const mergedStorageTypes = {};
358
- for (let i = 0; i < perSchema.length; i++) {
359
- const ir = perSchema[i];
360
- const pg = blindCast(ir?.annotations?.["pg"])?.storageTypes;
361
- if (!pg) continue;
362
- for (const [key, value] of Object.entries(pg)) mergedStorageTypes[key] = value;
363
- }
364
- const firstAnnotations = perSchema[0]?.annotations;
365
- const firstPg = blindCast(firstAnnotations?.["pg"]) ?? {};
366
- return {
367
- tables: mergedTables,
368
- ...ifDefined("annotations", {
369
- ...firstAnnotations,
370
- pg: {
371
- ...firstPg,
372
- ...ifDefined("storageTypes", Object.keys(mergedStorageTypes).length > 0 ? mergedStorageTypes : void 0)
373
- }
374
- })
375
- };
376
- }
377
- /**
378
- * Introspects a single Postgres schema and returns a raw SqlSchemaIR
379
- * containing only the tables in that schema. Used by `introspect` as
380
- * the per-namespace walk.
381
- */
382
- async introspectSchema(driver, schema) {
383
- const [tablesResult, columnsResult, pkResult, fkResult, uniqueResult, indexResult] = await Promise.all([
384
- driver.query(`SELECT table_name
385
- FROM information_schema.tables
386
- WHERE table_schema = $1
387
- AND table_type = 'BASE TABLE'
388
- ORDER BY table_name`, [schema]),
389
- driver.query(`SELECT
390
- c.table_name,
391
- column_name,
392
- data_type,
393
- udt_name,
394
- is_nullable,
395
- character_maximum_length,
396
- numeric_precision,
397
- numeric_scale,
398
- column_default,
399
- format_type(a.atttypid, a.atttypmod) AS formatted_type
400
- FROM information_schema.columns c
401
- JOIN pg_catalog.pg_class cl
402
- ON cl.relname = c.table_name
403
- JOIN pg_catalog.pg_namespace ns
404
- ON ns.nspname = c.table_schema
405
- AND ns.oid = cl.relnamespace
406
- JOIN pg_catalog.pg_attribute a
407
- ON a.attrelid = cl.oid
408
- AND a.attname = c.column_name
409
- AND a.attnum > 0
410
- AND NOT a.attisdropped
411
- WHERE c.table_schema = $1
412
- ORDER BY c.table_name, c.ordinal_position`, [schema]),
413
- driver.query(`SELECT
414
- tc.table_name,
415
- tc.constraint_name,
416
- kcu.column_name,
417
- kcu.ordinal_position
418
- FROM information_schema.table_constraints tc
419
- JOIN information_schema.key_column_usage kcu
420
- ON tc.constraint_name = kcu.constraint_name
421
- AND tc.table_schema = kcu.table_schema
422
- AND tc.table_name = kcu.table_name
423
- WHERE tc.table_schema = $1
424
- AND tc.constraint_type = 'PRIMARY KEY'
425
- ORDER BY tc.table_name, kcu.ordinal_position`, [schema]),
426
- driver.query(`SELECT
427
- tc.table_name,
428
- tc.constraint_name,
429
- kcu.column_name,
430
- kcu.ordinal_position,
431
- ref_ns.nspname AS referenced_table_schema,
432
- ref_cl.relname AS referenced_table_name,
433
- ref_att.attname AS referenced_column_name,
434
- rc.delete_rule,
435
- rc.update_rule
436
- FROM information_schema.table_constraints tc
437
- JOIN information_schema.key_column_usage kcu
438
- ON tc.constraint_name = kcu.constraint_name
439
- AND tc.table_schema = kcu.table_schema
440
- AND tc.table_name = kcu.table_name
441
- JOIN pg_catalog.pg_constraint pgc
442
- ON pgc.conname = tc.constraint_name
443
- AND pgc.connamespace = (
444
- SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = tc.table_schema
445
- )
446
- JOIN pg_catalog.pg_class ref_cl
447
- ON ref_cl.oid = pgc.confrelid
448
- JOIN pg_catalog.pg_namespace ref_ns
449
- ON ref_ns.oid = ref_cl.relnamespace
450
- JOIN pg_catalog.pg_attribute ref_att
451
- ON ref_att.attrelid = pgc.confrelid
452
- AND ref_att.attnum = pgc.confkey[kcu.ordinal_position]
453
- JOIN information_schema.referential_constraints rc
454
- ON rc.constraint_name = tc.constraint_name
455
- AND rc.constraint_schema = tc.table_schema
456
- WHERE tc.table_schema = $1
457
- AND tc.constraint_type = 'FOREIGN KEY'
458
- ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema]),
459
- driver.query(`SELECT
460
- tc.table_name,
461
- tc.constraint_name,
462
- kcu.column_name,
463
- kcu.ordinal_position
464
- FROM information_schema.table_constraints tc
465
- JOIN information_schema.key_column_usage kcu
466
- ON tc.constraint_name = kcu.constraint_name
467
- AND tc.table_schema = kcu.table_schema
468
- AND tc.table_name = kcu.table_name
469
- WHERE tc.table_schema = $1
470
- AND tc.constraint_type = 'UNIQUE'
471
- ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema]),
472
- driver.query(`SELECT
473
- i.tablename,
474
- i.indexname,
475
- ix.indisunique,
476
- a.attname,
477
- k.ord AS index_position,
478
- am.amname,
479
- ic.reloptions
480
- FROM pg_indexes i
481
- JOIN pg_class ic ON ic.relname = i.indexname
482
- JOIN pg_namespace ins ON ins.oid = ic.relnamespace AND ins.nspname = $1
483
- JOIN pg_index ix ON ix.indexrelid = ic.oid
484
- JOIN pg_am am ON am.oid = ic.relam
485
- JOIN pg_class t ON t.oid = ix.indrelid
486
- JOIN pg_namespace tn ON tn.oid = t.relnamespace AND tn.nspname = $1
487
- JOIN LATERAL unnest(ix.indkey::int[]) WITH ORDINALITY AS k(attnum, ord) ON true
488
- LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum AND a.attnum > 0
489
- WHERE i.schemaname = $1
490
- AND NOT EXISTS (
491
- SELECT 1
492
- FROM information_schema.table_constraints tc
493
- WHERE tc.table_schema = $1
494
- AND tc.table_name = i.tablename
495
- AND tc.constraint_name = i.indexname
496
- )
497
- ORDER BY i.tablename, i.indexname, k.ord`, [schema])
498
- ]);
499
- const columnsByTable = groupBy(columnsResult.rows, "table_name");
500
- const pksByTable = groupBy(pkResult.rows, "table_name");
501
- const fksByTable = groupBy(fkResult.rows, "table_name");
502
- const uniquesByTable = groupBy(uniqueResult.rows, "table_name");
503
- const indexesByTable = groupBy(indexResult.rows, "tablename");
504
- const pkConstraintsByTable = /* @__PURE__ */ new Map();
505
- for (const row of pkResult.rows) {
506
- let constraints = pkConstraintsByTable.get(row.table_name);
507
- if (!constraints) {
508
- constraints = /* @__PURE__ */ new Set();
509
- pkConstraintsByTable.set(row.table_name, constraints);
510
- }
511
- constraints.add(row.constraint_name);
512
- }
513
- const tables = {};
514
- for (const tableRow of tablesResult.rows) {
515
- const tableName = tableRow.table_name;
516
- const columns = {};
517
- for (const colRow of columnsByTable.get(tableName) ?? []) {
518
- let nativeType = colRow.udt_name;
519
- const formattedType = colRow.formatted_type ? normalizeFormattedType(colRow.formatted_type, colRow.data_type, colRow.udt_name) : null;
520
- if (formattedType) nativeType = formattedType;
521
- else if (colRow.data_type === "character varying" || colRow.data_type === "character") if (colRow.character_maximum_length) nativeType = `${colRow.data_type}(${colRow.character_maximum_length})`;
522
- else nativeType = colRow.data_type;
523
- else if (colRow.data_type === "numeric" || colRow.data_type === "decimal") if (colRow.numeric_precision && colRow.numeric_scale !== null) nativeType = `${colRow.data_type}(${colRow.numeric_precision},${colRow.numeric_scale})`;
524
- else if (colRow.numeric_precision) nativeType = `${colRow.data_type}(${colRow.numeric_precision})`;
525
- else nativeType = colRow.data_type;
526
- else nativeType = colRow.udt_name || colRow.data_type;
527
- columns[colRow.column_name] = {
528
- name: colRow.column_name,
529
- nativeType,
530
- nullable: colRow.is_nullable === "YES",
531
- ...ifDefined("default", colRow.column_default ?? void 0)
532
- };
533
- }
534
- const pkRows = [...pksByTable.get(tableName) ?? []];
535
- const primaryKeyColumns = pkRows.sort((a, b) => a.ordinal_position - b.ordinal_position).map((row) => row.column_name);
536
- const primaryKey = primaryKeyColumns.length > 0 ? {
537
- columns: primaryKeyColumns,
538
- ...pkRows[0]?.constraint_name ? { name: pkRows[0].constraint_name } : {}
539
- } : void 0;
540
- const foreignKeysMap = /* @__PURE__ */ new Map();
541
- for (const fkRow of fksByTable.get(tableName) ?? []) {
542
- const existing = foreignKeysMap.get(fkRow.constraint_name);
543
- if (existing) {
544
- existing.columns.push(fkRow.column_name);
545
- existing.referencedColumns.push(fkRow.referenced_column_name);
546
- } else foreignKeysMap.set(fkRow.constraint_name, {
547
- columns: [fkRow.column_name],
548
- referencedTable: fkRow.referenced_table_name,
549
- referencedSchema: fkRow.referenced_table_schema,
550
- referencedColumns: [fkRow.referenced_column_name],
551
- name: fkRow.constraint_name,
552
- deleteRule: fkRow.delete_rule,
553
- updateRule: fkRow.update_rule
554
- });
555
- }
556
- const foreignKeys = Array.from(foreignKeysMap.values()).map((fk) => ({
557
- columns: Object.freeze([...fk.columns]),
558
- referencedTable: fk.referencedTable,
559
- referencedSchema: fk.referencedSchema,
560
- referencedColumns: Object.freeze([...fk.referencedColumns]),
561
- name: fk.name,
562
- ...ifDefined("onDelete", mapReferentialAction(fk.deleteRule)),
563
- ...ifDefined("onUpdate", mapReferentialAction(fk.updateRule))
564
- }));
565
- const pkConstraints = pkConstraintsByTable.get(tableName) ?? /* @__PURE__ */ new Set();
566
- const uniquesMap = /* @__PURE__ */ new Map();
567
- for (const uniqueRow of uniquesByTable.get(tableName) ?? []) {
568
- if (pkConstraints.has(uniqueRow.constraint_name)) continue;
569
- const existing = uniquesMap.get(uniqueRow.constraint_name);
570
- if (existing) existing.columns.push(uniqueRow.column_name);
571
- else uniquesMap.set(uniqueRow.constraint_name, {
572
- columns: [uniqueRow.column_name],
573
- name: uniqueRow.constraint_name
574
- });
575
- }
576
- const uniques = Array.from(uniquesMap.values()).map((uq) => ({
577
- columns: Object.freeze([...uq.columns]),
578
- name: uq.name
579
- }));
580
- const indexesMap = /* @__PURE__ */ new Map();
581
- for (const idxRow of indexesByTable.get(tableName) ?? []) {
582
- if (!idxRow.attname) continue;
583
- const existing = indexesMap.get(idxRow.indexname);
584
- if (existing) existing.columns.push(idxRow.attname);
585
- else {
586
- const indexType = idxRow.amname && idxRow.amname !== "btree" ? idxRow.amname : void 0;
587
- const indexOptions = parsePgReloptions(idxRow.reloptions, idxRow.indexname);
588
- indexesMap.set(idxRow.indexname, {
589
- columns: [idxRow.attname],
590
- name: idxRow.indexname,
591
- unique: idxRow.indisunique,
592
- type: indexType,
593
- options: indexOptions
594
- });
595
- }
596
- }
597
- const indexes = Array.from(indexesMap.values()).map((idx) => ({
598
- columns: Object.freeze([...idx.columns]),
599
- name: idx.name,
600
- unique: idx.unique,
601
- ...idx.type !== void 0 && { type: idx.type },
602
- ...idx.options !== void 0 && { options: idx.options }
603
- }));
604
- tables[tableName] = {
605
- name: tableName,
606
- columns,
607
- ...ifDefined("primaryKey", primaryKey),
608
- foreignKeys,
609
- uniques,
610
- indexes
611
- };
612
- }
613
- const rawStorageTypes = await introspectPostgresEnumTypes({
614
- driver,
615
- schemaName: schema
616
- });
617
- const storageTypes = {};
618
- for (const [typeName, annotation] of Object.entries(rawStorageTypes)) storageTypes[enumStorageCompoundKey(schema, typeName)] = annotation;
619
- return {
620
- tables,
621
- annotations: { pg: {
622
- schema,
623
- version: await this.getPostgresVersion(driver),
624
- ...ifDefined("storageTypes", Object.keys(storageTypes).length > 0 ? storageTypes : void 0)
625
- } }
626
- };
627
- }
628
- /**
629
- * Gets the Postgres version from the database.
630
- */
631
- async getPostgresVersion(driver) {
632
- return ((await driver.query("SELECT version() AS version", [])).rows[0]?.version ?? "").match(/PostgreSQL (\d+\.\d+)/)?.[1] ?? "unknown";
633
- }
634
- };
635
- /**
636
- * Extracts the namespace coordinate ids declared on a contract's storage,
637
- * or returns an empty array when no contract (or no storage / namespaces)
638
- * is present. Used by `PostgresControlAdapter.introspect` to decide
639
- * between the multi-namespace walk and the single-schema fallback.
640
- */
641
- function extractContractNamespaceIds(contract) {
642
- if (contract === null || typeof contract !== "object") return [];
643
- const storage = contract.storage;
644
- if (storage === null || typeof storage !== "object") return [];
645
- const namespaces = storage.namespaces;
646
- if (namespaces === null || typeof namespaces !== "object") return [];
647
- return Object.keys(namespaces);
648
- }
649
- function normalizeFormattedType(formattedType, dataType, udtName) {
650
- if (formattedType === "integer") return "int4";
651
- if (formattedType === "smallint") return "int2";
652
- if (formattedType === "bigint") return "int8";
653
- if (formattedType === "real") return "float4";
654
- if (formattedType === "double precision") return "float8";
655
- if (formattedType === "boolean") return "bool";
656
- if (formattedType.startsWith("varchar")) return formattedType.replace("varchar", "character varying");
657
- if (formattedType.startsWith("bpchar")) return formattedType.replace("bpchar", "character");
658
- if (formattedType.startsWith("varbit")) return formattedType.replace("varbit", "bit varying");
659
- if (dataType === "timestamp with time zone" || udtName === "timestamptz") return formattedType.replace("timestamp", "timestamptz").replace(" with time zone", "").trim();
660
- if (dataType === "timestamp without time zone" || udtName === "timestamp") return formattedType.replace(" without time zone", "").trim();
661
- if (dataType === "time with time zone" || udtName === "timetz") return formattedType.replace("time", "timetz").replace(" with time zone", "").trim();
662
- if (dataType === "time without time zone" || udtName === "time") return formattedType.replace(" without time zone", "").trim();
663
- if (formattedType.startsWith("\"") && formattedType.endsWith("\"")) return formattedType.slice(1, -1);
664
- return formattedType;
665
- }
666
- const PG_REFERENTIAL_ACTION_MAP = {
667
- "NO ACTION": "noAction",
668
- RESTRICT: "restrict",
669
- CASCADE: "cascade",
670
- "SET NULL": "setNull",
671
- "SET DEFAULT": "setDefault"
672
- };
673
- /**
674
- * Maps a Postgres referential action rule to the canonical SqlReferentialAction.
675
- * Returns undefined for 'NO ACTION' (the database default) to keep the IR sparse.
676
- * Throws for unrecognized rules to prevent silent data loss.
677
- */
678
- function mapReferentialAction(rule) {
679
- const mapped = PG_REFERENTIAL_ACTION_MAP[rule];
680
- if (mapped === void 0) throw new Error(`Unknown PostgreSQL referential action rule: "${rule}". Expected one of: NO ACTION, RESTRICT, CASCADE, SET NULL, SET DEFAULT.`);
681
- if (mapped === "noAction") return void 0;
682
- return mapped;
683
- }
684
- /**
685
- * Groups an array of objects by a specified key.
686
- * Returns a Map for O(1) lookup by group key.
687
- */
688
- /**
689
- * Parses a `pg_class.reloptions` array into a `Record<string, string>`.
690
- *
691
- * Postgres returns reloptions as a `text[]` whose entries are `key=value`
692
- * strings; the value side is always a string regardless of the underlying
693
- * scalar type. The verifier compares contract options to introspected
694
- * options after coercing both sides to strings, so keeping the raw text
695
- * here is correct.
696
- *
697
- * Returns `undefined` when the input is null/empty (no WITH clause).
698
- */
699
- function parsePgReloptions(reloptions, indexName) {
700
- if (!reloptions || reloptions.length === 0) return;
701
- const result = {};
702
- for (const entry of reloptions) {
703
- const eq = entry.indexOf("=");
704
- if (eq === -1) throw new Error(`Postgres introspection: malformed reloption entry "${entry}" on index "${indexName}" (expected "key=value")`);
705
- const key = entry.slice(0, eq);
706
- result[key] = entry.slice(eq + 1);
707
- }
708
- return Object.keys(result).length > 0 ? result : void 0;
709
- }
710
- function groupBy(items, key) {
711
- const map = /* @__PURE__ */ new Map();
712
- for (const item of items) {
713
- const groupKey = item[key];
714
- let group = map.get(groupKey);
715
- if (!group) {
716
- group = [];
717
- map.set(groupKey, group);
718
- }
719
- group.push(item);
720
- }
721
- return map;
722
- }
723
- //#endregion
724
8
  //#region src/core/control-mutation-defaults.ts
725
9
  function invalidArgumentDiagnostic(input) {
726
10
  return {