@prisma-next/sql-relational-core 0.5.0-dev.60 → 0.5.0-dev.61
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 +45 -47
- package/dist/{errors-p3Ou_n9J.d.mts → errors-Chs-ph28.d.mts} +2 -2
- package/dist/errors-Chs-ph28.d.mts.map +1 -0
- package/dist/{errors-D6kqqjHM.mjs → errors-kgKOaDM1.mjs} +1 -1
- package/dist/{errors-D6kqqjHM.mjs.map → errors-kgKOaDM1.mjs.map} +1 -1
- package/dist/exports/ast.d.mts +141 -87
- package/dist/exports/ast.d.mts.map +1 -1
- package/dist/exports/ast.mjs +242 -272
- 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 +4 -4
- package/dist/exports/errors.mjs +1 -1
- package/dist/exports/expression.d.mts +29 -24
- package/dist/exports/expression.d.mts.map +1 -1
- package/dist/exports/expression.mjs +33 -11
- package/dist/exports/expression.mjs.map +1 -1
- package/dist/exports/plan.d.mts +2 -2
- package/dist/exports/query-lane-context.d.mts +2 -3
- package/dist/exports/types.d.mts +5 -4
- package/dist/index.d.mts +10 -11
- package/dist/index.mjs +5 -5
- package/dist/{plan-C7SiEWkN.d.mts → plan-nwFE15re.d.mts} +2 -2
- package/dist/plan-nwFE15re.d.mts.map +1 -0
- package/dist/query-lane-context-DlWgKvvt.d.mts +175 -0
- package/dist/query-lane-context-DlWgKvvt.d.mts.map +1 -0
- package/dist/{sql-execution-plan-Dgx7BGin.d.mts → sql-execution-plan-DTfj23Tj.d.mts} +2 -2
- package/dist/{sql-execution-plan-Dgx7BGin.d.mts.map → sql-execution-plan-DTfj23Tj.d.mts.map} +1 -1
- package/dist/{types-DUL-3vy6.mjs → types-CO7zrXfK.mjs} +15 -7
- package/dist/types-CO7zrXfK.mjs.map +1 -0
- package/dist/{types-DviRR7AL.d.mts → types-G3hdNPZZ.d.mts} +4 -4
- package/dist/{types-DviRR7AL.d.mts.map → types-G3hdNPZZ.d.mts.map} +1 -1
- package/dist/{types-B4dL4lc3.d.mts → types-U74HFwNI.d.mts} +22 -4
- package/dist/types-U74HFwNI.d.mts.map +1 -0
- package/dist/{types-BUlUvdIU.d.mts → types-dPxXIUPS.d.mts} +3 -3
- package/dist/{types-BUlUvdIU.d.mts.map → types-dPxXIUPS.d.mts.map} +1 -1
- package/package.json +10 -8
- package/src/ast/adapter-types.ts +3 -19
- package/src/ast/codec-types.ts +53 -541
- package/src/ast/sql-codec-helpers.ts +79 -0
- package/src/ast/sql-codecs.ts +280 -137
- package/src/ast/types.ts +33 -8
- 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/expression.ts +40 -23
- package/src/query-lane-context.ts +14 -96
- package/dist/codec-types-DJEaWT36.d.mts +0 -313
- package/dist/codec-types-DJEaWT36.d.mts.map +0 -1
- package/dist/errors-p3Ou_n9J.d.mts.map +0 -1
- package/dist/plan-C7SiEWkN.d.mts.map +0 -1
- package/dist/query-lane-context-Bwca4sc8.d.mts +0 -150
- package/dist/query-lane-context-Bwca4sc8.d.mts.map +0 -1
- package/dist/types-B4dL4lc3.d.mts.map +0 -1
- package/dist/types-DUL-3vy6.mjs.map +0 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared encode/decode/render constants and codec id literals for the six SQL base codecs (`sql/char@1`, `sql/varchar@1`, `sql/int@1`, `sql/float@1`, `sql/text@1`, `sql/timestamp@1`).
|
|
3
|
+
*
|
|
4
|
+
* The codec implementations live in `sql-codecs.ts` (TML-2357). This module retains only the conversion helpers + emit-path renderers the codec methods compose with — keeping a single source of truth for non-trivial conversions while the codec methods provide the framework-required `Promise<…>` boundary.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { JsonValue } from '@prisma-next/contract/types';
|
|
8
|
+
|
|
9
|
+
export const SQL_CHAR_CODEC_ID = 'sql/char@1' as const;
|
|
10
|
+
export const SQL_VARCHAR_CODEC_ID = 'sql/varchar@1' as const;
|
|
11
|
+
export const SQL_INT_CODEC_ID = 'sql/int@1' as const;
|
|
12
|
+
export const SQL_FLOAT_CODEC_ID = 'sql/float@1' as const;
|
|
13
|
+
export const SQL_TEXT_CODEC_ID = 'sql/text@1' as const;
|
|
14
|
+
export const SQL_TIMESTAMP_CODEC_ID = 'sql/timestamp@1' as const;
|
|
15
|
+
|
|
16
|
+
export const sqlCharEncode = (value: string): string => value;
|
|
17
|
+
export const sqlCharDecode = (wire: string): string => wire.trimEnd();
|
|
18
|
+
export const sqlCharRenderOutputType = (typeParams: { readonly length?: number }) => {
|
|
19
|
+
const length = typeParams.length;
|
|
20
|
+
if (length === undefined) return undefined;
|
|
21
|
+
if (typeof length !== 'number' || !Number.isFinite(length) || !Number.isInteger(length)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`renderOutputType: expected integer "length" in typeParams for Char, got ${String(length)}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return `Char<${length}>`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const sqlVarcharEncode = (value: string): string => value;
|
|
30
|
+
export const sqlVarcharDecode = (wire: string): string => wire;
|
|
31
|
+
export const sqlVarcharRenderOutputType = (typeParams: { readonly length?: number }) => {
|
|
32
|
+
const length = typeParams.length;
|
|
33
|
+
if (length === undefined) return undefined;
|
|
34
|
+
if (typeof length !== 'number' || !Number.isFinite(length) || !Number.isInteger(length)) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`renderOutputType: expected integer "length" in typeParams for Varchar, got ${String(length)}`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return `Varchar<${length}>`;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const sqlIntEncode = (value: number): number => value;
|
|
43
|
+
export const sqlIntDecode = (wire: number): number => wire;
|
|
44
|
+
|
|
45
|
+
export const sqlFloatEncode = (value: number): number => value;
|
|
46
|
+
export const sqlFloatDecode = (wire: number): number => wire;
|
|
47
|
+
|
|
48
|
+
export const sqlTextEncode = (value: string): string => value;
|
|
49
|
+
export const sqlTextDecode = (wire: string): string => wire;
|
|
50
|
+
|
|
51
|
+
export const sqlTimestampEncode = (value: Date): Date => value;
|
|
52
|
+
export const sqlTimestampDecode = (wire: Date): Date => wire;
|
|
53
|
+
export const sqlTimestampEncodeJson = (value: Date): JsonValue => value.toISOString();
|
|
54
|
+
export const sqlTimestampDecodeJson = (json: JsonValue): Date => {
|
|
55
|
+
if (typeof json !== 'string') {
|
|
56
|
+
throw new Error(`Expected ISO date string for sql/timestamp@1, got ${typeof json}`);
|
|
57
|
+
}
|
|
58
|
+
const date = new Date(json);
|
|
59
|
+
if (Number.isNaN(date.getTime())) {
|
|
60
|
+
throw new Error(`Invalid ISO date string for sql/timestamp@1: ${json}`);
|
|
61
|
+
}
|
|
62
|
+
return date;
|
|
63
|
+
};
|
|
64
|
+
export const sqlTimestampRenderOutputType = (typeParams: { readonly precision?: number }) => {
|
|
65
|
+
const precision = typeParams.precision;
|
|
66
|
+
if (precision === undefined) {
|
|
67
|
+
return 'Timestamp';
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
typeof precision !== 'number' ||
|
|
71
|
+
!Number.isFinite(precision) ||
|
|
72
|
+
!Number.isInteger(precision)
|
|
73
|
+
) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`renderOutputType: expected integer "precision" in typeParams for Timestamp, got ${String(precision)}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return `Timestamp<${precision}>`;
|
|
79
|
+
};
|
package/src/ast/sql-codecs.ts
CHANGED
|
@@ -1,159 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six SQL base codecs (TML-2357).
|
|
3
|
+
*
|
|
4
|
+
* Each codec ships as three artifacts:
|
|
5
|
+
*
|
|
6
|
+
* 1. A `SqlXCodec` class extending {@link CodecImpl} that wraps the module-level encode/decode constants exported from `sql-codec-helpers.ts` (the single source of truth for runtime behaviour). 2. A `SqlXDescriptor` class extending {@link CodecDescriptorImpl} declaring the codec id, traits, target types, params schema, and (where applicable) the emit-path `renderOutputType`. 3. A per-codec column helper (`sqlXColumn`)
|
|
7
|
+
* that calls `descriptor.factory(...)` directly and packages the result into a {@link ColumnSpec} via the framework {@link column} packager. The helper is tied to its descriptor with `satisfies ColumnHelperFor`.
|
|
8
|
+
*
|
|
9
|
+
* After TML-2357 this file is the canonical source of SQL base codec metadata and runtime behaviour — the legacy `mkCodec` / `defineCodec` carriers retired with the deletion sweep.
|
|
10
|
+
*/
|
|
11
|
+
|
|
1
12
|
import type { JsonValue } from '@prisma-next/contract/types';
|
|
13
|
+
import {
|
|
14
|
+
type CodecCallContext,
|
|
15
|
+
CodecDescriptorImpl,
|
|
16
|
+
CodecImpl,
|
|
17
|
+
type CodecInstanceContext,
|
|
18
|
+
type ColumnHelperFor,
|
|
19
|
+
type ColumnHelperForStrict,
|
|
20
|
+
column,
|
|
21
|
+
voidParamsSchema,
|
|
22
|
+
} from '@prisma-next/framework-components/codec';
|
|
23
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
24
|
import { type as arktype } from 'arktype';
|
|
3
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
SQL_CHAR_CODEC_ID,
|
|
27
|
+
SQL_FLOAT_CODEC_ID,
|
|
28
|
+
SQL_INT_CODEC_ID,
|
|
29
|
+
SQL_TEXT_CODEC_ID,
|
|
30
|
+
SQL_TIMESTAMP_CODEC_ID,
|
|
31
|
+
SQL_VARCHAR_CODEC_ID,
|
|
32
|
+
sqlCharDecode,
|
|
33
|
+
sqlCharEncode,
|
|
34
|
+
sqlCharRenderOutputType,
|
|
35
|
+
sqlFloatDecode,
|
|
36
|
+
sqlFloatEncode,
|
|
37
|
+
sqlIntDecode,
|
|
38
|
+
sqlIntEncode,
|
|
39
|
+
sqlTextDecode,
|
|
40
|
+
sqlTextEncode,
|
|
41
|
+
sqlTimestampDecode,
|
|
42
|
+
sqlTimestampDecodeJson,
|
|
43
|
+
sqlTimestampEncode,
|
|
44
|
+
sqlTimestampEncodeJson,
|
|
45
|
+
sqlTimestampRenderOutputType,
|
|
46
|
+
sqlVarcharDecode,
|
|
47
|
+
sqlVarcharEncode,
|
|
48
|
+
sqlVarcharRenderOutputType,
|
|
49
|
+
} from './sql-codec-helpers';
|
|
4
50
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export const SQL_INT_CODEC_ID = 'sql/int@1' as const;
|
|
8
|
-
export const SQL_FLOAT_CODEC_ID = 'sql/float@1' as const;
|
|
9
|
-
export const SQL_TEXT_CODEC_ID = 'sql/text@1' as const;
|
|
10
|
-
export const SQL_TIMESTAMP_CODEC_ID = 'sql/timestamp@1' as const;
|
|
51
|
+
type LengthParams = { readonly length?: number };
|
|
52
|
+
type PrecisionParams = { readonly precision?: number };
|
|
11
53
|
|
|
12
54
|
const lengthParamsSchema = arktype({
|
|
13
|
-
length: 'number.integer > 0',
|
|
14
|
-
})
|
|
55
|
+
'length?': 'number.integer > 0',
|
|
56
|
+
}) satisfies StandardSchemaV1<LengthParams>;
|
|
15
57
|
|
|
16
58
|
const precisionParamsSchema = arktype({
|
|
17
59
|
'precision?': 'number.integer >= 0 & number.integer <= 6',
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
readonly
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
60
|
+
}) satisfies StandardSchemaV1<PrecisionParams>;
|
|
61
|
+
|
|
62
|
+
export class SqlTextCodec extends CodecImpl<
|
|
63
|
+
typeof SQL_TEXT_CODEC_ID,
|
|
64
|
+
readonly ['equality', 'order', 'textual'],
|
|
65
|
+
string,
|
|
66
|
+
string
|
|
67
|
+
> {
|
|
68
|
+
async encode(value: string, _ctx: CodecCallContext): Promise<string> {
|
|
69
|
+
return sqlTextEncode(value);
|
|
70
|
+
}
|
|
71
|
+
async decode(wire: string, _ctx: CodecCallContext): Promise<string> {
|
|
72
|
+
return sqlTextDecode(wire);
|
|
73
|
+
}
|
|
74
|
+
encodeJson(value: string): JsonValue {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
decodeJson(json: JsonValue): string {
|
|
78
|
+
return json as string;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class SqlTextDescriptor extends CodecDescriptorImpl<void> {
|
|
83
|
+
override readonly codecId = SQL_TEXT_CODEC_ID;
|
|
84
|
+
override readonly traits = ['equality', 'order', 'textual'] as const;
|
|
85
|
+
override readonly targetTypes = ['text'] as const;
|
|
86
|
+
override readonly paramsSchema: StandardSchemaV1<void> = voidParamsSchema;
|
|
87
|
+
override factory(): (ctx: CodecInstanceContext) => SqlTextCodec {
|
|
88
|
+
return () => new SqlTextCodec(this);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const sqlTextDescriptor = new SqlTextDescriptor();
|
|
93
|
+
|
|
94
|
+
export const sqlTextColumn = () =>
|
|
95
|
+
column(sqlTextDescriptor.factory(), sqlTextDescriptor.codecId, undefined, 'text');
|
|
96
|
+
|
|
97
|
+
sqlTextColumn satisfies ColumnHelperFor<SqlTextDescriptor>;
|
|
98
|
+
sqlTextColumn satisfies ColumnHelperForStrict<SqlTextDescriptor>;
|
|
99
|
+
|
|
100
|
+
export class SqlIntCodec extends CodecImpl<
|
|
101
|
+
typeof SQL_INT_CODEC_ID,
|
|
102
|
+
readonly ['equality', 'order', 'numeric'],
|
|
103
|
+
number,
|
|
104
|
+
number
|
|
105
|
+
> {
|
|
106
|
+
async encode(value: number, _ctx: CodecCallContext): Promise<number> {
|
|
107
|
+
return sqlIntEncode(value);
|
|
108
|
+
}
|
|
109
|
+
async decode(wire: number, _ctx: CodecCallContext): Promise<number> {
|
|
110
|
+
return sqlIntDecode(wire);
|
|
111
|
+
}
|
|
112
|
+
encodeJson(value: number): JsonValue {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
decodeJson(json: JsonValue): number {
|
|
116
|
+
return json as number;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class SqlIntDescriptor extends CodecDescriptorImpl<void> {
|
|
121
|
+
override readonly codecId = SQL_INT_CODEC_ID;
|
|
122
|
+
override readonly traits = ['equality', 'order', 'numeric'] as const;
|
|
123
|
+
override readonly targetTypes = ['int'] as const;
|
|
124
|
+
override readonly paramsSchema: StandardSchemaV1<void> = voidParamsSchema;
|
|
125
|
+
override factory(): (ctx: CodecInstanceContext) => SqlIntCodec {
|
|
126
|
+
return () => new SqlIntCodec(this);
|
|
127
|
+
}
|
|
32
128
|
}
|
|
33
129
|
|
|
34
|
-
const
|
|
130
|
+
export const sqlIntDescriptor = new SqlIntDescriptor();
|
|
131
|
+
|
|
132
|
+
export const sqlIntColumn = () =>
|
|
133
|
+
column(sqlIntDescriptor.factory(), sqlIntDescriptor.codecId, undefined, 'int');
|
|
134
|
+
|
|
135
|
+
sqlIntColumn satisfies ColumnHelperFor<SqlIntDescriptor>;
|
|
136
|
+
sqlIntColumn satisfies ColumnHelperForStrict<SqlIntDescriptor>;
|
|
137
|
+
|
|
138
|
+
export class SqlFloatCodec extends CodecImpl<
|
|
139
|
+
typeof SQL_FLOAT_CODEC_ID,
|
|
140
|
+
readonly ['equality', 'order', 'numeric'],
|
|
141
|
+
number,
|
|
142
|
+
number
|
|
143
|
+
> {
|
|
144
|
+
async encode(value: number, _ctx: CodecCallContext): Promise<number> {
|
|
145
|
+
return sqlFloatEncode(value);
|
|
146
|
+
}
|
|
147
|
+
async decode(wire: number, _ctx: CodecCallContext): Promise<number> {
|
|
148
|
+
return sqlFloatDecode(wire);
|
|
149
|
+
}
|
|
150
|
+
encodeJson(value: number): JsonValue {
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
decodeJson(json: JsonValue): number {
|
|
154
|
+
return json as number;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export class SqlFloatDescriptor extends CodecDescriptorImpl<void> {
|
|
159
|
+
override readonly codecId = SQL_FLOAT_CODEC_ID;
|
|
160
|
+
override readonly traits = ['equality', 'order', 'numeric'] as const;
|
|
161
|
+
override readonly targetTypes = ['float'] as const;
|
|
162
|
+
override readonly paramsSchema: StandardSchemaV1<void> = voidParamsSchema;
|
|
163
|
+
override factory(): (ctx: CodecInstanceContext) => SqlFloatCodec {
|
|
164
|
+
return () => new SqlFloatCodec(this);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const sqlFloatDescriptor = new SqlFloatDescriptor();
|
|
169
|
+
|
|
170
|
+
export const sqlFloatColumn = () =>
|
|
171
|
+
column(sqlFloatDescriptor.factory(), sqlFloatDescriptor.codecId, undefined, 'float');
|
|
172
|
+
|
|
173
|
+
sqlFloatColumn satisfies ColumnHelperFor<SqlFloatDescriptor>;
|
|
174
|
+
sqlFloatColumn satisfies ColumnHelperForStrict<SqlFloatDescriptor>;
|
|
175
|
+
|
|
176
|
+
export class SqlCharCodec extends CodecImpl<
|
|
35
177
|
typeof SQL_CHAR_CODEC_ID,
|
|
36
178
|
readonly ['equality', 'order', 'textual'],
|
|
37
179
|
string,
|
|
38
180
|
string
|
|
39
|
-
>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
181
|
+
> {
|
|
182
|
+
async encode(value: string, _ctx: CodecCallContext): Promise<string> {
|
|
183
|
+
return sqlCharEncode(value);
|
|
184
|
+
}
|
|
185
|
+
async decode(wire: string, _ctx: CodecCallContext): Promise<string> {
|
|
186
|
+
return sqlCharDecode(wire);
|
|
187
|
+
}
|
|
188
|
+
encodeJson(value: string): JsonValue {
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
decodeJson(json: JsonValue): string {
|
|
192
|
+
return json as string;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class SqlCharDescriptor extends CodecDescriptorImpl<LengthParams> {
|
|
197
|
+
override readonly codecId = SQL_CHAR_CODEC_ID;
|
|
198
|
+
override readonly traits = ['equality', 'order', 'textual'] as const;
|
|
199
|
+
override readonly targetTypes = ['char'] as const;
|
|
200
|
+
override readonly paramsSchema: StandardSchemaV1<LengthParams> = lengthParamsSchema;
|
|
201
|
+
override renderOutputType(params: LengthParams): string | undefined {
|
|
202
|
+
return sqlCharRenderOutputType(params);
|
|
203
|
+
}
|
|
204
|
+
override factory(_params: LengthParams): (ctx: CodecInstanceContext) => SqlCharCodec {
|
|
205
|
+
return () => new SqlCharCodec(this);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export const sqlCharDescriptor = new SqlCharDescriptor();
|
|
210
|
+
|
|
211
|
+
export const sqlCharColumn = (params: LengthParams = {}) =>
|
|
212
|
+
column(sqlCharDescriptor.factory(params), sqlCharDescriptor.codecId, params, 'char');
|
|
213
|
+
|
|
214
|
+
sqlCharColumn satisfies ColumnHelperFor<SqlCharDescriptor>;
|
|
215
|
+
sqlCharColumn satisfies ColumnHelperForStrict<SqlCharDescriptor>;
|
|
216
|
+
|
|
217
|
+
export class SqlVarcharCodec extends CodecImpl<
|
|
60
218
|
typeof SQL_VARCHAR_CODEC_ID,
|
|
61
219
|
readonly ['equality', 'order', 'textual'],
|
|
62
220
|
string,
|
|
63
221
|
string
|
|
64
|
-
>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
typeId: SQL_TEXT_CODEC_ID,
|
|
102
|
-
targetTypes: ['text'],
|
|
103
|
-
traits: ['equality', 'order', 'textual'],
|
|
104
|
-
encode: (value: string): string => value,
|
|
105
|
-
decode: (wire: string): string => wire,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const sqlTimestampCodec = codec<
|
|
222
|
+
> {
|
|
223
|
+
async encode(value: string, _ctx: CodecCallContext): Promise<string> {
|
|
224
|
+
return sqlVarcharEncode(value);
|
|
225
|
+
}
|
|
226
|
+
async decode(wire: string, _ctx: CodecCallContext): Promise<string> {
|
|
227
|
+
return sqlVarcharDecode(wire);
|
|
228
|
+
}
|
|
229
|
+
encodeJson(value: string): JsonValue {
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
decodeJson(json: JsonValue): string {
|
|
233
|
+
return json as string;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export class SqlVarcharDescriptor extends CodecDescriptorImpl<LengthParams> {
|
|
238
|
+
override readonly codecId = SQL_VARCHAR_CODEC_ID;
|
|
239
|
+
override readonly traits = ['equality', 'order', 'textual'] as const;
|
|
240
|
+
override readonly targetTypes = ['varchar'] as const;
|
|
241
|
+
override readonly paramsSchema: StandardSchemaV1<LengthParams> = lengthParamsSchema;
|
|
242
|
+
override renderOutputType(params: LengthParams): string | undefined {
|
|
243
|
+
return sqlVarcharRenderOutputType(params);
|
|
244
|
+
}
|
|
245
|
+
override factory(_params: LengthParams): (ctx: CodecInstanceContext) => SqlVarcharCodec {
|
|
246
|
+
return () => new SqlVarcharCodec(this);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export const sqlVarcharDescriptor = new SqlVarcharDescriptor();
|
|
251
|
+
|
|
252
|
+
export const sqlVarcharColumn = (params: LengthParams = {}) =>
|
|
253
|
+
column(sqlVarcharDescriptor.factory(params), sqlVarcharDescriptor.codecId, params, 'varchar');
|
|
254
|
+
|
|
255
|
+
sqlVarcharColumn satisfies ColumnHelperFor<SqlVarcharDescriptor>;
|
|
256
|
+
sqlVarcharColumn satisfies ColumnHelperForStrict<SqlVarcharDescriptor>;
|
|
257
|
+
|
|
258
|
+
export class SqlTimestampCodec extends CodecImpl<
|
|
109
259
|
typeof SQL_TIMESTAMP_CODEC_ID,
|
|
110
260
|
readonly ['equality', 'order'],
|
|
111
261
|
Date,
|
|
112
262
|
Date
|
|
113
|
-
>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
.add('float', sqlFloatCodec)
|
|
154
|
-
.add('text', sqlTextCodec)
|
|
155
|
-
.add('timestamp', sqlTimestampCodec);
|
|
156
|
-
|
|
157
|
-
export const sqlCodecDefinitions = codecs.codecDefinitions;
|
|
158
|
-
export const sqlDataTypes = codecs.dataTypes;
|
|
159
|
-
export type SqlCodecTypes = typeof codecs.CodecTypes;
|
|
263
|
+
> {
|
|
264
|
+
async encode(value: Date, _ctx: CodecCallContext): Promise<Date> {
|
|
265
|
+
return sqlTimestampEncode(value);
|
|
266
|
+
}
|
|
267
|
+
async decode(wire: Date, _ctx: CodecCallContext): Promise<Date> {
|
|
268
|
+
return sqlTimestampDecode(wire);
|
|
269
|
+
}
|
|
270
|
+
encodeJson(value: Date): JsonValue {
|
|
271
|
+
return sqlTimestampEncodeJson(value);
|
|
272
|
+
}
|
|
273
|
+
decodeJson(json: JsonValue): Date {
|
|
274
|
+
return sqlTimestampDecodeJson(json);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export class SqlTimestampDescriptor extends CodecDescriptorImpl<PrecisionParams> {
|
|
279
|
+
override readonly codecId = SQL_TIMESTAMP_CODEC_ID;
|
|
280
|
+
override readonly traits = ['equality', 'order'] as const;
|
|
281
|
+
override readonly targetTypes = ['timestamp'] as const;
|
|
282
|
+
override readonly paramsSchema: StandardSchemaV1<PrecisionParams> = precisionParamsSchema;
|
|
283
|
+
override renderOutputType(params: PrecisionParams): string | undefined {
|
|
284
|
+
return sqlTimestampRenderOutputType(params);
|
|
285
|
+
}
|
|
286
|
+
override factory(_params: PrecisionParams): (ctx: CodecInstanceContext) => SqlTimestampCodec {
|
|
287
|
+
return () => new SqlTimestampCodec(this);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export const sqlTimestampDescriptor = new SqlTimestampDescriptor();
|
|
292
|
+
|
|
293
|
+
export const sqlTimestampColumn = (params: PrecisionParams = {}) =>
|
|
294
|
+
column(
|
|
295
|
+
sqlTimestampDescriptor.factory(params),
|
|
296
|
+
sqlTimestampDescriptor.codecId,
|
|
297
|
+
params,
|
|
298
|
+
'timestamp',
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
sqlTimestampColumn satisfies ColumnHelperFor<SqlTimestampDescriptor>;
|
|
302
|
+
sqlTimestampColumn satisfies ColumnHelperForStrict<SqlTimestampDescriptor>;
|
package/src/ast/types.ts
CHANGED
|
@@ -156,7 +156,7 @@ function rewriteProjectionItem(item: ProjectionItem, rewriter: AstRewriter): Pro
|
|
|
156
156
|
? rewriter.literal(item.expr)
|
|
157
157
|
: item.expr
|
|
158
158
|
: item.expr.rewrite(rewriter);
|
|
159
|
-
return new ProjectionItem(item.alias, rewrittenExpr, item.codecId);
|
|
159
|
+
return new ProjectionItem(item.alias, rewrittenExpr, item.codecId, item.refs);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
function rewriteInsertValue(value: InsertValue, rewriter: AstRewriter): InsertValue {
|
|
@@ -320,9 +320,7 @@ export class DerivedTableSource extends FromSource {
|
|
|
320
320
|
return new DerivedTableSource(alias, query);
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
-
// Intentionally does not call rewriter.tableSource — derived tables are rewritten
|
|
324
|
-
// via their inner query, not intercepted at the FromSource level. A future
|
|
325
|
-
// fromSource?(source: AnyFromSource) callback would be needed for that.
|
|
323
|
+
// Intentionally does not call rewriter.tableSource — derived tables are rewritten via their inner query, not intercepted at the FromSource level. A future fromSource?(source: AnyFromSource) callback would be needed for that.
|
|
326
324
|
override rewrite(rewriter: AstRewriter): AnyFromSource {
|
|
327
325
|
return new DerivedTableSource(this.alias, this.query.rewrite(rewriter));
|
|
328
326
|
}
|
|
@@ -392,23 +390,37 @@ export class IdentifierRef extends Expression {
|
|
|
392
390
|
}
|
|
393
391
|
}
|
|
394
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Column ref carried by a {@link ParamRef} that was constructed at a column-bound site (e.g. an INSERT/UPDATE column assignment, a `WHERE column = $param` comparison). The encode-side dispatch path uses `refs` to call `contractCodecs.forColumn(refs.table, refs.column)`, which selects the per-instance parameterized codec for the column — required when a parameterized codec id is shared by multiple columns with distinct
|
|
395
|
+
* typeParams (e.g. `vector(1024)` vs. `vector(1536)`).
|
|
396
|
+
*
|
|
397
|
+
* `refs` may legitimately be `undefined` for `ParamRef`s constructed without a column context — the validator pass (`validateParamRefRefs`) treats refs-less ParamRefs as a hard error only when their codec id is parameterized.
|
|
398
|
+
*/
|
|
399
|
+
export interface ParamRefBindingRefs {
|
|
400
|
+
readonly table: string;
|
|
401
|
+
readonly column: string;
|
|
402
|
+
}
|
|
403
|
+
|
|
395
404
|
export class ParamRef extends Expression {
|
|
396
405
|
readonly kind = 'param-ref' as const;
|
|
397
406
|
readonly value: unknown;
|
|
398
407
|
readonly name: string | undefined;
|
|
399
408
|
readonly codecId: string | undefined;
|
|
409
|
+
readonly refs: ParamRefBindingRefs | undefined;
|
|
400
410
|
|
|
401
411
|
constructor(
|
|
402
412
|
value: unknown,
|
|
403
413
|
options?: {
|
|
404
414
|
name?: string;
|
|
405
415
|
codecId?: string;
|
|
416
|
+
refs?: ParamRefBindingRefs;
|
|
406
417
|
},
|
|
407
418
|
) {
|
|
408
419
|
super();
|
|
409
420
|
this.value = value;
|
|
410
421
|
this.name = options?.name;
|
|
411
422
|
this.codecId = options?.codecId;
|
|
423
|
+
this.refs = options?.refs ? Object.freeze({ ...options.refs }) : undefined;
|
|
412
424
|
this.freeze();
|
|
413
425
|
}
|
|
414
426
|
|
|
@@ -417,6 +429,7 @@ export class ParamRef extends Expression {
|
|
|
417
429
|
options?: {
|
|
418
430
|
name?: string;
|
|
419
431
|
codecId?: string;
|
|
432
|
+
refs?: ParamRefBindingRefs;
|
|
420
433
|
},
|
|
421
434
|
): ParamRef {
|
|
422
435
|
return new ParamRef(value, options);
|
|
@@ -1069,21 +1082,32 @@ export class ProjectionItem extends AstNode {
|
|
|
1069
1082
|
readonly alias: string;
|
|
1070
1083
|
readonly expr: ProjectionExpr;
|
|
1071
1084
|
readonly codecId: string | undefined;
|
|
1085
|
+
/**
|
|
1086
|
+
* Column-bound metadata for the projection. Populated by builder paths that promote a top-level field shortcut (`select('email', ...)`) into a bare `IdentifierRef` AST while still knowing the originating `(table, column)`. Decode-side dispatch consults `refs` to call `forColumn(table, column)` so parameterized codec ids resolve to the per-instance codec — required when multiple columns share a codec id with distinct
|
|
1087
|
+
* typeParams (e.g. `varchar(36)` vs. `varchar(255)`). Stays `undefined` for `column-ref` projections (the AST already carries the binding) and for non-column-bound projections (computed expressions, subqueries, raw aliases).
|
|
1088
|
+
*/
|
|
1089
|
+
readonly refs: ParamRefBindingRefs | undefined;
|
|
1072
1090
|
|
|
1073
|
-
constructor(alias: string, expr: ProjectionExpr, codecId?: string) {
|
|
1091
|
+
constructor(alias: string, expr: ProjectionExpr, codecId?: string, refs?: ParamRefBindingRefs) {
|
|
1074
1092
|
super();
|
|
1075
1093
|
this.alias = alias;
|
|
1076
1094
|
this.expr = expr;
|
|
1077
1095
|
this.codecId = codecId;
|
|
1096
|
+
this.refs = refs ? Object.freeze({ ...refs }) : undefined;
|
|
1078
1097
|
this.freeze();
|
|
1079
1098
|
}
|
|
1080
1099
|
|
|
1081
|
-
static of(
|
|
1082
|
-
|
|
1100
|
+
static of(
|
|
1101
|
+
alias: string,
|
|
1102
|
+
expr: ProjectionExpr,
|
|
1103
|
+
codecId?: string,
|
|
1104
|
+
refs?: ParamRefBindingRefs,
|
|
1105
|
+
): ProjectionItem {
|
|
1106
|
+
return new ProjectionItem(alias, expr, codecId, refs);
|
|
1083
1107
|
}
|
|
1084
1108
|
|
|
1085
1109
|
withCodecId(codecId: string | undefined): ProjectionItem {
|
|
1086
|
-
return new ProjectionItem(this.alias, this.expr, codecId);
|
|
1110
|
+
return new ProjectionItem(this.alias, this.expr, codecId, this.refs);
|
|
1087
1111
|
}
|
|
1088
1112
|
}
|
|
1089
1113
|
|
|
@@ -1241,6 +1265,7 @@ export class SelectAst extends QueryAst {
|
|
|
1241
1265
|
: projection.expr
|
|
1242
1266
|
: projection.expr.rewrite(rewriter),
|
|
1243
1267
|
projection.codecId,
|
|
1268
|
+
projection.refs,
|
|
1244
1269
|
),
|
|
1245
1270
|
),
|
|
1246
1271
|
where: this.where?.rewrite(rewriter),
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder-pipeline validator pass: every {@link ParamRef} whose `codecId` resolves to a *parameterized* descriptor must carry `refs: { table, column }` so encode-side dispatch can call `contractCodecs.forColumn(table, column)`. Refs-less parameterized `ParamRef`s are a hard error — the codec-id-keyed `forCodecId` fallback cannot disambiguate per-instance codecs (e.g. `vector(1024)` vs. `vector(1536)`), so the dispatch
|
|
3
|
+
* path must reject them at validation time rather than silently bind to the wrong instance.
|
|
4
|
+
*
|
|
5
|
+
* Non-parameterized codec ids (the `voidParamsSchema` case) are always dispatch-safe via codec id alone, so refs-less ParamRefs for those ids are accepted unchanged.
|
|
6
|
+
*
|
|
7
|
+
* The pass runs post-build / pre-execute — the natural location is the SQL runtime's `lower()` step, between any `beforeCompile` rewrites and `encodeParams`. See AC-5 in the codec-registry-unification spec.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { runtimeError } from '@prisma-next/framework-components/runtime';
|
|
11
|
+
import type { CodecDescriptorRegistry } from '../query-lane-context';
|
|
12
|
+
import type { AnyQueryAst, ParamRef } from './types';
|
|
13
|
+
import { collectOrderedParamRefs } from './util';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validate that every parameterized-codec `ParamRef` in `plan` carries `refs`. Throws `RUNTIME.PARAM_REF_REFS_REQUIRED` (a runtime envelope) naming the codec id and the binding label when the invariant is violated. Returns the plan unchanged on success — callers that prefer a side-effecting assertion can ignore the return value.
|
|
17
|
+
*
|
|
18
|
+
* The `registry` is consulted via `descriptorFor(codecId).isParameterized` — `true` whenever the descriptor's `paramsSchema` is not the singleton `voidParamsSchema`.
|
|
19
|
+
*/
|
|
20
|
+
export function validateParamRefRefs(plan: AnyQueryAst, registry: CodecDescriptorRegistry): void {
|
|
21
|
+
for (const ref of collectOrderedParamRefs(plan)) {
|
|
22
|
+
diagnoseRef(ref, registry);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function diagnoseRef(ref: ParamRef, registry: CodecDescriptorRegistry): void {
|
|
27
|
+
if (!ref.codecId) return;
|
|
28
|
+
const descriptor = registry.descriptorFor(ref.codecId);
|
|
29
|
+
if (descriptor === undefined) return;
|
|
30
|
+
if (!descriptor.isParameterized) return;
|
|
31
|
+
if (ref.refs) return;
|
|
32
|
+
|
|
33
|
+
const label = ref.name ?? '<anonymous>';
|
|
34
|
+
throw runtimeError(
|
|
35
|
+
'RUNTIME.PARAM_REF_REFS_REQUIRED',
|
|
36
|
+
`ParamRef '${label}' for parameterized codec '${ref.codecId}' is missing column refs; column-aware dispatch requires { table, column } at the binding site.`,
|
|
37
|
+
{ codecId: ref.codecId, paramName: ref.name },
|
|
38
|
+
);
|
|
39
|
+
}
|