@prisma-next/adapter-postgres 0.3.0-dev.41 → 0.3.0-dev.43
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/adapter.mjs +0 -2
- package/dist/codec-types.mjs +0 -1
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +44 -9
- package/dist/control.mjs.map +1 -1
- package/package.json +14 -14
- package/src/core/control-adapter.ts +69 -8
package/dist/adapter.mjs
CHANGED
package/dist/codec-types.mjs
CHANGED
package/dist/control.d.mts.map
CHANGED
|
@@ -1 +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;;;;
|
|
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;;;;ACqYA;;;;ACndgG;;;;;;;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;;;;ACqYA;;;;ACndgG;iBF+BhF,eAAA;;;;;;;;;;;;iBAiCA,aAAA;;;;iBAcA,WAAA;;;;;;;;;;iBCqYA,yBAAA;;;AH/bhB,cIlBM,yBJqBH,EIrB8B,2BJqBjB,CAAA,UAAA,CAAA"}
|
package/dist/control.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import "./codec-ids-Bsm9c7ns.mjs";
|
|
2
1
|
import { i as quoteIdentifier, n as escapeLiteral, r as qualifyName, t as SqlEscapeError } from "./sql-utils-CSfAGEwF.mjs";
|
|
3
2
|
import { n as expandParameterizedNativeType, r as pgEnumControlHooks, t as postgresAdapterDescriptorMeta } from "./descriptor-meta-ilnFI7bx.mjs";
|
|
4
3
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
@@ -175,17 +174,31 @@ var PostgresControlAdapter = class {
|
|
|
175
174
|
tc.constraint_name,
|
|
176
175
|
kcu.column_name,
|
|
177
176
|
kcu.ordinal_position,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
ref_ns.nspname AS referenced_table_schema,
|
|
178
|
+
ref_cl.relname AS referenced_table_name,
|
|
179
|
+
ref_att.attname AS referenced_column_name,
|
|
180
|
+
rc.delete_rule,
|
|
181
|
+
rc.update_rule
|
|
181
182
|
FROM information_schema.table_constraints tc
|
|
182
183
|
JOIN information_schema.key_column_usage kcu
|
|
183
184
|
ON tc.constraint_name = kcu.constraint_name
|
|
184
185
|
AND tc.table_schema = kcu.table_schema
|
|
185
186
|
AND tc.table_name = kcu.table_name
|
|
186
|
-
JOIN
|
|
187
|
-
ON
|
|
188
|
-
AND
|
|
187
|
+
JOIN pg_catalog.pg_constraint pgc
|
|
188
|
+
ON pgc.conname = tc.constraint_name
|
|
189
|
+
AND pgc.connamespace = (
|
|
190
|
+
SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = tc.table_schema
|
|
191
|
+
)
|
|
192
|
+
JOIN pg_catalog.pg_class ref_cl
|
|
193
|
+
ON ref_cl.oid = pgc.confrelid
|
|
194
|
+
JOIN pg_catalog.pg_namespace ref_ns
|
|
195
|
+
ON ref_ns.oid = ref_cl.relnamespace
|
|
196
|
+
JOIN pg_catalog.pg_attribute ref_att
|
|
197
|
+
ON ref_att.attrelid = pgc.confrelid
|
|
198
|
+
AND ref_att.attnum = pgc.confkey[kcu.ordinal_position]
|
|
199
|
+
JOIN information_schema.referential_constraints rc
|
|
200
|
+
ON rc.constraint_name = tc.constraint_name
|
|
201
|
+
AND rc.constraint_schema = tc.table_schema
|
|
189
202
|
WHERE tc.table_schema = $1
|
|
190
203
|
AND tc.constraint_type = 'FOREIGN KEY'
|
|
191
204
|
ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema]),
|
|
@@ -279,14 +292,18 @@ var PostgresControlAdapter = class {
|
|
|
279
292
|
columns: [fkRow.column_name],
|
|
280
293
|
referencedTable: fkRow.referenced_table_name,
|
|
281
294
|
referencedColumns: [fkRow.referenced_column_name],
|
|
282
|
-
name: fkRow.constraint_name
|
|
295
|
+
name: fkRow.constraint_name,
|
|
296
|
+
deleteRule: fkRow.delete_rule,
|
|
297
|
+
updateRule: fkRow.update_rule
|
|
283
298
|
});
|
|
284
299
|
}
|
|
285
300
|
const foreignKeys = Array.from(foreignKeysMap.values()).map((fk) => ({
|
|
286
301
|
columns: Object.freeze([...fk.columns]),
|
|
287
302
|
referencedTable: fk.referencedTable,
|
|
288
303
|
referencedColumns: Object.freeze([...fk.referencedColumns]),
|
|
289
|
-
name: fk.name
|
|
304
|
+
name: fk.name,
|
|
305
|
+
...ifDefined("onDelete", mapReferentialAction(fk.deleteRule)),
|
|
306
|
+
...ifDefined("onUpdate", mapReferentialAction(fk.updateRule))
|
|
290
307
|
}));
|
|
291
308
|
const pkConstraints = pkConstraintsByTable.get(tableName) ?? /* @__PURE__ */ new Set();
|
|
292
309
|
const uniquesMap = /* @__PURE__ */ new Map();
|
|
@@ -393,6 +410,24 @@ function normalizeFormattedType(formattedType, dataType, udtName) {
|
|
|
393
410
|
if (formattedType.startsWith("\"") && formattedType.endsWith("\"")) return formattedType.slice(1, -1);
|
|
394
411
|
return formattedType;
|
|
395
412
|
}
|
|
413
|
+
const PG_REFERENTIAL_ACTION_MAP = {
|
|
414
|
+
"NO ACTION": "noAction",
|
|
415
|
+
RESTRICT: "restrict",
|
|
416
|
+
CASCADE: "cascade",
|
|
417
|
+
"SET NULL": "setNull",
|
|
418
|
+
"SET DEFAULT": "setDefault"
|
|
419
|
+
};
|
|
420
|
+
/**
|
|
421
|
+
* Maps a Postgres referential action rule to the canonical SqlReferentialAction.
|
|
422
|
+
* Returns undefined for 'NO ACTION' (the database default) to keep the IR sparse.
|
|
423
|
+
* Throws for unrecognized rules to prevent silent data loss.
|
|
424
|
+
*/
|
|
425
|
+
function mapReferentialAction(rule) {
|
|
426
|
+
const mapped = PG_REFERENTIAL_ACTION_MAP[rule];
|
|
427
|
+
if (mapped === void 0) throw new Error(`Unknown PostgreSQL referential action rule: "${rule}". Expected one of: NO ACTION, RESTRICT, CASCADE, SET NULL, SET DEFAULT.`);
|
|
428
|
+
if (mapped === "noAction") return void 0;
|
|
429
|
+
return mapped;
|
|
430
|
+
}
|
|
396
431
|
/**
|
|
397
432
|
* Groups an array of objects by a specified key.
|
|
398
433
|
* Returns a Map for O(1) lookup by group key.
|
package/dist/control.mjs.map
CHANGED
|
@@ -1 +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 const normalizedType = _nativeType?.toLowerCase();\n const isBigInt = normalizedType === 'bigint' || normalizedType === 'int8';\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', value: true };\n }\n if (FALSE_PATTERN.test(trimmed)) {\n return { kind: 'literal', value: false };\n }\n\n // Numeric literals (integer or decimal)\n if (NUMERIC_PATTERN.test(trimmed)) {\n if (isBigInt) {\n return { kind: 'literal', value: { $type: 'bigint', value: trimmed } };\n }\n return { kind: 'literal', value: Number(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 const unescaped = stringMatch[1].replace(/''/g, \"'\");\n if (normalizedType === 'json' || normalizedType === 'jsonb') {\n try {\n return { kind: 'literal', value: JSON.parse(unescaped) };\n } catch {\n // Keep legacy behavior for malformed/non-JSON string content.\n }\n }\n return { kind: 'literal', value: unescaped };\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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"}
|
|
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>","PG_REFERENTIAL_ACTION_MAP: Record<PgReferentialActionRule, SqlReferentialAction>","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 const normalizedType = _nativeType?.toLowerCase();\n const isBigInt = normalizedType === 'bigint' || normalizedType === 'int8';\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', value: true };\n }\n if (FALSE_PATTERN.test(trimmed)) {\n return { kind: 'literal', value: false };\n }\n\n // Numeric literals (integer or decimal)\n if (NUMERIC_PATTERN.test(trimmed)) {\n if (isBigInt) {\n return { kind: 'literal', value: { $type: 'bigint', value: trimmed } };\n }\n return { kind: 'literal', value: Number(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 const unescaped = stringMatch[1].replace(/''/g, \"'\");\n if (normalizedType === 'json' || normalizedType === 'jsonb') {\n try {\n return { kind: 'literal', value: JSON.parse(unescaped) };\n } catch {\n // Keep legacy behavior for malformed/non-JSON string content.\n }\n }\n return { kind: 'literal', value: unescaped };\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 SqlReferentialAction,\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, including referential actions.\n // Uses pg_catalog for correct positional pairing of composite FK columns\n // (information_schema.constraint_column_usage lacks ordinal_position,\n // which causes Cartesian products for multi-column FKs).\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 delete_rule: string;\n update_rule: string;\n }>(\n `SELECT\n tc.table_name,\n tc.constraint_name,\n kcu.column_name,\n kcu.ordinal_position,\n ref_ns.nspname AS referenced_table_schema,\n ref_cl.relname AS referenced_table_name,\n ref_att.attname AS referenced_column_name,\n rc.delete_rule,\n rc.update_rule\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 pg_catalog.pg_constraint pgc\n ON pgc.conname = tc.constraint_name\n AND pgc.connamespace = (\n SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = tc.table_schema\n )\n JOIN pg_catalog.pg_class ref_cl\n ON ref_cl.oid = pgc.confrelid\n JOIN pg_catalog.pg_namespace ref_ns\n ON ref_ns.oid = ref_cl.relnamespace\n JOIN pg_catalog.pg_attribute ref_att\n ON ref_att.attrelid = pgc.confrelid\n AND ref_att.attnum = pgc.confkey[kcu.ordinal_position]\n JOIN information_schema.referential_constraints rc\n ON rc.constraint_name = tc.constraint_name\n AND rc.constraint_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 {\n columns: string[];\n referencedTable: string;\n referencedColumns: string[];\n name: string;\n deleteRule: string;\n updateRule: string;\n }\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 deleteRule: fkRow.delete_rule,\n updateRule: fkRow.update_rule,\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 ...ifDefined('onDelete', mapReferentialAction(fk.deleteRule)),\n ...ifDefined('onUpdate', mapReferentialAction(fk.updateRule)),\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 * The five standard PostgreSQL referential action rules as returned by\n * `information_schema.referential_constraints.delete_rule` / `update_rule`.\n */\ntype PgReferentialActionRule = 'NO ACTION' | 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'SET DEFAULT';\n\nconst PG_REFERENTIAL_ACTION_MAP: Record<PgReferentialActionRule, SqlReferentialAction> = {\n 'NO ACTION': 'noAction',\n RESTRICT: 'restrict',\n CASCADE: 'cascade',\n 'SET NULL': 'setNull',\n 'SET DEFAULT': 'setDefault',\n};\n\n/**\n * Maps a Postgres referential action rule to the canonical SqlReferentialAction.\n * Returns undefined for 'NO ACTION' (the database default) to keep the IR sparse.\n * Throws for unrecognized rules to prevent silent data loss.\n */\nfunction mapReferentialAction(rule: string): SqlReferentialAction | undefined {\n const mapped = PG_REFERENTIAL_ACTION_MAP[rule as PgReferentialActionRule];\n if (mapped === undefined) {\n throw new Error(\n `Unknown PostgreSQL referential action rule: \"${rule}\". Expected one of: NO ACTION, RESTRICT, CASCADE, SET NULL, SET DEFAULT.`,\n );\n }\n if (mapped === 'noAction') return undefined;\n return mapped;\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/adapter-postgres",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.43",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ajv": "^8.17.1",
|
|
12
12
|
"arktype": "^2.0.0",
|
|
13
|
-
"@prisma-next/
|
|
14
|
-
"@prisma-next/
|
|
15
|
-
"@prisma-next/contract-authoring": "0.3.0-dev.
|
|
16
|
-
"@prisma-next/core-control-plane": "0.3.0-dev.
|
|
17
|
-
"@prisma-next/core-execution-plane": "0.3.0-dev.
|
|
18
|
-
"@prisma-next/
|
|
19
|
-
"@prisma-next/sql-
|
|
20
|
-
"@prisma-next/sql-
|
|
21
|
-
"@prisma-next/sql-
|
|
22
|
-
"@prisma-next/sql-
|
|
23
|
-
"@prisma-next/
|
|
24
|
-
"@prisma-next/sql-
|
|
25
|
-
"@prisma-next/
|
|
13
|
+
"@prisma-next/cli": "0.3.0-dev.43",
|
|
14
|
+
"@prisma-next/contract": "0.3.0-dev.43",
|
|
15
|
+
"@prisma-next/contract-authoring": "0.3.0-dev.43",
|
|
16
|
+
"@prisma-next/core-control-plane": "0.3.0-dev.43",
|
|
17
|
+
"@prisma-next/core-execution-plane": "0.3.0-dev.43",
|
|
18
|
+
"@prisma-next/sql-contract": "0.3.0-dev.43",
|
|
19
|
+
"@prisma-next/sql-operations": "0.3.0-dev.43",
|
|
20
|
+
"@prisma-next/sql-relational-core": "0.3.0-dev.43",
|
|
21
|
+
"@prisma-next/sql-runtime": "0.3.0-dev.43",
|
|
22
|
+
"@prisma-next/sql-schema-ir": "0.3.0-dev.43",
|
|
23
|
+
"@prisma-next/utils": "0.3.0-dev.43",
|
|
24
|
+
"@prisma-next/sql-contract-ts": "0.3.0-dev.43",
|
|
25
|
+
"@prisma-next/family-sql": "0.3.0-dev.43"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"tsdown": "0.18.4",
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
SqlColumnIR,
|
|
6
6
|
SqlForeignKeyIR,
|
|
7
7
|
SqlIndexIR,
|
|
8
|
+
SqlReferentialAction,
|
|
8
9
|
SqlSchemaIR,
|
|
9
10
|
SqlTableIR,
|
|
10
11
|
SqlUniqueIR,
|
|
@@ -137,7 +138,10 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
137
138
|
ORDER BY tc.table_name, kcu.ordinal_position`,
|
|
138
139
|
[schema],
|
|
139
140
|
),
|
|
140
|
-
// Query all foreign keys for all tables in schema
|
|
141
|
+
// Query all foreign keys for all tables in schema, including referential actions.
|
|
142
|
+
// Uses pg_catalog for correct positional pairing of composite FK columns
|
|
143
|
+
// (information_schema.constraint_column_usage lacks ordinal_position,
|
|
144
|
+
// which causes Cartesian products for multi-column FKs).
|
|
141
145
|
driver.query<{
|
|
142
146
|
table_name: string;
|
|
143
147
|
constraint_name: string;
|
|
@@ -146,23 +150,39 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
146
150
|
referenced_table_schema: string;
|
|
147
151
|
referenced_table_name: string;
|
|
148
152
|
referenced_column_name: string;
|
|
153
|
+
delete_rule: string;
|
|
154
|
+
update_rule: string;
|
|
149
155
|
}>(
|
|
150
156
|
`SELECT
|
|
151
157
|
tc.table_name,
|
|
152
158
|
tc.constraint_name,
|
|
153
159
|
kcu.column_name,
|
|
154
160
|
kcu.ordinal_position,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
ref_ns.nspname AS referenced_table_schema,
|
|
162
|
+
ref_cl.relname AS referenced_table_name,
|
|
163
|
+
ref_att.attname AS referenced_column_name,
|
|
164
|
+
rc.delete_rule,
|
|
165
|
+
rc.update_rule
|
|
158
166
|
FROM information_schema.table_constraints tc
|
|
159
167
|
JOIN information_schema.key_column_usage kcu
|
|
160
168
|
ON tc.constraint_name = kcu.constraint_name
|
|
161
169
|
AND tc.table_schema = kcu.table_schema
|
|
162
170
|
AND tc.table_name = kcu.table_name
|
|
163
|
-
JOIN
|
|
164
|
-
ON
|
|
165
|
-
AND
|
|
171
|
+
JOIN pg_catalog.pg_constraint pgc
|
|
172
|
+
ON pgc.conname = tc.constraint_name
|
|
173
|
+
AND pgc.connamespace = (
|
|
174
|
+
SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = tc.table_schema
|
|
175
|
+
)
|
|
176
|
+
JOIN pg_catalog.pg_class ref_cl
|
|
177
|
+
ON ref_cl.oid = pgc.confrelid
|
|
178
|
+
JOIN pg_catalog.pg_namespace ref_ns
|
|
179
|
+
ON ref_ns.oid = ref_cl.relnamespace
|
|
180
|
+
JOIN pg_catalog.pg_attribute ref_att
|
|
181
|
+
ON ref_att.attrelid = pgc.confrelid
|
|
182
|
+
AND ref_att.attnum = pgc.confkey[kcu.ordinal_position]
|
|
183
|
+
JOIN information_schema.referential_constraints rc
|
|
184
|
+
ON rc.constraint_name = tc.constraint_name
|
|
185
|
+
AND rc.constraint_schema = tc.table_schema
|
|
166
186
|
WHERE tc.table_schema = $1
|
|
167
187
|
AND tc.constraint_type = 'FOREIGN KEY'
|
|
168
188
|
ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`,
|
|
@@ -305,7 +325,14 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
305
325
|
// Process foreign keys
|
|
306
326
|
const foreignKeysMap = new Map<
|
|
307
327
|
string,
|
|
308
|
-
{
|
|
328
|
+
{
|
|
329
|
+
columns: string[];
|
|
330
|
+
referencedTable: string;
|
|
331
|
+
referencedColumns: string[];
|
|
332
|
+
name: string;
|
|
333
|
+
deleteRule: string;
|
|
334
|
+
updateRule: string;
|
|
335
|
+
}
|
|
309
336
|
>();
|
|
310
337
|
for (const fkRow of fksByTable.get(tableName) ?? []) {
|
|
311
338
|
const existing = foreignKeysMap.get(fkRow.constraint_name);
|
|
@@ -318,6 +345,8 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
318
345
|
referencedTable: fkRow.referenced_table_name,
|
|
319
346
|
referencedColumns: [fkRow.referenced_column_name],
|
|
320
347
|
name: fkRow.constraint_name,
|
|
348
|
+
deleteRule: fkRow.delete_rule,
|
|
349
|
+
updateRule: fkRow.update_rule,
|
|
321
350
|
});
|
|
322
351
|
}
|
|
323
352
|
}
|
|
@@ -327,6 +356,8 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
327
356
|
referencedTable: fk.referencedTable,
|
|
328
357
|
referencedColumns: Object.freeze([...fk.referencedColumns]) as readonly string[],
|
|
329
358
|
name: fk.name,
|
|
359
|
+
...ifDefined('onDelete', mapReferentialAction(fk.deleteRule)),
|
|
360
|
+
...ifDefined('onUpdate', mapReferentialAction(fk.updateRule)),
|
|
330
361
|
}),
|
|
331
362
|
);
|
|
332
363
|
|
|
@@ -517,6 +548,36 @@ function normalizeFormattedType(formattedType: string, dataType: string, udtName
|
|
|
517
548
|
return formattedType;
|
|
518
549
|
}
|
|
519
550
|
|
|
551
|
+
/**
|
|
552
|
+
* The five standard PostgreSQL referential action rules as returned by
|
|
553
|
+
* `information_schema.referential_constraints.delete_rule` / `update_rule`.
|
|
554
|
+
*/
|
|
555
|
+
type PgReferentialActionRule = 'NO ACTION' | 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'SET DEFAULT';
|
|
556
|
+
|
|
557
|
+
const PG_REFERENTIAL_ACTION_MAP: Record<PgReferentialActionRule, SqlReferentialAction> = {
|
|
558
|
+
'NO ACTION': 'noAction',
|
|
559
|
+
RESTRICT: 'restrict',
|
|
560
|
+
CASCADE: 'cascade',
|
|
561
|
+
'SET NULL': 'setNull',
|
|
562
|
+
'SET DEFAULT': 'setDefault',
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Maps a Postgres referential action rule to the canonical SqlReferentialAction.
|
|
567
|
+
* Returns undefined for 'NO ACTION' (the database default) to keep the IR sparse.
|
|
568
|
+
* Throws for unrecognized rules to prevent silent data loss.
|
|
569
|
+
*/
|
|
570
|
+
function mapReferentialAction(rule: string): SqlReferentialAction | undefined {
|
|
571
|
+
const mapped = PG_REFERENTIAL_ACTION_MAP[rule as PgReferentialActionRule];
|
|
572
|
+
if (mapped === undefined) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
`Unknown PostgreSQL referential action rule: "${rule}". Expected one of: NO ACTION, RESTRICT, CASCADE, SET NULL, SET DEFAULT.`,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
if (mapped === 'noAction') return undefined;
|
|
578
|
+
return mapped;
|
|
579
|
+
}
|
|
580
|
+
|
|
520
581
|
/**
|
|
521
582
|
* Groups an array of objects by a specified key.
|
|
522
583
|
* Returns a Map for O(1) lookup by group key.
|