@prisma-next/sql-runtime 0.9.0-dev.1 → 0.9.0-dev.3

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.
@@ -1,6 +1,6 @@
1
1
  import { AsyncIterableResult, RuntimeCore, checkAborted, checkMiddlewareCompatibility, isRuntimeError, raceAgainstAbort, runBeforeExecuteChain, runWithMiddleware, runtimeError } from "@prisma-next/framework-components/runtime";
2
2
  import { type } from "arktype";
3
- import { collectOrderedParamRefs, isQueryAst } from "@prisma-next/sql-relational-core/ast";
3
+ import { PreparedParamRef, collectOrderedParamRefs, isQueryAst } from "@prisma-next/sql-relational-core/ast";
4
4
  import { ifDefined } from "@prisma-next/utils/defined";
5
5
  import { checkContractComponentRequirements } from "@prisma-next/framework-components/components";
6
6
  import { createExecutionStack } from "@prisma-next/framework-components/execution";
@@ -60,19 +60,24 @@ function validateCodecRegistryCompleteness(registry, contract) {
60
60
  /**
61
61
  * Lowers a SQL query plan to an executable Plan by calling the adapter's lower method.
62
62
  *
63
- * @param adapter - Adapter to lower AST to SQL
64
- * @param contract - Contract for lowering context
65
- * @param queryPlan - SQL query plan from a lane (contains AST, params, meta, but no SQL)
66
- * @returns Fully executable Plan with SQL string
63
+ * Ad-hoc lowerings produce only `{kind: 'literal'}` slots; this helper
64
+ * unwraps them into the bare-value array `SqlExecutionPlan` exposes.
65
+ * Encountering a `{kind: 'bind'}` slot here means the caller passed an
66
+ * AST containing `PreparedParamRef` to the ad-hoc execute path — that's a
67
+ * caller error, surfaced as `RUNTIME.PREPARE_BIND_ON_ADHOC`.
67
68
  */
68
69
  function lowerSqlPlan(adapter, contract, queryPlan) {
69
70
  const lowered = adapter.lower(queryPlan.ast, {
70
71
  contract,
71
72
  params: queryPlan.params
72
73
  });
74
+ const params = lowered.params.map((slot) => {
75
+ if (slot.kind === "literal") return slot.value;
76
+ 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 });
77
+ });
73
78
  return Object.freeze({
74
79
  sql: lowered.sql,
75
- params: lowered.params ?? queryPlan.params,
80
+ params,
76
81
  ast: queryPlan.ast,
77
82
  meta: queryPlan.meta
78
83
  });
