@prisma-next/adapter-postgres 0.14.0 → 0.15.0-dev.10

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.
@@ -5,10 +5,13 @@ import { parseMarkerRowSafely, rethrowMarkerReadError, withMarkerReadErrorHandli
5
5
  import { parseContractMarkerRow } from "@prisma-next/family-sql/verify";
6
6
  import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
7
7
  import { REFERENTIAL_ACTION_SQL } from "@prisma-next/sql-contract/referential-action-sql";
8
+ import { RelationalSchemaNodeKind } from "@prisma-next/sql-schema-ir/types";
8
9
  import { buildControlTableBootstrapQueries, buildSignMarkerBootstrapQueries, int4, int8, jsonb, pgTable, text, textArray, timestamptz } from "@prisma-next/target-postgres/contract-free";
9
10
  import { parsePostgresDefault } from "@prisma-next/target-postgres/default-normalizer";
10
11
  import { normalizeSchemaNativeType } from "@prisma-next/target-postgres/native-type-normalizer";
12
+ import { parseRlsPolicyWireName } from "@prisma-next/target-postgres/rls-canonicalize";
11
13
  import { escapeLiteral, quoteIdentifier } from "@prisma-next/target-postgres/sql-utils";
14
+ import { PostgresDatabaseSchemaNode, PostgresNamespaceSchemaNode, PostgresNativeEnumSchemaNode, PostgresPolicySchemaNode, PostgresRoleSchemaNode, PostgresSchemaNodeKind, PostgresTableSchemaNode } from "@prisma-next/target-postgres/types";
12
15
  import { blindCast } from "@prisma-next/utils/casts";
13
16
  import { ifDefined } from "@prisma-next/utils/defined";
14
17
  import { createAstCodecRegistry, deriveParamMetadata, encodeParamsWithMetadata } from "@prisma-next/sql-runtime";
@@ -76,6 +79,20 @@ const ledger = pgTable({
76
79
  operations: jsonb()
77
80
  });
78
81
  /**
82
+ * Content-addressed contract store: one row per distinct contract, keyed
83
+ * by its storage hash. The ledger's `origin_core_hash` /
84
+ * `destination_core_hash` resolve here by hash equality, so both
85
+ * endpoints of every edge are direct lookups and a contract revisited by
86
+ * a rollback cycle is stored exactly once (upsert DO NOTHING).
87
+ */
88
+ const ledgerContract = pgTable({
89
+ name: "contract",
90
+ schema: "prisma_contract"
91
+ }, {
92
+ core_hash: text(),
93
+ contract_json: jsonb()
94
+ });
95
+ /**
79
96
  * Read-side handle covering every column of `prisma_contract.ledger`,
80
97
  * including the DB-generated `id` (for ORDER BY) and `created_at`.
81
98
  */
@@ -107,7 +124,7 @@ const NOW = new RawExpr({
107
124
  }
108
125
  });
109
126
  function mergeInvariants(current, incoming) {
110
- return [...new Set([...current, ...incoming])].sort();
127
+ return [.../* @__PURE__ */ new Set([...current, ...incoming])].sort();
111
128
  }
