@prisma-next/adapter-postgres 0.3.0-dev.34 → 0.3.0-dev.37

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 (88) hide show
  1. package/README.md +64 -2
  2. package/dist/adapter-DB1CK2jM.mjs +265 -0
  3. package/dist/adapter-DB1CK2jM.mjs.map +1 -0
  4. package/dist/adapter.d.mts +23 -0
  5. package/dist/adapter.d.mts.map +1 -0
  6. package/dist/adapter.mjs +3 -0
  7. package/dist/codec-ids-Bsm9c7ns.mjs +29 -0
  8. package/dist/codec-ids-Bsm9c7ns.mjs.map +1 -0
  9. package/dist/codec-types.d.mts +141 -0
  10. package/dist/codec-types.d.mts.map +1 -0
  11. package/dist/codec-types.mjs +3 -0
  12. package/dist/codecs-DcC1nPzh.mjs +206 -0
  13. package/dist/codecs-DcC1nPzh.mjs.map +1 -0
  14. package/dist/column-types.d.mts +110 -0
  15. package/dist/column-types.d.mts.map +1 -0
  16. package/dist/column-types.mjs +180 -0
  17. package/dist/column-types.mjs.map +1 -0
  18. package/dist/control.d.mts +111 -0
  19. package/dist/control.d.mts.map +1 -0
  20. package/dist/control.mjs +405 -0
  21. package/dist/control.mjs.map +1 -0
  22. package/dist/descriptor-meta-D7pxo-wo.mjs +996 -0
  23. package/dist/descriptor-meta-D7pxo-wo.mjs.map +1 -0
  24. package/dist/runtime.d.mts +19 -0
  25. package/dist/runtime.d.mts.map +1 -0
  26. package/dist/runtime.mjs +85 -0
  27. package/dist/runtime.mjs.map +1 -0
  28. package/dist/types-BY395pUv.d.mts +19 -0
  29. package/dist/types-BY395pUv.d.mts.map +1 -0
  30. package/dist/types.d.mts +2 -0
  31. package/dist/types.mjs +1 -0
  32. package/package.json +32 -41
  33. package/src/core/adapter.ts +90 -17
  34. package/src/core/codec-ids.ts +28 -0
  35. package/src/core/codecs.ts +316 -19
  36. package/src/core/control-adapter.ts +341 -180
  37. package/src/core/default-normalizer.ts +77 -0
  38. package/src/core/descriptor-meta.ts +221 -9
  39. package/src/core/enum-control-hooks.ts +735 -0
  40. package/src/core/json-schema-type-expression.ts +131 -0
  41. package/src/core/json-schema-validator.ts +53 -0
  42. package/src/core/parameterized-types.ts +118 -0
  43. package/src/core/sql-utils.ts +111 -0
  44. package/src/core/standard-schema.ts +71 -0
  45. package/src/exports/codec-types.ts +73 -1
  46. package/src/exports/column-types.ts +233 -9
  47. package/src/exports/control.ts +16 -9
  48. package/src/exports/runtime.ts +61 -18
  49. package/dist/chunk-HD5YISNQ.js +0 -47
  50. package/dist/chunk-HD5YISNQ.js.map +0 -1
  51. package/dist/chunk-J3XSOAM2.js +0 -162
  52. package/dist/chunk-J3XSOAM2.js.map +0 -1
  53. package/dist/chunk-Y6L4BBLR.js +0 -309
  54. package/dist/chunk-Y6L4BBLR.js.map +0 -1
  55. package/dist/core/adapter.d.ts +0 -19
  56. package/dist/core/adapter.d.ts.map +0 -1
  57. package/dist/core/codecs.d.ts +0 -110
  58. package/dist/core/codecs.d.ts.map +0 -1
  59. package/dist/core/control-adapter.d.ts +0 -33
  60. package/dist/core/control-adapter.d.ts.map +0 -1
  61. package/dist/core/descriptor-meta.d.ts +0 -72
  62. package/dist/core/descriptor-meta.d.ts.map +0 -1
  63. package/dist/core/types.d.ts +0 -16
  64. package/dist/core/types.d.ts.map +0 -1
  65. package/dist/exports/adapter.d.ts +0 -2
  66. package/dist/exports/adapter.d.ts.map +0 -1
  67. package/dist/exports/adapter.js +0 -8
  68. package/dist/exports/adapter.js.map +0 -1
  69. package/dist/exports/codec-types.d.ts +0 -11
  70. package/dist/exports/codec-types.d.ts.map +0 -1
  71. package/dist/exports/codec-types.js +0 -7
  72. package/dist/exports/codec-types.js.map +0 -1
  73. package/dist/exports/column-types.d.ts +0 -17
  74. package/dist/exports/column-types.d.ts.map +0 -1
  75. package/dist/exports/column-types.js +0 -49
  76. package/dist/exports/column-types.js.map +0 -1
  77. package/dist/exports/control.d.ts +0 -8
  78. package/dist/exports/control.d.ts.map +0 -1
  79. package/dist/exports/control.js +0 -279
  80. package/dist/exports/control.js.map +0 -1
  81. package/dist/exports/runtime.d.ts +0 -15
  82. package/dist/exports/runtime.d.ts.map +0 -1
  83. package/dist/exports/runtime.js +0 -20
  84. package/dist/exports/runtime.js.map +0 -1
  85. package/dist/exports/types.d.ts +0 -2
  86. package/dist/exports/types.d.ts.map +0 -1
  87. package/dist/exports/types.js +0 -1
  88. package/dist/exports/types.js.map +0 -1
