@prisma-next/sql-runtime 0.6.0-dev.1 → 0.6.0-dev.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{exports-BSTHn_rH.mjs → exports-6tjocFbX.mjs} +225 -179
- package/dist/exports-6tjocFbX.mjs.map +1 -0
- package/dist/{index-CTCvZOWI.d.mts → index-CNy2q72g.d.mts} +24 -12
- package/dist/index-CNy2q72g.d.mts.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/test/utils.d.mts +2 -2
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +61 -6
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +11 -11
- package/src/codecs/ast-codec-resolver.ts +99 -0
- package/src/codecs/decoding.ts +4 -39
- package/src/codecs/encoding.ts +20 -46
- package/src/middleware/sql-middleware.ts +22 -10
- package/src/sql-context.ts +157 -124
- package/src/sql-runtime.ts +84 -24
- package/dist/exports-BSTHn_rH.mjs.map +0 -1
- package/dist/index-CTCvZOWI.d.mts.map +0 -1
- package/src/codecs/alias-resolver.ts +0 -37
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { AsyncIterableResult, RuntimeCore, checkAborted, checkMiddlewareCompatibility, isRuntimeError, raceAgainstAbort, runWithMiddleware, runtimeError } from "@prisma-next/framework-components/runtime";
|
|
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
|
|
3
|
+
import { 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";
|
|
7
|
+
import { canonicalizeJson } from "@prisma-next/framework-components/utils";
|
|
7
8
|
import { createSqlOperationRegistry } from "@prisma-next/sql-operations";
|
|
8
9
|
import { buildCodecDescriptorRegistry } from "@prisma-next/sql-relational-core/codec-descriptor-registry";
|
|
9
10
|
import { APP_SPACE_ID } from "@prisma-next/framework-components/control";
|
|
@@ -388,6 +389,46 @@ function lints(options) {
|
|
|
388
389
|
});
|
|
389
390
|
}
|
|
390
391
|
//#endregion
|
|
392
|
+
//#region src/codecs/ast-codec-resolver.ts
|
|
393
|
+
/**
|
|
394
|
+
* Build an {@link AstCodecResolver} bound to a descriptor registry and a per-call instance-context factory.
|
|
395
|
+
*
|
|
396
|
+
* The instance-context factory lets callers control `name` / `usedAt` for refs the AST supplies (e.g. AST-embedded migration ops where the materialisation site is the AST node, not a contract column). The contract-walk pre-population path constructs its own contexts and invokes the resolver with those refs to seed the cache.
|
|
397
|
+
*/
|
|
398
|
+
function createAstCodecResolver(descriptors, instanceContextFor) {
|
|
399
|
+
const cache = /* @__PURE__ */ new Map();
|
|
400
|
+
return { forCodecRef(ref) {
|
|
401
|
+
const key = `${ref.codecId}:${canonicalizeJson(ref.typeParams)}`;
|
|
402
|
+
const cached = cache.get(key);
|
|
403
|
+
if (cached) return cached;
|
|
404
|
+
const descriptor = descriptors.descriptorFor(ref.codecId);
|
|
405
|
+
if (!descriptor) throw runtimeError("RUNTIME.CODEC_DESCRIPTOR_MISSING", `No codec descriptor registered for codecId '${ref.codecId}'.`, { codecId: ref.codecId });
|
|
406
|
+
const validated = validateTypeParams$1(descriptor.paramsSchema, descriptor.isParameterized && ref.typeParams === void 0 ? {
|
|
407
|
+
...ref,
|
|
408
|
+
typeParams: {}
|
|
409
|
+
} : ref);
|
|
410
|
+
const ctx = instanceContextFor(ref);
|
|
411
|
+
const codec = descriptor.factory(validated)(ctx);
|
|
412
|
+
cache.set(key, codec);
|
|
413
|
+
return codec;
|
|
414
|
+
} };
|
|
415
|
+
}
|
|
416
|
+
function validateTypeParams$1(paramsSchema, ref) {
|
|
417
|
+
const result = paramsSchema["~standard"].validate(ref.typeParams);
|
|
418
|
+
if (result instanceof Promise) throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `paramsSchema for codec '${ref.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`, {
|
|
419
|
+
codecId: ref.codecId,
|
|
420
|
+
typeParams: ref.typeParams
|
|
421
|
+
});
|
|
422
|
+
if ("issues" in result && result.issues) {
|
|
423
|
+
const messages = result.issues.map((issue) => issue.message).join("; ");
|
|
424
|
+
throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Invalid typeParams for codec '${ref.codecId}': ${messages}`, {
|
|
425
|
+
codecId: ref.codecId,
|
|
426
|
+
typeParams: ref.typeParams
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return result.value;
|
|
430
|
+
}
|
|
431
|
+
//#endregion
|
|
391
432
|
//#region src/sql-context.ts
|
|
392
433
|
function createSqlExecutionStack(options) {
|
|
393
434
|
return createExecutionStack({
|
|
@@ -502,93 +543,124 @@ function validateColumnTypeParams(storage, codecDescriptors) {
|
|
|
502
543
|
});
|
|
503
544
|
}
|
|
504
545
|
}
|
|
505
|
-
function isResolvedCodec(candidate) {
|
|
506
|
-
return candidate !== null && typeof candidate === "object" && "id" in candidate && "decode" in candidate;
|
|
507
|
-
}
|
|
508
546
|
/**
|
|
509
|
-
*
|
|
547
|
+
* Build-time contract-integrity check: every `(table, column)` resolves to a {@link CodecRef} whose `codecId` is registered and whose `typeParams` presence matches the descriptor's `isParameterized` flag.
|
|
510
548
|
*
|
|
511
|
-
*
|
|
512
|
-
* - **inline-typeParams columns**: call `descriptor.factory(typeParams) (ctx)` once per column (per-column anonymous instance).
|
|
513
|
-
* - **non-parameterized columns**: call `descriptor.factory()(ctx)` once. The synthesized descriptor's factory is constant — every call returns the same shared codec instance — so columns sharing a non-parameterized codec id share one resolved codec without explicit caching.
|
|
549
|
+
* Surfaces three classes of malformed contract that AST-bound codec resolution would otherwise mask silently:
|
|
514
550
|
*
|
|
515
|
-
*
|
|
551
|
+
* - column references a codecId no contributor registered → `RUNTIME.CODEC_DESCRIPTOR_MISSING`.
|
|
552
|
+
* - parameterized codec, no `typeParams` (legacy "tolerate refs without params" shape) → `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH`.
|
|
553
|
+
* - non-parameterized codec, `typeParams` supplied → `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH`.
|
|
554
|
+
*
|
|
555
|
+
* Runs unconditionally from `createExecutionContext` so contract bugs fail fast at construction time instead of silently skipping affected columns in the codec registry's pre-population walk.
|
|
516
556
|
*/
|
|
517
|
-
function
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (descriptor
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
|
|
557
|
+
function assertColumnCodecIntegrity(storage, codecDescriptors) {
|
|
558
|
+
for (const [tableName, table] of Object.entries(storage.tables)) for (const columnName of Object.keys(table.columns)) {
|
|
559
|
+
const ref = codecDescriptors.codecRefForColumn(tableName, columnName);
|
|
560
|
+
if (!ref) continue;
|
|
561
|
+
const descriptor = codecDescriptors.descriptorFor(ref.codecId);
|
|
562
|
+
if (!descriptor) throw runtimeError("RUNTIME.CODEC_DESCRIPTOR_MISSING", `Column '${tableName}.${columnName}' references codec '${ref.codecId}' but no contributor registered a codec descriptor for that codecId. Add the extension pack that owns the codec to the runtime stack.`, {
|
|
563
|
+
table: tableName,
|
|
564
|
+
column: columnName,
|
|
565
|
+
codecId: ref.codecId
|
|
566
|
+
});
|
|
567
|
+
if (descriptor.isParameterized && ref.typeParams === void 0) {
|
|
568
|
+
const probe = descriptor.paramsSchema["~standard"].validate({});
|
|
569
|
+
if (probe instanceof Promise) {
|
|
570
|
+
probe.catch(() => {});
|
|
571
|
+
throw runtimeError("RUNTIME.TYPE_PARAMS_INVALID", `Column '${tableName}.${columnName}' uses parameterized codec '${ref.codecId}' whose paramsSchema returned a Promise; paramsSchema must be a synchronous Standard Schema validator. Return a value/issues result directly instead of a Promise.`, {
|
|
572
|
+
table: tableName,
|
|
573
|
+
column: columnName,
|
|
574
|
+
codecId: ref.codecId
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
if ("issues" in probe && !!probe.issues) throw runtimeError("RUNTIME.CODEC_PARAMETERIZATION_MISMATCH", `Column '${tableName}.${columnName}' uses parameterized codec '${ref.codecId}' but no typeParams are supplied. Provide typeParams on the column, or use a typeRef pointing at a storage.types entry that carries them.`, {
|
|
578
|
+
table: tableName,
|
|
579
|
+
column: columnName,
|
|
580
|
+
codecId: ref.codecId,
|
|
581
|
+
expected: "parameterized",
|
|
582
|
+
actual: "no typeParams"
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
if (!descriptor.isParameterized && ref.typeParams !== void 0) throw runtimeError("RUNTIME.CODEC_PARAMETERIZATION_MISMATCH", `Column '${tableName}.${columnName}' supplies typeParams to non-parameterized codec '${ref.codecId}'. Remove the typeParams or switch to a parameterized codec id.`, {
|
|
586
|
+
table: tableName,
|
|
587
|
+
column: columnName,
|
|
588
|
+
codecId: ref.codecId,
|
|
589
|
+
expected: "non-parameterized",
|
|
590
|
+
actual: "has typeParams"
|
|
591
|
+
});
|
|
529
592
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Build a {@link ContractCodecRegistry} that resolves codecs exclusively through the `forCodecRef` content-keyed cache.
|
|
596
|
+
*
|
|
597
|
+
* One pre-population pass walks `storage.types` and `storage.tables[].columns[]` to seed the resolver's per-ref instance context with the *aggregated* `usedAt` set for each canonical `(codecId, typeParams)` key. The same codec materialised through `forColumn` or `forCodecRef` is therefore one instance with one `SqlCodecInstanceContext` — stateful codecs reading `usedAt` see the full column set regardless of which surface the caller used.
|
|
598
|
+
*
|
|
599
|
+
* Per-key instance-name policy:
|
|
600
|
+
*
|
|
601
|
+
* - typeRef-shared columns use the `storage.types[name]` name.
|
|
602
|
+
* - inline-`typeParams` columns use `<col:Table.column>` (the first column observed at that key; additional columns sharing the key extend `usedAt`).
|
|
603
|
+
* - non-parameterized codec ids use `<codec:codecId>`, aggregating every column on that codec id into one `usedAt` set.
|
|
604
|
+
* - ad-hoc refs the contract walk did not pre-populate (e.g. AST-supplied refs from deserialised migration ops) fall back to the canonical cache key `${codecId}:${canonicalizeJson(typeParams)}` — the only structurally honest identity for an ad-hoc ref, distinct per `(codecId, typeParams)`.
|
|
605
|
+
*
|
|
606
|
+
* Contract integrity is enforced upstream by {@link assertColumnCodecIntegrity}: every column must reference a registered `codecId` whose `descriptor.isParameterized` flag matches the presence of `typeParams` (via `codecRefForColumn`). The pre-population walk and `forColumn` therefore make no defensive checks — malformed columns fail fast at `createExecutionContext` construction with `RUNTIME.CODEC_DESCRIPTOR_MISSING` or `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH` rather than being silently skipped here.
|
|
607
|
+
*
|
|
608
|
+
* `forColumn(t, c)` is a thin delegate over `forCodecRef(codecRefForColumn(t, c))`; encode/decode hot paths read the resolver directly via `forCodecRef`. The only `undefined` `forColumn` returns is the legitimate "no such column in the contract" case.
|
|
609
|
+
*/
|
|
610
|
+
function buildContractCodecRegistry(contract, codecDescriptors) {
|
|
611
|
+
const refKeyOf = (ref) => `${ref.codecId}:${canonicalizeJson(ref.typeParams)}`;
|
|
612
|
+
const usedAtByKey = /* @__PURE__ */ new Map();
|
|
613
|
+
const nameByKey = /* @__PURE__ */ new Map();
|
|
614
|
+
const typeRefSites = collectTypeRefSites(contract.storage);
|
|
615
|
+
for (const [typeName, typeInstance] of Object.entries(contract.storage.types ?? {})) {
|
|
616
|
+
const key = refKeyOf({
|
|
617
|
+
codecId: typeInstance.codecId,
|
|
618
|
+
typeParams: typeInstance.typeParams
|
|
619
|
+
});
|
|
620
|
+
const sites = typeRefSites.get(typeName) ?? [];
|
|
621
|
+
const existing = usedAtByKey.get(key);
|
|
622
|
+
if (existing) existing.push(...sites);
|
|
623
|
+
else {
|
|
624
|
+
usedAtByKey.set(key, [...sites]);
|
|
625
|
+
nameByKey.set(key, typeName);
|
|
626
|
+
}
|
|
541
627
|
}
|
|
542
628
|
for (const [tableName, table] of Object.entries(contract.storage.tables)) for (const [columnName, column] of Object.entries(table.columns)) {
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
});
|
|
558
|
-
const ctx = {
|
|
559
|
-
name: `<col:${tableName}.${columnName}>`,
|
|
560
|
-
usedAt: [{
|
|
561
|
-
table: tableName,
|
|
562
|
-
column: columnName
|
|
563
|
-
}]
|
|
564
|
-
};
|
|
565
|
-
resolvedCodec = parameterizedDescriptor.factory(validatedParams)(ctx);
|
|
566
|
-
}
|
|
567
|
-
} else if (!isParameterized) {
|
|
568
|
-
const ctx = {
|
|
569
|
-
name: `<col:${tableName}.${columnName}>`,
|
|
570
|
-
usedAt: [{
|
|
571
|
-
table: tableName,
|
|
572
|
-
column: columnName
|
|
573
|
-
}]
|
|
574
|
-
};
|
|
575
|
-
resolvedCodec = descriptor.factory.bind(descriptor)(void 0)(ctx);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if (resolvedCodec) {
|
|
579
|
-
byColumn.set(columnKey, resolvedCodec);
|
|
580
|
-
const existing = byCodecId.get(column.codecId);
|
|
581
|
-
if (existing === void 0) byCodecId.set(column.codecId, resolvedCodec);
|
|
582
|
-
else if (existing !== resolvedCodec && parameterizedDescriptors.has(column.codecId)) ambiguousCodecIds.add(column.codecId);
|
|
629
|
+
if (column.typeRef !== void 0) continue;
|
|
630
|
+
const ref = codecDescriptors.codecRefForColumn(tableName, columnName);
|
|
631
|
+
if (!ref) continue;
|
|
632
|
+
const key = refKeyOf(ref);
|
|
633
|
+
const site = {
|
|
634
|
+
table: tableName,
|
|
635
|
+
column: columnName
|
|
636
|
+
};
|
|
637
|
+
const existing = usedAtByKey.get(key);
|
|
638
|
+
if (existing) existing.push(site);
|
|
639
|
+
else {
|
|
640
|
+
usedAtByKey.set(key, [site]);
|
|
641
|
+
const name = ref.typeParams !== void 0 ? `<col:${tableName}.${columnName}>` : `<codec:${ref.codecId}>`;
|
|
642
|
+
nameByKey.set(key, name);
|
|
583
643
|
}
|
|
584
644
|
}
|
|
645
|
+
const resolver = createAstCodecResolver(codecDescriptors, (ref) => {
|
|
646
|
+
const key = refKeyOf(ref);
|
|
647
|
+
return {
|
|
648
|
+
name: nameByKey.get(key) ?? key,
|
|
649
|
+
usedAt: usedAtByKey.get(key) ?? []
|
|
650
|
+
};
|
|
651
|
+
});
|
|
652
|
+
for (const [tableName, table] of Object.entries(contract.storage.tables)) for (const columnName of Object.keys(table.columns)) {
|
|
653
|
+
const ref = codecDescriptors.codecRefForColumn(tableName, columnName);
|
|
654
|
+
if (!ref) continue;
|
|
655
|
+
resolver.forCodecRef(ref);
|
|
656
|
+
}
|
|
585
657
|
return {
|
|
586
658
|
forColumn(table, column) {
|
|
587
|
-
|
|
659
|
+
const ref = codecDescriptors.codecRefForColumn(table, column);
|
|
660
|
+
return ref ? resolver.forCodecRef(ref) : void 0;
|
|
588
661
|
},
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return byCodecId.get(codecId) ?? parameterizedRepresentatives.get(codecId);
|
|
662
|
+
forCodecRef(ref) {
|
|
663
|
+
return resolver.forCodecRef(ref);
|
|
592
664
|
}
|
|
593
665
|
};
|
|
594
666
|
}
|
|
@@ -683,14 +755,15 @@ function createExecutionContext(options) {
|
|
|
683
755
|
const ops = contributor.queryOperations?.() ?? {};
|
|
684
756
|
for (const [name, op] of Object.entries(ops)) queryOperationRegistry.register(name, op);
|
|
685
757
|
}
|
|
686
|
-
const codecDescriptors = buildCodecDescriptorRegistry(allCodecDescriptors);
|
|
758
|
+
const codecDescriptors = buildCodecDescriptorRegistry(allCodecDescriptors, contract.storage);
|
|
759
|
+
assertColumnCodecIntegrity(contract.storage, codecDescriptors);
|
|
687
760
|
const mutationDefaultGeneratorRegistry = collectMutationDefaultGenerators(contributors);
|
|
688
761
|
assertMutationDefaultGeneratorsAvailable(contract, mutationDefaultGeneratorRegistry);
|
|
689
762
|
if (parameterizedCodecDescriptors.size > 0) validateColumnTypeParams(contract.storage, parameterizedCodecDescriptors);
|
|
690
763
|
const types = initializeTypeHelpers(contract.storage, parameterizedCodecDescriptors);
|
|
691
764
|
return {
|
|
692
765
|
contract,
|
|
693
|
-
contractCodecs: buildContractCodecRegistry(contract, codecDescriptors
|
|
766
|
+
contractCodecs: buildContractCodecRegistry(contract, codecDescriptors),
|
|
694
767
|
codecDescriptors,
|
|
695
768
|
queryOperations: queryOperationRegistry,
|
|
696
769
|
types,
|
|
@@ -817,33 +890,6 @@ function writeContractMarker(input) {
|
|
|
817
890
|
};
|
|
818
891
|
}
|
|
819
892
|
//#endregion
|
|
820
|
-
//#region src/codecs/alias-resolver.ts
|
|
821
|
-
/**
|
|
822
|
-
* Build a map from query-local table aliases to their underlying source table names.
|
|
823
|
-
*
|
|
824
|
-
* Self-joins like `db.sql.post.as('p1').innerJoin(db.sql.post.as('p2'), …)` produce `ColumnRef`s whose `table` is the alias (`p1`, `p2`) — the SQL renderer needs the alias for `SELECT p1.id, …`. Codec dispatch keys `byColumn` by the underlying source table, so aliases must be resolved back to the source name for `forColumn(...)` to hit. Tables that already use their canonical name (no alias) are also entered so a single
|
|
825
|
-
* lookup works for both shapes.
|
|
826
|
-
*/
|
|
827
|
-
function buildAliasMap(ast) {
|
|
828
|
-
const aliases = /* @__PURE__ */ new Map();
|
|
829
|
-
const recordSource = (source) => {
|
|
830
|
-
if (source.kind === "table-source") {
|
|
831
|
-
const key = source.alias ?? source.name;
|
|
832
|
-
aliases.set(key, source.name);
|
|
833
|
-
} else aliases.set(source.alias, source.alias);
|
|
834
|
-
};
|
|
835
|
-
if (ast.kind === "select") {
|
|
836
|
-
recordSource(ast.from);
|
|
837
|
-
for (const join of ast.joins ?? []) recordSource(join.source);
|
|
838
|
-
} else if (ast.kind === "raw-sql") {} else recordSource(ast.table);
|
|
839
|
-
return aliases;
|
|
840
|
-
}
|
|
841
|
-
function makeAliasResolver(ast) {
|
|
842
|
-
if (!ast) return (alias) => alias;
|
|
843
|
-
const map = buildAliasMap(ast);
|
|
844
|
-
return (alias) => map.get(alias) ?? alias;
|
|
845
|
-
}
|
|
846
|
-
//#endregion
|
|
847
893
|
//#region src/codecs/decoding.ts
|
|
848
894
|
const WIRE_PREVIEW_LIMIT = 100;
|
|
849
895
|
const EMPTY_INCLUDE_ALIASES = /* @__PURE__ */ new Set();
|
|
@@ -855,30 +901,8 @@ function projectionListFromAst(ast) {
|
|
|
855
901
|
if (ast.kind === "raw-sql") return;
|
|
856
902
|
return ast.returning;
|
|
857
903
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
*
|
|
861
|
-
* When a `(table, column)` ref is available — either implicit on a `column-ref` expression or carried explicitly via `item.refs` for column-bound non-`column-ref` projections — prefer `contractCodecs.forColumn(table, column)`: that returns the per-instance codec materialized from the descriptor's factory for that column, encoding any per-instance state (typeParams like vector length, schema validators, etc.).
|
|
862
|
-
*
|
|
863
|
-
* The wrong-instance risk for parameterized codecs is closed off structurally:
|
|
864
|
-
*
|
|
865
|
-
* 1. `buildContractCodecRegistry` pre-populates `byCodecId` with one canonical instance per non-parameterized descriptor; parameterized descriptors are intentionally absent. 2. `forCodecId` rejects ambiguous parameterized fallbacks (`ambiguousCodecIds`). 3. The non-ambiguous parameterized case stores the column-correct per-instance codec under `byCodecId`, so the fall-through still resolves to the right instance.
|
|
866
|
-
*
|
|
867
|
-
* The `forCodecId` fallback otherwise covers projections that are *not* column-bound (computed projections, raw SQL aliases) but still carry a `codecId` (ADR 205 stamps every `ProjectionItem` with the producer's codec id).
|
|
868
|
-
*
|
|
869
|
-
* Codec-registry-unification spec § AC-4 / AC-5.
|
|
870
|
-
*/
|
|
871
|
-
function resolveProjectionCodec(item, contractCodecs, aliasResolver) {
|
|
872
|
-
if (contractCodecs) {
|
|
873
|
-
if (item.expr.kind === "column-ref") {
|
|
874
|
-
const byColumn = contractCodecs.forColumn(aliasResolver(item.expr.table), item.expr.column);
|
|
875
|
-
if (byColumn && (item.codecId === void 0 || byColumn.id === item.codecId)) return byColumn;
|
|
876
|
-
} else if (item.refs) {
|
|
877
|
-
const byColumn = contractCodecs.forColumn(aliasResolver(item.refs.table), item.refs.column);
|
|
878
|
-
if (byColumn && (item.codecId === void 0 || byColumn.id === item.codecId)) return byColumn;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
if (item.codecId) return contractCodecs?.forCodecId(item.codecId);
|
|
904
|
+
function resolveProjectionCodec(item, contractCodecs) {
|
|
905
|
+
if (item.codec && contractCodecs) return contractCodecs.forCodecRef(item.codec);
|
|
882
906
|
}
|
|
883
907
|
function buildDecodeContext(plan, contractCodecs) {
|
|
884
908
|
if (!isAstBackedPlan(plan)) return {
|
|
@@ -898,19 +922,14 @@ function buildDecodeContext(plan, contractCodecs) {
|
|
|
898
922
|
const codecs = /* @__PURE__ */ new Map();
|
|
899
923
|
const columnRefs = /* @__PURE__ */ new Map();
|
|
900
924
|
const includeAliases = /* @__PURE__ */ new Set();
|
|
901
|
-
const aliasResolver = makeAliasResolver(plan.ast);
|
|
902
925
|
for (const item of projection) {
|
|
903
926
|
aliases.push(item.alias);
|
|
904
|
-
const codec = resolveProjectionCodec(item, contractCodecs
|
|
927
|
+
const codec = resolveProjectionCodec(item, contractCodecs);
|
|
905
928
|
if (codec) codecs.set(item.alias, codec);
|
|
906
929
|
if (item.expr.kind === "column-ref") columnRefs.set(item.alias, {
|
|
907
|
-
table:
|
|
930
|
+
table: item.expr.table,
|
|
908
931
|
column: item.expr.column
|
|
909
932
|
});
|
|
910
|
-
else if (item.refs) columnRefs.set(item.alias, {
|
|
911
|
-
table: aliasResolver(item.refs.table),
|
|
912
|
-
column: item.refs.column
|
|
913
|
-
});
|
|
914
933
|
else if (item.expr.kind === "subquery" || item.expr.kind === "json-array-agg") includeAliases.add(item.alias);
|
|
915
934
|
}
|
|
916
935
|
return {
|
|
@@ -1035,29 +1054,11 @@ async function decodeRow(row, plan, rowCtx, contractCodecs) {
|
|
|
1035
1054
|
//#endregion
|
|
1036
1055
|
//#region src/codecs/encoding.ts
|
|
1037
1056
|
const NO_METADATA = Object.freeze({
|
|
1038
|
-
|
|
1039
|
-
name: void 0
|
|
1040
|
-
refs: void 0
|
|
1057
|
+
codec: void 0,
|
|
1058
|
+
name: void 0
|
|
1041
1059
|
});
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
*
|
|
1045
|
-
* Column-aware dispatch: when `metadata.refs` is populated by a column-bound construction site, prefer `contractCodecs.forColumn(refs.table, refs.column)` — that returns the per-instance codec the contract walk materialized for the `(table, column)` pair, encoding the column's typeParams (e.g. `vector(1024)` vs. `vector(1536)`).
|
|
1046
|
-
*
|
|
1047
|
-
* On a column-lookup miss the resolver falls through to `forCodecId`. The wrong-instance risk for parameterized codecs is closed off structurally:
|
|
1048
|
-
*
|
|
1049
|
-
* 1. `buildContractCodecRegistry` pre-populates `byCodecId` with one canonical instance per non-parameterized descriptor; parameterized descriptors are intentionally absent from this pre-population. 2. `forCodecId` rejects ambiguous parameterized fallbacks (`ambiguousCodecIds`) — if the contract walk resolved more than one distinct instance under a single parameterized id, the call throws rather than binding to
|
|
1050
|
-
* whichever landed first. 3. For the non-ambiguous parameterized case (a single column with that id), `byCodecId` stores the column-correct per-instance codec, so the fall-through still resolves to the right instance.
|
|
1051
|
-
*
|
|
1052
|
-
* Refs-less fallback: ParamRefs constructed outside a column-bound site (literals, transient builder state) carry a non-parameterized `codecId` whose dispatch is ambiguity-free. The validator pass (`validateParamRefRefs`) already enforced refs on every parameterized ParamRef before encode runs.
|
|
1053
|
-
*/
|
|
1054
|
-
function resolveParamCodec(metadata, contractCodecs, aliasResolver) {
|
|
1055
|
-
if (!metadata.codecId) return void 0;
|
|
1056
|
-
if (metadata.refs && contractCodecs) {
|
|
1057
|
-
const byColumn = contractCodecs.forColumn(aliasResolver(metadata.refs.table), metadata.refs.column);
|
|
1058
|
-
if (byColumn && byColumn.id === metadata.codecId) return byColumn;
|
|
1059
|
-
}
|
|
1060
|
-
return contractCodecs?.forCodecId(metadata.codecId);
|
|
1060
|
+
function resolveParamCodec(metadata, contractCodecs) {
|
|
1061
|
+
if (metadata.codec && contractCodecs) return contractCodecs.forCodecRef(metadata.codec);
|
|
1061
1062
|
}
|
|
1062
1063
|
function paramLabel(metadata, paramIndex) {
|
|
1063
1064
|
return metadata.name ?? `param[${paramIndex}]`;
|
|
@@ -1072,13 +1073,14 @@ function wrapEncodeFailure(error, metadata, paramIndex, codecId) {
|
|
|
1072
1073
|
wrapped.cause = error;
|
|
1073
1074
|
throw wrapped;
|
|
1074
1075
|
}
|
|
1075
|
-
async function encodeParamValue(value, metadata, paramIndex, ctx, contractCodecs
|
|
1076
|
+
async function encodeParamValue(value, metadata, paramIndex, ctx, contractCodecs) {
|
|
1076
1077
|
if (value === null || value === void 0) return null;
|
|
1077
|
-
const codec = resolveParamCodec(metadata, contractCodecs
|
|
1078
|
+
const codec = resolveParamCodec(metadata, contractCodecs);
|
|
1078
1079
|
if (!codec) return value;
|
|
1079
1080
|
try {
|
|
1080
1081
|
return await codec.encode(value, ctx);
|
|
1081
1082
|
} catch (error) {
|
|
1083
|
+
if (isRuntimeError(error)) throw error;
|
|
1082
1084
|
wrapEncodeFailure(error, metadata, paramIndex, codec.id);
|
|
1083
1085
|
}
|
|
1084
1086
|
}
|
|
@@ -1102,15 +1104,13 @@ async function encodeParams(plan, ctx, contractCodecs) {
|
|
|
1102
1104
|
for (let i = 0; i < paramCount && i < refs.length; i++) {
|
|
1103
1105
|
const ref = refs[i];
|
|
1104
1106
|
if (ref) metadata[i] = {
|
|
1105
|
-
|
|
1106
|
-
name: ref.name
|
|
1107
|
-
refs: ref.refs
|
|
1107
|
+
codec: ref.codec,
|
|
1108
|
+
name: ref.name
|
|
1108
1109
|
};
|
|
1109
1110
|
}
|
|
1110
1111
|
}
|
|
1111
|
-
const aliasResolver = makeAliasResolver(plan.ast);
|
|
1112
1112
|
const tasks = new Array(paramCount);
|
|
1113
|
-
for (let i = 0; i < paramCount; i++) tasks[i] = encodeParamValue(plan.params[i], metadata[i] ?? NO_METADATA, i, ctx, contractCodecs
|
|
1113
|
+
for (let i = 0; i < paramCount; i++) tasks[i] = encodeParamValue(plan.params[i], metadata[i] ?? NO_METADATA, i, ctx, contractCodecs);
|
|
1114
1114
|
const settled = await raceAgainstAbort(Promise.all(tasks), signal, "encode");
|
|
1115
1115
|
return Object.freeze(settled);
|
|
1116
1116
|
}
|
|
@@ -1266,16 +1266,49 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1266
1266
|
}
|
|
1267
1267
|
}
|
|
1268
1268
|
/**
|
|
1269
|
-
* Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan`
|
|
1269
|
+
* Lower a `SqlQueryPlan` (AST + meta) into a `SqlExecutionPlan`
|
|
1270
|
+
* with encoded parameters ready for the driver.
|
|
1271
|
+
*
|
|
1272
|
+
* Implementation note: SQL splits lower-then-encode across
|
|
1273
|
+
* {@link lowerToDraft} + {@link encodeDraftParams} so the runtime
|
|
1274
|
+
* can fire the `beforeExecute` middleware chain between them
|
|
1275
|
+
* (cipherstash bulk-encrypt, for example, mutates pre-encode
|
|
1276
|
+
* `ParamRef.value` slots). This protected hook composes the two
|
|
1277
|
+
* back into the cross-family `lower()` shape `RuntimeCore.execute`
|
|
1278
|
+
* expects, and is called from the no-middleware fast paths /
|
|
1279
|
+
* fixtures that hit `RuntimeCore`'s default template directly.
|
|
1280
|
+
* `execute()` overrides the template and uses the split form so
|
|
1281
|
+
* `beforeExecute` lands between the two halves.
|
|
1270
1282
|
*
|
|
1271
|
-
* `ctx: SqlCodecCallContext` is forwarded to `encodeParams` so
|
|
1283
|
+
* `ctx: SqlCodecCallContext` is forwarded to `encodeParams` so
|
|
1284
|
+
* per-query cancellation reaches every codec body during parameter
|
|
1285
|
+
* encoding. SQL params do not populate `ctx.column` — encode-side
|
|
1286
|
+
* column metadata is the middleware's domain.
|
|
1272
1287
|
*/
|
|
1273
1288
|
async lower(plan, ctx) {
|
|
1274
|
-
|
|
1275
|
-
|
|
1289
|
+
const draft = this.lowerToDraft(plan);
|
|
1290
|
+
return await this.encodeDraftParams(draft, ctx);
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* AST → pre-encode draft. The returned plan has `sql` rendered and
|
|
1294
|
+
* `params` populated with the user-domain values the lowering site
|
|
1295
|
+
* collected from `ParamRef` nodes. No codec encode has happened
|
|
1296
|
+
* yet; consumers can mutate `params` via the `SqlParamRefMutator`
|
|
1297
|
+
* before {@link encodeDraftParams} runs.
|
|
1298
|
+
*/
|
|
1299
|
+
lowerToDraft(plan) {
|
|
1300
|
+
return lowerSqlPlan(this.adapter, this.contract, plan);
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Encode a draft plan's params through the per-column codecs and
|
|
1304
|
+
* freeze the result into the final `SqlExecutionPlan` the driver
|
|
1305
|
+
* sees. Errors surface as `RUNTIME.ENCODE_FAILED` envelopes from
|
|
1306
|
+
* {@link encodeParams}.
|
|
1307
|
+
*/
|
|
1308
|
+
async encodeDraftParams(draft, ctx) {
|
|
1276
1309
|
return Object.freeze({
|
|
1277
|
-
...
|
|
1278
|
-
params: await encodeParams(
|
|
1310
|
+
...draft,
|
|
1311
|
+
params: await encodeParams(draft, ctx, this.contractCodecs)
|
|
1279
1312
|
});
|
|
1280
1313
|
}
|
|
1281
1314
|
/**
|
|
@@ -1319,12 +1352,26 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1319
1352
|
checkAborted(codecCtx, "stream");
|
|
1320
1353
|
let exec;
|
|
1321
1354
|
if (isExecutionPlan(plan)) {
|
|
1322
|
-
|
|
1355
|
+
const preEncodeMutator = createSqlParamRefMutator(plan);
|
|
1356
|
+
await runBeforeExecuteChain(plan, self.middleware, execMiddlewareCtx, preEncodeMutator);
|
|
1323
1357
|
exec = Object.freeze({
|
|
1324
1358
|
...plan,
|
|
1325
|
-
params: await encodeParams(
|
|
1359
|
+
params: await encodeParams({
|
|
1360
|
+
...plan,
|
|
1361
|
+
params: preEncodeMutator.currentParams()
|
|
1362
|
+
}, codecCtx, self.contractCodecs)
|
|
1326
1363
|
});
|
|
1327
|
-
} else
|
|
1364
|
+
} else {
|
|
1365
|
+
const compiled = await self.runBeforeCompile(plan);
|
|
1366
|
+
const draft = self.lowerToDraft(compiled);
|
|
1367
|
+
const preEncodeMutator = createSqlParamRefMutator(draft);
|
|
1368
|
+
await runBeforeExecuteChain(draft, self.middleware, execMiddlewareCtx, preEncodeMutator);
|
|
1369
|
+
const draftWithMutations = Object.freeze({
|
|
1370
|
+
...draft,
|
|
1371
|
+
params: preEncodeMutator.currentParams()
|
|
1372
|
+
});
|
|
1373
|
+
exec = await self.encodeDraftParams(draftWithMutations, codecCtx);
|
|
1374
|
+
}
|
|
1328
1375
|
self.familyAdapter.validatePlan(exec, self.contract);
|
|
1329
1376
|
self._telemetry = null;
|
|
1330
1377
|
if (!self.startupVerified && self.verify.mode === "startup") await self.verifyMarker();
|
|
@@ -1333,11 +1380,10 @@ var SqlRuntimeImpl = class extends RuntimeCore {
|
|
|
1333
1380
|
let outcome = null;
|
|
1334
1381
|
try {
|
|
1335
1382
|
if (self.verify.mode === "always") await self.verifyMarker();
|
|
1336
|
-
const paramsMutator = createSqlParamRefMutator(exec);
|
|
1337
1383
|
const iterator = runWithMiddleware(exec, self.middleware, execMiddlewareCtx, () => queryable.execute({
|
|
1338
1384
|
sql: exec.sql,
|
|
1339
|
-
params:
|
|
1340
|
-
})
|
|
1385
|
+
params: exec.params
|
|
1386
|
+
}))[Symbol.asyncIterator]();
|
|
1341
1387
|
try {
|
|
1342
1388
|
while (true) {
|
|
1343
1389
|
checkAborted(codecCtx, "stream");
|
|
@@ -1513,4 +1559,4 @@ function createRuntime(options) {
|
|
|
1513
1559
|
//#endregion
|
|
1514
1560
|
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 };
|
|
1515
1561
|
|
|
1516
|
-
//# sourceMappingURL=exports-
|
|
1562
|
+
//# sourceMappingURL=exports-6tjocFbX.mjs.map
|