@prisma-next/adapter-postgres 0.5.0-dev.9 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +14 -16
  2. package/dist/adapter-CBaRvhQF.mjs +52 -0
  3. package/dist/adapter-CBaRvhQF.mjs.map +1 -0
  4. package/dist/adapter.d.mts +3 -4
  5. package/dist/adapter.d.mts.map +1 -1
  6. package/dist/adapter.mjs +2 -3
  7. package/dist/column-types.d.mts +19 -24
  8. package/dist/column-types.d.mts.map +1 -1
  9. package/dist/column-types.mjs +20 -60
  10. package/dist/column-types.mjs.map +1 -1
  11. package/dist/control.d.mts +83 -3
  12. package/dist/control.d.mts.map +1 -1
  13. package/dist/control.mjs +115 -22
  14. package/dist/control.mjs.map +1 -1
  15. package/dist/{descriptor-meta-RTDzyrae.mjs → descriptor-meta-CyjnYUWG.mjs} +35 -27
  16. package/dist/descriptor-meta-CyjnYUWG.mjs.map +1 -0
  17. package/dist/operation-types.d.mts +11 -10
  18. package/dist/operation-types.d.mts.map +1 -1
  19. package/dist/operation-types.mjs +1 -1
  20. package/dist/runtime.d.mts +3 -11
  21. package/dist/runtime.d.mts.map +1 -1
  22. package/dist/runtime.mjs +19 -81
  23. package/dist/runtime.mjs.map +1 -1
  24. package/dist/{sql-renderer-pEaSP82_.mjs → sql-renderer-wTVSEy5H.mjs} +109 -48
  25. package/dist/sql-renderer-wTVSEy5H.mjs.map +1 -0
  26. package/dist/{types-CfRPdAk8.d.mts → types-B1eiuBHQ.d.mts} +12 -1
  27. package/dist/types-B1eiuBHQ.d.mts.map +1 -0
  28. package/dist/types.d.mts +1 -1
  29. package/dist/types.mjs +1 -1
  30. package/package.json +25 -24
  31. package/src/core/adapter.ts +36 -43
  32. package/src/core/codec-lookup.ts +19 -0
  33. package/src/core/control-adapter.ts +178 -4
  34. package/src/core/control-mutation-defaults.ts +24 -18
  35. package/src/core/descriptor-meta.ts +27 -20
  36. package/src/core/sql-renderer.ts +129 -66
  37. package/src/core/types.ts +11 -0
  38. package/src/exports/column-types.ts +21 -61
  39. package/src/exports/control.ts +3 -2
  40. package/src/exports/runtime.ts +27 -66
  41. package/src/types/operation-types.ts +19 -9
  42. package/dist/adapter-hNElNHo4.mjs +0 -60
  43. package/dist/adapter-hNElNHo4.mjs.map +0 -1
  44. package/dist/descriptor-meta-RTDzyrae.mjs.map +0 -1
  45. package/dist/sql-renderer-pEaSP82_.mjs.map +0 -1
  46. package/dist/types-CfRPdAk8.d.mts.map +0 -1
  47. package/src/core/json-schema-validator.ts +0 -54
  48. package/src/core/standard-schema.ts +0 -71
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-renderer-wTVSEy5H.mjs","names":[],"sources":["../src/core/codec-lookup.ts","../src/core/sql-renderer.ts"],"sourcesContent":["import type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport { extractCodecLookup } from '@prisma-next/framework-components/control';\nimport { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';\n\n/**\n * Build a {@link CodecLookup} populated with the Postgres-builtin codec definitions only.\n *\n * This is the default lookup used by `createPostgresAdapter()` and `new PostgresControlAdapter()` when called without a stack-derived lookup (e.g. from tests, or one-off scripts that don't compose a full stack).\n *\n * Extension codecs (e.g. `pg/vector@1` from `@prisma-next/extension-pgvector`) are intentionally NOT included here: a bare adapter cannot see extensions. Stack-composed paths (`SqlControlAdapterDescriptor.create(stack)` / `SqlRuntimeAdapterDescriptor.create(stack)`) supply the broader, extension-inclusive lookup at construction time.\n */\nexport function createPostgresBuiltinCodecLookup(): CodecLookup {\n return extractCodecLookup([\n {\n id: 'postgres-builtin-codecs',\n types: { codecTypes: { codecDescriptors: Array.from(postgresCodecRegistry.values()) } },\n },\n ]);\n}\n","import type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport {\n type AggregateExpr,\n type AnyExpression,\n type AnyFromSource,\n type AnyQueryAst,\n type BinaryExpr,\n type ColumnRef,\n collectOrderedParamRefs,\n type DeleteAst,\n type InsertAst,\n type InsertValue,\n type JoinAst,\n type JoinOnExpr,\n type JsonArrayAggExpr,\n type JsonObjectExpr,\n type ListExpression,\n LiteralExpr,\n type NullCheckExpr,\n type OperationExpr,\n type OrderByItem,\n type ParamRef,\n type ProjectionItem,\n type RawSqlExpr,\n type SelectAst,\n type SubqueryExpr,\n type UpdateAst,\n} from '@prisma-next/sql-relational-core/ast';\nimport { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';\nimport type { PostgresContract } from './types';\n\n/**\n * Postgres native types whose unknown-OID parameter inference is reliable in arbitrary expression positions. Parameters bound to a codec whose `meta.db.sql.postgres.nativeType` falls in this set are emitted as plain `$N`; everything else (including `json`, `jsonb`, extension types like `vector`, and unknown user types) is emitted as `$N::<nativeType>` so the planner picks an unambiguous overload.\n *\n * `json` / `jsonb` are intentionally excluded despite being Postgres builtins: their operator overloads make context inference unreliable in expression positions (e.g. `$1 -> 'key'` is ambiguous between the two).\n *\n * 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.\n */\nconst POSTGRES_INFERRABLE_NATIVE_TYPES: ReadonlySet<string> = new Set([\n // Numeric\n 'integer',\n 'smallint',\n 'bigint',\n 'real',\n 'double precision',\n 'numeric',\n // Boolean\n 'boolean',\n // Strings\n 'text',\n 'character',\n 'character varying',\n // Temporal\n 'timestamp',\n 'timestamp without time zone',\n 'timestamp with time zone',\n 'time',\n 'timetz',\n 'interval',\n // Bit strings\n 'bit',\n 'bit varying',\n]);\n\nfunction renderTypedParam(\n index: number,\n codecId: string | undefined,\n codecLookup: CodecLookup,\n): string {\n if (codecId === undefined) {\n return `$${index}`;\n }\n if (codecLookup.get(codecId) === undefined) {\n throw new Error(\n `Postgres lowering: ParamRef carries codecId \"${codecId}\" but the ` +\n 'assembled codec lookup has no entry for it. This usually indicates ' +\n 'a missing extension pack in the runtime stack — register the pack ' +\n 'that contributes this codec (e.g. `extensionPacks: [pgvectorRuntime]`), ' +\n 'or use the codec directly from `@prisma-next/target-postgres/codecs` ' +\n \"if it's a builtin.\",\n );\n }\n // The framework `CodecLookup.metaFor` returns the family-agnostic `CodecMeta` whose `db` is `Record<string, unknown>`. The SQL family populates a narrower shape with `db.sql.<dialect>.nativeType: string`; navigate that path defensively and string-check the leaf.\n const meta = codecLookup.metaFor(codecId);\n const dbRecord = meta?.db;\n const sqlBlock = isRecord(dbRecord) ? dbRecord['sql'] : undefined;\n const dialectBlock = isRecord(sqlBlock) ? sqlBlock['postgres'] : undefined;\n const nativeType = isRecord(dialectBlock) ? dialectBlock['nativeType'] : undefined;\n if (typeof nativeType === 'string' && !POSTGRES_INFERRABLE_NATIVE_TYPES.has(nativeType)) {\n return `$${index}::${nativeType}`;\n }\n return `$${index}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Per-render carrier threaded through every helper. Bundles the param-index map (for `$N` numbering) and the assembled-stack `codecLookup` (for cast policy at the `renderTypedParam` chokepoint). Carrying both on a single value keeps helper signatures stable.\n */\ninterface ParamIndexMap {\n readonly indexMap: Map<ParamRef, number>;\n readonly codecLookup: CodecLookup;\n}\n\n/**\n * Render a SQL query AST to a Postgres-flavored `{ sql, params }` payload.\n *\n * Shared between the runtime (`PostgresAdapterImpl.lower`) and control (`PostgresControlAdapter.lower`) entrypoints so emit-time and run-time paths produce byte-identical output for the same AST.\n */\nexport function renderLoweredSql(\n ast: AnyQueryAst,\n contract: PostgresContract,\n codecLookup: CodecLookup,\n): { readonly sql: string; readonly params: readonly unknown[] } {\n const orderedRefs = collectOrderedParamRefs(ast);\n const indexMap = new Map<ParamRef, number>();\n const params: unknown[] = orderedRefs.map((ref, i) => {\n indexMap.set(ref, i + 1);\n return ref.value;\n });\n const pim: ParamIndexMap = { indexMap, codecLookup };\n\n const node = ast;\n let sql: string;\n switch (node.kind) {\n case 'select':\n sql = renderSelect(node, contract, pim);\n break;\n case 'insert':\n sql = renderInsert(node, contract, pim);\n break;\n case 'update':\n sql = renderUpdate(node, contract, pim);\n break;\n case 'delete':\n sql = renderDelete(node, contract, pim);\n break;\n case 'raw-sql':\n sql = renderRawSql(node, contract, pim);\n break;\n // v8 ignore next 4\n default:\n throw new Error(\n `Unsupported AST node kind: ${(node satisfies never as { kind: string }).kind}`,\n );\n }\n\n return Object.freeze({ sql, params: Object.freeze(params) });\n}\n\nfunction renderSelect(ast: SelectAst, contract: PostgresContract, pim: ParamIndexMap): string {\n const selectClause = `SELECT ${renderDistinctPrefix(ast.distinct, ast.distinctOn, contract, pim)}${renderProjection(\n ast.projection,\n contract,\n pim,\n )}`;\n const fromClause = `FROM ${renderSource(ast.from, contract, pim)}`;\n\n const joinsClause = ast.joins?.length\n ? ast.joins.map((join) => renderJoin(join, contract, pim)).join(' ')\n : '';\n\n const whereClause = ast.where ? `WHERE ${renderWhere(ast.where, contract, pim)}` : '';\n const groupByClause = ast.groupBy?.length\n ? `GROUP BY ${ast.groupBy.map((expr) => renderExpr(expr, contract, pim)).join(', ')}`\n : '';\n const havingClause = ast.having ? `HAVING ${renderWhere(ast.having, contract, pim)}` : '';\n const orderClause = ast.orderBy?.length\n ? `ORDER BY ${ast.orderBy\n .map((order) => {\n const expr = renderExpr(order.expr, contract, pim);\n return `${expr} ${order.dir.toUpperCase()}`;\n })\n .join(', ')}`\n : '';\n const limitClause = typeof ast.limit === 'number' ? `LIMIT ${ast.limit}` : '';\n const offsetClause = typeof ast.offset === 'number' ? `OFFSET ${ast.offset}` : '';\n\n const clauses = [\n selectClause,\n fromClause,\n joinsClause,\n whereClause,\n groupByClause,\n havingClause,\n orderClause,\n limitClause,\n offsetClause,\n ]\n .filter((part) => part.length > 0)\n .join(' ');\n return clauses.trim();\n}\n\nfunction renderProjection(\n projection: ReadonlyArray<ProjectionItem>,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n return projection\n .map((item) => {\n const alias = quoteIdentifier(item.alias);\n if (item.expr.kind === 'literal') {\n return `${renderLiteral(item.expr)} AS ${alias}`;\n }\n return `${renderExpr(item.expr, contract, pim)} AS ${alias}`;\n })\n .join(', ');\n}\n\nfunction renderReturning(\n items: ReadonlyArray<ProjectionItem>,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n return items\n .map((item) => {\n if (item.expr.kind === 'column-ref') {\n const rendered = renderColumn(item.expr);\n return item.expr.column === item.alias\n ? rendered\n : `${rendered} AS ${quoteIdentifier(item.alias)}`;\n }\n if (item.expr.kind === 'literal') {\n return `${renderLiteral(item.expr)} AS ${quoteIdentifier(item.alias)}`;\n }\n return `${renderExpr(item.expr, contract, pim)} AS ${quoteIdentifier(item.alias)}`;\n })\n .join(', ');\n}\n\nfunction renderDistinctPrefix(\n distinct: true | undefined,\n distinctOn: ReadonlyArray<AnyExpression> | undefined,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n if (distinctOn && distinctOn.length > 0) {\n const rendered = distinctOn.map((expr) => renderExpr(expr, contract, pim)).join(', ');\n return `DISTINCT ON (${rendered}) `;\n }\n if (distinct) {\n return 'DISTINCT ';\n }\n return '';\n}\n\nfunction renderSource(\n source: AnyFromSource,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n const node = source;\n switch (node.kind) {\n case 'table-source': {\n const table = quoteIdentifier(node.name);\n if (!node.alias) {\n return table;\n }\n return `${table} AS ${quoteIdentifier(node.alias)}`;\n }\n case 'derived-table-source':\n return `(${renderSelect(node.query, contract, pim)}) AS ${quoteIdentifier(node.alias)}`;\n // v8 ignore next 4\n default:\n throw new Error(\n `Unsupported source node kind: ${(node satisfies never as { kind: string }).kind}`,\n );\n }\n}\n\nfunction assertScalarSubquery(query: SelectAst): void {\n if (query.projection.length !== 1) {\n throw new Error('Subquery expressions must project exactly one column');\n }\n}\n\nfunction renderSubqueryExpr(\n expr: SubqueryExpr,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n assertScalarSubquery(expr.query);\n return `(${renderSelect(expr.query, contract, pim)})`;\n}\n\nfunction renderWhere(expr: AnyExpression, contract: PostgresContract, pim: ParamIndexMap): string {\n return renderExpr(expr, contract, pim);\n}\n\nfunction renderNullCheck(\n expr: NullCheckExpr,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n const rendered = renderExpr(expr.expr, contract, pim);\n const renderedExpr = isAtomicExpressionKind(expr.expr.kind) ? rendered : `(${rendered})`;\n return expr.isNull ? `${renderedExpr} IS NULL` : `${renderedExpr} IS NOT NULL`;\n}\n\n/**\n * Atomic expression kinds whose rendered SQL is already self-delimited (a column reference, parameter, literal, function call, aggregate, etc.) and therefore does not need surrounding parentheses when used as the left operand of a postfix predicate like `IS NULL` or `IS NOT NULL`, or as either operand of a binary infix operator.\n *\n * Anything not in this set is treated as composite (binary, AND/OR/NOT, EXISTS, nested IS NULL, subqueries, operation templates) and gets wrapped to preserve grouping.\n */\nfunction isAtomicExpressionKind(kind: AnyExpression['kind']): boolean {\n switch (kind) {\n case 'column-ref':\n case 'identifier-ref':\n case 'param-ref':\n case 'literal':\n case 'aggregate':\n case 'json-object':\n case 'json-array-agg':\n case 'list':\n return true;\n case 'subquery':\n case 'operation':\n case 'binary':\n case 'and':\n case 'or':\n case 'exists':\n case 'null-check':\n case 'not':\n return false;\n }\n}\n\nfunction renderBinary(expr: BinaryExpr, contract: PostgresContract, pim: ParamIndexMap): string {\n if (expr.right.kind === 'list' && expr.right.values.length === 0) {\n if (expr.op === 'in') {\n return 'FALSE';\n }\n if (expr.op === 'notIn') {\n return 'TRUE';\n }\n }\n\n const leftExpr = expr.left;\n const left = renderExpr(leftExpr, contract, pim);\n const leftRendered =\n leftExpr.kind === 'operation' || leftExpr.kind === 'subquery' ? `(${left})` : left;\n\n const rightNode = expr.right;\n let right: string;\n switch (rightNode.kind) {\n case 'list':\n right = renderListLiteral(rightNode, contract, pim);\n break;\n case 'literal':\n right = renderLiteral(rightNode);\n break;\n case 'column-ref':\n right = renderColumn(rightNode);\n break;\n case 'param-ref':\n right = renderParamRef(rightNode, pim);\n break;\n default:\n right = renderExpr(rightNode, contract, pim);\n break;\n }\n\n const operatorMap: Record<BinaryExpr['op'], string> = {\n eq: '=',\n neq: '!=',\n gt: '>',\n lt: '<',\n gte: '>=',\n lte: '<=',\n like: 'LIKE',\n in: 'IN',\n notIn: 'NOT IN',\n };\n\n return `${leftRendered} ${operatorMap[expr.op]} ${right}`;\n}\n\nfunction renderListLiteral(\n expr: ListExpression,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n if (expr.values.length === 0) {\n return '(NULL)';\n }\n const values = expr.values\n .map((v) => {\n if (v.kind === 'param-ref') return renderParamRef(v, pim);\n if (v.kind === 'literal') return renderLiteral(v);\n return renderExpr(v, contract, pim);\n })\n .join(', ');\n return `(${values})`;\n}\n\nfunction renderColumn(ref: ColumnRef): string {\n if (ref.table === 'excluded') {\n return `excluded.${quoteIdentifier(ref.column)}`;\n }\n return `${quoteIdentifier(ref.table)}.${quoteIdentifier(ref.column)}`;\n}\n\nfunction renderAggregateExpr(\n expr: AggregateExpr,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n const fn = expr.fn.toUpperCase();\n if (!expr.expr) {\n return `${fn}(*)`;\n }\n return `${fn}(${renderExpr(expr.expr, contract, pim)})`;\n}\n\nfunction renderJsonObjectExpr(\n expr: JsonObjectExpr,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n const args = expr.entries\n .flatMap((entry): [string, string] => {\n const key = `'${escapeLiteral(entry.key)}'`;\n if (entry.value.kind === 'literal') {\n return [key, renderLiteral(entry.value)];\n }\n return [key, renderExpr(entry.value, contract, pim)];\n })\n .join(', ');\n return `json_build_object(${args})`;\n}\n\nfunction renderOrderByItems(\n items: ReadonlyArray<OrderByItem>,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n return items\n .map((item) => `${renderExpr(item.expr, contract, pim)} ${item.dir.toUpperCase()}`)\n .join(', ');\n}\n\nfunction renderJsonArrayAggExpr(\n expr: JsonArrayAggExpr,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n const aggregateOrderBy =\n expr.orderBy && expr.orderBy.length > 0\n ? ` ORDER BY ${renderOrderByItems(expr.orderBy, contract, pim)}`\n : '';\n const aggregated = `json_agg(${renderExpr(expr.expr, contract, pim)}${aggregateOrderBy})`;\n if (expr.onEmpty === 'emptyArray') {\n return `coalesce(${aggregated}, json_build_array())`;\n }\n return aggregated;\n}\n\nfunction renderExpr(expr: AnyExpression, contract: PostgresContract, pim: ParamIndexMap): string {\n const node = expr;\n switch (node.kind) {\n case 'column-ref':\n return renderColumn(node);\n case 'identifier-ref':\n return quoteIdentifier(node.name);\n case 'operation':\n return renderOperation(node, contract, pim);\n case 'subquery':\n return renderSubqueryExpr(node, contract, pim);\n case 'aggregate':\n return renderAggregateExpr(node, contract, pim);\n case 'json-object':\n return renderJsonObjectExpr(node, contract, pim);\n case 'json-array-agg':\n return renderJsonArrayAggExpr(node, contract, pim);\n case 'binary':\n return renderBinary(node, contract, pim);\n case 'and':\n if (node.exprs.length === 0) {\n return 'TRUE';\n }\n return `(${node.exprs.map((part) => renderExpr(part, contract, pim)).join(' AND ')})`;\n case 'or':\n if (node.exprs.length === 0) {\n return 'FALSE';\n }\n return `(${node.exprs.map((part) => renderExpr(part, contract, pim)).join(' OR ')})`;\n case 'exists': {\n const notKeyword = node.notExists ? 'NOT ' : '';\n const subquery = renderSelect(node.subquery, contract, pim);\n return `${notKeyword}EXISTS (${subquery})`;\n }\n case 'null-check':\n return renderNullCheck(node, contract, pim);\n case 'not':\n return `NOT (${renderExpr(node.expr, contract, pim)})`;\n case 'param-ref':\n return renderParamRef(node, pim);\n case 'literal':\n return renderLiteral(node);\n case 'list':\n return renderListLiteral(node, contract, pim);\n // v8 ignore next 4\n default:\n throw new Error(\n `Unsupported expression node kind: ${(node satisfies never as { kind: string }).kind}`,\n );\n }\n}\n\nfunction renderParamRef(ref: ParamRef, pim: ParamIndexMap): string {\n const index = pim.indexMap.get(ref);\n if (index === undefined) {\n throw new Error('ParamRef not found in index map');\n }\n return renderTypedParam(index, ref.codecId, pim.codecLookup);\n}\n\nfunction renderLiteral(expr: LiteralExpr): string {\n if (typeof expr.value === 'string') {\n return `'${escapeLiteral(expr.value)}'`;\n }\n if (typeof expr.value === 'number' || typeof expr.value === 'boolean') {\n return String(expr.value);\n }\n if (typeof expr.value === 'bigint') {\n return String(expr.value);\n }\n if (expr.value === null) {\n return 'NULL';\n }\n if (expr.value === undefined) {\n return 'NULL';\n }\n if (expr.value instanceof Date) {\n return `'${escapeLiteral(expr.value.toISOString())}'`;\n }\n if (Array.isArray(expr.value)) {\n return `ARRAY[${expr.value.map((v: unknown) => renderLiteral(new LiteralExpr(v))).join(', ')}]`;\n }\n const json = JSON.stringify(expr.value);\n if (json === undefined) {\n return 'NULL';\n }\n return `'${escapeLiteral(json)}'`;\n}\n\nfunction renderOperation(\n expr: OperationExpr,\n contract: PostgresContract,\n pim: ParamIndexMap,\n): string {\n const self = renderExpr(expr.self, contract, pim);\n const args = expr.args.map((arg) => {\n return renderExpr(arg, contract, pim);\n });\n\n // Resolve `{{self}}` and `{{argN}}` from the original template in a single pass. Doing this with sequential `String.prototype.replace` calls is unsafe: a substituted fragment can itself contain text that matches a later token (e.g. an arg literal containing the substring `{{arg1}}`), and the next iteration would corrupt it. A single regex callback never re-scans already-substituted output.\n return expr.lowering.template.replace(\n /\\{\\{self\\}\\}|\\{\\{arg(\\d+)\\}\\}/g,\n (token, argIndex: string | undefined) => {\n if (token === '{{self}}') {\n return self;\n }\n const arg = args[Number(argIndex)];\n if (arg === undefined) {\n throw new Error(\n `Operation lowering template for \"${expr.method}\" referenced missing argument {{arg${argIndex}}}; template has ${args.length} arg(s)`,\n );\n }\n return arg;\n },\n );\n}\n\nfunction renderJoin(join: JoinAst, contract: PostgresContract, pim: ParamIndexMap): string {\n const joinType = join.joinType.toUpperCase();\n const lateral = join.lateral ? 'LATERAL ' : '';\n const source = renderSource(join.source, contract, pim);\n const onClause = renderJoinOn(join.on, contract, pim);\n return `${joinType} JOIN ${lateral}${source} ON ${onClause}`;\n}\n\nfunction renderJoinOn(on: JoinOnExpr, contract: PostgresContract, pim: ParamIndexMap): string {\n if (on.kind === 'eq-col-join-on') {\n const left = renderColumn(on.left);\n const right = renderColumn(on.right);\n return `${left} = ${right}`;\n }\n return renderWhere(on, contract, pim);\n}\n\nfunction getInsertColumnOrder(\n rows: ReadonlyArray<Record<string, InsertValue>>,\n contract: PostgresContract,\n tableName: string,\n): string[] {\n const orderedColumns: string[] = [];\n const seenColumns = new Set<string>();\n\n for (const row of rows) {\n for (const column of Object.keys(row)) {\n if (seenColumns.has(column)) {\n continue;\n }\n seenColumns.add(column);\n orderedColumns.push(column);\n }\n }\n\n if (orderedColumns.length > 0) {\n return orderedColumns;\n }\n\n const table = contract.storage.tables[tableName];\n if (!table) {\n throw new Error(`INSERT target table not found in contract storage: ${tableName}`);\n }\n return Object.keys(table.columns);\n}\n\nfunction renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap): string {\n if (!value || value.kind === 'default-value') {\n return 'DEFAULT';\n }\n\n switch (value.kind) {\n case 'param-ref':\n return renderParamRef(value, pim);\n case 'column-ref':\n return renderColumn(value);\n // v8 ignore next 4\n default:\n throw new Error(\n `Unsupported value node in INSERT: ${(value satisfies never as { kind: string }).kind}`,\n );\n }\n}\n\nfunction renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamIndexMap): string {\n const table = quoteIdentifier(ast.table.name);\n const rows = ast.rows;\n if (rows.length === 0) {\n throw new Error('INSERT requires at least one row');\n }\n const hasExplicitValues = rows.some((row) => Object.keys(row).length > 0);\n const insertClause = (() => {\n if (!hasExplicitValues) {\n if (rows.length === 1) {\n return `INSERT INTO ${table} DEFAULT VALUES`;\n }\n\n const defaultColumns = getInsertColumnOrder(rows, contract, ast.table.name);\n if (defaultColumns.length === 0) {\n return `INSERT INTO ${table} VALUES ${rows.map(() => '()').join(', ')}`;\n }\n\n const quotedColumns = defaultColumns.map((column) => quoteIdentifier(column));\n const defaultRow = `(${defaultColumns.map(() => 'DEFAULT').join(', ')})`;\n return `INSERT INTO ${table} (${quotedColumns.join(', ')}) VALUES ${rows\n .map(() => defaultRow)\n .join(', ')}`;\n }\n\n const columnOrder = getInsertColumnOrder(rows, contract, ast.table.name);\n const columns = columnOrder.map((column) => quoteIdentifier(column));\n const values = rows\n .map((row) => {\n const renderedRow = columnOrder.map((column) => renderInsertValue(row[column], pim));\n return `(${renderedRow.join(', ')})`;\n })\n .join(', ');\n\n return `INSERT INTO ${table} (${columns.join(', ')}) VALUES ${values}`;\n })();\n const onConflictClause = ast.onConflict\n ? (() => {\n const conflictColumns = ast.onConflict.columns.map((col) => quoteIdentifier(col.column));\n if (conflictColumns.length === 0) {\n throw new Error('INSERT onConflict requires at least one conflict column');\n }\n\n const action = ast.onConflict.action;\n switch (action.kind) {\n case 'do-nothing':\n return ` ON CONFLICT (${conflictColumns.join(', ')}) DO NOTHING`;\n case 'do-update-set': {\n const updateEntries = Object.entries(action.set);\n if (updateEntries.length === 0) {\n throw new Error('INSERT onConflict do-update-set requires at least one assignment');\n }\n const updates = updateEntries.map(([colName, value]) => {\n const target = quoteIdentifier(colName);\n if (value.kind === 'param-ref') {\n return `${target} = ${renderParamRef(value, pim)}`;\n }\n return `${target} = ${renderColumn(value)}`;\n });\n return ` ON CONFLICT (${conflictColumns.join(', ')}) DO UPDATE SET ${updates.join(', ')}`;\n }\n // v8 ignore next 4\n default:\n throw new Error(\n `Unsupported onConflict action: ${(action satisfies never as { kind: string }).kind}`,\n );\n }\n })()\n : '';\n const returningClause = ast.returning?.length\n ? ` RETURNING ${renderReturning(ast.returning, contract, pim)}`\n : '';\n\n return `${insertClause}${onConflictClause}${returningClause}`;\n}\n\nfunction renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamIndexMap): string {\n const table = quoteIdentifier(ast.table.name);\n const setEntries = Object.entries(ast.set);\n if (setEntries.length === 0) {\n throw new Error('UPDATE requires at least one SET assignment');\n }\n const setClauses = setEntries.map(([col, val]) => {\n const column = quoteIdentifier(col);\n let value: string;\n switch (val.kind) {\n case 'param-ref':\n value = renderParamRef(val, pim);\n break;\n case 'column-ref':\n value = renderColumn(val);\n break;\n // v8 ignore next 4\n default:\n throw new Error(\n `Unsupported value node in UPDATE: ${(val satisfies never as { kind: string }).kind}`,\n );\n }\n return `${column} = ${value}`;\n });\n\n const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';\n const returningClause = ast.returning?.length\n ? ` RETURNING ${renderReturning(ast.returning, contract, pim)}`\n : '';\n\n return `UPDATE ${table} SET ${setClauses.join(', ')}${whereClause}${returningClause}`;\n}\n\nfunction renderDelete(ast: DeleteAst, contract: PostgresContract, pim: ParamIndexMap): string {\n const table = quoteIdentifier(ast.table.name);\n const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';\n const returningClause = ast.returning?.length\n ? ` RETURNING ${renderReturning(ast.returning, contract, pim)}`\n : '';\n\n return `DELETE FROM ${table}${whereClause}${returningClause}`;\n}\n\nfunction renderRawSql(ast: RawSqlExpr, contract: PostgresContract, pim: ParamIndexMap): string {\n const out: string[] = [];\n for (let i = 0; i < ast.fragments.length; i++) {\n out.push(ast.fragments[i] ?? '');\n if (i < ast.args.length) {\n const arg = ast.args[i];\n if (arg !== undefined) {\n out.push(renderExpr(arg, contract, pim));\n }\n }\n }\n return out.join('');\n}\n"],"mappings":"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"}
@@ -1,10 +1,21 @@
1
1
  import { BinaryExpr, ColumnRef, DefaultValueExpr, Direction, LoweredStatement, ParamRef, SelectAst } from "@prisma-next/sql-relational-core/ast";