@@ -138,7 +143,7 @@ function primaryTableFromAst(ast) {
138
143
  function estimateRowsFromAst(ast, tableRows, defaultTableRows, hasAggregateWithoutGroup) {
139
144
  if (hasAggregateWithoutGroup) return 1;
140
145
  const tableEstimate = tableRows[primaryTableFromAst(ast)] ?? defaultTableRows;
141
- if (ast.limit !== void 0) return Math.min(ast.limit, tableEstimate);
146
+ if (typeof ast.limit === "number") return Math.min(ast.limit, tableEstimate);
142
147
  return tableEstimate;
143
148
  }
144
149
  function emitBudgetViolation(error, shouldBlock, ctx) {
@@ -920,9 +925,6 @@ function writeContractMarker(input) {
920
925
  //#region src/codecs/decoding.ts
921
926
  const WIRE_PREVIEW_LIMIT = 100;
922
927
  const EMPTY_INCLUDE_ALIASES = /* @__PURE__ */ new Set();
923
- function isAstBackedPlan(plan) {
924
- return plan.ast !== void 0;
925
- }
926
928
  function projectionListFromAst(ast) {
927
929
  if (ast.kind === "select") return ast.projection;
928
930
  if (ast.kind === "raw-sql") return;
@@ -931,15 +933,9 @@ function projectionListFromAst(ast) {
931
933
  function resolveProjectionCodec(item, contractCodecs) {
932
934
  if (item.codec && contractCodecs) return contractCodecs.forCodecRef(item.codec);
933
935
  }
934
- function buildDecodeContext(plan, contractCodecs) {
935
- if (!isAstBackedPlan(plan)) return {
936
- aliases: void 0,
937
- codecs: /* @__PURE__ */ new Map(),
938
- columnRefs: /* @__PURE__ */ new Map(),
939
- includeAliases: EMPTY_INCLUDE_ALIASES
940
- };
941
- const projection = projectionListFromAst(plan.ast);
942
- if (!projection) return {
936
+ function buildDecodeContext(ast, contractCodecs) {
937
+ const projection = projectionListFromAst(ast);
938
+ if (!projection || projection.length === 0) return {
943
939
  aliases: void 0,
944
940
  codecs: /* @__PURE__ */ new Map(),
945
941
  columnRefs: /* @__PURE__ */ new Map(),
@@ -1044,10 +1040,9 @@ async function decodeField(alias, wireValue, decodeCtx, rowCtx) {
1044
1040
  * - **Mid-flight aborts** race the per-cell `Promise.all` against the signal so the runtime returns promptly even when codec bodies ignore it. In-flight bodies that ignore the signal complete in the background (cooperative cancellation).
1045
1041
  * - Existing `RUNTIME.DECODE_FAILED` envelopes from codec bodies pass through unchanged (no double wrap).
1046
1042
  */
1047
- async function decodeRow(row, plan, rowCtx, contractCodecs) {
1043
+ async function decodeRow(row, decodeCtx, rowCtx) {
1048
1044
  checkAborted(rowCtx, "decode");
1049
1045
  const signal = rowCtx.signal;
1050
- const decodeCtx = buildDecodeContext(plan, contractCodecs);
1051
1046
  const aliases = decodeCtx.aliases ?? Object.keys(row);
1052
1047
  if (decodeCtx.aliases !== void 0) {
1053
1048
  for (const alias of decodeCtx.aliases) if (!Object.hasOwn(row, alias)) throw runtimeError("RUNTIME.DECODE_FAILED", `Row missing projection alias "${alias}"`, {
@@ -1084,6 +1079,14 @@ const NO_METADATA = Object.freeze({
1084
1079
  codec: void 0,
1085
1080
  name: void 0
1086
1081
  });
1082
+ function deriveParamMetadata(ast) {
1083
+ return collectOrderedParamRefs(ast).map((ref) => {
1084
+ return {
1085
+ codec: ref.codec,
1086
+ name: ref.name
1087
+ };
1088
+ });
1089
+ }
1087
1090
  function resolveParamCodec(metadata, contractCodecs) {
1088
1091
  if (metadata.codec && contractCodecs) return contractCodecs.forCodecRef(metadata.codec);
1089
1092
  }
@@ -1121,23 +1124,13 @@ async function encodeParamValue(value, metadata, paramIndex, ctx, contractCodecs
1121
1124
  * - Existing `RUNTIME.ENCODE_FAILED` envelopes that surface from a codec body before the runtime observes the abort pass through unchanged (no double wrap).
1122
1125
  */
1123
1126
  async function encodeParams(plan, ctx, contractCodecs) {
1127
+ return encodeParamsWithMetadata(plan.params, deriveParamMetadata(plan.ast), ctx, contractCodecs);
1128
+ }
1129
+ async function encodeParamsWithMetadata(values, metadata, ctx, contractCodecs) {
1124
1130
  checkAborted(ctx, "encode");
1125
1131
  const signal = ctx.signal;
1126
- if (plan.params.length === 0) return plan.params;
1127
- const paramCount = plan.params.length;
1128
- const metadata = new Array(paramCount).fill(NO_METADATA);
1129
- if (plan.ast) {
1130
- const refs = collectOrderedParamRefs(plan.ast);
1131
- for (let i = 0; i < paramCount && i < refs.length; i++) {
1132
- const ref = refs[i];
1133
- if (ref) metadata[i] = {
1134
- codec: ref.codec,
1135
- name: ref.name
1136
- };
1137
- }
1138
- }
1139
- const tasks = new Array(paramCount);
1140
- for (let i = 0; i < paramCount; i++) tasks[i] = encodeParamValue(plan.params[i], metadata[i] ?? NO_METADATA, i, ctx, contractCodecs);
1132
+ if (values.length === 0) return values;
1133
+ const tasks = values.map((value, i) => encodeParamValue(value, metadata[i] ?? NO_METADATA, i, ctx, contractCodecs));
1141
1134
  const settled = await raceAgainstAbort(Promise.all(tasks), signal, "encode");
1142
1135
  return Object.freeze(settled);
1143
1136
  }
@@ -1217,6 +1210,81 @@ async function runBeforeCompileChain(middleware, initial, ctx) {
1217
1210
  return current;
1218
1211
  }
1219
1212
  //#endregion
1213
+ //#region src/prepared/bind-site-params.ts
1214
+ function normalizeSpec(spec) {
1215
+ if (typeof spec === "string") return {
1216
+ codec: { codecId: spec },
1217
+ nullable: false
1218
+ };
1219
+ return {
1220
+ codec: spec.typeParams !== void 0 ? {
1221
+ codecId: spec.codecId,
1222
+ typeParams: spec.typeParams
1223
+ } : { codecId: spec.codecId },
1224
+ nullable: spec.nullable === true
1225
+ };
1226
+ }
1227
+ var BindSiteExpression = class {
1228
+ returnType;
1229
+ #ast;
1230
+ constructor(ref, returnType) {
1231
+ this.#ast = ref;
1232
+ this.returnType = returnType;
1233
+ }
1234
+ buildAst() {
1235
+ return this.#ast;
1236
+ }
1237
+ };
1238
+ function buildBindSiteParams(declaration) {
1239
+ const params = {};
1240
+ for (const [name, spec] of Object.entries(declaration)) {
1241
+ const { codec, nullable } = normalizeSpec(spec);
1242
+ params[name] = new BindSiteExpression(PreparedParamRef.of(name, codec), {
1243
+ codecId: codec.codecId,
1244
+ nullable
1245
+ });
1246
+ }
1247
+ return Object.freeze(params);
1248
+ }
1249
+ //#endregion
1250
+ //#region src/prepared/encode-prepared.ts
1251
+ /**
1252
+ * Resolve a PreparedStatement's slot order to the unencoded values it
1253
+ * will carry into encode. Literal slots come from the lowered AST;
1254
+ * bind slots are looked up by name on `userParams`. Missing user params
1255
+ * surface `RUNTIME.PREPARE_MISSING_PARAM` so the caller cannot silently
1256
+ * bind `undefined`.
1257
+ */
1258
+ function resolvePreparedSlotValues(ps, userParams) {
1259
+ return ps.slots.map((slot) => {
1260
+ if (slot.kind === "literal") return slot.value;
1261
+ if (!Object.hasOwn(userParams, slot.name)) throw runtimeError("RUNTIME.PREPARE_MISSING_PARAM", `Prepared statement execute is missing parameter '${slot.name}'`, { name: slot.name });
1262
+ return userParams[slot.name];
1263
+ });
1264
+ }
1265
+ //#endregion
1266
+ //#region src/prepared/prepared-statement.ts
1267
+ var PreparedStatementImpl = class {
1268
+ sql;
1269
+ ast;
1270
+ meta;
1271
+ slots;
1272
+ decodeContext;
1273
+ paramMetadata;
1274
+ constructor(internals) {
1275
+ this.sql = internals.sql;
1276
+ this.ast = internals.ast;
1277
+ this.meta = internals.meta;
1278
+ this.slots = internals.slots;
1279
+ this.decodeContext = internals.decodeContext;
1280
+ this.paramMetadata = internals.paramMetadata;
1281
+ Object.freeze(this);
1282
+ }
1283
+ execute(target, params, options) {
1284
+ return target.executePrepared(this, params, options);
1285
+ }
1286
+ };
1287
+ //#endregion
1220
1288
  //#region src/sql-family-adapter.ts
1221
1289
  var SqlFamilyAdapter = class {
1222
1290
  contract;
@@ -1257,6 +1325,7 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1257
1325
  codecDescriptors;
1258
1326
  sqlCtx;
1259
1327
  verify;
1328
+ #preparedStatementHandles = /* @__PURE__ */ new WeakMap();
1260
1329
  codecRegistryValidated;
1261
1330
  verified;
1262
1331
  startupVerified;
@@ -1367,6 +1436,37 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1367
1436
  execute(plan, options) {
1368
1437
  return this.executeAgainstQueryable(plan, this.driver, options);
1369
1438
  }
1439
+ executePrepared(ps, params, options) {
1440
+ return this.executePreparedAgainstQueryable(ps, params, this.driver, options);
1441
+ }
1442
+ async *streamRows(exec, decodeContext, driverCall, codecCtx, execMiddlewareCtx) {
1443
+ this.familyAdapter.validatePlan(exec, this.contract);
1444
+ this._telemetry = null;
1445
+ if (!this.startupVerified && this.verify.mode === "startup") await this.verifyMarker();
1446
+ if (!this.verified && this.verify.mode === "onFirstUse") await this.verifyMarker();
1447
+ const startedAt = Date.now();
1448
+ let outcome = null;
1449
+ try {
1450
+ if (this.verify.mode === "always") await this.verifyMarker();
1451
+ const iterator = runWithMiddleware(exec, this.middleware, execMiddlewareCtx, driverCall)[Symbol.asyncIterator]();
1452
+ try {
1453
+ while (true) {
1454
+ checkAborted(codecCtx, "stream");
1455
+ const next = await iterator.next();
1456
+ if (next.done) break;
1457
+ yield await decodeRow(next.value, decodeContext, codecCtx);
1458
+ }
1459
+ } finally {
1460
+ await iterator.return?.();
1461
+ }
1462
+ outcome = "success";
1463
+ } catch (error) {
1464
+ outcome = "runtime-error";
1465
+ throw error;
1466
+ } finally {
1467
+ if (outcome !== null) this.recordTelemetry(exec, outcome, Date.now() - startedAt);
1468
+ }
1469
+ }
1370
1470
  executeAgainstQueryable(plan, queryable, options) {
1371
1471
  this.ensureCodecRegistryValidated();
1372
1472
  const self = this;
@@ -1402,35 +1502,79 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1402
1502
  });
1403
1503
  exec = await self.encodeDraftParams(draftWithMutations, codecCtx);
1404
1504
  }
1405
- self.familyAdapter.validatePlan(exec, self.contract);
1406
- self._telemetry = null;
1407
- if (!self.startupVerified && self.verify.mode === "startup") await self.verifyMarker();
1408
- if (!self.verified && self.verify.mode === "onFirstUse") await self.verifyMarker();
1409
- const startedAt = Date.now();
1410
- let outcome = null;
1411
- try {
1412
- if (self.verify.mode === "always") await self.verifyMarker();
1413
- const iterator = runWithMiddleware(exec, self.middleware, execMiddlewareCtx, () => queryable.execute({
1414
- sql: exec.sql,
1415
- params: exec.params
1416
- }))[Symbol.asyncIterator]();
1417
- try {
1418
- while (true) {
1419
- checkAborted(codecCtx, "stream");
1420
- const next = await iterator.next();
1421
- if (next.done) break;
1422
- yield await decodeRow(next.value, exec, codecCtx, self.contractCodecs);
1505
+ const decodeContext = buildDecodeContext(exec.ast, self.contractCodecs);
1506
+ yield* self.streamRows(exec, decodeContext, () => queryable.execute({
1507
+ sql: exec.sql,
1508
+ params: exec.params
1509
+ }), codecCtx, execMiddlewareCtx);
1510
+ };
1511
+ return new AsyncIterableResult(generator());
1512
+ }
1513
+ async prepare(declaration, callback) {
1514
+ this.ensureCodecRegistryValidated();
1515
+ const userPlan = callback(buildBindSiteParams(declaration));
1516
+ const finalPlan = await this.runBeforeCompile(userPlan);
1517
+ const orderedRefs = collectOrderedParamRefs(finalPlan.ast);
1518
+ const referencedNames = /* @__PURE__ */ new Set();
1519
+ for (const ref of orderedRefs) if (ref.kind === "prepared-param-ref") referencedNames.add(ref.name);
1520
+ const missing = Object.keys(declaration).filter((name) => !referencedNames.has(name));
1521
+ if (missing.length > 0) throw runtimeError("RUNTIME.PREPARE_UNUSED_PARAM", `Prepared statement declaration includes parameter${missing.length === 1 ? "" : "s"} not referenced by the callback's plan: ${missing.join(", ")}`, { unused: missing });
1522
+ const lowered = this.adapter.lower(finalPlan.ast, {
1523
+ contract: this.contract,
1524
+ params: orderedRefs.map((r) => r.kind === "param-ref" ? r.value : void 0)
1525
+ });
1526
+ const decodeContext = buildDecodeContext(finalPlan.ast, this.contractCodecs);
1527
+ const paramMetadata = deriveParamMetadata(finalPlan.ast);
1528
+ return new PreparedStatementImpl(Object.freeze({
1529
+ sql: lowered.sql,
1530
+ ast: finalPlan.ast,
1531
+ meta: finalPlan.meta,
1532
+ slots: lowered.params,
1533
+ decodeContext,
1534
+ paramMetadata
1535
+ }));
1536
+ }
1537
+ executePreparedAgainstQueryable(ps, userParams, queryable, options) {
1538
+ this.ensureCodecRegistryValidated();
1539
+ const self = this;
1540
+ const signal = options?.signal;
1541
+ const scope = options?.scope ?? "runtime";
1542
+ const codecCtx = signal === void 0 ? {} : { signal };
1543
+ const execMiddlewareCtx = {
1544
+ ...self.ctx,
1545
+ ...ifDefined("signal", signal),
1546
+ ...scope !== "runtime" ? { scope } : {}
1547
+ };
1548
+ const generator = async function* () {
1549
+ checkAborted(codecCtx, "stream");
1550
+ const preEncodeValues = resolvePreparedSlotValues(ps, userParams);
1551
+ const preEncodeExec = {
1552
+ sql: ps.sql,
1553
+ params: preEncodeValues,
1554
+ ast: ps.ast,
1555
+ meta: ps.meta
1556
+ };
1557
+ const mutator = createSqlParamRefMutator(preEncodeExec);
1558
+ await runBeforeExecuteChain(preEncodeExec, self.middleware, execMiddlewareCtx, mutator);
1559
+ const encodedParams = await encodeParamsWithMetadata(mutator.currentParams(), ps.paramMetadata, codecCtx, self.contractCodecs);
1560
+ const exec = {
1561
+ sql: ps.sql,
1562
+ params: encodedParams,
1563
+ ast: ps.ast,
1564
+ meta: ps.meta
1565
+ };
1566
+ const handles = self.#preparedStatementHandles;
1567
+ const request = {
1568
+ sql: exec.sql,
1569
+ params: exec.params,
1570
+ handle: {
1571
+ get: () => handles.get(ps),
1572
+ set: (value) => {
1573
+ handles.set(ps, value);
1423
1574
  }
1424
- } finally {
1425
- await iterator.return?.();
1426
1575
  }
1427
- outcome = "success";
1428
- } catch (error) {
1429
- outcome = "runtime-error";
1430
- throw error;
1431
- } finally {
1432
- if (outcome !== null) self.recordTelemetry(exec, outcome, Date.now() - startedAt);
1433
- }
1576
+ };
1577
+ yield* self.streamRows(exec, ps.decodeContext, () => queryable.executePrepared(request), codecCtx, execMiddlewareCtx);
1434
1578
  };
1435
1579
  return new AsyncIterableResult(generator());
1436
1580
  }
@@ -1453,6 +1597,12 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1453
1597
  ...options,
1454
1598
  scope: "connection"
1455
1599
  });
1600
+ },
1601
+ executePrepared(ps, params, options) {
1602
+ return self.executePreparedAgainstQueryable(ps, params, driverConn, {
1603
+ ...options,
1604
+ scope: "connection"
1605
+ });
1456
1606
  }
1457
1607
  };
1458
1608
  }
@@ -1470,6 +1620,12 @@ var SqlRuntimeImpl = class extends RuntimeCore {
1470
1620
  ...options,
1471
1621
  scope: "transaction"
1472
1622
  });
1623
+ },
1624
+ executePrepared(ps, params, options) {
1625
+ return self.executePreparedAgainstQueryable(ps, params, driverTx, {
1626
+ ...options,
1627
+ scope: "transaction"
1628
+ });
1473
1629
  }
1474
1630
  };
1475
1631
  }
@@ -1538,6 +1694,17 @@ async function withTransaction(runtime, fn) {
1538
1694
  }
1539
1695
  };
1540
1696
  return new AsyncIterableResult(guarded());
1697
+ },
1698
+ executePrepared(ps, params, options) {
1699
+ if (invalidated) throw transactionClosedError();
1700
+ const inner = transaction.executePrepared(ps, params, options);
1701
+ const guarded = async function* () {
1702
+ for await (const row of inner) {
1703
+ if (invalidated) throw transactionClosedError();
1704
+ yield row;
1705
+ }
1706
+ };
1707
+ return new AsyncIterableResult(guarded());
1541
1708
  }
1542
1709
  };
1543
1710
  let connectionDisposed = false;
@@ -1595,4 +1762,4 @@ function createRuntime(options) {
1595
1762
  //#endregion
1596
1763
  export { ensureTableStatement as a, createExecutionContext as c, budgets as d, parseContractMarkerRow as f, validateContractCodecMappings as g, validateCodecRegistryCompleteness as h, ensureSchemaStatement as i, createSqlExecutionStack as l, extractCodecIds as m, withTransaction as n, readContractMarker as o, lowerSqlPlan as p, APP_SPACE_ID as r, writeContractMarker as s, createRuntime as t, lints as u };
1597
1764
 
1598
- //# sourceMappingURL=exports-C2zFVOFS.mjs.map
1765
+ //# sourceMappingURL=exports-DHQOSWty.mjs.map