112
129
  async function execute(lower, driver, query) {
113
130
  const lowered = lower(query);
@@ -123,7 +140,7 @@ async function execute(lower, driver, query) {
123
140
  *
124
141
  * Spellings match the on-disk `meta.db.sql.postgres.nativeType` values in `@prisma-next/target-postgres`'s codec definitions, not the `udt_name` abbreviations that ADR 205 used as illustrative shorthand. The lookup-based cast policy compares against these strings directly.
125
142
  */
126
- const POSTGRES_INFERRABLE_NATIVE_TYPES = new Set([
143
+ const POSTGRES_INFERRABLE_NATIVE_TYPES = /* @__PURE__ */ new Set([
127
144
  "integer",
128
145
  "smallint",
129
146
  "bigint",
@@ -143,15 +160,19 @@ const POSTGRES_INFERRABLE_NATIVE_TYPES = new Set([
143
160
  "bit",
144
161
  "bit varying"
145
162
  ]);
146
- function renderTypedParam(index, codecId, codecLookup) {
163
+ function renderTypedParam(index, codecId, codecLookup, many, typeParams) {
147
164
  if (codecId === void 0) return `$${index}`;
148
- const meta = codecLookup.metaFor(codecId);
165
+ const meta = codecLookup.metaFor(codecId, typeParams);
149
166
  if (!(codecLookup.get(codecId) !== void 0 || meta !== void 0 || codecLookup.targetTypesFor(codecId) !== void 0)) throw new Error(`Postgres lowering: ParamRef carries codecId "${codecId}" but the assembled codec lookup has no entry for it. This usually indicates a missing extension pack in the runtime stack — register the pack that contributes this codec (e.g. \`extensionPacks: [pgvectorRuntime]\`), or use the codec directly from \`@prisma-next/target-postgres/codecs\` if it's a builtin.`);
150
167
  const dbRecord = meta?.db;
151
168
  const sqlBlock = isRecord(dbRecord) ? dbRecord["sql"] : void 0;
152
169
  const dialectBlock = isRecord(sqlBlock) ? sqlBlock["postgres"] : void 0;
153
170
  const nativeType = isRecord(dialectBlock) ? dialectBlock["nativeType"] : void 0;
154
- if (typeof nativeType === "string" && !POSTGRES_INFERRABLE_NATIVE_TYPES.has(nativeType)) return `$${index}::${nativeType}`;
171
+ if (typeof nativeType === "string") {
172
+ const arraySuffix = many ? "[]" : "";
173
+ if (!POSTGRES_INFERRABLE_NATIVE_TYPES.has(nativeType)) return `$${index}::${nativeType}${arraySuffix}`;
174
+ if (many) return `$${index}::${nativeType}${arraySuffix}`;
175
+ }
155
176
  return `$${index}`;
156
177
  }
157
178
  function isRecord(value) {
@@ -497,12 +518,12 @@ function renderExpr(expr, contract, pim) {
497
518
  function renderParamRef(ref, pim) {
498
519
  const index = pim.indexMap.get(ref);
499
520
  if (index === void 0) throw new Error("ParamRef not found in index map");
500
- if (ref.kind === "prepared-param-ref") return renderTypedParam(index, ref.codec.codecId, pim.codecLookup);
521
+ if (ref.kind === "prepared-param-ref") return renderTypedParam(index, ref.codec.codecId, pim.codecLookup, ref.codec.many, ref.codec.typeParams);
501
522
  if (ref.codec === void 0) throw runtimeError("RUNTIME.PARAM_REF_MISSING_CODEC", "Postgres renderer: ParamRef reached lowering without a bound CodecRef. Every column-bound ParamRef must carry a codec under the AST-bound codec contract. This usually indicates a builder path that constructed a ParamRef without threading the column codec.", {
502
523
  paramIndex: index,
503
524
  ...ifDefined("name", ref.name)
504
525
  });
505
- return renderTypedParam(index, ref.codec.codecId, pim.codecLookup);
526
+ return renderTypedParam(index, ref.codec.codecId, pim.codecLookup, ref.codec.many, ref.codec.typeParams);
506
527
  }
507
528
  function renderLiteral(expr) {
508
529
  if (typeof expr.value === "string") return `'${escapeLiteral(expr.value)}'`;
@@ -824,11 +845,20 @@ var PostgresControlAdapter = class {
824
845
  }).where(marker.space.eq(space).and(marker.core_hash.eq(expectedFrom))).returning(marker.space).build())).length > 0;
825
846
  }
826
847
  /**
827
- * Appends a ledger entry for `space`. See the
848
+ * Appends a ledger entry for `space`. When the edge carries a
849
+ * destination contract snapshot, the content-addressed
850
+ * `prisma_contract.contract` store is populated first (keyed by the
851
+ * destination hash, DO NOTHING on revisit) so a reader never sees a
852
+ * ledger row whose stored destination contract is missing. See the
828
853
  * `SqlControlAdapter.writeLedgerEntry` contract.
829
854
  */
830
855
  async writeLedgerEntry(driver, space, entry) {
831
- await execute((query) => this.lower(query, { contract: void 0 }), driver, ledger.insert({
856
+ const lower = (query) => this.lower(query, { contract: void 0 });
857
+ if (entry.destinationContractJson !== void 0) await execute(lower, driver, ledgerContract.upsert({
858
+ core_hash: entry.to,
859
+ contract_json: entry.destinationContractJson
860
+ }).onConflict(ledgerContract.core_hash).doNothing().build());
861
+ await execute(lower, driver, ledger.insert({
832
862
  space,
833
863
  migration_name: entry.migrationName,
834
864
  migration_hash: entry.migrationHash,
@@ -887,20 +917,34 @@ var PostgresControlAdapter = class {
887
917
  */
888
918
  async introspect(driver, contract, schema = "public") {
889
919
  const declaredNamespaces = extractContractNamespaceIds(contract);
890
- const ir = declaredNamespaces.length > 0 ? await this.introspectNamespaces(driver, declaredNamespaces) : await this.introspectSchema(driver, schema);
891
- const existingSchemas = await this.listExistingSchemas(driver);
892
- const annotations = ir.annotations ?? {};
893
- const pg = annotations.pg ?? {};
894
- return {
895
- ...ir,
896
- annotations: {
897
- ...annotations,
898
- pg: {
899
- ...pg,
900
- existingSchemas
901
- }
902
- }
903
- };
920
+ const resolvedSchemas = declaredNamespaces.length > 0 ? await this.resolveNamespaceSchemas(driver, declaredNamespaces) : [schema];
921
+ const namespaces = {};
922
+ let pgVersion = "unknown";
923
+ for (const resolved of resolvedSchemas) {
924
+ const { namespace, pgVersion: version } = await this.introspectSchema(driver, resolved);
925
+ namespaces[resolved] = namespace;
926
+ pgVersion = version;
927
+ }
928
+ return new PostgresDatabaseSchemaNode({
929
+ namespaces,
930
+ roles: await this.introspectRoles(driver),
931
+ existingSchemas: await this.listExistingSchemas(driver),
932
+ pgVersion
933
+ });
934
+ }
935
+ /**
936
+ * Reads cluster-scoped database roles. Roles are not schema-qualified, so
937
+ * this is queried once for the whole database rather than per namespace.
938
+ */
939
+ async introspectRoles(driver) {
940
+ return (await driver.query(`SELECT rolname
941
+ FROM pg_catalog.pg_roles
942
+ WHERE rolname NOT LIKE 'pg_%'
943
+ AND rolname != 'postgres'
944
+ ORDER BY rolname`)).rows.map((row) => new PostgresRoleSchemaNode({
945
+ name: row.rolname,
946
+ namespaceId: UNBOUND_NAMESPACE_ID
947
+ }));
904
948
  }
905
949
  /**
906
950
  * Lists every non-system schema present in the connected database.
@@ -919,36 +963,24 @@ var PostgresControlAdapter = class {
919
963
  ORDER BY nspname`)).rows.map((row) => row.nspname);
920
964
  }
921
965
  /**
922
- * Walks every declared namespace, resolving `UNBOUND_NAMESPACE_ID` to
923
- * the connection's `current_schema()`, and merges the per-schema results
924
- * into a single `SqlSchemaIR`. The merged `tables` map is flat (keyed by
925
- * table name) so callers that look up by `tableName` see every contract
926
- * table regardless of which namespace it lives in.
966
+ * Resolves the declared namespace ids to their live DDL schema names,
967
+ * mapping `UNBOUND_NAMESPACE_ID` to the connection's `current_schema()`
968
+ * and de-duplicating. The caller introspects one namespace node per
969
+ * resolved schema there is no flat cross-schema merge, so two schemas
970
+ * holding a same-named table no longer collide.
927
971
  */
928
- async introspectNamespaces(driver, namespaceIds) {
972
+ async resolveNamespaceSchemas(driver, namespaceIds) {
929
973
  const resolvedSchemas = [];
930
974
  for (const id of namespaceIds) if (id === UNBOUND_NAMESPACE_ID) {
931
975
  const { rows } = await driver.query("SELECT current_schema() AS current_schema");
932
976
  resolvedSchemas.push(rows[0]?.current_schema ?? "public");
933
977
  } else resolvedSchemas.push(id);
934
- const uniqueSchemas = Array.from(new Set(resolvedSchemas));
935
- const perSchema = [];
936
- for (const schema of uniqueSchemas) perSchema.push(await this.introspectSchema(driver, schema));
937
- const mergedTables = {};
938
- for (const ir of perSchema) for (const [tableName, table] of Object.entries(ir.tables)) mergedTables[tableName] = table;
939
- const firstAnnotations = perSchema[0]?.annotations;
940
- const firstPg = blindCast(firstAnnotations?.["pg"]) ?? {};
941
- return {
942
- tables: mergedTables,
943
- ...ifDefined("annotations", {
944
- ...firstAnnotations,
945
- pg: { ...firstPg }
946
- })
947
- };
978
+ return Array.from(new Set(resolvedSchemas));
948
979
  }
949
980
  /**
950
- * Introspects a single Postgres schema and returns a raw SqlSchemaIR
951
- * containing only the tables in that schema. Used by `introspect` as
981
+ * Introspects a single Postgres schema and returns the namespace node for
982
+ * that schema (its tables, their policies, and its native enum type names),
983
+ * alongside the cluster-scoped Postgres version. Used by `introspect` as
952
984
  * the per-namespace walk.
953
985
  */
954
986
  async introspectSchema(driver, schema) {
@@ -982,64 +1014,74 @@ var PostgresControlAdapter = class {
982
1014
  WHERE c.table_schema = $1
983
1015
  ORDER BY c.table_name, c.ordinal_position`, [schema]);
984
1016
  const pkResult = await driver.query(`SELECT
985
- tc.table_name,
986
- tc.constraint_name,
987
- kcu.column_name,
988
- kcu.ordinal_position
989
- FROM information_schema.table_constraints tc
990
- JOIN information_schema.key_column_usage kcu
991
- ON tc.constraint_name = kcu.constraint_name
992
- AND tc.table_schema = kcu.table_schema
993
- AND tc.table_name = kcu.table_name
994
- WHERE tc.table_schema = $1
995
- AND tc.constraint_type = 'PRIMARY KEY'
996
- ORDER BY tc.table_name, kcu.ordinal_position`, [schema]);
1017
+ cl.relname AS table_name,
1018
+ con.conname AS constraint_name,
1019
+ a.attname AS column_name,
1020
+ k.ord AS ordinal_position
1021
+ FROM pg_catalog.pg_constraint con
1022
+ JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid
1023
+ JOIN pg_catalog.pg_namespace ns ON ns.oid = cl.relnamespace
1024
+ JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
1025
+ JOIN pg_catalog.pg_attribute a
1026
+ ON a.attrelid = con.conrelid
1027
+ AND a.attnum = k.attnum
1028
+ WHERE ns.nspname = $1
1029
+ AND con.contype = 'p'
1030
+ ORDER BY cl.relname, k.ord`, [schema]);
997
1031
  const fkResult = await driver.query(`SELECT
998
- tc.table_name,
999
- tc.constraint_name,
1000
- kcu.column_name,
1001
- kcu.ordinal_position,
1032
+ cl.relname AS table_name,
1033
+ con.conname AS constraint_name,
1034
+ a.attname AS column_name,
1035
+ k.ord AS ordinal_position,
1002
1036
  ref_ns.nspname AS referenced_table_schema,
1003
1037
  ref_cl.relname AS referenced_table_name,
1004
1038
  ref_att.attname AS referenced_column_name,
1005
- rc.delete_rule,
1006
- rc.update_rule
1007
- FROM information_schema.table_constraints tc
1008
- JOIN information_schema.key_column_usage kcu
1009
- ON tc.constraint_name = kcu.constraint_name
1010
- AND tc.table_schema = kcu.table_schema
1011
- AND tc.table_name = kcu.table_name
1012
- JOIN pg_catalog.pg_constraint pgc
1013
- ON pgc.conname = tc.constraint_name
1014
- AND pgc.connamespace = (
1015
- SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = tc.table_schema
1016
- )
1039
+ CASE con.confdeltype
1040
+ WHEN 'a' THEN 'NO ACTION'
1041
+ WHEN 'r' THEN 'RESTRICT'
1042
+ WHEN 'c' THEN 'CASCADE'
1043
+ WHEN 'n' THEN 'SET NULL'
1044
+ WHEN 'd' THEN 'SET DEFAULT'
1045
+ END AS delete_rule,
1046
+ CASE con.confupdtype
1047
+ WHEN 'a' THEN 'NO ACTION'
1048
+ WHEN 'r' THEN 'RESTRICT'
1049
+ WHEN 'c' THEN 'CASCADE'
1050
+ WHEN 'n' THEN 'SET NULL'
1051
+ WHEN 'd' THEN 'SET DEFAULT'
1052
+ END AS update_rule
1053
+ FROM pg_catalog.pg_constraint con
1054
+ JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid
1055
+ JOIN pg_catalog.pg_namespace ns ON ns.oid = cl.relnamespace
1056
+ JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
1057
+ JOIN pg_catalog.pg_attribute a
1058
+ ON a.attrelid = con.conrelid
1059
+ AND a.attnum = k.attnum
1017
1060
  JOIN pg_catalog.pg_class ref_cl
1018
- ON ref_cl.oid = pgc.confrelid
1061
+ ON ref_cl.oid = con.confrelid
1019
1062
  JOIN pg_catalog.pg_namespace ref_ns
1020
1063
  ON ref_ns.oid = ref_cl.relnamespace
1021
1064
  JOIN pg_catalog.pg_attribute ref_att
1022
- ON ref_att.attrelid = pgc.confrelid
1023
- AND ref_att.attnum = pgc.confkey[kcu.ordinal_position]
1024
- JOIN information_schema.referential_constraints rc
1025
- ON rc.constraint_name = tc.constraint_name
1026
- AND rc.constraint_schema = tc.table_schema
1027
- WHERE tc.table_schema = $1
1028
- AND tc.constraint_type = 'FOREIGN KEY'
1029
- ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema]);
1065
+ ON ref_att.attrelid = con.confrelid
1066
+ AND ref_att.attnum = con.confkey[k.ord]
1067
+ WHERE ns.nspname = $1
1068
+ AND con.contype = 'f'
1069
+ ORDER BY cl.relname, con.conname, k.ord`, [schema]);
1030
1070
  const uniqueResult = await driver.query(`SELECT
1031
- tc.table_name,
1032
- tc.constraint_name,
1033
- kcu.column_name,
1034
- kcu.ordinal_position
1035
- FROM information_schema.table_constraints tc
1036
- JOIN information_schema.key_column_usage kcu
1037
- ON tc.constraint_name = kcu.constraint_name
1038
- AND tc.table_schema = kcu.table_schema
1039
- AND tc.table_name = kcu.table_name
1040
- WHERE tc.table_schema = $1
1041
- AND tc.constraint_type = 'UNIQUE'
1042
- ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`, [schema]);
1071
+ cl.relname AS table_name,
1072
+ con.conname AS constraint_name,
1073
+ a.attname AS column_name,
1074
+ k.ord AS ordinal_position
1075
+ FROM pg_catalog.pg_constraint con
1076
+ JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid
1077
+ JOIN pg_catalog.pg_namespace ns ON ns.oid = cl.relnamespace
1078
+ JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
1079
+ JOIN pg_catalog.pg_attribute a
1080
+ ON a.attrelid = con.conrelid
1081
+ AND a.attnum = k.attnum
1082
+ WHERE ns.nspname = $1
1083
+ AND con.contype = 'u'
1084
+ ORDER BY cl.relname, con.conname, k.ord`, [schema]);
1043
1085
  const indexResult = await driver.query(`SELECT
1044
1086
  i.tablename,
1045
1087
  i.indexname,
@@ -1060,10 +1102,9 @@ var PostgresControlAdapter = class {
1060
1102
  WHERE i.schemaname = $1
1061
1103
  AND NOT EXISTS (
1062
1104
  SELECT 1
1063
- FROM information_schema.table_constraints tc
1064
- WHERE tc.table_schema = $1
1065
- AND tc.table_name = i.tablename
1066
- AND tc.constraint_name = i.indexname
1105
+ FROM pg_catalog.pg_constraint con
1106
+ WHERE con.conindid = ic.oid
1107
+ AND con.contype IN ('p', 'u', 'x')
1067
1108
  )
1068
1109
  ORDER BY i.tablename, i.indexname, k.ord`, [schema]);
1069
1110
  const checkResult = await driver.query(`SELECT
@@ -1091,7 +1132,7 @@ var PostgresControlAdapter = class {
1091
1132
  }
1092
1133
  constraints.add(row.constraint_name);
1093
1134
  }
1094
- const tables = {};
1135
+ const tableInputs = {};
1095
1136
  for (const tableRow of tablesResult.rows) {
1096
1137
  const tableName = tableRow.table_name;
1097
1138
  const columns = {};
@@ -1105,18 +1146,26 @@ var PostgresControlAdapter = class {
1105
1146
  else if (colRow.numeric_precision) nativeType = `${colRow.data_type}(${colRow.numeric_precision})`;
1106
1147
  else nativeType = colRow.data_type;
1107
1148
  else nativeType = colRow.udt_name || colRow.data_type;
1149
+ const many = nativeType.endsWith("[]") ? true : void 0;
1150
+ if (many) nativeType = normalizeSchemaNativeType(nativeType.slice(0, -2));
1151
+ const resolvedNativeType = `${normalizeSchemaNativeType(nativeType)}${many ? "[]" : ""}`;
1152
+ const rawDefault = colRow.column_default ?? void 0;
1108
1153
  columns[colRow.column_name] = {
1109
1154
  name: colRow.column_name,
1110
1155
  nativeType,
1111
1156
  nullable: colRow.is_nullable === "YES",
1112
- ...ifDefined("default", colRow.column_default ?? void 0)
1157
+ ...ifDefined("default", rawDefault),
1158
+ ...ifDefined("many", many),
1159
+ resolvedNativeType,
1160
+ ...ifDefined("resolvedDefault", rawDefault !== void 0 ? parsePostgresDefault(rawDefault, resolvedNativeType) : void 0)
1113
1161
  };
1114
1162
  }
1115
1163
  const pkRows = [...pksByTable.get(tableName) ?? []];
1116
1164
  const primaryKeyColumns = pkRows.sort((a, b) => a.ordinal_position - b.ordinal_position).map((row) => row.column_name);
1117
1165
  const primaryKey = primaryKeyColumns.length > 0 ? {
1118
1166
  columns: primaryKeyColumns,
1119
- ...pkRows[0]?.constraint_name ? { name: pkRows[0].constraint_name } : {}
1167
+ ...pkRows[0]?.constraint_name ? { name: pkRows[0].constraint_name } : {},
1168
+ dependsOn: postgresColumnDependsOn(schema, tableName, primaryKeyColumns)
1120
1169
  } : void 0;
1121
1170
  const foreignKeysMap = /* @__PURE__ */ new Map();
1122
1171
  for (const fkRow of fksByTable.get(tableName) ?? []) {
@@ -1141,7 +1190,8 @@ var PostgresControlAdapter = class {
1141
1190
  referencedColumns: Object.freeze([...fk.referencedColumns]),
1142
1191
  name: fk.name,
1143
1192
  ...ifDefined("onDelete", mapReferentialAction(fk.deleteRule)),
1144
- ...ifDefined("onUpdate", mapReferentialAction(fk.updateRule))
1193
+ ...ifDefined("onUpdate", mapReferentialAction(fk.updateRule)),
1194
+ dependsOn: [postgresTableDependsOn(fk.referencedSchema, fk.referencedTable), ...postgresColumnDependsOn(schema, tableName, fk.columns)]
1145
1195
  }));
1146
1196
  const pkConstraints = pkConstraintsByTable.get(tableName) ?? /* @__PURE__ */ new Set();
1147
1197
  const uniquesMap = /* @__PURE__ */ new Map();
@@ -1156,11 +1206,16 @@ var PostgresControlAdapter = class {
1156
1206
  }
1157
1207
  const uniques = Array.from(uniquesMap.values()).map((uq) => ({
1158
1208
  columns: Object.freeze([...uq.columns]),
1159
- name: uq.name
1209
+ name: uq.name,
1210
+ dependsOn: postgresColumnDependsOn(schema, tableName, uq.columns)
1160
1211
  }));
1161
1212
  const indexesMap = /* @__PURE__ */ new Map();
1213
+ const indexNamesWithExpressionKey = /* @__PURE__ */ new Set();
1162
1214
  for (const idxRow of indexesByTable.get(tableName) ?? []) {
1163
- if (!idxRow.attname) continue;
1215
+ if (!idxRow.attname) {
1216
+ indexNamesWithExpressionKey.add(idxRow.indexname);
1217
+ continue;
1218
+ }
1164
1219
  const existing = indexesMap.get(idxRow.indexname);
1165
1220
  if (existing) existing.columns.push(idxRow.attname);
1166
1221
  else {
@@ -1175,12 +1230,20 @@ var PostgresControlAdapter = class {
1175
1230
  });
1176
1231
  }
1177
1232
  }
1178
- const indexes = Array.from(indexesMap.values()).map((idx) => ({
1233
+ const survivingIndexes = Array.from(indexesMap.values()).filter((idx) => !indexNamesWithExpressionKey.has(idx.name));
1234
+ const bestByColumnTuple = /* @__PURE__ */ new Map();
1235
+ for (const idx of survivingIndexes) {
1236
+ const tupleKey = idx.columns.join(",");
1237
+ const existing = bestByColumnTuple.get(tupleKey);
1238
+ if (!existing || idx.unique && !existing.unique || idx.unique === existing.unique && idx.name < existing.name) bestByColumnTuple.set(tupleKey, idx);
1239
+ }
1240
+ const indexes = Array.from(bestByColumnTuple.values()).map((idx) => ({
1179
1241
  columns: Object.freeze([...idx.columns]),
1180
1242
  name: idx.name,
1181
1243
  unique: idx.unique,
1182
1244
  ...idx.type !== void 0 && { type: idx.type },
1183
- ...idx.options !== void 0 && { options: idx.options }
1245
+ ...idx.options !== void 0 && { options: idx.options },
1246
+ dependsOn: postgresColumnDependsOn(schema, tableName, idx.columns)
1184
1247
  }));
1185
1248
  const checksForTable = [];
1186
1249
  for (const checkRow of checksByTable.get(tableName) ?? []) {
@@ -1191,7 +1254,7 @@ var PostgresControlAdapter = class {
1191
1254
  permittedValues: parsed.permittedValues
1192
1255
  });
1193
1256
  }
1194
- tables[tableName] = {
1257
+ tableInputs[tableName] = {
1195
1258
  name: tableName,
1196
1259
  columns,
1197
1260
  ...ifDefined("primaryKey", primaryKey),
@@ -1201,19 +1264,64 @@ var PostgresControlAdapter = class {
1201
1264
  ...ifDefined("checks", checksForTable.length > 0 ? checksForTable : void 0)
1202
1265
  };
1203
1266
  }
1204
- const nativeEnumTypeNames = (await driver.query(`SELECT t.typname
1267
+ const enums = (await driver.query(`SELECT t.typname, array_agg(e.enumlabel ORDER BY e.enumsortorder) AS enumvalues
1205
1268
  FROM pg_catalog.pg_type t
1206
1269
  JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
1270
+ JOIN pg_catalog.pg_enum e ON e.enumtypid = t.oid
1207
1271
  WHERE t.typtype = 'e'
1208
1272
  AND n.nspname = $1
1209
- ORDER BY t.typname`, [schema])).rows.map((r) => r.typname);
1273
+ GROUP BY t.typname
1274
+ ORDER BY t.typname`, [schema])).rows.map((r) => new PostgresNativeEnumSchemaNode({
1275
+ typeName: r.typname,
1276
+ namespaceId: schema,
1277
+ members: parsePgNameArray(r.enumvalues)
1278
+ }));
1279
+ const policiesResult = await driver.query(`SELECT schemaname, tablename, policyname, cmd, roles, qual, with_check, permissive
1280
+ FROM pg_catalog.pg_policies
1281
+ WHERE schemaname = $1
1282
+ ORDER BY tablename, policyname`, [schema]);
1283
+ const policiesByTable = /* @__PURE__ */ new Map();
1284
+ for (const row of policiesResult.rows) {
1285
+ const operation = mapPgCmd(row.cmd);
1286
+ const policyRoles = [...new Set(parsePgNameArray(row.roles).map((r) => r.toLowerCase()))].sort();
1287
+ const permissive = row.permissive.toUpperCase() === "PERMISSIVE";
1288
+ const prefix = parseRlsPolicyWireName(row.policyname)?.prefix ?? row.policyname;
1289
+ const policy = new PostgresPolicySchemaNode({
1290
+ name: row.policyname,
1291
+ prefix,
1292
+ tableName: row.tablename,
1293
+ namespaceId: row.schemaname,
1294
+ operation,
1295
+ roles: policyRoles,
1296
+ ...row.qual !== null ? { using: row.qual } : {},
1297
+ ...row.with_check !== null ? { withCheck: row.with_check } : {},
1298
+ permissive,
1299
+ dependsOn: [postgresTableDependsOn(row.schemaname, row.tablename), ...policyRoles.map(postgresRoleDependsOn)]
1300
+ });
1301
+ const list = policiesByTable.get(row.tablename) ?? [];
1302
+ list.push(policy);
1303
+ policiesByTable.set(row.tablename, list);
1304
+ }
1305
+ const rlsEnabledResult = await driver.query(`SELECT c.relname AS tablename, c.relrowsecurity AS rls_enabled
1306
+ FROM pg_catalog.pg_class c
1307
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
1308
+ WHERE n.nspname = $1
1309
+ AND c.relkind IN ('r', 'p')
1310
+ ORDER BY c.relname`, [schema]);
1311
+ const rlsEnabledByTable = new Map(rlsEnabledResult.rows.map((row) => [row.tablename, row.rls_enabled]));
1312
+ const tables = {};
1313
+ for (const [tableName, input] of Object.entries(tableInputs)) tables[tableName] = new PostgresTableSchemaNode({
1314
+ ...input,
1315
+ policies: policiesByTable.get(tableName) ?? [],
1316
+ rlsEnabled: rlsEnabledByTable.get(tableName) ?? false
1317
+ });
1210
1318
  return {
1211
- tables,
1212
- annotations: { pg: {
1213
- schema,
1214
- version: await this.getPostgresVersion(driver),
1215
- ...nativeEnumTypeNames.length > 0 && { nativeEnumTypeNames }
1216
- } }
1319
+ namespace: new PostgresNamespaceSchemaNode({
1320
+ schemaName: schema,
1321
+ tables,
1322
+ nativeEnums: enums
1323
+ }),
1324
+ pgVersion: await this.getPostgresVersion(driver)
1217
1325
  };
1218
1326
  }
1219
1327
  /**
@@ -1224,6 +1332,86 @@ var PostgresControlAdapter = class {
1224
1332
  }
1225
1333
  };
1226
1334
  /**
1335
+ * Normalises a `name[]` column value from `pg_policies.roles`.
1336
+ *
1337
+ * The `pg` client's type-parser registry handles `text[]` (OID 1009) but not
1338
+ * `name[]` (OID 1003). When the parser is absent the raw Postgres text-array
1339
+ * literal (`{role1,role2}`) is returned as a string instead of a JS array.
1340
+ * This function accepts either form and returns a plain string array.
1341
+ *
1342
+ * The string branch honors Postgres array-literal quoting: an element
1343
+ * containing a comma, quote, backslash, brace, or significant whitespace is
1344
+ * emitted double-quoted with `\"` / `\\` escapes, and unquoted elements are
1345
+ * whitespace-trimmed — so a label like `in progress` or `say "hi"` parses to
1346
+ * its true value instead of being split or kept escaped.
1347
+ */
1348
+ function parsePgNameArray(value) {
1349
+ if (Array.isArray(value)) return value.map(String);
1350
+ if (typeof value !== "string") return [];
1351
+ const trimmed = value.trim();
1352
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return [];
1353
+ const inner = trimmed.slice(1, -1);
1354
+ if (inner === "") return [];
1355
+ const elements = [];
1356
+ let current = "";
1357
+ let inQuotes = false;
1358
+ let wasQuoted = false;
1359
+ const pushCurrent = () => {
1360
+ elements.push(wasQuoted ? current : current.trim());
1361
+ current = "";
1362
+ wasQuoted = false;
1363
+ };
1364
+ let i = 0;
1365
+ while (i < inner.length) {
1366
+ const char = inner.charAt(i);
1367
+ if (inQuotes) {
1368
+ if (char === "\\") {
1369
+ current += inner[i + 1] ?? "";
1370
+ i += 2;
1371
+ continue;
1372
+ }
1373
+ if (char === "\"") {
1374
+ inQuotes = false;
1375
+ i++;
1376
+ continue;
1377
+ }
1378
+ current += char;
1379
+ i++;
1380
+ continue;
1381
+ }
1382
+ if (char === "\"") {
1383
+ inQuotes = true;
1384
+ wasQuoted = true;
1385
+ i++;
1386
+ continue;
1387
+ }
1388
+ if (char === ",") {
1389
+ pushCurrent();
1390
+ i++;
1391
+ continue;
1392
+ }
1393
+ current += char;
1394
+ i++;
1395
+ }
1396
+ if (inQuotes) return [];
1397
+ pushCurrent();
1398
+ return elements;
1399
+ }
1400
+ /**
1401
+ * Maps `pg_policies.cmd` text values to the `RlsPolicyOperation` union.
1402
+ * The `pg_policies` view renders the internal command code as an uppercase
1403
+ * English keyword; this function lowercases to match the IR type.
1404
+ */
1405
+ function mapPgCmd(cmd) {
1406
+ switch (cmd.toUpperCase()) {
1407
+ case "SELECT": return "select";
1408
+ case "INSERT": return "insert";
1409
+ case "UPDATE": return "update";
1410
+ case "DELETE": return "delete";
1411
+ default: return "all";
1412
+ }
1413
+ }
1414
+ /**
1227
1415
  * Extracts the namespace coordinate ids declared on a contract's storage,
1228
1416
  * or returns an empty array when no contract (or no storage / namespaces)
1229
1417
  * is present. Used by `PostgresControlAdapter.introspect` to decide
@@ -1238,6 +1426,7 @@ function extractContractNamespaceIds(contract) {
1238
1426
  return Object.keys(namespaces);
1239
1427
  }
1240
1428
  function normalizeFormattedType(formattedType, dataType, udtName) {
1429
+ if (formattedType.endsWith("[]")) return `${normalizeFormattedType(formattedType.slice(0, -2), dataType, udtName)}[]`;
1241
1430
  if (formattedType === "integer") return "int4";
1242
1431
  if (formattedType === "smallint") return "int2";
1243
1432
  if (formattedType === "bigint") return "int8";
@@ -1273,6 +1462,49 @@ function mapReferentialAction(rule) {
1273
1462
  return mapped;
1274
1463
  }
1275
1464
  /**
1465
+ * A FK/policy dependency chain, mirroring the shape the expected-side
1466
+ * derivation stamps (`contractToPostgresDatabaseSchemaNode`): the database
1467
+ * root's fixed sentinel id, then the namespace, then the table.
1468
+ */
1469
+ function postgresTableDependsOn(namespaceId, tableName) {
1470
+ return [
1471
+ {
1472
+ nodeKind: PostgresSchemaNodeKind.database,
1473
+ id: "database"
1474
+ },
1475
+ {
1476
+ nodeKind: PostgresSchemaNodeKind.namespace,
1477
+ id: namespaceId
1478
+ },
1479
+ {
1480
+ nodeKind: PostgresSchemaNodeKind.table,
1481
+ id: tableName
1482
+ }
1483
+ ];
1484
+ }
1485
+ /** A policy's dependency chain onto one of the roles it grants to. */
1486
+ function postgresRoleDependsOn(role) {
1487
+ return [{
1488
+ nodeKind: PostgresSchemaNodeKind.database,
1489
+ id: "database"
1490
+ }, {
1491
+ nodeKind: PostgresSchemaNodeKind.role,
1492
+ id: role
1493
+ }];
1494
+ }
1495
+ /**
1496
+ * The chains from a table-child object (foreign key, index, unique, primary
1497
+ * key) to each of the own columns it is built on — the introspection-side
1498
+ * mirror of `contractToPostgresDatabaseSchemaNode`'s `columnDependsOn`. An
1499
+ * object is dropped before the columns it covers.
1500
+ */
1501
+ function postgresColumnDependsOn(namespaceId, tableName, columns) {
1502
+ return columns.map((column) => [...postgresTableDependsOn(namespaceId, tableName), {
1503
+ nodeKind: RelationalSchemaNodeKind.column,
1504
+ id: `column:${column}`
1505
+ }]);
1506
+ }
1507
+ /**
1276
1508
  * Groups an array of objects by a specified key.
1277
1509
  * Returns a Map for O(1) lookup by group key.
1278
1510
  */
@@ -1383,6 +1615,16 @@ function extractQuotedLiterals(listBody) {
1383
1615
  function pgIsTextLikeNativeType(nativeType) {
1384
1616
  return nativeType === "text" || nativeType === "varchar" || nativeType.startsWith("varchar(") || nativeType === "character varying" || nativeType.startsWith("character varying(") || nativeType === "char" || nativeType.startsWith("char(") || nativeType === "character" || nativeType.startsWith("character(");
1385
1617
  }
1618
+ function pgRenderArrayElement(el) {
1619
+ if (el === null) return "NULL";
1620
+ if (typeof el === "number" || typeof el === "boolean") return String(el);
1621
+ if (typeof el === "string") return `'${escapeLiteral(el)}'`;
1622
+ return `'${escapeLiteral(JSON.stringify(el))}'`;
1623
+ }
1624
+ function pgRenderArrayLiteral(elements) {
1625
+ if (elements.length === 0) return "'{}'";
1626
+ return `ARRAY[${elements.map(pgRenderArrayElement).join(", ")}]`;
1627
+ }
1386
1628
  function pgInlineLiteral(wire, nativeType) {
1387
1629
  if (wire === null) return "NULL";
1388
1630
  if (typeof wire === "boolean") return wire ? "true" : "false";
@@ -1401,6 +1643,7 @@ function pgInlineLiteral(wire, nativeType) {
1401
1643
  return pgIsTextLikeNativeType(nativeType) ? quoted : `${quoted}::${nativeType}`;
1402
1644
  }
1403
1645
  if (wire instanceof Uint8Array) return `'\\x${Array.from(wire).map((b) => b.toString(16).padStart(2, "0")).join("")}'::${nativeType}`;
1646
+ if (Array.isArray(wire) && nativeType.endsWith("[]")) return pgRenderArrayLiteral(wire);
1404
1647
  if (typeof wire === "object") return `${`'${escapeLiteral(JSON.stringify(wire))}'`}::${nativeType}`;
1405
1648
  throw new Error(`pgRenderDdlExecuteRequest: unexpected wire type "${typeof wire}" for native type "${nativeType}"`);
1406
1649
  }
@@ -1438,6 +1681,7 @@ function pgRenderDdlConstraint(constraint) {
1438
1681
  if (constraint.name !== void 0) sql = `CONSTRAINT ${quoteIdentifier(constraint.name)} ${sql}`;
1439
1682
  return sql;
1440
1683
  }
1684
+ if (constraint.kind === "check-expression") return `CONSTRAINT ${quoteIdentifier(constraint.name)} CHECK (${constraint.expression})`;
1441
1685
  const cols = constraint.columns.map(quoteIdentifier).join(", ");
1442
1686
  if (constraint.name !== void 0) return `CONSTRAINT ${quoteIdentifier(constraint.name)} UNIQUE (${cols})`;
1443
1687
  return `UNIQUE (${cols})`;
@@ -1458,24 +1702,87 @@ function pgRenderCreateSchema(node) {
1458
1702
  params: []
1459
1703
  };
1460
1704
  }
1705
+ function pgRenderCreateType(node) {
1706
+ return {
1707
+ sql: `CREATE TYPE ${node.schema ? `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.name)}` : quoteIdentifier(node.name)} AS ENUM (${node.values.map((value) => `'${escapeLiteral(value)}'`).join(", ")})`,
1708
+ params: []
1709
+ };
1710
+ }
1711
+ function pgRenderDropType(node) {
1712
+ return {
1713
+ sql: `DROP TYPE ${node.schema ? `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.name)}` : quoteIdentifier(node.name)}`,
1714
+ params: []
1715
+ };
1716
+ }
1461
1717
  async function pgRenderAlterTable(node, codecLookup) {
1462
1718
  const tableRef = node.schema ? `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}` : quoteIdentifier(node.table);
1463
- const actionVisitor = { async addColumn(action) {
1464
- return `ADD COLUMN ${await pgRenderDdlColumn(action.column, codecLookup)}`;
1465
- } };
1719
+ const actionVisitor = {
1720
+ async addColumn(action) {
1721
+ return `ADD COLUMN ${await pgRenderDdlColumn(action.column, codecLookup)}`;
1722
+ },
1723
+ dropDefault(action) {
1724
+ return Promise.resolve(`ALTER COLUMN ${quoteIdentifier(action.columnName)} DROP DEFAULT`);
1725
+ }
1726
+ };
1466
1727
  return {
1467
1728
  sql: `ALTER TABLE ${tableRef} ${(await Promise.all(node.actions.map((a) => a.accept(actionVisitor)))).join(", ")}`,
1468
1729
  params: []
1469
1730
  };
1470
1731
  }
1732
+ const POLICY_OPERATION_SQL = {
1733
+ select: "SELECT",
1734
+ insert: "INSERT",
1735
+ update: "UPDATE",
1736
+ delete: "DELETE",
1737
+ all: "ALL"
1738
+ };
1739
+ function pgRenderCreatePolicy(node) {
1740
+ const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
1741
+ const permissiveness = node.permissive ? "PERMISSIVE" : "RESTRICTIVE";
1742
+ const command = POLICY_OPERATION_SQL[node.operation];
1743
+ const roles = node.roles.length === 0 ? "PUBLIC" : node.roles.join(", ");
1744
+ let sql = `CREATE POLICY ${quoteIdentifier(node.name)} ON ${tableRef} AS ${permissiveness} FOR ${command} TO ${roles}`;
1745
+ if (node.using !== void 0) sql += ` USING (${node.using})`;
1746
+ if (node.withCheck !== void 0) sql += ` WITH CHECK (${node.withCheck})`;
1747
+ return {
1748
+ sql,
1749
+ params: []
1750
+ };
1751
+ }
1752
+ function pgRenderDropPolicy(node) {
1753
+ const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
1754
+ return {
1755
+ sql: `DROP POLICY ${quoteIdentifier(node.name)} ON ${tableRef}`,
1756
+ params: []
1757
+ };
1758
+ }
1759
+ function pgRenderAlterPolicyRename(node) {
1760
+ const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
1761
+ return {
1762
+ sql: `ALTER POLICY ${quoteIdentifier(node.name)} ON ${tableRef} RENAME TO ${quoteIdentifier(node.newName)}`,
1763
+ params: []
1764
+ };
1765
+ }
1766
+ function pgRenderDisableRowLevelSecurity(node) {
1767
+ return {
1768
+ sql: `ALTER TABLE ${`${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`} DISABLE ROW LEVEL SECURITY`,
1769
+ params: []
1770
+ };
1771
+ }
1471
1772
  async function pgRenderDdlExecuteRequest(ast, codecLookup) {
1472
1773
  return ast.accept({
1473
1774
  createTable: (node) => pgRenderCreateTable(node, codecLookup),
1474
1775
  createSchema: (node) => Promise.resolve(pgRenderCreateSchema(node)),
1475
- alterTable: (node) => pgRenderAlterTable(node, codecLookup)
1776
+ createType: (node) => Promise.resolve(pgRenderCreateType(node)),
1777
+ dropType: (node) => Promise.resolve(pgRenderDropType(node)),
1778
+ alterTable: (node) => pgRenderAlterTable(node, codecLookup),
1779
+ createPolicy: (node) => Promise.resolve(pgRenderCreatePolicy(node)),
1780
+ dropPolicy: (node) => Promise.resolve(pgRenderDropPolicy(node)),
1781
+ alterPolicyRename: (node) => Promise.resolve(pgRenderAlterPolicyRename(node)),
1782
+ disableRowLevelSecurity: (node) => Promise.resolve(pgRenderDisableRowLevelSecurity(node))
1476
1783
  });
1477
1784
  }
1478
1785
  //#endregion
1479
1786
  export { renderLoweredSql as n, createPostgresBuiltinCodecLookup as r, PostgresControlAdapter as t };
1480
1787
 
1481
- //# sourceMappingURL=control-adapter-Dspz5uKp.mjs.map
1788
+ //# sourceMappingURL=control-adapter-aFfSI2bK.mjs.map