@prisma-next/sql-relational-core 0.5.0-dev.8 → 0.5.0-dev.81
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/README.md +59 -25
- package/dist/codec-types-BUfBna4G.d.mts +107 -0
- package/dist/codec-types-BUfBna4G.d.mts.map +1 -0
- package/dist/{errors-D3xmG4h-.mjs → errors-BF7W5uUd.mjs} +2 -2
- package/dist/{errors-D3xmG4h-.mjs.map → errors-BF7W5uUd.mjs.map} +1 -1
- package/dist/{errors-DI_RVH66.d.mts → errors-BgNpVAT8.d.mts} +2 -2
- package/dist/errors-BgNpVAT8.d.mts.map +1 -0
- package/dist/exports/ast.d.mts +155 -85
- package/dist/exports/ast.d.mts.map +1 -1
- package/dist/exports/ast.mjs +207 -1322
- package/dist/exports/ast.mjs.map +1 -1
- package/dist/exports/codec-descriptor-registry.d.mts +18 -0
- package/dist/exports/codec-descriptor-registry.d.mts.map +1 -0
- package/dist/exports/codec-descriptor-registry.mjs +40 -0
- package/dist/exports/codec-descriptor-registry.mjs.map +1 -0
- package/dist/exports/errors.d.mts +1 -4
- package/dist/exports/errors.mjs +2 -3
- package/dist/exports/expression.d.mts +84 -0
- package/dist/exports/expression.d.mts.map +1 -0
- package/dist/exports/expression.mjs +62 -0
- package/dist/exports/expression.mjs.map +1 -0
- package/dist/exports/middleware.d.mts +2 -0
- package/dist/exports/middleware.mjs +2 -0
- package/dist/exports/plan.d.mts +3 -2
- package/dist/exports/plan.mjs +27 -10
- package/dist/exports/plan.mjs.map +1 -1
- package/dist/exports/query-lane-context.d.mts +2 -3
- package/dist/exports/query-lane-context.mjs +1 -1
- package/dist/exports/types.d.mts +3 -4
- package/dist/exports/types.mjs +1 -1
- package/dist/index.d.mts +12 -11
- package/dist/index.mjs +8 -5
- package/dist/middleware-D2Wv9QIf.mjs +82 -0
- package/dist/middleware-D2Wv9QIf.mjs.map +1 -0
- package/dist/middleware-USDeR8p1.d.mts +121 -0
- package/dist/middleware-USDeR8p1.d.mts.map +1 -0
- package/dist/plan-DFpOIsKB.d.mts +44 -0
- package/dist/plan-DFpOIsKB.d.mts.map +1 -0
- package/dist/query-lane-context-C2tLZGp4.d.mts +72 -0
- package/dist/query-lane-context-C2tLZGp4.d.mts.map +1 -0
- package/dist/sql-execution-plan-ffz8TSfr.d.mts +32 -0
- package/dist/sql-execution-plan-ffz8TSfr.d.mts.map +1 -0
- package/dist/{types-C3Hg-CVz.d.mts → types-BZcKgaYA.d.mts} +61 -24
- package/dist/types-BZcKgaYA.d.mts.map +1 -0
- package/dist/types-CK9ZJ6OU.mjs +1112 -0
- package/dist/types-CK9ZJ6OU.mjs.map +1 -0
- package/dist/types-CL18G37a.d.mts +23 -0
- package/dist/types-CL18G37a.d.mts.map +1 -0
- package/dist/{types-D3vVZzRZ.d.mts → types-D6Pk1DTr.d.mts} +18 -25
- package/dist/types-D6Pk1DTr.d.mts.map +1 -0
- package/dist/util-DWmhUCEO.mjs +34 -0
- package/dist/util-DWmhUCEO.mjs.map +1 -0
- package/package.json +17 -14
- package/src/ast/adapter-types.ts +6 -14
- package/src/ast/codec-types.ts +87 -419
- package/src/ast/sql-codec-helpers.ts +79 -0
- package/src/ast/sql-codecs.ts +285 -125
- package/src/ast/types.ts +222 -173
- package/src/ast/util.ts +23 -0
- package/src/ast/validate-param-refs.ts +39 -0
- package/src/codec-descriptor-registry.ts +52 -0
- package/src/exports/ast.ts +2 -0
- package/src/exports/codec-descriptor-registry.ts +1 -0
- package/src/exports/expression.ts +1 -0
- package/src/exports/middleware.ts +8 -0
- package/src/exports/plan.ts +1 -0
- package/src/exports/types.ts +1 -0
- package/src/expression.ts +134 -0
- package/src/index.ts +2 -0
- package/src/middleware/param-ref-mutator.ts +230 -0
- package/src/plan.ts +43 -26
- package/src/query-lane-context.ts +35 -55
- package/src/runtime-scope.ts +20 -0
- package/src/sql-execution-plan.ts +28 -0
- package/src/types.ts +9 -22
- package/dist/codec-types-B2Xdq0Wr.d.mts +0 -167
- package/dist/codec-types-B2Xdq0Wr.d.mts.map +0 -1
- package/dist/errors-DI_RVH66.d.mts.map +0 -1
- package/dist/plan-Cs65hb-E.d.mts +0 -28
- package/dist/plan-Cs65hb-E.d.mts.map +0 -1
- package/dist/query-lane-context-D2H78Mg0.d.mts +0 -89
- package/dist/query-lane-context-D2H78Mg0.d.mts.map +0 -1
- package/dist/types-C3Hg-CVz.d.mts.map +0 -1
- package/dist/types-D3vVZzRZ.d.mts.map +0 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CodecDescriptor } from '@prisma-next/framework-components/codec';
|
|
2
|
+
import type { AnyCodecDescriptor } from './ast/codec-types';
|
|
3
|
+
import type { CodecDescriptorRegistry } from './query-lane-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build a {@link CodecDescriptorRegistry} from a flat descriptor list.
|
|
7
|
+
*
|
|
8
|
+
* Used by:
|
|
9
|
+
* - Each codec-shipping package's `core/registry.ts` to expose a package-scoped registry as the public consumer surface (replacing raw descriptor-array exports). See ADR 208.
|
|
10
|
+
* - The runtime's `buildExecutionContext` to construct the contract-bound combined registry from every contributor's `codecs:` slot.
|
|
11
|
+
*
|
|
12
|
+
* The descriptor map is heterogeneous in `P` — each codec id has its own params shape. The public {@link CodecDescriptorRegistry} interface widens to `CodecDescriptor<unknown>` and consumers narrow per codec id at the call site (the descriptor's `paramsSchema` validates JSON-sourced params before the factory ever sees them, so the runtime narrow is safe). The cast at registration goes through `unknown` because
|
|
13
|
+
* `CodecDescriptor<P>` is invariant in `P` (the `factory` and `renderOutputType` slots use `P` contravariantly).
|
|
14
|
+
*/
|
|
15
|
+
export function buildCodecDescriptorRegistry(
|
|
16
|
+
allDescriptors: ReadonlyArray<AnyCodecDescriptor>,
|
|
17
|
+
): CodecDescriptorRegistry {
|
|
18
|
+
type AnyDescriptor = CodecDescriptor<unknown>;
|
|
19
|
+
const byId = new Map<string, AnyDescriptor>();
|
|
20
|
+
const byTargetType = new Map<string, Array<AnyDescriptor>>();
|
|
21
|
+
|
|
22
|
+
for (const descriptor of allDescriptors) {
|
|
23
|
+
if (byId.has(descriptor.codecId)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Duplicate codec descriptor id: '${descriptor.codecId}' — registered twice during registry construction. ` +
|
|
26
|
+
'Each codecId must be contributed by exactly one component (target / adapter / extension pack).',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const widened = descriptor as unknown as AnyDescriptor;
|
|
30
|
+
byId.set(descriptor.codecId, widened);
|
|
31
|
+
for (const targetType of descriptor.targetTypes) {
|
|
32
|
+
const list = byTargetType.get(targetType);
|
|
33
|
+
if (list) {
|
|
34
|
+
list.push(widened);
|
|
35
|
+
} else {
|
|
36
|
+
byTargetType.set(targetType, [widened]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
descriptorFor(codecId: string): AnyDescriptor | undefined {
|
|
43
|
+
return byId.get(codecId);
|
|
44
|
+
},
|
|
45
|
+
*values(): IterableIterator<AnyDescriptor> {
|
|
46
|
+
yield* byId.values();
|
|
47
|
+
},
|
|
48
|
+
byTargetType(targetType: string): readonly AnyDescriptor[] {
|
|
49
|
+
return byTargetType.get(targetType) ?? Object.freeze([]);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
package/src/exports/ast.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from '../ast/adapter-types';
|
|
2
2
|
export * from '../ast/codec-types';
|
|
3
3
|
export * from '../ast/driver-types';
|
|
4
|
+
export * from '../ast/sql-codec-helpers';
|
|
4
5
|
export * from '../ast/sql-codecs';
|
|
5
6
|
export * from '../ast/types';
|
|
6
7
|
export * from '../ast/util';
|
|
8
|
+
export * from '../ast/validate-param-refs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../codec-descriptor-registry';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../expression';
|
package/src/exports/plan.ts
CHANGED
package/src/exports/types.ts
CHANGED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ParamSpec } from '@prisma-next/operations';
|
|
2
|
+
import type { QueryOperationReturn } from '@prisma-next/sql-contract/types';
|
|
3
|
+
import type { SqlLoweringSpec } from '@prisma-next/sql-operations';
|
|
4
|
+
import type { AnyExpression as AstExpression } from './ast/types';
|
|
5
|
+
import { OperationExpr, ParamRef } from './ast/types';
|
|
6
|
+
|
|
7
|
+
export type ScopeField = { codecId: string; nullable: boolean };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A typed SQL expression. Identity is carried by the `returnType` descriptor (inherited from `QueryOperationReturn` and narrowed to `T`) — distinct `T` makes distinct Expression types structurally. `buildAst()` materialises the underlying AST node.
|
|
11
|
+
*/
|
|
12
|
+
export type Expression<T extends ScopeField> = QueryOperationReturn & {
|
|
13
|
+
readonly returnType: T;
|
|
14
|
+
buildAst(): AstExpression;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type CodecIdsWithTrait<
|
|
18
|
+
CT extends Record<string, { readonly input: unknown }>,
|
|
19
|
+
RequiredTraits extends readonly string[],
|
|
20
|
+
> = {
|
|
21
|
+
[K in keyof CT & string]: CT[K] extends { readonly traits: infer T }
|
|
22
|
+
? [RequiredTraits[number]] extends [T]
|
|
23
|
+
? K
|
|
24
|
+
: never
|
|
25
|
+
: never;
|
|
26
|
+
}[keyof CT & string];
|
|
27
|
+
|
|
28
|
+
type NullSuffix<N> = N extends true ? null : never;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* An expression or literal value targeting a specific codec.
|
|
32
|
+
*
|
|
33
|
+
* Accepts any of:
|
|
34
|
+
* - An `Expression` whose codec matches exactly
|
|
35
|
+
* - A raw JS value of the codec's `input` type
|
|
36
|
+
* - `null` when `Nullable` is true
|
|
37
|
+
*/
|
|
38
|
+
export type CodecExpression<
|
|
39
|
+
CodecId extends string,
|
|
40
|
+
Nullable extends boolean,
|
|
41
|
+
CT extends Record<string, { readonly input: unknown }>,
|
|
42
|
+
> =
|
|
43
|
+
| Expression<{ codecId: CodecId; nullable: Nullable }>
|
|
44
|
+
| (CodecId extends keyof CT ? CT[CodecId]['input'] : never)
|
|
45
|
+
| NullSuffix<Nullable>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* An expression or literal value targeting any codec whose trait set contains all the required traits.
|
|
49
|
+
*
|
|
50
|
+
* Resolves the trait set to the union of matching codec identities via `CodecIdsWithTrait`, then reuses `CodecExpression` for the codec-id form.
|
|
51
|
+
*/
|
|
52
|
+
export type TraitExpression<
|
|
53
|
+
Traits extends readonly string[],
|
|
54
|
+
Nullable extends boolean,
|
|
55
|
+
CT extends Record<string, { readonly input: unknown }>,
|
|
56
|
+
> = CodecExpression<CodecIdsWithTrait<CT, Traits>, Nullable, CT>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a raw value or an Expression into an AST expression node.
|
|
60
|
+
*
|
|
61
|
+
* When `value` is an Expression (duck-typed by its `buildAst` method), the AST it wraps is returned. Otherwise the value is embedded as a ParamRef tagged with `codecId` (if given) and optionally `refs: { table, column }` (if the caller knows the column-bound site).
|
|
62
|
+
*
|
|
63
|
+
* For parameterized codec ids (e.g. `pg/vector@1`), encode-side dispatch requires `refs` to select the per-instance codec — so operation implementations that compare a column to a user-supplied value should derive `refs` from the column-bound side and pass it down. Non-parameterized codec ids (e.g. `pg/int4@1`) tolerate refs-less ParamRefs; the validator pass enforces refs only for parameterized ids.
|
|
64
|
+
*/
|
|
65
|
+
export function toExpr(
|
|
66
|
+
value: unknown,
|
|
67
|
+
codecId?: string,
|
|
68
|
+
refs?: { table: string; column: string },
|
|
69
|
+
): AstExpression {
|
|
70
|
+
if (isExpressionLike(value)) {
|
|
71
|
+
return value.buildAst();
|
|
72
|
+
}
|
|
73
|
+
if (codecId === undefined && refs === undefined) return ParamRef.of(value);
|
|
74
|
+
return ParamRef.of(value, {
|
|
75
|
+
...(codecId !== undefined ? { codecId } : {}),
|
|
76
|
+
...(refs !== undefined ? { refs } : {}),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Derive `(table, column)` refs from an expression-like value when it carries column-bound metadata. Returns `undefined` for non-column-bound expressions and for raw scalar values.
|
|
82
|
+
*
|
|
83
|
+
* Two sources are consulted, in order: 1. An optional `refs` slot on the `Expression` wrapper (the SQL builder's `ExpressionImpl` records `(table, column)` for top-level fields whose AST is `IdentifierRef` — the AST stays bare to preserve SQL rendering, the metadata lives on the wrapper). 2. The wrapped AST when it's already a `ColumnRef` (the namespaced field-proxy form, or operation impls passing column-bound exprs
|
|
84
|
+
* directly).
|
|
85
|
+
*
|
|
86
|
+
* Operation implementations call this on the column-bound side of a comparison and forward the refs to {@link toExpr} on the user-value side, so the resulting `ParamRef` carries the table+column required by encode-side `forColumn` dispatch.
|
|
87
|
+
*/
|
|
88
|
+
export function refsOf(value: unknown): { table: string; column: string } | undefined {
|
|
89
|
+
if (!isExpressionLike(value)) return undefined;
|
|
90
|
+
const wrapperRefs = (value as { refs?: { table: string; column: string } }).refs;
|
|
91
|
+
if (wrapperRefs) return { table: wrapperRefs.table, column: wrapperRefs.column };
|
|
92
|
+
const ast = value.buildAst();
|
|
93
|
+
if (ast.kind === 'column-ref') {
|
|
94
|
+
return { table: ast.table, column: ast.column };
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isExpressionLike(value: unknown): value is Expression<ScopeField> {
|
|
100
|
+
return (
|
|
101
|
+
typeof value === 'object' &&
|
|
102
|
+
value !== null &&
|
|
103
|
+
'buildAst' in value &&
|
|
104
|
+
typeof (value as { buildAst: unknown }).buildAst === 'function'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface BuildOperationSpec<R extends ScopeField> {
|
|
109
|
+
readonly method: string;
|
|
110
|
+
/**
|
|
111
|
+
* The operation's arguments. The first element is the self argument (the value the operation is being applied to); the rest are the remaining user-supplied arguments.
|
|
112
|
+
*/
|
|
113
|
+
readonly args: readonly [AstExpression, ...AstExpression[]];
|
|
114
|
+
readonly returns: R & ParamSpec;
|
|
115
|
+
readonly lowering: SqlLoweringSpec;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Construct an OperationExpr AST node and wrap it as a typed Expression. Operation implementations use this to turn their user-facing arguments into the AST node the compilation pipeline eventually lowers to SQL.
|
|
120
|
+
*/
|
|
121
|
+
export function buildOperation<R extends ScopeField>(spec: BuildOperationSpec<R>): Expression<R> {
|
|
122
|
+
const [self, ...rest] = spec.args;
|
|
123
|
+
const op = new OperationExpr({
|
|
124
|
+
method: spec.method,
|
|
125
|
+
self,
|
|
126
|
+
args: rest.length > 0 ? rest : undefined,
|
|
127
|
+
returns: spec.returns,
|
|
128
|
+
lowering: spec.lowering,
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
returnType: spec.returns,
|
|
132
|
+
buildAst: () => op,
|
|
133
|
+
};
|
|
134
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type { ParamRefMutator } from '@prisma-next/framework-components/runtime';
|
|
2
|
+
import type { SqlColumnRef } from '../ast/codec-types';
|
|
3
|
+
import type { ParamRef } from '../ast/types';
|
|
4
|
+
import { collectOrderedParamRefs } from '../ast/util';
|
|
5
|
+
import type { SqlExecutionPlan } from '../sql-execution-plan';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Brand applied to {@link ParamRefHandle} so user-constructed handles
|
|
9
|
+
* are rejected by the type system. The mutator only accepts handles it
|
|
10
|
+
* produced from `entries()`.
|
|
11
|
+
*
|
|
12
|
+
* The brand is a phantom type — there is no runtime token. At runtime
|
|
13
|
+
* the handle is the underlying `ParamRef` instance from the plan's
|
|
14
|
+
* `ast`; the brand only narrows the type-level surface so callers
|
|
15
|
+
* cannot fabricate a handle from a fresh `ParamRef.of(...)`.
|
|
16
|
+
*/
|
|
17
|
+
declare const paramRefHandleBrand: unique symbol;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Opaque token identifying a single `ParamRef` in the plan. Produced by
|
|
21
|
+
* {@link SqlParamRefMutator.entries}; consumed by `replaceValue` /
|
|
22
|
+
* `replaceValues`.
|
|
23
|
+
*
|
|
24
|
+
* The phantom `TCodecId` parameter records the codec id of the
|
|
25
|
+
* referenced `ParamRef` so type-level inference can route replacement
|
|
26
|
+
* values through `TCodecMap` to the codec's declared `TInput`.
|
|
27
|
+
*/
|
|
28
|
+
export interface ParamRefHandle<TCodecId extends string | undefined = string | undefined> {
|
|
29
|
+
readonly [paramRefHandleBrand]: TCodecId;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* One outbound `ParamRef` slot in the plan exposed to middleware.
|
|
34
|
+
* `value` is the current value (post any prior middleware mutations);
|
|
35
|
+
* `codecId` is the codec id declared on the underlying `ParamRef`;
|
|
36
|
+
* `column` is populated for `ParamRef`s the lowering site could resolve
|
|
37
|
+
* to a single `(table, column)` via `ParamRef.refs` (encode-side column
|
|
38
|
+
* metadata is the middleware's domain — encode itself currently leaves
|
|
39
|
+
* `ctx.column` unset).
|
|
40
|
+
*/
|
|
41
|
+
export interface ParamRefEntry<TCodecId extends string | undefined = string | undefined> {
|
|
42
|
+
readonly ref: ParamRefHandle<TCodecId>;
|
|
43
|
+
readonly value: unknown;
|
|
44
|
+
readonly codecId: TCodecId;
|
|
45
|
+
readonly column?: SqlColumnRef;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Discriminated entry union over a codec map. For each `K` in
|
|
50
|
+
* `TCodecMap`, `entries()` may yield a `ParamRefEntry<K>`; ParamRefs
|
|
51
|
+
* with no codec id (or a codec id outside the map) yield a
|
|
52
|
+
* `ParamRefEntry<undefined>`. Pattern-matching on `entry.codecId`
|
|
53
|
+
* narrows `entry.ref` to a `ParamRefHandle<K>`, which routes through
|
|
54
|
+
* the typed `replaceValue` overload.
|
|
55
|
+
*/
|
|
56
|
+
export type ParamRefEntryUnion<TCodecMap extends Record<string, unknown>> =
|
|
57
|
+
| { [K in keyof TCodecMap & string]: ParamRefEntry<K> }[keyof TCodecMap & string]
|
|
58
|
+
| ParamRefEntry<undefined>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* SQL-family mutator threaded into `SqlMiddleware.beforeExecute` as
|
|
62
|
+
* `params`. Scope is `ParamRef.value` slots only — middleware cannot
|
|
63
|
+
* insert / remove `ParamRef`s, rewrite SQL, or modify projection. The
|
|
64
|
+
* type-level `ParamRefHandle` brand and the `replaceValue(ref,
|
|
65
|
+
* newValue)` shape enforce this at compile time.
|
|
66
|
+
*
|
|
67
|
+
* Allocation discipline: the mutator is constructed lazily from the
|
|
68
|
+
* plan. `entries()` walks the plan's existing AST without allocating
|
|
69
|
+
* an intermediate array; the working params buffer is only allocated
|
|
70
|
+
* on the first `replaceValue` / `replaceValues` call. If no middleware
|
|
71
|
+
* mutates, `currentParams()` returns the plan's original `params` by
|
|
72
|
+
* reference identity.
|
|
73
|
+
*
|
|
74
|
+
* The `TCodecMap` parameter is a record keyed by codec id; `replaceValue`
|
|
75
|
+
* infers `newValue` from `TCodecMap[H['codecId']]` for handles whose
|
|
76
|
+
* codec id is statically resolvable. For codec ids the type system
|
|
77
|
+
* cannot resolve, `newValue` falls back to `unknown` and the middleware
|
|
78
|
+
* is on the hook for runtime correctness.
|
|
79
|
+
*/
|
|
80
|
+
export interface SqlParamRefMutator<
|
|
81
|
+
TCodecMap extends Record<string, unknown> = Record<string, unknown>,
|
|
82
|
+
> extends ParamRefMutator {
|
|
83
|
+
/** Iterate every outbound `ParamRef` the plan currently carries, in canonical order. */
|
|
84
|
+
entries(): IterableIterator<ParamRefEntryUnion<TCodecMap>>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Replace one `ParamRef`'s value with the result of bulk processing.
|
|
88
|
+
* `newValue` is constrained to the codec's declared `TInput` for codec
|
|
89
|
+
* ids the type system can resolve via `TCodecMap`; for unresolvable
|
|
90
|
+
* codec ids `newValue` is `unknown` (the second overload).
|
|
91
|
+
*/
|
|
92
|
+
replaceValue<TCodecId extends keyof TCodecMap & string>(
|
|
93
|
+
ref: ParamRefHandle<TCodecId>,
|
|
94
|
+
newValue: TCodecMap[TCodecId],
|
|
95
|
+
): void;
|
|
96
|
+
replaceValue(ref: ParamRefHandle<undefined>, newValue: unknown): void;
|
|
97
|
+
|
|
98
|
+
/** Replace many at once (typical for bulk-pattern middleware). */
|
|
99
|
+
replaceValues(
|
|
100
|
+
updates: Iterable<{
|
|
101
|
+
readonly ref: ParamRefHandle<(keyof TCodecMap & string) | undefined>;
|
|
102
|
+
readonly newValue: unknown;
|
|
103
|
+
}>,
|
|
104
|
+
): void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Internal-only view of the mutator that exposes the post-mutation params
|
|
109
|
+
* array to the SQL runtime. The runtime calls `currentParams()` after the
|
|
110
|
+
* `beforeExecute` chain has run; the result is the plan's original
|
|
111
|
+
* `params` by reference identity if no middleware mutated, otherwise a
|
|
112
|
+
* frozen new array carrying the mutations applied in chain order.
|
|
113
|
+
*
|
|
114
|
+
* Family-internal contract — `SqlMiddleware` consumers never see this
|
|
115
|
+
* shape; they receive the public `SqlParamRefMutator` view above.
|
|
116
|
+
*/
|
|
117
|
+
export interface SqlParamRefMutatorInternal<
|
|
118
|
+
TCodecMap extends Record<string, unknown> = Record<string, unknown>,
|
|
119
|
+
> extends SqlParamRefMutator<TCodecMap> {
|
|
120
|
+
currentParams(): readonly unknown[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type AnyHandle = ParamRefHandle<string | undefined>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build a {@link SqlParamRefMutatorInternal} for the given lowered plan.
|
|
127
|
+
*
|
|
128
|
+
* The mutator captures `plan.params` by reference and walks
|
|
129
|
+
* `plan.ast` (via `collectOrderedParamRefs`) on demand to build
|
|
130
|
+
* entries. Mutations write to a lazily-allocated working copy so the
|
|
131
|
+
* fast path (no mutation) preserves bit-for-bit reference identity to
|
|
132
|
+
* the original `plan.params`.
|
|
133
|
+
*
|
|
134
|
+
* Threading: `plan.ast` carries the canonical `ParamRef` ordering used
|
|
135
|
+
* by every consumer (renderer's `$N` index map, encode-side metadata
|
|
136
|
+
* walk, etc.). The mutator's `entries()` yields the same order so
|
|
137
|
+
* middleware that filters by codec id sees ParamRefs in the order the
|
|
138
|
+
* runtime will encode them.
|
|
139
|
+
*/
|
|
140
|
+
export function createSqlParamRefMutator<
|
|
141
|
+
TCodecMap extends Record<string, unknown> = Record<string, unknown>,
|
|
142
|
+
>(plan: SqlExecutionPlan): SqlParamRefMutatorInternal<TCodecMap> {
|
|
143
|
+
const originalParams = plan.params;
|
|
144
|
+
const refs: ReadonlyArray<ParamRef> = plan.ast ? collectOrderedParamRefs(plan.ast) : [];
|
|
145
|
+
let workingParams: unknown[] | undefined;
|
|
146
|
+
|
|
147
|
+
const indexOfRef = (handle: AnyHandle): number => {
|
|
148
|
+
// The handle is the underlying ParamRef instance the mutator yielded
|
|
149
|
+
// from entries(); equality is identity equality on the ParamRef. The
|
|
150
|
+
// brand on ParamRefHandle is unforgeable from outside, so the only
|
|
151
|
+
// legal handles came from this mutator's entries().
|
|
152
|
+
return refs.indexOf(handle as unknown as ParamRef);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const ensureWorkingParams = (): unknown[] => {
|
|
156
|
+
if (!workingParams) {
|
|
157
|
+
workingParams = [...originalParams];
|
|
158
|
+
}
|
|
159
|
+
return workingParams;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const writeAt = (index: number, value: unknown): void => {
|
|
163
|
+
const buffer = ensureWorkingParams();
|
|
164
|
+
buffer[index] = value;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function* entries(): IterableIterator<ParamRefEntryUnion<TCodecMap>> {
|
|
168
|
+
const view = workingParams ?? originalParams;
|
|
169
|
+
for (let i = 0; i < refs.length; i++) {
|
|
170
|
+
const ref = refs[i];
|
|
171
|
+
if (!ref) continue;
|
|
172
|
+
const handle = ref as unknown as ParamRefHandle<string | undefined>;
|
|
173
|
+
const value = i < view.length ? view[i] : ref.value;
|
|
174
|
+
const codecId = ref.codecId;
|
|
175
|
+
// Surface the column binding the lowering site resolved onto
|
|
176
|
+
// `ParamRef.refs` so column-aware middleware (e.g. cipherstash
|
|
177
|
+
// routing-key resolution) can address the cell without
|
|
178
|
+
// re-walking the AST. ParamRefs without a column binding stay
|
|
179
|
+
// `column: undefined`.
|
|
180
|
+
const column = ref.refs
|
|
181
|
+
? ({ table: ref.refs.table, name: ref.refs.column } as const)
|
|
182
|
+
: undefined;
|
|
183
|
+
// The runtime erases the discriminated union to a single shape; the
|
|
184
|
+
// public type pins each entry's `ref` to the matching `codecId`
|
|
185
|
+
// arm at compile time.
|
|
186
|
+
const entry: ParamRefEntry<string | undefined> = column
|
|
187
|
+
? { ref: handle, value, codecId, column }
|
|
188
|
+
: { ref: handle, value, codecId };
|
|
189
|
+
yield entry as ParamRefEntryUnion<TCodecMap>;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function replaceValue(handle: AnyHandle, newValue: unknown): void {
|
|
194
|
+
const index = indexOfRef(handle);
|
|
195
|
+
if (index < 0) {
|
|
196
|
+
// Handle does not belong to this plan. The type system pins this
|
|
197
|
+
// at the brand level; this runtime check guards against handles
|
|
198
|
+
// smuggled across plans.
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
writeAt(index, newValue);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function replaceValues(
|
|
205
|
+
updates: Iterable<{ readonly ref: AnyHandle; readonly newValue: unknown }>,
|
|
206
|
+
): void {
|
|
207
|
+
for (const { ref, newValue } of updates) {
|
|
208
|
+
const index = indexOfRef(ref);
|
|
209
|
+
if (index < 0) continue;
|
|
210
|
+
writeAt(index, newValue);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// The public `SqlParamRefMutator` declares overloaded `replaceValue`
|
|
215
|
+
// signatures (typed-by-codec / unresolvable-codec). The implementation
|
|
216
|
+
// is one function with a permissive runtime signature; the cast is the
|
|
217
|
+
// single point at which the runtime function meets the typed overload
|
|
218
|
+
// surface, matching the overload-implementation pattern.
|
|
219
|
+
return {
|
|
220
|
+
entries,
|
|
221
|
+
replaceValue: replaceValue as SqlParamRefMutator<TCodecMap>['replaceValue'],
|
|
222
|
+
replaceValues,
|
|
223
|
+
currentParams(): readonly unknown[] {
|
|
224
|
+
if (!workingParams) {
|
|
225
|
+
return originalParams;
|
|
226
|
+
}
|
|
227
|
+
return Object.freeze([...workingParams]);
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
package/src/plan.ts
CHANGED
|
@@ -1,39 +1,56 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { QueryPlan } from '@prisma-next/framework-components/runtime';
|
|
3
|
+
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
3
4
|
import type { AnyQueryAst } from './ast/types';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* SQL query plan produced by lanes before lowering.
|
|
7
8
|
*
|
|
8
|
-
* Lanes build ASTs and metadata but do not perform SQL lowering.
|
|
9
|
-
*
|
|
9
|
+
* Lanes build ASTs and metadata but do not perform SQL lowering. The `sql`
|
|
10
|
+
* field is absent — `RuntimeCore` (the runtime base class in
|
|
11
|
+
* `@prisma-next/framework-components/runtime`) drives lowering via the
|
|
12
|
+
* SQL adapter and produces a `SqlExecutionPlan`.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* Extends the framework-level `QueryPlan<Row>` marker (`meta + _row`) and
|
|
15
|
+
* adds SQL-specific fields (`ast`, `params`). The phantom `_row` property
|
|
16
|
+
* (inherited from `QueryPlan`) is what `ResultType<P>` inspects to recover
|
|
17
|
+
* the row type.
|
|
14
18
|
*/
|
|
15
|
-
export interface SqlQueryPlan<
|
|
16
|
-
extends Pick<ExecutionPlan<_Row, AnyQueryAst>, 'params' | 'meta'> {
|
|
19
|
+
export interface SqlQueryPlan<Row = unknown> extends QueryPlan<Row> {
|
|
17
20
|
readonly ast: AnyQueryAst;
|
|
18
|
-
|
|
19
|
-
// This allows ResultType to extract _Row for SqlQueryPlan values.
|
|
20
|
-
readonly _Row?: _Row;
|
|
21
|
+
readonly params: readonly unknown[];
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
25
|
+
* Wraps an `AnyQueryAst` (typically a `RawSqlExpr` constructed package-internally
|
|
26
|
+
* by an extension's migration factory) in a fully-populated `SqlQueryPlan`
|
|
27
|
+
* whose `meta` is sourced from the supplied contract.
|
|
28
|
+
*
|
|
29
|
+
* Centralising the envelope here means consumers (cipherstash migration
|
|
30
|
+
* factories today; future raw-sql callers) cannot drift on `storageHash` /
|
|
31
|
+
* `target` / `targetFamily`, which would otherwise surface as a subtle
|
|
32
|
+
* `assertContractMatches` failure inside `dataTransform`. `params` defaults
|
|
33
|
+
* to `[]` because parameters embedded in the AST as `ParamRef`s are resolved
|
|
34
|
+
* at lowering time (`encodeParams` walks `plan.ast.collectParamRefs()`),
|
|
35
|
+
* not at plan-construction time.
|
|
36
|
+
*
|
|
37
|
+
* The default `laneId` of `'raw'` reflects raw-SQL plans' standard lane tag;
|
|
38
|
+
* callers (e.g. a future `sql-raw-factory`) may override to differentiate
|
|
39
|
+
* the plan's provenance.
|
|
26
40
|
*/
|
|
27
|
-
export function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
export function planFromAst<Row = unknown>(
|
|
42
|
+
ast: AnyQueryAst,
|
|
43
|
+
contract: Contract<SqlStorage>,
|
|
44
|
+
laneId = 'raw',
|
|
45
|
+
): SqlQueryPlan<Row> {
|
|
46
|
+
return {
|
|
47
|
+
ast,
|
|
48
|
+
params: [],
|
|
49
|
+
meta: {
|
|
50
|
+
target: contract.target,
|
|
51
|
+
targetFamily: contract.targetFamily,
|
|
52
|
+
storageHash: contract.storage.storageHash,
|
|
53
|
+
lane: laneId,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
39
56
|
}
|