2
2
  import { Contract } from "@prisma-next/contract/types";
3
+ import { CodecLookup } from "@prisma-next/framework-components/codec";
3
4
  import { SqlStorage, StorageColumn, StorageTable } from "@prisma-next/sql-contract/types";
4
5
 
5
6
  //#region src/core/types.d.ts
6
7
  interface PostgresAdapterOptions {
7
8
  readonly profileId?: string;
9
+ /**
10
+ * Codec lookup used by the SQL renderer to resolve per-codec metadata at
11
+ * lower-time. Defaults to a Postgres-builtins-only lookup when omitted —
12
+ * see {@link createPostgresBuiltinCodecLookup} in `./codec-lookup`.
13
+ *
14
+ * Stack-aware callers (`SqlRuntimeAdapterDescriptor.create(stack)` /
15
+ * `SqlControlAdapterDescriptor.create(stack)`) supply the assembled stack
16
+ * lookup so extension codecs are visible to the renderer.
17
+ */
18
+ readonly codecLookup?: CodecLookup;
8
19
  }
9
20
  type PostgresContract = Contract<SqlStorage> & {
10
21
  readonly target: 'postgres';
@@ -17,4 +28,4 @@ interface OrderClause {
17
28
  type PostgresLoweredStatement = LoweredStatement;
18
29
  //#endregion
19
30
  export { OrderClause as a, PostgresContract as c, StorageColumn as d, StorageTable as f, Expr as i, PostgresLoweredStatement as l, ColumnRef as n, ParamRef as o, Direction as r, PostgresAdapterOptions as s, BinaryExpr as t, SelectAst as u };
20
- //# sourceMappingURL=types-CfRPdAk8.d.mts.map
31
+ //# sourceMappingURL=types-B1eiuBHQ.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-B1eiuBHQ.d.mts","names":[],"sources":["../src/core/types.ts"],"mappings":";;;;;;UAoBiB,sBAAA;EAAA,SACN,SAAA;EADM;;;;;;;;;EAAA,SAWN,WAAA,GAAc,WAAA;AAAA;AAAA,KAGb,gBAAA,GAAmB,QAAA,CAAS,UAAA;EAAA,SAAyB,MAAA;AAAA;AAAA,KAErD,IAAA,GAAO,SAAA,GAAY,QAAA,GAAW,gBAAA;AAAA,UAEzB,WAAA;EAAA,SACN,IAAA,EAAM,SAAA;EAAA,SACN,GAAA,EAAK,SAAA;AAAA;AAAA,KAGJ,wBAAA,GAA2B,gBAAA"}
package/dist/types.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as OrderClause, c as PostgresContract, d as StorageColumn, f as StorageTable, i as Expr, l as PostgresLoweredStatement, n as ColumnRef, o as ParamRef, r as Direction, s as PostgresAdapterOptions, t as BinaryExpr, u as SelectAst } from "./types-CfRPdAk8.mjs";
1
+ import { a as OrderClause, c as PostgresContract, d as StorageColumn, f as StorageTable, i as Expr, l as PostgresLoweredStatement, n as ColumnRef, o as ParamRef, r as Direction, s as PostgresAdapterOptions, t as BinaryExpr, u as SelectAst } from "./types-B1eiuBHQ.mjs";
2
2
  export { type BinaryExpr, type ColumnRef, type Direction, type Expr, type OrderClause, type ParamRef, type PostgresAdapterOptions, type PostgresContract, type PostgresLoweredStatement, type SelectAst, type StorageColumn, type StorageTable };
package/dist/types.mjs CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@prisma-next/adapter-postgres",
3
- "version": "0.5.0-dev.9",
3
+ "version": "0.5.0",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "sideEffects": false,
6
7
  "files": [
@@ -8,35 +9,35 @@
8
9
  "src"
9
10
  ],
10
11
  "dependencies": {
11
- "ajv": "^8.18.0",
12
- "arktype": "^2.0.0",
13
- "@prisma-next/contract": "0.5.0-dev.9",
14
- "@prisma-next/family-sql": "0.5.0-dev.9",
15
- "@prisma-next/contract-authoring": "0.5.0-dev.9",
16
- "@prisma-next/ids": "0.5.0-dev.9",
17
- "@prisma-next/framework-components": "0.5.0-dev.9",
18
- "@prisma-next/sql-contract": "0.5.0-dev.9",
19
- "@prisma-next/sql-contract-psl": "0.5.0-dev.9",
20
- "@prisma-next/sql-operations": "0.5.0-dev.9",
21
- "@prisma-next/sql-relational-core": "0.5.0-dev.9",
22
- "@prisma-next/sql-contract-ts": "0.5.0-dev.9",
23
- "@prisma-next/sql-schema-ir": "0.5.0-dev.9",
24
- "@prisma-next/sql-runtime": "0.5.0-dev.9",
25
- "@prisma-next/target-postgres": "0.5.0-dev.9",
26
- "@prisma-next/utils": "0.5.0-dev.9"
12
+ "arktype": "^2.1.29",
13
+ "@prisma-next/contract": "0.5.0",
14
+ "@prisma-next/framework-components": "0.5.0",
15
+ "@prisma-next/family-sql": "0.5.0",
16
+ "@prisma-next/ids": "0.5.0",
17
+ "@prisma-next/contract-authoring": "0.5.0",
18
+ "@prisma-next/sql-contract": "0.5.0",
19
+ "@prisma-next/sql-contract-psl": "0.5.0",
20
+ "@prisma-next/sql-contract-ts": "0.5.0",
21
+ "@prisma-next/sql-operations": "0.5.0",
22
+ "@prisma-next/sql-relational-core": "0.5.0",
23
+ "@prisma-next/sql-runtime": "0.5.0",
24
+ "@prisma-next/target-postgres": "0.5.0",
25
+ "@prisma-next/sql-schema-ir": "0.5.0",
26
+ "@prisma-next/utils": "0.5.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "pathe": "^2.0.3",
30
- "tsdown": "0.18.4",
30
+ "tsdown": "0.22.0",
31
31
  "typescript": "5.9.3",
32
- "vitest": "4.0.17",
33
- "@prisma-next/cli": "0.5.0-dev.9",
34
- "@prisma-next/driver-postgres": "0.5.0-dev.9",
35
- "@prisma-next/errors": "0.5.0-dev.9",
32
+ "vitest": "4.1.5",
33
+ "@prisma-next/driver-postgres": "0.5.0",
34
+ "@prisma-next/cli": "0.5.0",
35
+ "@prisma-next/errors": "0.5.0",
36
36
  "@prisma-next/test-utils": "0.0.1",
37
- "@prisma-next/extension-pgvector": "0.5.0-dev.9",
38
37
  "@prisma-next/tsconfig": "0.0.0",
39
- "@prisma-next/tsdown": "0.0.0"
38
+ "@prisma-next/extension-pgvector": "0.5.0",
39
+ "@prisma-next/tsdown": "0.0.0",
40
+ "@prisma-next/migration-tools": "0.5.0"
40
41
  },
41
42
  "exports": {
42
43
  "./adapter": "./dist/adapter.mjs",
@@ -1,13 +1,15 @@
1
- import {
2
- type Adapter,
3
- type AdapterProfile,
4
- type AnyQueryAst,
5
- type CodecParamsDescriptor,
6
- createCodecRegistry,
7
- type LowererContext,
1
+ import type { CodecLookup } from '@prisma-next/framework-components/codec';
2
+ import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
3
+ import type {
4
+ Adapter,
5
+ AdapterProfile,
6
+ AnyQueryAst,
7
+ LowererContext,
8
+ MarkerReadResult,
9
+ SqlQueryable,
8
10
  } from '@prisma-next/sql-relational-core/ast';
9
- import { codecDefinitions } from '@prisma-next/target-postgres/codecs';
10
- import { ifDefined } from '@prisma-next/utils/defined';
11
+ import { parseContractMarkerRow } from '@prisma-next/sql-runtime';
12
+ import { createPostgresBuiltinCodecLookup } from './codec-lookup';
11
13
  import { renderLoweredSql } from './sql-renderer';
12
14
  import type { PostgresAdapterOptions, PostgresContract, PostgresLoweredStatement } from './types';
13
15
 
@@ -26,59 +28,50 @@ const defaultCapabilities = Object.freeze({
26
28
  },
27
29
  });
28
30
 
29
- type AdapterCodec = (typeof codecDefinitions)[keyof typeof codecDefinitions]['codec'];
30
- type ParameterizedCodec = AdapterCodec & {
31
- readonly paramsSchema: NonNullable<AdapterCodec['paramsSchema']>;
32
- };
33
-
34
- const parameterizedCodecs: ReadonlyArray<CodecParamsDescriptor> = Object.values(codecDefinitions)
35
- .map((definition) => definition.codec)
36
- .filter((codec): codec is ParameterizedCodec => codec.paramsSchema !== undefined)
37
- .map((codec) =>
38
- Object.freeze({
39
- codecId: codec.id,
40
- paramsSchema: codec.paramsSchema,
41
- ...ifDefined('init', codec.init),
42
- }),
43
- );
44
-
45
31
  class PostgresAdapterImpl
46
32
  implements Adapter<AnyQueryAst, PostgresContract, PostgresLoweredStatement>
47
33
  {
48
- // These fields make the adapter instance structurally compatible with
49
- // RuntimeAdapterInstance<'sql', 'postgres'> without introducing a runtime-plane dependency.
34
+ // These fields make the adapter instance structurally compatible with RuntimeAdapterInstance<'sql', 'postgres'> without introducing a runtime-plane dependency.
50
35
  readonly familyId = 'sql' as const;
51
36
  readonly targetId = 'postgres' as const;
52
37
 
53
38
  readonly profile: AdapterProfile<'postgres'>;
54
- private readonly codecRegistry = (() => {
55
- const registry = createCodecRegistry();
56
- for (const definition of Object.values(codecDefinitions)) {
57
- registry.register(definition.codec);
58
- }
59
- return registry;
60
- })();
39
+ private readonly codecLookup: CodecLookup;
61
40
 
62
41
  constructor(options?: PostgresAdapterOptions) {
42
+ this.codecLookup = options?.codecLookup ?? createPostgresBuiltinCodecLookup();
63
43
  this.profile = Object.freeze({
64
44
  id: options?.profileId ?? 'postgres/default@1',
65
45
  target: 'postgres',
66
46
  capabilities: defaultCapabilities,
67
- codecs: () => this.codecRegistry,
68
- readMarkerStatement: () => ({
69
- sql: 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta from prisma_contract.marker where id = $1',
70
- params: [1],
71
- }),
47
+ readMarker: (queryable: SqlQueryable) => readPostgresMarker(queryable),
72
48
  });
73
49
  }
74
50
 
75
- parameterizedCodecs(): ReadonlyArray<CodecParamsDescriptor> {
76
- return parameterizedCodecs;
51
+ lower(ast: AnyQueryAst, context: LowererContext<PostgresContract>): PostgresLoweredStatement {
52
+ return renderLoweredSql(ast, context.contract, this.codecLookup);
53
+ }
54
+ }
55
+
56
+ async function readPostgresMarker(queryable: SqlQueryable): Promise<MarkerReadResult> {
57
+ const exists = await queryable.query(
58
+ 'select 1 from information_schema.tables where table_schema = $1 and table_name = $2',
59
+ ['prisma_contract', 'marker'],
60
+ );
61
+ if (exists.rows.length === 0) {
62
+ return { kind: 'no-table' };
77
63
  }
78
64
 
79
- lower(ast: AnyQueryAst, context: LowererContext<PostgresContract>): PostgresLoweredStatement {
80
- return renderLoweredSql(ast, context.contract);
65
+ const result = await queryable.query(
66
+ 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta, invariants from prisma_contract.marker where space = $1',
67
+ [APP_SPACE_ID],
68
+ );
69
+ const row = result.rows[0];
70
+ if (!row) {
71
+ return { kind: 'absent' };
81
72
  }
73
+ // Postgres' driver hydrates `text[]` columns as native JS arrays, so the row is already in the shape the shared parser expects.
74
+ return { kind: 'present', record: parseContractMarkerRow(row) };
82
75
  }
83
76
 
84
77
  export function createPostgresAdapter(options?: PostgresAdapterOptions) {
@@ -0,0 +1,19 @@
1
+ import type { CodecLookup } from '@prisma-next/framework-components/codec';
2
+ import { extractCodecLookup } from '@prisma-next/framework-components/control';
3
+ import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
4
+
5
+ /**
6
+ * Build a {@link CodecLookup} populated with the Postgres-builtin codec definitions only.
7
+ *
8
+ * This is the default lookup used by `createPostgresAdapter()` and `new PostgresControlAdapter()` when called without a stack-derived lookup (e.g. from tests, or one-off scripts that don't compose a full stack).
9
+ *
10
+ * Extension codecs (e.g. `pg/vector@1` from `@prisma-next/extension-pgvector`) are intentionally NOT included here: a bare adapter cannot see extensions. Stack-composed paths (`SqlControlAdapterDescriptor.create(stack)` / `SqlRuntimeAdapterDescriptor.create(stack)`) supply the broader, extension-inclusive lookup at construction time.
11
+ */
12
+ export function createPostgresBuiltinCodecLookup(): CodecLookup {
13
+ return extractCodecLookup([
14
+ {
15
+ id: 'postgres-builtin-codecs',
16
+ types: { codecTypes: { codecDescriptors: Array.from(postgresCodecRegistry.values()) } },
17
+ },
18
+ ]);
19
+ }
@@ -1,4 +1,7 @@
1
+ import type { ContractMarkerRecord } from '@prisma-next/contract/types';
1
2
  import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
3
+ import { parseContractMarkerRow } from '@prisma-next/family-sql/verify';
4
+ import type { CodecLookup } from '@prisma-next/framework-components/codec';
2
5
  import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
3
6
  import type {
4
7
  AnyQueryAst,
@@ -19,6 +22,7 @@ import type {
19
22
  import { parsePostgresDefault } from '@prisma-next/target-postgres/default-normalizer';
20
23
  import { normalizeSchemaNativeType } from '@prisma-next/target-postgres/native-type-normalizer';
21
24
  import { ifDefined } from '@prisma-next/utils/defined';
25
+ import { createPostgresBuiltinCodecLookup } from './codec-lookup';
22
26
  import { pgEnumControlHooks } from './enum-control-hooks';
23
27
  import { renderLoweredSql } from './sql-renderer';
24
28
  import type { PostgresContract } from './types';
@@ -31,6 +35,19 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
31
35
  readonly familyId = 'sql' as const;
32
36
  readonly targetId = 'postgres' as const;
33
37
 
38
+ private readonly codecLookup: CodecLookup;
39
+
40
+ /**
41
+ * @param codecLookup - Codec lookup used by the SQL renderer to resolve
42
+ * per-codec metadata at lower-time. Defaults to a Postgres-builtins-only
43
+ * lookup when omitted. Stack-aware callers
44
+ * (`SqlControlAdapterDescriptor.create(stack)`) supply
45
+ * `stack.codecLookup` so extension codecs are visible to the renderer.
46
+ */
47
+ constructor(codecLookup?: CodecLookup) {
48
+ this.codecLookup = codecLookup ?? createPostgresBuiltinCodecLookup();
49
+ }
50
+
34
51
  /**
35
52
  * Target-specific normalizer for raw Postgres default expressions.
36
53
  * Used by schema verification to normalize raw defaults before comparison.
@@ -53,7 +70,108 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
53
70
  * without instantiating the runtime adapter.
54
71
  */
55
72
  lower(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement {
56
- return renderLoweredSql(ast, context.contract as PostgresContract);
73
+ return renderLoweredSql(ast, context.contract as PostgresContract, this.codecLookup);
74
+ }
75
+
76
+ /**
77
+ * Reads the contract marker from `prisma_contract.marker`. Probes
78
+ * `information_schema.tables` first so a fresh database (where the
79
+ * `prisma_contract` schema doesn't yet exist) returns `null` instead of a
80
+ * "relation does not exist" error — some Postgres wire-protocol clients
81
+ * (e.g. PGlite's TCP proxy) don't fully recover from extended-protocol
82
+ * parse errors, so we probe before reading.
83
+ */
84
+ async readMarker(
85
+ driver: ControlDriverInstance<'sql', 'postgres'>,
86
+ space: string,
87
+ ): Promise<ContractMarkerRecord | null> {
88
+ const exists = await driver.query(
89
+ `select 1
90
+ from information_schema.tables
91
+ where table_schema = $1 and table_name = $2`,
92
+ ['prisma_contract', 'marker'],
93
+ );
94
+ if (exists.rows.length === 0) {
95
+ return null;
96
+ }
97
+
98
+ const result = await driver.query<{
99
+ core_hash: string;
100
+ profile_hash: string;
101
+ contract_json: unknown | null;
102
+ canonical_version: number | null;
103
+ updated_at: Date | string;
104
+ app_tag: string | null;
105
+ meta: unknown | null;
106
+ invariants: readonly string[];
107
+ }>(
108
+ `select
109
+ core_hash,
110
+ profile_hash,
111
+ contract_json,
112
+ canonical_version,
113
+ updated_at,
114
+ app_tag,
115
+ meta,
116
+ invariants
117
+ from prisma_contract.marker
118
+ where space = $1`,
119
+ [space],
120
+ );
121
+
122
+ const row = result.rows[0];
123
+ if (!row) return null;
124
+ return parseContractMarkerRow(row);
125
+ }
126
+
127
+ /**
128
+ * Reads every row from `prisma_contract.marker` and returns them keyed
129
+ * by `space`. Mirrors the existence probe in {@link readMarker}: a
130
+ * fresh database without the `prisma_contract` schema returns an empty
131
+ * map rather than raising "relation does not exist".
132
+ */
133
+ async readAllMarkers(
134
+ driver: ControlDriverInstance<'sql', 'postgres'>,
135
+ ): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
136
+ const exists = await driver.query(
137
+ `select 1
138
+ from information_schema.tables
139
+ where table_schema = $1 and table_name = $2`,
140
+ ['prisma_contract', 'marker'],
141
+ );
142
+ if (exists.rows.length === 0) {
143
+ return new Map();
144
+ }
145
+
146
+ const result = await driver.query<{
147
+ space: string;
148
+ core_hash: string;
149
+ profile_hash: string;
150
+ contract_json: unknown | null;
151
+ canonical_version: number | null;
152
+ updated_at: Date | string;
153
+ app_tag: string | null;
154
+ meta: unknown | null;
155
+ invariants: readonly string[];
156
+ }>(
157
+ `select
158
+ space,
159
+ core_hash,
160
+ profile_hash,
161
+ contract_json,
162
+ canonical_version,
163
+ updated_at,
164
+ app_tag,
165
+ meta,
166
+ invariants
167
+ from prisma_contract.marker`,
168
+ );
169
+
170
+ const rows = new Map<string, ContractMarkerRecord>();
171
+ for (const row of result.rows) {
172
+ rows.set(row.space, parseContractMarkerRow(row));
173
+ }
174
+ return rows;
57
175
  }
58
176
 
59
177
  /**
@@ -234,17 +352,22 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
234
352
  indisunique: boolean;
235
353
  attname: string;
236
354
  attnum: number;
355
+ amname: string;
356
+ reloptions: readonly string[] | null;
237
357
  }>(
238
358
  `SELECT
239
359
  i.tablename,
240
360
  i.indexname,
241
361
  ix.indisunique,
242
362
  a.attname,
243
- a.attnum
363
+ a.attnum,
364
+ am.amname,
365
+ ic.reloptions
244
366
  FROM pg_indexes i
245
367
  JOIN pg_class ic ON ic.relname = i.indexname
246
368
  JOIN pg_namespace ins ON ins.oid = ic.relnamespace AND ins.nspname = $1
247
369
  JOIN pg_index ix ON ix.indexrelid = ic.oid
370
+ JOIN pg_am am ON am.oid = ic.relam
248
371
  JOIN pg_class t ON t.oid = ix.indrelid
249
372
  JOIN pg_namespace tn ON tn.oid = t.relnamespace AND tn.nspname = $1
250
373
  LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) AND a.attnum > 0
@@ -256,7 +379,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
256
379
  AND tc.table_name = i.tablename
257
380
  AND tc.constraint_name = i.indexname
258
381
  )
259
- ORDER BY i.tablename, i.indexname, a.attnum`,
382
+ ORDER BY i.tablename, i.indexname, array_position(ix.indkey::int[], a.attnum)`,
260
383
  [schema],
261
384
  ),
262
385
  // Query extensions
@@ -402,7 +525,16 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
402
525
  }));
403
526
 
404
527
  // Process indexes
405
- const indexesMap = new Map<string, { columns: string[]; name: string; unique: boolean }>();
528
+ const indexesMap = new Map<
529
+ string,
530
+ {
531
+ columns: string[];
532
+ name: string;
533
+ unique: boolean;
534
+ type: string | undefined;
535
+ options: Record<string, string> | undefined;
536
+ }
537
+ >();
406
538
  for (const idxRow of indexesByTable.get(tableName) ?? []) {
407
539
  if (!idxRow.attname) {
408
540
  continue;
@@ -411,10 +543,17 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
411
543
  if (existing) {
412
544
  existing.columns.push(idxRow.attname);
413
545
  } else {
546
+ // Drop btree (the Postgres default) so a contract index without an
547
+ // explicit type matches a default-method introspected index without
548
+ // forcing DROP+CREATE on every plan.
549
+ const indexType = idxRow.amname === 'btree' ? undefined : idxRow.amname;
550
+ const indexOptions = parsePgReloptions(idxRow.reloptions, idxRow.indexname);
414
551
  indexesMap.set(idxRow.indexname, {
415
552
  columns: [idxRow.attname],
416
553
  name: idxRow.indexname,
417
554
  unique: idxRow.indisunique,
555
+ type: indexType,
556
+ options: indexOptions,
418
557
  });
419
558
  }
420
559
  }
@@ -422,6 +561,8 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
422
561
  columns: Object.freeze([...idx.columns]) as readonly string[],
423
562
  name: idx.name,
424
563
  unique: idx.unique,
564
+ ...(idx.type !== undefined && { type: idx.type }),
565
+ ...(idx.options !== undefined && { options: idx.options }),
425
566
  }));
426
567
 
427
568
  tables[tableName] = {
@@ -555,6 +696,39 @@ function mapReferentialAction(rule: string): SqlReferentialAction | undefined {
555
696
  * Groups an array of objects by a specified key.
556
697
  * Returns a Map for O(1) lookup by group key.
557
698
  */
699
+ /**
700
+ * Parses a `pg_class.reloptions` array into a `Record<string, string>`.
701
+ *
702
+ * Postgres returns reloptions as a `text[]` whose entries are `key=value`
703
+ * strings; the value side is always a string regardless of the underlying
704
+ * scalar type. The verifier compares contract options to introspected
705
+ * options after coercing both sides to strings, so keeping the raw text
706
+ * here is correct.
707
+ *
708
+ * Returns `undefined` when the input is null/empty (no WITH clause).
709
+ */
710
+ export function parsePgReloptions(
711
+ reloptions: readonly string[] | null,
712
+ indexName: string,
713
+ ): Record<string, string> | undefined {
714
+ if (!reloptions || reloptions.length === 0) {
715
+ return undefined;
716
+ }
717
+ const result: Record<string, string> = {};
718
+ for (const entry of reloptions) {
719
+ const eq = entry.indexOf('=');
720
+ if (eq === -1) {
721
+ throw new Error(
722
+ `Postgres introspection: malformed reloption entry "${entry}" on index "${indexName}" (expected "key=value")`,
723
+ );
724
+ }
725
+ const key = entry.slice(0, eq);
726
+ const value = entry.slice(eq + 1);
727
+ result[key] = value;
728
+ }
729
+ return Object.keys(result).length > 0 ? result : undefined;
730
+ }
731
+
558
732
  function groupBy<T, K extends keyof T>(items: readonly T[], key: K): Map<T[K], T[]> {
559
733
  const map = new Map<T[K], T[]>();
560
734
  for (const item of items) {
@@ -1,4 +1,5 @@
1
1
  import type { ExecutionMutationDefaultValue } from '@prisma-next/contract/types';
2
+ import { timestampNowControlDescriptor } from '@prisma-next/family-sql/control';
2
3
  import type {
3
4
  ControlMutationDefaultEntry,
4
5
  DefaultFunctionLoweringContext,
@@ -301,25 +302,30 @@ export function createPostgresDefaultFunctionRegistry(): ReadonlyMap<
301
302
  }
302
303
 
303
304
  export function createPostgresMutationDefaultGeneratorDescriptors(): readonly MutationDefaultGeneratorDescriptor[] {
304
- return builtinGeneratorRegistryMetadata.map(({ id, applicableCodecIds }) => ({
305
- id,
306
- applicableCodecIds,
307
- resolveGeneratedColumnDescriptor: ({ generated }) => {
308
- if (generated.kind !== 'generator' || generated.id !== id) {
309
- return undefined;
310
- }
311
- const descriptor = resolveBuiltinGeneratedColumnDescriptor({
305
+ return [
306
+ ...builtinGeneratorRegistryMetadata.map(
307
+ ({ id, applicableCodecIds }): MutationDefaultGeneratorDescriptor => ({
312
308
  id,
313
- ...(generated.params ? { params: generated.params } : {}),
314
- });
315
- return {
316
- codecId: descriptor.type.codecId,
317
- nativeType: descriptor.type.nativeType,
318
- ...(descriptor.type.typeRef ? { typeRef: descriptor.type.typeRef } : {}),
319
- ...(descriptor.typeParams ? { typeParams: descriptor.typeParams } : {}),
320
- };
321
- },
322
- }));
309
+ applicableCodecIds,
310
+ resolveGeneratedColumnDescriptor: ({ generated }) => {
311
+ if (generated.kind !== 'generator' || generated.id !== id) {
312
+ return undefined;
313
+ }
314
+ const descriptor = resolveBuiltinGeneratedColumnDescriptor({
315
+ id,
316
+ ...(generated.params ? { params: generated.params } : {}),
317
+ });
318
+ return {
319
+ codecId: descriptor.type.codecId,
320
+ nativeType: descriptor.type.nativeType,
321
+ ...(descriptor.type.typeRef ? { typeRef: descriptor.type.typeRef } : {}),
322
+ ...(descriptor.typeParams ? { typeParams: descriptor.typeParams } : {}),
323
+ };
324
+ },
325
+ }),
326
+ ),
327
+ timestampNowControlDescriptor(),
328
+ ];
323
329
  }
324
330
 
325
331
  export function createPostgresScalarTypeDescriptors(): ReadonlyMap<string, string> {