@@ -0,0 +1,111 @@
1
+ import { SqlControlAdapterDescriptor } from "@prisma-next/family-sql/control";
2
+ import { ColumnDefault } from "@prisma-next/contract/types";
3
+
4
+ //#region src/core/default-normalizer.d.ts
5
+
6
+ /**
7
+ * Parses a raw Postgres column default expression into a normalized ColumnDefault.
8
+ * This enables semantic comparison between contract defaults and introspected schema defaults.
9
+ *
10
+ * Used by the migration diff layer to normalize raw database defaults during comparison,
11
+ * keeping the introspection layer focused on faithful data capture.
12
+ *
13
+ * @param rawDefault - Raw default expression from information_schema.columns.column_default
14
+ * @param _nativeType - Native column type (currently unused, reserved for future type-aware parsing)
15
+ * @returns Normalized ColumnDefault or undefined if the expression cannot be parsed
16
+ */
17
+ declare function parsePostgresDefault(rawDefault: string, _nativeType?: string): ColumnDefault | undefined;
18
+ //#endregion
19
+ //#region src/core/parameterized-types.d.ts
20
+ /**
21
+ * Shared utility for expanding parameterized Postgres types to their full SQL representation.
22
+ *
23
+ * This module provides a single source of truth for type expansion logic, used by:
24
+ * - Schema verification (verify-sql-schema.ts) via the expandNativeType codec control hook
25
+ * - Migration planner (planner.ts) via direct import
26
+ *
27
+ * @module
28
+ */
29
+ /**
30
+ * Input for expanding parameterized native types.
31
+ */
32
+ interface ExpandNativeTypeInput {
33
+ readonly nativeType: string;
34
+ readonly codecId?: string;
35
+ readonly typeParams?: Record<string, unknown>;
36
+ }
37
+ /**
38
+ * Expands a parameterized native type to its full SQL representation.
39
+ *
40
+ * For example:
41
+ * - { nativeType: 'character varying', typeParams: { length: 255 } } -> 'character varying(255)'
42
+ * - { nativeType: 'numeric', typeParams: { precision: 10, scale: 2 } } -> 'numeric(10,2)'
43
+ * - { nativeType: 'timestamp without time zone', typeParams: { precision: 3 } } -> 'timestamp without time zone(3)'
44
+ *
45
+ * Returns the original nativeType if:
46
+ * - No typeParams are provided
47
+ * - No codecId is provided
48
+ * - The codecId is not a known parameterized type
49
+ * - The typeParams values are invalid
50
+ */
51
+ declare function expandParameterizedNativeType(input: ExpandNativeTypeInput): string;
52
+ //#endregion
53
+ //#region src/core/sql-utils.d.ts
54
+ /**
55
+ * Shared SQL utility functions for the Postgres adapter.
56
+ *
57
+ * These functions handle safe SQL identifier and literal escaping
58
+ * with security validations to prevent injection and encoding issues.
59
+ */
60
+ /**
61
+ * Error thrown when an invalid SQL identifier or literal is detected.
62
+ * Boundary layers map this to structured envelopes.
63
+ */
64
+ declare class SqlEscapeError extends Error {
65
+ readonly value: string;
66
+ readonly kind: 'identifier' | 'literal';
67
+ constructor(message: string, value: string, kind: 'identifier' | 'literal');
68
+ }
69
+ /**
70
+ * Validates and quotes a PostgreSQL identifier (table, column, type, schema names).
71
+ *
72
+ * Security validations:
73
+ * - Rejects null bytes which could cause truncation or unexpected behavior
74
+ * - Rejects empty identifiers
75
+ * - Warns on identifiers exceeding PostgreSQL's 63-character limit
76
+ *
77
+ * @throws {SqlEscapeError} If the identifier contains null bytes or is empty
78
+ */
79
+ declare function quoteIdentifier(identifier: string): string;
80
+ /**
81
+ * Escapes a string literal for safe use in SQL statements.
82
+ *
83
+ * Security validations:
84
+ * - Rejects null bytes which could cause truncation or unexpected behavior
85
+ *
86
+ * Note: This assumes PostgreSQL's `standard_conforming_strings` is ON (default since PG 9.1).
87
+ * Backslashes are treated as literal characters, not escape sequences.
88
+ *
89
+ * @throws {SqlEscapeError} If the value contains null bytes
90
+ */
91
+ declare function escapeLiteral(value: string): string;
92
+ /**
93
+ * Builds a qualified name (schema.object) with proper quoting.
94
+ */
95
+ declare function qualifyName(schemaName: string, objectName: string): string;
96
+ //#endregion
97
+ //#region src/core/control-adapter.d.ts
98
+
99
+ /**
100
+ * Normalizes a Postgres schema native type to its canonical form for comparison.
101
+ *
102
+ * Uses a pre-computed lookup map for simple prefix replacements (O(1))
103
+ * and handles complex temporal type normalization separately.
104
+ */
105
+ declare function normalizeSchemaNativeType(nativeType: string): string;
106
+ //#endregion
107
+ //#region src/exports/control.d.ts
108
+ declare const postgresAdapterDescriptor: SqlControlAdapterDescriptor<'postgres'>;
109
+ //#endregion
110
+ export { SqlEscapeError, postgresAdapterDescriptor as default, escapeLiteral, expandParameterizedNativeType, normalizeSchemaNativeType, parsePostgresDefault, qualifyName, quoteIdentifier };
111
+ //# sourceMappingURL=control.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/default-normalizer.ts","../src/core/parameterized-types.ts","../src/core/sql-utils.ts","../src/core/control-adapter.ts","../src/exports/control.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA0BA;;;;ACEA;AAiDA;;;iBDnDgB,oBAAA,4CAGb;;;;;;;AAHH;;;;ACEA;AAiDA;;;UAjDiB,qBAAA;ECjBJ,SAAA,UAAe,EAAA,MAAA;EA0BZ,SAAA,OAAA,CAAA,EAAe,MAAA;EAiCf,SAAA,UAAa,CAAA,EDvCL,MCuCK,CAAA,MAAA,EAAA,OAAA,CAAA;AAc7B;;;;ACsWA;;;;ACpbgG;;;;;;;iBHuEhF,6BAAA,QAAqC;;;;;;;ADnDrD;;;;ACEA;AAiDA;cClEa,cAAA,SAAuB,KAAA;;;EAAvB,WAAA,CAAA,OAAe,EAAA,MAAA,EAAQ,KAAA,EAAK,MAAA,EAAA,IAAA,EAAA,YAAA,GAAA,SAAA;AA0BzC;AAiCA;AAcA;;;;ACsWA;;;;ACpbgG;iBF+BhF,eAAA;;;;;;;;;;;;iBAiCA,aAAA;;;;iBAcA,WAAA;;;;;;;;;;iBCsWA,yBAAA;;;AHhahB,cIlBM,yBJqBH,EIrB8B,2BJqBjB,CAAA,UAAA,CAAA"}
@@ -0,0 +1,405 @@
1
+ import { a as escapeLiteral, i as SqlEscapeError, n as expandParameterizedNativeType, o as qualifyName, r as pgEnumControlHooks, s as quoteIdentifier, t as postgresAdapterDescriptorMeta } from "./descriptor-meta-D7pxo-wo.mjs";
2
+ import { ifDefined } from "@prisma-next/utils/defined";
3
+
4
+ //#region src/core/default-normalizer.ts
5
+ /**
6
+ * Pre-compiled regex patterns for performance.
7
+ * These are compiled once at module load time rather than on each function call.
8
+ */
9
+ const NEXTVAL_PATTERN = /^nextval\s*\(/i;
10
+ const TIMESTAMP_PATTERN = /^(now\s*\(\s*\)|CURRENT_TIMESTAMP|clock_timestamp\s*\(\s*\))$/i;
11
+ const UUID_PATTERN = /^gen_random_uuid\s*\(\s*\)$/i;
12
+ const UUID_OSSP_PATTERN = /^uuid_generate_v4\s*\(\s*\)$/i;
13
+ const TRUE_PATTERN = /^true$/i;
14
+ const FALSE_PATTERN = /^false$/i;
15
+ const NUMERIC_PATTERN = /^-?\d+(\.\d+)?$/;
16
+ const STRING_LITERAL_PATTERN = /^'((?:[^']|'')*)'(?:::(?:"[^"]+"|[\w\s]+)(?:\(\d+\))?)?$/;
17
+ /**
18
+ * Parses a raw Postgres column default expression into a normalized ColumnDefault.
19
+ * This enables semantic comparison between contract defaults and introspected schema defaults.
20
+ *
21
+ * Used by the migration diff layer to normalize raw database defaults during comparison,
22
+ * keeping the introspection layer focused on faithful data capture.
23
+ *
24
+ * @param rawDefault - Raw default expression from information_schema.columns.column_default
25
+ * @param _nativeType - Native column type (currently unused, reserved for future type-aware parsing)
26
+ * @returns Normalized ColumnDefault or undefined if the expression cannot be parsed
27
+ */
28
+ function parsePostgresDefault(rawDefault, _nativeType) {
29
+ const trimmed = rawDefault.trim();
30
+ if (NEXTVAL_PATTERN.test(trimmed)) return {
31
+ kind: "function",
32
+ expression: "autoincrement()"
33
+ };
34
+ if (TIMESTAMP_PATTERN.test(trimmed)) return {
35
+ kind: "function",
36
+ expression: "now()"
37
+ };
38
+ if (UUID_PATTERN.test(trimmed)) return {
39
+ kind: "function",
40
+ expression: "gen_random_uuid()"
41
+ };
42
+ if (UUID_OSSP_PATTERN.test(trimmed)) return {
43
+ kind: "function",
44
+ expression: "gen_random_uuid()"
45
+ };
46
+ if (TRUE_PATTERN.test(trimmed)) return {
47
+ kind: "literal",
48
+ expression: "true"
49
+ };
50
+ if (FALSE_PATTERN.test(trimmed)) return {
51
+ kind: "literal",
52
+ expression: "false"
53
+ };
54
+ if (NUMERIC_PATTERN.test(trimmed)) return {
55
+ kind: "literal",
56
+ expression: trimmed
57
+ };
58
+ const stringMatch = trimmed.match(STRING_LITERAL_PATTERN);
59
+ if (stringMatch?.[1] !== void 0) return {
60
+ kind: "literal",
61
+ expression: `'${stringMatch[1]}'`
62
+ };
63
+ return {
64
+ kind: "function",
65
+ expression: trimmed
66
+ };
67
+ }
68
+
69
+ //#endregion
70
+ //#region src/core/control-adapter.ts
71
+ /**
72
+ * Postgres control plane adapter for control-plane operations like introspection.
73
+ * Provides target-specific implementations for control-plane domain actions.
74
+ */
75
+ var PostgresControlAdapter = class {
76
+ familyId = "sql";
77
+ targetId = "postgres";
78
+ /**
79
+ * @deprecated Use targetId instead
80
+ */
81
+ target = "postgres";
82
+ /**
83
+ * Target-specific normalizer for raw Postgres default expressions.
84
+ * Used by schema verification to normalize raw defaults before comparison.
85
+ */
86
+ normalizeDefault = parsePostgresDefault;
87
+ /**
88
+ * Target-specific normalizer for Postgres schema native type names.
89
+ * Used by schema verification to normalize introspected type names
90
+ * before comparison with contract native types.
91
+ */
92
+ normalizeNativeType = normalizeSchemaNativeType;
93
+ /**
94
+ * Introspects a Postgres database schema and returns a raw SqlSchemaIR.
95
+ *
96
+ * This is a pure schema discovery operation that queries the Postgres catalog
97
+ * and returns the schema structure without type mapping or contract enrichment.
98
+ * Type mapping and enrichment are handled separately by enrichment helpers.
99
+ *
100
+ * Uses batched queries to minimize database round trips (7 queries instead of 5T+3).
101
+ *
102
+ * @param driver - ControlDriverInstance<'sql', 'postgres'> instance for executing queries
103
+ * @param contractIR - Optional contract IR for contract-guided introspection (filtering, optimization)
104
+ * @param schema - Schema name to introspect (defaults to 'public')
105
+ * @returns Promise resolving to SqlSchemaIR representing the live database schema
106
+ */
107
+ async introspect(driver, _contractIR, schema = "public") {
108
+ const [tablesResult, columnsResult, pkResult, fkResult, uniqueResult, indexResult, extensionsResult] = await Promise.all([
109
+ driver.query(`SELECT table_name
110
+ FROM information_schema.tables
111
+ WHERE table_schema = $1
112
+ AND table_type = 'BASE TABLE'
113
+ ORDER BY table_name`, [schema]),
114
+ driver.query(`SELECT
115
+ c.table_name,
116
+ column_name,
117
+ data_type,
118
+ udt_name,
119
+ is_nullable,
120
+ character_maximum_length,
121
+ numeric_precision,
122
+ numeric_scale,
123
+ column_default,
124
+ format_type(a.atttypid, a.atttypmod) AS formatted_type
125
+ FROM information_schema.columns c
126
+ JOIN pg_catalog.pg_class cl
127
+ ON cl.relname = c.table_name
128
+ JOIN pg_catalog.pg_namespace ns
129
+ ON ns.nspname = c.table_schema
130
+ AND ns.oid = cl.relnamespace
131
+ JOIN pg_catalog.pg_attribute a
132
+ ON a.attrelid = cl.oid
133
+ AND a.attname = c.column_name
134
+ AND a.attnum > 0
135
+ AND NOT a.attisdropped
136
+ WHERE c.table_schema = $1
137
+ ORDER BY c.table_name, c.ordinal_position`, [schema]),
138
+ driver.query(`SELECT
139
+ tc.table_name,
140
+ tc.constraint_name,
141
+ kcu.column_name,
142
+ kcu.ordinal_position
143
+ FROM information_schema.table_constraints tc
144
+ JOIN information_schema.key_column_usage kcu
145
+ ON tc.constraint_name = kcu.constraint_name
146
+ AND tc.table_schema = kcu.table_schema
147
+ AND tc.table_name = kcu.table_name
148
+ WHERE tc.table_schema = $1
149
+ AND tc.constraint_type = 'PRIMARY KEY'
150
+ ORDER BY tc.table_name, kcu.ordinal_position`, [schema]),
151
+ driver.query(`SELECT
152
+ tc.table_name,
153
+ tc.constraint_name,
154
+ kcu.column_name,
155
+ kcu.ordinal_position,
156
+ ccu.table_schema AS referenced_table_schema,
157
+ ccu.table_name AS referenced_table_name,
158
+ ccu.column_name AS referenced_column_name
159
+ FROM information_schema.table_constraints tc
160
+ JOIN information_schema.key_column_usage kcu
161
+ ON tc.constraint_name = kcu.constraint_name
162
+ AND tc.table_schema = kcu.table_schema
163
+ AND tc.table_name = kcu.table_name
164
+ JOIN information_schema.constraint_column_usage ccu
165
+ ON ccu.constraint_name = tc.constraint_name
166
+ AND ccu.table_schema = tc.table_schema
167
+ WHERE tc.table_schema = $1
168
+ AND tc.constraint_type = 'FOREIGN KEY'
169
+ ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema]),
170
+ driver.query(`SELECT
171
+ tc.table_name,
172
+ tc.constraint_name,
173
+ kcu.column_name,
174
+ kcu.ordinal_position
175
+ FROM information_schema.table_constraints tc
176
+ JOIN information_schema.key_column_usage kcu
177
+ ON tc.constraint_name = kcu.constraint_name
178
+ AND tc.table_schema = kcu.table_schema
179
+ AND tc.table_name = kcu.table_name
180
+ WHERE tc.table_schema = $1
181
+ AND tc.constraint_type = 'UNIQUE'
182
+ ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema]),
183
+ driver.query(`SELECT
184
+ i.tablename,
185
+ i.indexname,
186
+ ix.indisunique,
187
+ a.attname,
188
+ a.attnum
189
+ FROM pg_indexes i
190
+ JOIN pg_class ic ON ic.relname = i.indexname
191
+ JOIN pg_namespace ins ON ins.oid = ic.relnamespace AND ins.nspname = $1
192
+ JOIN pg_index ix ON ix.indexrelid = ic.oid
193
+ JOIN pg_class t ON t.oid = ix.indrelid
194
+ JOIN pg_namespace tn ON tn.oid = t.relnamespace AND tn.nspname = $1
195
+ LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) AND a.attnum > 0
196
+ WHERE i.schemaname = $1
197
+ AND NOT EXISTS (
198
+ SELECT 1
199
+ FROM information_schema.table_constraints tc
200
+ WHERE tc.table_schema = $1
201
+ AND tc.table_name = i.tablename
202
+ AND tc.constraint_name = i.indexname
203
+ )
204
+ ORDER BY i.tablename, i.indexname, a.attnum`, [schema]),
205
+ driver.query(`SELECT extname
206
+ FROM pg_extension
207
+ ORDER BY extname`, [])
208
+ ]);
209
+ const columnsByTable = groupBy(columnsResult.rows, "table_name");
210
+ const pksByTable = groupBy(pkResult.rows, "table_name");
211
+ const fksByTable = groupBy(fkResult.rows, "table_name");
212
+ const uniquesByTable = groupBy(uniqueResult.rows, "table_name");
213
+ const indexesByTable = groupBy(indexResult.rows, "tablename");
214
+ const pkConstraintsByTable = /* @__PURE__ */ new Map();
215
+ for (const row of pkResult.rows) {
216
+ let constraints = pkConstraintsByTable.get(row.table_name);
217
+ if (!constraints) {
218
+ constraints = /* @__PURE__ */ new Set();
219
+ pkConstraintsByTable.set(row.table_name, constraints);
220
+ }
221
+ constraints.add(row.constraint_name);
222
+ }
223
+ const tables = {};
224
+ for (const tableRow of tablesResult.rows) {
225
+ const tableName = tableRow.table_name;
226
+ const columns = {};
227
+ for (const colRow of columnsByTable.get(tableName) ?? []) {
228
+ let nativeType = colRow.udt_name;
229
+ const formattedType = colRow.formatted_type ? normalizeFormattedType(colRow.formatted_type, colRow.data_type, colRow.udt_name) : null;
230
+ if (formattedType) nativeType = formattedType;
231
+ else if (colRow.data_type === "character varying" || colRow.data_type === "character") if (colRow.character_maximum_length) nativeType = `${colRow.data_type}(${colRow.character_maximum_length})`;
232
+ else nativeType = colRow.data_type;
233
+ 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})`;
234
+ else if (colRow.numeric_precision) nativeType = `${colRow.data_type}(${colRow.numeric_precision})`;
235
+ else nativeType = colRow.data_type;
236
+ else nativeType = colRow.udt_name || colRow.data_type;
237
+ columns[colRow.column_name] = {
238
+ name: colRow.column_name,
239
+ nativeType,
240
+ nullable: colRow.is_nullable === "YES",
241
+ ...ifDefined("default", colRow.column_default ?? void 0)
242
+ };
243
+ }
244
+ const pkRows = [...pksByTable.get(tableName) ?? []];
245
+ const primaryKeyColumns = pkRows.sort((a, b) => a.ordinal_position - b.ordinal_position).map((row) => row.column_name);
246
+ const primaryKey = primaryKeyColumns.length > 0 ? {
247
+ columns: primaryKeyColumns,
248
+ ...pkRows[0]?.constraint_name ? { name: pkRows[0].constraint_name } : {}
249
+ } : void 0;
250
+ const foreignKeysMap = /* @__PURE__ */ new Map();
251
+ for (const fkRow of fksByTable.get(tableName) ?? []) {
252
+ const existing = foreignKeysMap.get(fkRow.constraint_name);
253
+ if (existing) {
254
+ existing.columns.push(fkRow.column_name);
255
+ existing.referencedColumns.push(fkRow.referenced_column_name);
256
+ } else foreignKeysMap.set(fkRow.constraint_name, {
257
+ columns: [fkRow.column_name],
258
+ referencedTable: fkRow.referenced_table_name,
259
+ referencedColumns: [fkRow.referenced_column_name],
260
+ name: fkRow.constraint_name
261
+ });
262
+ }
263
+ const foreignKeys = Array.from(foreignKeysMap.values()).map((fk) => ({
264
+ columns: Object.freeze([...fk.columns]),
265
+ referencedTable: fk.referencedTable,
266
+ referencedColumns: Object.freeze([...fk.referencedColumns]),
267
+ name: fk.name
268
+ }));
269
+ const pkConstraints = pkConstraintsByTable.get(tableName) ?? /* @__PURE__ */ new Set();
270
+ const uniquesMap = /* @__PURE__ */ new Map();
271
+ for (const uniqueRow of uniquesByTable.get(tableName) ?? []) {
272
+ if (pkConstraints.has(uniqueRow.constraint_name)) continue;
273
+ const existing = uniquesMap.get(uniqueRow.constraint_name);
274
+ if (existing) existing.columns.push(uniqueRow.column_name);
275
+ else uniquesMap.set(uniqueRow.constraint_name, {
276
+ columns: [uniqueRow.column_name],
277
+ name: uniqueRow.constraint_name
278
+ });
279
+ }
280
+ const uniques = Array.from(uniquesMap.values()).map((uq) => ({
281
+ columns: Object.freeze([...uq.columns]),
282
+ name: uq.name
283
+ }));
284
+ const indexesMap = /* @__PURE__ */ new Map();
285
+ for (const idxRow of indexesByTable.get(tableName) ?? []) {
286
+ if (!idxRow.attname) continue;
287
+ const existing = indexesMap.get(idxRow.indexname);
288
+ if (existing) existing.columns.push(idxRow.attname);
289
+ else indexesMap.set(idxRow.indexname, {
290
+ columns: [idxRow.attname],
291
+ name: idxRow.indexname,
292
+ unique: idxRow.indisunique
293
+ });
294
+ }
295
+ const indexes = Array.from(indexesMap.values()).map((idx) => ({
296
+ columns: Object.freeze([...idx.columns]),
297
+ name: idx.name,
298
+ unique: idx.unique
299
+ }));
300
+ tables[tableName] = {
301
+ name: tableName,
302
+ columns,
303
+ ...ifDefined("primaryKey", primaryKey),
304
+ foreignKeys,
305
+ uniques,
306
+ indexes
307
+ };
308
+ }
309
+ const extensions = extensionsResult.rows.map((row) => row.extname);
310
+ const storageTypes = await pgEnumControlHooks.introspectTypes?.({
311
+ driver,
312
+ schemaName: schema
313
+ }) ?? {};
314
+ return {
315
+ tables,
316
+ extensions,
317
+ annotations: { pg: {
318
+ schema,
319
+ version: await this.getPostgresVersion(driver),
320
+ ...ifDefined("storageTypes", Object.keys(storageTypes).length > 0 ? storageTypes : void 0)
321
+ } }
322
+ };
323
+ }
324
+ /**
325
+ * Gets the Postgres version from the database.
326
+ */
327
+ async getPostgresVersion(driver) {
328
+ return ((await driver.query("SELECT version() AS version", [])).rows[0]?.version ?? "").match(/PostgreSQL (\d+\.\d+)/)?.[1] ?? "unknown";
329
+ }
330
+ };
331
+ /**
332
+ * Pre-computed lookup map for simple prefix-based type normalization.
333
+ * Maps short Postgres type names to their canonical SQL names.
334
+ * Using a Map for O(1) lookup instead of multiple startsWith checks.
335
+ */
336
+ const TYPE_PREFIX_MAP = new Map([
337
+ ["varchar", "character varying"],
338
+ ["bpchar", "character"],
339
+ ["varbit", "bit varying"]
340
+ ]);
341
+ /**
342
+ * Normalizes a Postgres schema native type to its canonical form for comparison.
343
+ *
344
+ * Uses a pre-computed lookup map for simple prefix replacements (O(1))
345
+ * and handles complex temporal type normalization separately.
346
+ */
347
+ function normalizeSchemaNativeType(nativeType) {
348
+ const trimmed = nativeType.trim();
349
+ for (const [prefix, replacement] of TYPE_PREFIX_MAP) if (trimmed.startsWith(prefix)) return replacement + trimmed.slice(prefix.length);
350
+ if (trimmed.includes(" with time zone")) {
351
+ if (trimmed.startsWith("timestamp")) return `timestamptz${trimmed.slice(9).replace(" with time zone", "")}`;
352
+ if (trimmed.startsWith("time")) return `timetz${trimmed.slice(4).replace(" with time zone", "")}`;
353
+ }
354
+ if (trimmed.includes(" without time zone")) return trimmed.replace(" without time zone", "");
355
+ return trimmed;
356
+ }
357
+ function normalizeFormattedType(formattedType, dataType, udtName) {
358
+ if (formattedType === "integer") return "int4";
359
+ if (formattedType === "smallint") return "int2";
360
+ if (formattedType === "bigint") return "int8";
361
+ if (formattedType === "real") return "float4";
362
+ if (formattedType === "double precision") return "float8";
363
+ if (formattedType === "boolean") return "bool";
364
+ if (formattedType.startsWith("varchar")) return formattedType.replace("varchar", "character varying");
365
+ if (formattedType.startsWith("bpchar")) return formattedType.replace("bpchar", "character");
366
+ if (formattedType.startsWith("varbit")) return formattedType.replace("varbit", "bit varying");
367
+ if (dataType === "timestamp with time zone" || udtName === "timestamptz") return formattedType.replace("timestamp", "timestamptz").replace(" with time zone", "").trim();
368
+ if (dataType === "timestamp without time zone" || udtName === "timestamp") return formattedType.replace(" without time zone", "").trim();
369
+ if (dataType === "time with time zone" || udtName === "timetz") return formattedType.replace("time", "timetz").replace(" with time zone", "").trim();
370
+ if (dataType === "time without time zone" || udtName === "time") return formattedType.replace(" without time zone", "").trim();
371
+ if (formattedType.startsWith("\"") && formattedType.endsWith("\"")) return formattedType.slice(1, -1);
372
+ return formattedType;
373
+ }
374
+ /**
375
+ * Groups an array of objects by a specified key.
376
+ * Returns a Map for O(1) lookup by group key.
377
+ */
378
+ function groupBy(items, key) {
379
+ const map = /* @__PURE__ */ new Map();
380
+ for (const item of items) {
381
+ const groupKey = item[key];
382
+ let group = map.get(groupKey);
383
+ if (!group) {
384
+ group = [];
385
+ map.set(groupKey, group);
386
+ }
387
+ group.push(item);
388
+ }
389
+ return map;
390
+ }
391
+
392
+ //#endregion
393
+ //#region src/exports/control.ts
394
+ const postgresAdapterDescriptor = {
395
+ ...postgresAdapterDescriptorMeta,
396
+ operationSignatures: () => [],
397
+ create() {
398
+ return new PostgresControlAdapter();
399
+ }
400
+ };
401
+ var control_default = postgresAdapterDescriptor;
402
+
403
+ //#endregion
404
+ export { SqlEscapeError, control_default as default, escapeLiteral, expandParameterizedNativeType, normalizeSchemaNativeType, parsePostgresDefault, qualifyName, quoteIdentifier };
405
+ //# sourceMappingURL=control.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control.mjs","names":["tables: Record<string, SqlTableIR>","columns: Record<string, SqlColumnIR>","primaryKey: PrimaryKey | undefined","foreignKeys: readonly SqlForeignKeyIR[]","uniques: readonly SqlUniqueIR[]","indexes: readonly SqlIndexIR[]","TYPE_PREFIX_MAP: ReadonlyMap<string, string>","postgresAdapterDescriptor: SqlControlAdapterDescriptor<'postgres'>"],"sources":["../src/core/default-normalizer.ts","../src/core/control-adapter.ts","../src/exports/control.ts"],"sourcesContent":["import type { ColumnDefault } from '@prisma-next/contract/types';\n\n/**\n * Pre-compiled regex patterns for performance.\n * These are compiled once at module load time rather than on each function call.\n */\nconst NEXTVAL_PATTERN = /^nextval\\s*\\(/i;\nconst TIMESTAMP_PATTERN = /^(now\\s*\\(\\s*\\)|CURRENT_TIMESTAMP|clock_timestamp\\s*\\(\\s*\\))$/i;\nconst UUID_PATTERN = /^gen_random_uuid\\s*\\(\\s*\\)$/i;\nconst UUID_OSSP_PATTERN = /^uuid_generate_v4\\s*\\(\\s*\\)$/i;\nconst TRUE_PATTERN = /^true$/i;\nconst FALSE_PATTERN = /^false$/i;\nconst NUMERIC_PATTERN = /^-?\\d+(\\.\\d+)?$/;\nconst STRING_LITERAL_PATTERN = /^'((?:[^']|'')*)'(?:::(?:\"[^\"]+\"|[\\w\\s]+)(?:\\(\\d+\\))?)?$/;\n\n/**\n * Parses a raw Postgres column default expression into a normalized ColumnDefault.\n * This enables semantic comparison between contract defaults and introspected schema defaults.\n *\n * Used by the migration diff layer to normalize raw database defaults during comparison,\n * keeping the introspection layer focused on faithful data capture.\n *\n * @param rawDefault - Raw default expression from information_schema.columns.column_default\n * @param _nativeType - Native column type (currently unused, reserved for future type-aware parsing)\n * @returns Normalized ColumnDefault or undefined if the expression cannot be parsed\n */\nexport function parsePostgresDefault(\n rawDefault: string,\n _nativeType?: string,\n): ColumnDefault | undefined {\n const trimmed = rawDefault.trim();\n\n // Autoincrement: nextval('tablename_column_seq'::regclass)\n if (NEXTVAL_PATTERN.test(trimmed)) {\n return { kind: 'function', expression: 'autoincrement()' };\n }\n\n // now() / CURRENT_TIMESTAMP / clock_timestamp()\n if (TIMESTAMP_PATTERN.test(trimmed)) {\n return { kind: 'function', expression: 'now()' };\n }\n\n // gen_random_uuid()\n if (UUID_PATTERN.test(trimmed)) {\n return { kind: 'function', expression: 'gen_random_uuid()' };\n }\n\n // uuid_generate_v4() from uuid-ossp extension\n if (UUID_OSSP_PATTERN.test(trimmed)) {\n return { kind: 'function', expression: 'gen_random_uuid()' };\n }\n\n // Boolean literals\n if (TRUE_PATTERN.test(trimmed)) {\n return { kind: 'literal', expression: 'true' };\n }\n if (FALSE_PATTERN.test(trimmed)) {\n return { kind: 'literal', expression: 'false' };\n }\n\n // Numeric literals (integer or decimal)\n if (NUMERIC_PATTERN.test(trimmed)) {\n return { kind: 'literal', expression: trimmed };\n }\n\n // String literals: 'value'::type or just 'value'\n // Match: 'some text'::text, 'hello'::character varying, 'value', etc.\n // Strip the ::type cast so the normalized expression matches what contract authors write.\n const stringMatch = trimmed.match(STRING_LITERAL_PATTERN);\n if (stringMatch?.[1] !== undefined) {\n return { kind: 'literal', expression: `'${stringMatch[1]}'` };\n }\n\n // Unrecognized expression - return as a function with the raw expression\n // This preserves the information for debugging while still being comparable\n return { kind: 'function', expression: trimmed };\n}\n","import type { ControlDriverInstance } from '@prisma-next/core-control-plane/types';\nimport type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';\nimport type {\n PrimaryKey,\n SqlColumnIR,\n SqlForeignKeyIR,\n SqlIndexIR,\n SqlSchemaIR,\n SqlTableIR,\n SqlUniqueIR,\n} from '@prisma-next/sql-schema-ir/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { parsePostgresDefault } from './default-normalizer';\nimport { pgEnumControlHooks } from './enum-control-hooks';\n\n/**\n * Postgres control plane adapter for control-plane operations like introspection.\n * Provides target-specific implementations for control-plane domain actions.\n */\nexport class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {\n readonly familyId = 'sql' as const;\n readonly targetId = 'postgres' as const;\n /**\n * @deprecated Use targetId instead\n */\n readonly target = 'postgres' as const;\n\n /**\n * Target-specific normalizer for raw Postgres default expressions.\n * Used by schema verification to normalize raw defaults before comparison.\n */\n readonly normalizeDefault = parsePostgresDefault;\n\n /**\n * Target-specific normalizer for Postgres schema native type names.\n * Used by schema verification to normalize introspected type names\n * before comparison with contract native types.\n */\n readonly normalizeNativeType = normalizeSchemaNativeType;\n\n /**\n * Introspects a Postgres database schema and returns a raw SqlSchemaIR.\n *\n * This is a pure schema discovery operation that queries the Postgres catalog\n * and returns the schema structure without type mapping or contract enrichment.\n * Type mapping and enrichment are handled separately by enrichment helpers.\n *\n * Uses batched queries to minimize database round trips (7 queries instead of 5T+3).\n *\n * @param driver - ControlDriverInstance<'sql', 'postgres'> instance for executing queries\n * @param contractIR - Optional contract IR for contract-guided introspection (filtering, optimization)\n * @param schema - Schema name to introspect (defaults to 'public')\n * @returns Promise resolving to SqlSchemaIR representing the live database schema\n */\n async introspect(\n driver: ControlDriverInstance<'sql', 'postgres'>,\n _contractIR?: unknown,\n schema = 'public',\n ): Promise<SqlSchemaIR> {\n // Execute all queries in parallel for efficiency (7 queries instead of 5T+3)\n const [\n tablesResult,\n columnsResult,\n pkResult,\n fkResult,\n uniqueResult,\n indexResult,\n extensionsResult,\n ] = await Promise.all([\n // Query all tables\n driver.query<{ table_name: string }>(\n `SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = $1\n AND table_type = 'BASE TABLE'\n ORDER BY table_name`,\n [schema],\n ),\n // Query all columns for all tables in schema\n driver.query<{\n table_name: string;\n column_name: string;\n data_type: string;\n udt_name: string;\n is_nullable: string;\n character_maximum_length: number | null;\n numeric_precision: number | null;\n numeric_scale: number | null;\n column_default: string | null;\n formatted_type: string | null;\n }>(\n `SELECT\n c.table_name,\n column_name,\n data_type,\n udt_name,\n is_nullable,\n character_maximum_length,\n numeric_precision,\n numeric_scale,\n column_default,\n format_type(a.atttypid, a.atttypmod) AS formatted_type\n FROM information_schema.columns c\n JOIN pg_catalog.pg_class cl\n ON cl.relname = c.table_name\n JOIN pg_catalog.pg_namespace ns\n ON ns.nspname = c.table_schema\n AND ns.oid = cl.relnamespace\n JOIN pg_catalog.pg_attribute a\n ON a.attrelid = cl.oid\n AND a.attname = c.column_name\n AND a.attnum > 0\n AND NOT a.attisdropped\n WHERE c.table_schema = $1\n ORDER BY c.table_name, c.ordinal_position`,\n [schema],\n ),\n // Query all primary keys for all tables in schema\n driver.query<{\n table_name: string;\n constraint_name: string;\n column_name: string;\n ordinal_position: number;\n }>(\n `SELECT\n tc.table_name,\n tc.constraint_name,\n kcu.column_name,\n kcu.ordinal_position\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n AND tc.table_name = kcu.table_name\n WHERE tc.table_schema = $1\n AND tc.constraint_type = 'PRIMARY KEY'\n ORDER BY tc.table_name, kcu.ordinal_position`,\n [schema],\n ),\n // Query all foreign keys for all tables in schema\n driver.query<{\n table_name: string;\n constraint_name: string;\n column_name: string;\n ordinal_position: number;\n referenced_table_schema: string;\n referenced_table_name: string;\n referenced_column_name: string;\n }>(\n `SELECT\n tc.table_name,\n tc.constraint_name,\n kcu.column_name,\n kcu.ordinal_position,\n ccu.table_schema AS referenced_table_schema,\n ccu.table_name AS referenced_table_name,\n ccu.column_name AS referenced_column_name\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n AND tc.table_name = kcu.table_name\n JOIN information_schema.constraint_column_usage ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.table_schema = $1\n AND tc.constraint_type = 'FOREIGN KEY'\n ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`,\n [schema],\n ),\n // Query all unique constraints for all tables in schema (excluding PKs)\n driver.query<{\n table_name: string;\n constraint_name: string;\n column_name: string;\n ordinal_position: number;\n }>(\n `SELECT\n tc.table_name,\n tc.constraint_name,\n kcu.column_name,\n kcu.ordinal_position\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n AND tc.table_name = kcu.table_name\n WHERE tc.table_schema = $1\n AND tc.constraint_type = 'UNIQUE'\n ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`,\n [schema],\n ),\n // Query all indexes for all tables in schema (excluding constraints)\n driver.query<{\n tablename: string;\n indexname: string;\n indisunique: boolean;\n attname: string;\n attnum: number;\n }>(\n `SELECT\n i.tablename,\n i.indexname,\n ix.indisunique,\n a.attname,\n a.attnum\n FROM pg_indexes i\n JOIN pg_class ic ON ic.relname = i.indexname\n JOIN pg_namespace ins ON ins.oid = ic.relnamespace AND ins.nspname = $1\n JOIN pg_index ix ON ix.indexrelid = ic.oid\n JOIN pg_class t ON t.oid = ix.indrelid\n JOIN pg_namespace tn ON tn.oid = t.relnamespace AND tn.nspname = $1\n LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) AND a.attnum > 0\n WHERE i.schemaname = $1\n AND NOT EXISTS (\n SELECT 1\n FROM information_schema.table_constraints tc\n WHERE tc.table_schema = $1\n AND tc.table_name = i.tablename\n AND tc.constraint_name = i.indexname\n )\n ORDER BY i.tablename, i.indexname, a.attnum`,\n [schema],\n ),\n // Query extensions\n driver.query<{ extname: string }>(\n `SELECT extname\n FROM pg_extension\n ORDER BY extname`,\n [],\n ),\n ]);\n\n // Group results by table name for efficient lookup\n const columnsByTable = groupBy(columnsResult.rows, 'table_name');\n const pksByTable = groupBy(pkResult.rows, 'table_name');\n const fksByTable = groupBy(fkResult.rows, 'table_name');\n const uniquesByTable = groupBy(uniqueResult.rows, 'table_name');\n const indexesByTable = groupBy(indexResult.rows, 'tablename');\n\n // Get set of PK constraint names per table (to exclude from uniques)\n const pkConstraintsByTable = new Map<string, Set<string>>();\n for (const row of pkResult.rows) {\n let constraints = pkConstraintsByTable.get(row.table_name);\n if (!constraints) {\n constraints = new Set();\n pkConstraintsByTable.set(row.table_name, constraints);\n }\n constraints.add(row.constraint_name);\n }\n\n const tables: Record<string, SqlTableIR> = {};\n\n for (const tableRow of tablesResult.rows) {\n const tableName = tableRow.table_name;\n\n // Process columns for this table\n const columns: Record<string, SqlColumnIR> = {};\n for (const colRow of columnsByTable.get(tableName) ?? []) {\n let nativeType = colRow.udt_name;\n const formattedType = colRow.formatted_type\n ? normalizeFormattedType(colRow.formatted_type, colRow.data_type, colRow.udt_name)\n : null;\n if (formattedType) {\n nativeType = formattedType;\n } else if (colRow.data_type === 'character varying' || colRow.data_type === 'character') {\n if (colRow.character_maximum_length) {\n nativeType = `${colRow.data_type}(${colRow.character_maximum_length})`;\n } else {\n nativeType = colRow.data_type;\n }\n } else if (colRow.data_type === 'numeric' || colRow.data_type === 'decimal') {\n if (colRow.numeric_precision && colRow.numeric_scale !== null) {\n nativeType = `${colRow.data_type}(${colRow.numeric_precision},${colRow.numeric_scale})`;\n } else if (colRow.numeric_precision) {\n nativeType = `${colRow.data_type}(${colRow.numeric_precision})`;\n } else {\n nativeType = colRow.data_type;\n }\n } else {\n nativeType = colRow.udt_name || colRow.data_type;\n }\n\n columns[colRow.column_name] = {\n name: colRow.column_name,\n nativeType,\n nullable: colRow.is_nullable === 'YES',\n ...ifDefined('default', colRow.column_default ?? undefined),\n };\n }\n\n // Process primary key\n const pkRows = [...(pksByTable.get(tableName) ?? [])];\n const primaryKeyColumns = pkRows\n .sort((a, b) => a.ordinal_position - b.ordinal_position)\n .map((row) => row.column_name);\n const primaryKey: PrimaryKey | undefined =\n primaryKeyColumns.length > 0\n ? {\n columns: primaryKeyColumns,\n ...(pkRows[0]?.constraint_name ? { name: pkRows[0].constraint_name } : {}),\n }\n : undefined;\n\n // Process foreign keys\n const foreignKeysMap = new Map<\n string,\n { columns: string[]; referencedTable: string; referencedColumns: string[]; name: string }\n >();\n for (const fkRow of fksByTable.get(tableName) ?? []) {\n const existing = foreignKeysMap.get(fkRow.constraint_name);\n if (existing) {\n existing.columns.push(fkRow.column_name);\n existing.referencedColumns.push(fkRow.referenced_column_name);\n } else {\n foreignKeysMap.set(fkRow.constraint_name, {\n columns: [fkRow.column_name],\n referencedTable: fkRow.referenced_table_name,\n referencedColumns: [fkRow.referenced_column_name],\n name: fkRow.constraint_name,\n });\n }\n }\n const foreignKeys: readonly SqlForeignKeyIR[] = Array.from(foreignKeysMap.values()).map(\n (fk) => ({\n columns: Object.freeze([...fk.columns]) as readonly string[],\n referencedTable: fk.referencedTable,\n referencedColumns: Object.freeze([...fk.referencedColumns]) as readonly string[],\n name: fk.name,\n }),\n );\n\n // Process unique constraints (excluding those that are also PKs)\n const pkConstraints = pkConstraintsByTable.get(tableName) ?? new Set();\n const uniquesMap = new Map<string, { columns: string[]; name: string }>();\n for (const uniqueRow of uniquesByTable.get(tableName) ?? []) {\n // Skip if this constraint is also a primary key\n if (pkConstraints.has(uniqueRow.constraint_name)) {\n continue;\n }\n const existing = uniquesMap.get(uniqueRow.constraint_name);\n if (existing) {\n existing.columns.push(uniqueRow.column_name);\n } else {\n uniquesMap.set(uniqueRow.constraint_name, {\n columns: [uniqueRow.column_name],\n name: uniqueRow.constraint_name,\n });\n }\n }\n const uniques: readonly SqlUniqueIR[] = Array.from(uniquesMap.values()).map((uq) => ({\n columns: Object.freeze([...uq.columns]) as readonly string[],\n name: uq.name,\n }));\n\n // Process indexes\n const indexesMap = new Map<string, { columns: string[]; name: string; unique: boolean }>();\n for (const idxRow of indexesByTable.get(tableName) ?? []) {\n if (!idxRow.attname) {\n continue;\n }\n const existing = indexesMap.get(idxRow.indexname);\n if (existing) {\n existing.columns.push(idxRow.attname);\n } else {\n indexesMap.set(idxRow.indexname, {\n columns: [idxRow.attname],\n name: idxRow.indexname,\n unique: idxRow.indisunique,\n });\n }\n }\n const indexes: readonly SqlIndexIR[] = Array.from(indexesMap.values()).map((idx) => ({\n columns: Object.freeze([...idx.columns]) as readonly string[],\n name: idx.name,\n unique: idx.unique,\n }));\n\n tables[tableName] = {\n name: tableName,\n columns,\n ...ifDefined('primaryKey', primaryKey),\n foreignKeys,\n uniques,\n indexes,\n };\n }\n\n const extensions = extensionsResult.rows.map((row) => row.extname);\n\n const storageTypes =\n (await pgEnumControlHooks.introspectTypes?.({ driver, schemaName: schema })) ?? {};\n\n const annotations = {\n pg: {\n schema,\n version: await this.getPostgresVersion(driver),\n ...ifDefined(\n 'storageTypes',\n Object.keys(storageTypes).length > 0 ? storageTypes : undefined,\n ),\n },\n };\n\n return {\n tables,\n extensions,\n annotations,\n };\n }\n\n /**\n * Gets the Postgres version from the database.\n */\n private async getPostgresVersion(\n driver: ControlDriverInstance<'sql', 'postgres'>,\n ): Promise<string> {\n const result = await driver.query<{ version: string }>('SELECT version() AS version', []);\n const versionString = result.rows[0]?.version ?? '';\n // Extract version number from \"PostgreSQL 15.1 ...\" format\n const match = versionString.match(/PostgreSQL (\\d+\\.\\d+)/);\n return match?.[1] ?? 'unknown';\n }\n}\n\n/**\n * Pre-computed lookup map for simple prefix-based type normalization.\n * Maps short Postgres type names to their canonical SQL names.\n * Using a Map for O(1) lookup instead of multiple startsWith checks.\n */\nconst TYPE_PREFIX_MAP: ReadonlyMap<string, string> = new Map([\n ['varchar', 'character varying'],\n ['bpchar', 'character'],\n ['varbit', 'bit varying'],\n]);\n\n/**\n * Normalizes a Postgres schema native type to its canonical form for comparison.\n *\n * Uses a pre-computed lookup map for simple prefix replacements (O(1))\n * and handles complex temporal type normalization separately.\n */\nexport function normalizeSchemaNativeType(nativeType: string): string {\n const trimmed = nativeType.trim();\n\n // Fast path: check simple prefix replacements using the lookup map\n for (const [prefix, replacement] of TYPE_PREFIX_MAP) {\n if (trimmed.startsWith(prefix)) {\n return replacement + trimmed.slice(prefix.length);\n }\n }\n\n // Temporal types with time zone handling\n // Check for 'with time zone' suffix first (more specific)\n if (trimmed.includes(' with time zone')) {\n if (trimmed.startsWith('timestamp')) {\n return `timestamptz${trimmed.slice(9).replace(' with time zone', '')}`;\n }\n if (trimmed.startsWith('time')) {\n return `timetz${trimmed.slice(4).replace(' with time zone', '')}`;\n }\n }\n\n // Handle 'without time zone' suffix - just strip it\n if (trimmed.includes(' without time zone')) {\n return trimmed.replace(' without time zone', '');\n }\n\n return trimmed;\n}\n\nfunction normalizeFormattedType(formattedType: string, dataType: string, udtName: string): string {\n if (formattedType === 'integer') {\n return 'int4';\n }\n if (formattedType === 'smallint') {\n return 'int2';\n }\n if (formattedType === 'bigint') {\n return 'int8';\n }\n if (formattedType === 'real') {\n return 'float4';\n }\n if (formattedType === 'double precision') {\n return 'float8';\n }\n if (formattedType === 'boolean') {\n return 'bool';\n }\n if (formattedType.startsWith('varchar')) {\n return formattedType.replace('varchar', 'character varying');\n }\n if (formattedType.startsWith('bpchar')) {\n return formattedType.replace('bpchar', 'character');\n }\n if (formattedType.startsWith('varbit')) {\n return formattedType.replace('varbit', 'bit varying');\n }\n if (dataType === 'timestamp with time zone' || udtName === 'timestamptz') {\n return formattedType.replace('timestamp', 'timestamptz').replace(' with time zone', '').trim();\n }\n if (dataType === 'timestamp without time zone' || udtName === 'timestamp') {\n return formattedType.replace(' without time zone', '').trim();\n }\n if (dataType === 'time with time zone' || udtName === 'timetz') {\n return formattedType.replace('time', 'timetz').replace(' with time zone', '').trim();\n }\n if (dataType === 'time without time zone' || udtName === 'time') {\n return formattedType.replace(' without time zone', '').trim();\n }\n // Only dataType === 'USER-DEFINED' should ever be quoted, but this should be safe without\n // checking that explicitly either way\n if (formattedType.startsWith('\"') && formattedType.endsWith('\"')) {\n return formattedType.slice(1, -1);\n }\n return formattedType;\n}\n\n/**\n * Groups an array of objects by a specified key.\n * Returns a Map for O(1) lookup by group key.\n */\nfunction groupBy<T, K extends keyof T>(items: readonly T[], key: K): Map<T[K], T[]> {\n const map = new Map<T[K], T[]>();\n for (const item of items) {\n const groupKey = item[key];\n let group = map.get(groupKey);\n if (!group) {\n group = [];\n map.set(groupKey, group);\n }\n group.push(item);\n }\n return map;\n}\n","import type { SqlControlAdapterDescriptor } from '@prisma-next/family-sql/control';\nimport type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';\nimport { PostgresControlAdapter } from '../core/control-adapter';\nimport { parsePostgresDefault } from '../core/default-normalizer';\nimport { postgresAdapterDescriptorMeta } from '../core/descriptor-meta';\nimport { expandParameterizedNativeType } from '../core/parameterized-types';\nimport { escapeLiteral, qualifyName, quoteIdentifier, SqlEscapeError } from '../core/sql-utils';\n\nconst postgresAdapterDescriptor: SqlControlAdapterDescriptor<'postgres'> = {\n ...postgresAdapterDescriptorMeta,\n operationSignatures: () => [],\n create(): SqlControlAdapter<'postgres'> {\n return new PostgresControlAdapter();\n },\n};\n\nexport default postgresAdapterDescriptor;\n\nexport { normalizeSchemaNativeType } from '../core/control-adapter';\nexport {\n escapeLiteral,\n expandParameterizedNativeType,\n parsePostgresDefault,\n qualifyName,\n quoteIdentifier,\n SqlEscapeError,\n};\n"],"mappings":"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"}