import type { Contract } from '@prisma-next/contract/types'; import { runtimeError } from '@prisma-next/framework-components/runtime'; import type { SqlStorage } from '@prisma-next/sql-contract/types'; import type { Adapter, AnyQueryAst, LoweredStatement } from '@prisma-next/sql-relational-core/ast'; import type { SqlExecutionPlan, SqlQueryPlan } from '@prisma-next/sql-relational-core/plan'; /** * Lowers a SQL query plan to an executable Plan by calling the adapter's lower method. * * Ad-hoc lowerings produce only `{kind: 'literal'}` slots; this helper * unwraps them into the bare-value array `SqlExecutionPlan` exposes. * Encountering a `{kind: 'bind'}` slot here means the caller passed an * AST containing `PreparedParamRef` to the ad-hoc execute path — that's a * caller error, surfaced as `RUNTIME.PREPARE_BIND_ON_ADHOC`. */ export function lowerSqlPlan( adapter: Adapter, LoweredStatement>, contract: Contract, queryPlan: SqlQueryPlan, ): SqlExecutionPlan { const lowered = adapter.lower(queryPlan.ast, { contract, params: queryPlan.params, }); const params: unknown[] = lowered.params.map((slot) => { if (slot.kind === 'literal') return slot.value; throw runtimeError( 'RUNTIME.PREPARE_BIND_ON_ADHOC', `Ad-hoc execute received a bind-site slot for '${slot.name}' — bind-site references are only valid inside runtime.prepare(...).`, { name: slot.name }, ); }); return Object.freeze({ sql: lowered.sql, params, ast: queryPlan.ast, meta: queryPlan.meta, }); }