@prisma-next/sql-runtime 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -21
- package/dist/exports-CrHMfIKo.mjs +1564 -0
- package/dist/exports-CrHMfIKo.mjs.map +1 -0
- package/dist/{index-DyDQ4fyK.d.mts → index-_dXSGeho.d.mts} +112 -32
- package/dist/index-_dXSGeho.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/test/utils.d.mts +6 -5
- package/dist/test/utils.d.mts.map +1 -1
- package/dist/test/utils.mjs +16 -13
- package/dist/test/utils.mjs.map +1 -1
- package/package.json +12 -14
- package/src/codecs/decoding.ts +294 -173
- package/src/codecs/encoding.ts +162 -37
- package/src/codecs/validation.ts +22 -3
- package/src/exports/index.ts +11 -7
- package/src/fingerprint.ts +22 -0
- package/src/guardrails/raw.ts +165 -0
- package/src/lower-sql-plan.ts +5 -7
- package/src/marker.ts +75 -0
- package/src/middleware/before-compile-chain.ts +29 -0
- package/src/middleware/budgets.ts +34 -115
- package/src/middleware/lints.ts +5 -5
- package/src/middleware/sql-middleware.ts +36 -6
- package/src/runtime-spi.ts +44 -0
- package/src/sql-context.ts +332 -78
- package/src/sql-family-adapter.ts +3 -2
- package/src/sql-marker.ts +62 -47
- package/src/sql-runtime.ts +339 -104
- package/dist/exports-Cv7I7ZD5.mjs +0 -953
- package/dist/exports-Cv7I7ZD5.mjs.map +0 -1
- package/dist/index-DyDQ4fyK.d.mts.map +0 -1
- package/test/async-iterable-result.test.ts +0 -141
- package/test/budgets.test.ts +0 -431
- package/test/context.types.test-d.ts +0 -68
- package/test/execution-stack.test.ts +0 -164
- package/test/json-schema-validation.test.ts +0 -571
- package/test/lints.test.ts +0 -159
- package/test/mutation-default-generators.test.ts +0 -254
- package/test/parameterized-types.test.ts +0 -529
- package/test/sql-context.test.ts +0 -384
- package/test/sql-family-adapter.test.ts +0 -103
- package/test/sql-runtime.test.ts +0 -637
- package/test/utils.ts +0 -300
|
@@ -1,571 +0,0 @@
|
|
|
1
|
-
import type { Contract, ExecutionPlan, ParamDescriptor } from '@prisma-next/contract/types';
|
|
2
|
-
import { coreHash, profileHash } from '@prisma-next/contract/types';
|
|
3
|
-
import type { SqlStorage, StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
4
|
-
import type { CodecRegistry } from '@prisma-next/sql-relational-core/ast';
|
|
5
|
-
import { codec, createCodecRegistry } from '@prisma-next/sql-relational-core/ast';
|
|
6
|
-
import type {
|
|
7
|
-
JsonSchemaValidateFn,
|
|
8
|
-
JsonSchemaValidatorRegistry,
|
|
9
|
-
} from '@prisma-next/sql-relational-core/query-lane-context';
|
|
10
|
-
import { ifDefined } from '@prisma-next/utils/defined';
|
|
11
|
-
import { type as arktype } from 'arktype';
|
|
12
|
-
import { describe, expect, it } from 'vitest';
|
|
13
|
-
import { decodeRow } from '../src/codecs/decoding';
|
|
14
|
-
import { encodeParam, encodeParams } from '../src/codecs/encoding';
|
|
15
|
-
import type {
|
|
16
|
-
RuntimeParameterizedCodecDescriptor,
|
|
17
|
-
SqlRuntimeExtensionDescriptor,
|
|
18
|
-
} from '../src/sql-context';
|
|
19
|
-
import { createStubAdapter, createTestContext } from './utils';
|
|
20
|
-
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// Shared test helpers
|
|
23
|
-
// =============================================================================
|
|
24
|
-
|
|
25
|
-
function createStubValidator(schema: Record<string, unknown>): JsonSchemaValidateFn {
|
|
26
|
-
return (value: unknown) => {
|
|
27
|
-
if (schema['type'] === 'object' && typeof value === 'object' && value !== null) {
|
|
28
|
-
const required = (schema['required'] ?? []) as string[];
|
|
29
|
-
const obj = value as Record<string, unknown>;
|
|
30
|
-
for (const prop of required) {
|
|
31
|
-
if (!(prop in obj)) {
|
|
32
|
-
return {
|
|
33
|
-
valid: false,
|
|
34
|
-
errors: [
|
|
35
|
-
{
|
|
36
|
-
path: '/',
|
|
37
|
-
message: `must have required property '${prop}'`,
|
|
38
|
-
keyword: 'required',
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return { valid: true };
|
|
45
|
-
}
|
|
46
|
-
if (schema['type'] === 'object' && (typeof value !== 'object' || value === null)) {
|
|
47
|
-
return {
|
|
48
|
-
valid: false,
|
|
49
|
-
errors: [{ path: '/', message: 'must be object', keyword: 'type' }],
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return { valid: true };
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const metadataSchema: Record<string, unknown> = {
|
|
57
|
-
type: 'object',
|
|
58
|
-
properties: { name: { type: 'string' } },
|
|
59
|
-
required: ['name'],
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const userMetadataSchema: Record<string, unknown> = {
|
|
63
|
-
type: 'object',
|
|
64
|
-
properties: { userName: { type: 'string' } },
|
|
65
|
-
required: ['userName'],
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const postMetadataSchema: Record<string, unknown> = {
|
|
69
|
-
type: 'object',
|
|
70
|
-
properties: { postTitle: { type: 'string' } },
|
|
71
|
-
required: ['postTitle'],
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function createMetadataValidatorRegistry(): JsonSchemaValidatorRegistry {
|
|
75
|
-
const validators = new Map<string, JsonSchemaValidateFn>();
|
|
76
|
-
validators.set('user.metadata', createStubValidator(metadataSchema));
|
|
77
|
-
return { get: (key) => validators.get(key), size: validators.size };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function createJoinMetadataValidatorRegistry(): JsonSchemaValidatorRegistry {
|
|
81
|
-
const validators = new Map<string, JsonSchemaValidateFn>();
|
|
82
|
-
validators.set('user.metadata', createStubValidator(userMetadataSchema));
|
|
83
|
-
validators.set('post.metadata', createStubValidator(postMetadataSchema));
|
|
84
|
-
return { get: (key) => validators.get(key), size: validators.size };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function createTestCodecRegistry(): CodecRegistry {
|
|
88
|
-
const registry = createCodecRegistry();
|
|
89
|
-
registry.register(
|
|
90
|
-
codec({
|
|
91
|
-
typeId: 'pg/jsonb@1',
|
|
92
|
-
targetTypes: ['jsonb'],
|
|
93
|
-
encode: (v: unknown) => JSON.stringify(v),
|
|
94
|
-
decode: (w: string) => (typeof w === 'string' ? JSON.parse(w) : w),
|
|
95
|
-
}),
|
|
96
|
-
);
|
|
97
|
-
registry.register(
|
|
98
|
-
codec({
|
|
99
|
-
typeId: 'pg/json@1',
|
|
100
|
-
targetTypes: ['json'],
|
|
101
|
-
encode: (v: unknown) => JSON.stringify(v),
|
|
102
|
-
decode: (w: string) => (typeof w === 'string' ? JSON.parse(w) : w),
|
|
103
|
-
}),
|
|
104
|
-
);
|
|
105
|
-
registry.register(
|
|
106
|
-
codec({
|
|
107
|
-
typeId: 'pg/int4@1',
|
|
108
|
-
targetTypes: ['int4'],
|
|
109
|
-
encode: (v: number) => v,
|
|
110
|
-
decode: (w: number) => w,
|
|
111
|
-
}),
|
|
112
|
-
);
|
|
113
|
-
return registry;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function createJsonSchemaContract(
|
|
117
|
-
options?: Partial<{
|
|
118
|
-
types: Record<string, StorageTypeInstance>;
|
|
119
|
-
tableColumns: Record<
|
|
120
|
-
string,
|
|
121
|
-
{
|
|
122
|
-
nativeType: string;
|
|
123
|
-
codecId: string;
|
|
124
|
-
nullable: boolean;
|
|
125
|
-
typeParams?: Record<string, unknown>;
|
|
126
|
-
typeRef?: string;
|
|
127
|
-
}
|
|
128
|
-
>;
|
|
129
|
-
}>,
|
|
130
|
-
): Contract<SqlStorage> {
|
|
131
|
-
return {
|
|
132
|
-
targetFamily: 'sql',
|
|
133
|
-
target: 'postgres',
|
|
134
|
-
profileHash: profileHash('sha256:test'),
|
|
135
|
-
models: {},
|
|
136
|
-
roots: {},
|
|
137
|
-
storage: {
|
|
138
|
-
storageHash: coreHash('sha256:test'),
|
|
139
|
-
tables: {
|
|
140
|
-
user: {
|
|
141
|
-
columns: options?.tableColumns ?? {
|
|
142
|
-
id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false },
|
|
143
|
-
metadata: {
|
|
144
|
-
nativeType: 'jsonb',
|
|
145
|
-
codecId: 'pg/jsonb@1',
|
|
146
|
-
nullable: true,
|
|
147
|
-
typeParams: { schema: metadataSchema },
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
primaryKey: { columns: ['id'] },
|
|
151
|
-
uniques: [],
|
|
152
|
-
indexes: [],
|
|
153
|
-
foreignKeys: [],
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
...ifDefined('types', options?.types),
|
|
157
|
-
},
|
|
158
|
-
extensionPacks: {},
|
|
159
|
-
capabilities: {},
|
|
160
|
-
meta: {},
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const jsonTypeParamsSchema = arktype({
|
|
165
|
-
schema: 'object',
|
|
166
|
-
'type?': 'string',
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
function createJsonbExtensionDescriptor(): SqlRuntimeExtensionDescriptor<'postgres'> {
|
|
170
|
-
const parameterizedCodecs: RuntimeParameterizedCodecDescriptor[] = [
|
|
171
|
-
{
|
|
172
|
-
codecId: 'pg/json@1',
|
|
173
|
-
paramsSchema: jsonTypeParamsSchema,
|
|
174
|
-
init: (params: Record<string, unknown>) => ({
|
|
175
|
-
validate: createStubValidator(params['schema'] as Record<string, unknown>),
|
|
176
|
-
}),
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
codecId: 'pg/jsonb@1',
|
|
180
|
-
paramsSchema: jsonTypeParamsSchema,
|
|
181
|
-
init: (params: Record<string, unknown>) => ({
|
|
182
|
-
validate: createStubValidator(params['schema'] as Record<string, unknown>),
|
|
183
|
-
}),
|
|
184
|
-
},
|
|
185
|
-
];
|
|
186
|
-
|
|
187
|
-
const registry = createCodecRegistry();
|
|
188
|
-
registry.register(
|
|
189
|
-
codec({
|
|
190
|
-
typeId: 'pg/json@1',
|
|
191
|
-
targetTypes: ['json'],
|
|
192
|
-
encode: (v: unknown) => JSON.stringify(v),
|
|
193
|
-
decode: (w: string) => (typeof w === 'string' ? JSON.parse(w) : w),
|
|
194
|
-
}),
|
|
195
|
-
);
|
|
196
|
-
registry.register(
|
|
197
|
-
codec({
|
|
198
|
-
typeId: 'pg/jsonb@1',
|
|
199
|
-
targetTypes: ['jsonb'],
|
|
200
|
-
encode: (v: unknown) => JSON.stringify(v),
|
|
201
|
-
decode: (w: string) => (typeof w === 'string' ? JSON.parse(w) : w),
|
|
202
|
-
}),
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
kind: 'extension' as const,
|
|
207
|
-
id: 'json-validation',
|
|
208
|
-
version: '0.0.1',
|
|
209
|
-
familyId: 'sql' as const,
|
|
210
|
-
targetId: 'postgres' as const,
|
|
211
|
-
codecs: () => registry,
|
|
212
|
-
parameterizedCodecs: () => parameterizedCodecs,
|
|
213
|
-
create() {
|
|
214
|
-
return { familyId: 'sql' as const, targetId: 'postgres' as const };
|
|
215
|
-
},
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function createTestPlan(overrides?: Partial<ExecutionPlan>): ExecutionPlan {
|
|
220
|
-
return {
|
|
221
|
-
sql: 'SELECT 1',
|
|
222
|
-
params: [],
|
|
223
|
-
meta: {
|
|
224
|
-
target: 'postgres',
|
|
225
|
-
storageHash: 'sha256:test',
|
|
226
|
-
lane: 'dsl',
|
|
227
|
-
paramDescriptors: [],
|
|
228
|
-
},
|
|
229
|
-
...overrides,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// =============================================================================
|
|
234
|
-
// Tests: Validator Registry via createExecutionContext
|
|
235
|
-
// =============================================================================
|
|
236
|
-
|
|
237
|
-
describe('JSON Schema validator registry', () => {
|
|
238
|
-
describe('context creation', () => {
|
|
239
|
-
it('builds validator registry for contract with JSON columns that have schemas', () => {
|
|
240
|
-
const contract = createJsonSchemaContract();
|
|
241
|
-
const context = createTestContext(contract, createStubAdapter(), {
|
|
242
|
-
extensionPacks: [createJsonbExtensionDescriptor()],
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
expect(context.jsonSchemaValidators).toBeDefined();
|
|
246
|
-
expect(context.jsonSchemaValidators!.size).toBe(1);
|
|
247
|
-
expect(context.jsonSchemaValidators!.get('user.metadata')).toBeDefined();
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('omits validator registry when no JSON columns have schemas', () => {
|
|
251
|
-
const contract = createJsonSchemaContract({
|
|
252
|
-
tableColumns: {
|
|
253
|
-
id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false },
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
const context = createTestContext(contract, createStubAdapter(), {
|
|
257
|
-
extensionPacks: [createJsonbExtensionDescriptor()],
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
expect(context.jsonSchemaValidators).toBeUndefined();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('builds validators for columns with typeRef', () => {
|
|
264
|
-
const contract = createJsonSchemaContract({
|
|
265
|
-
types: {
|
|
266
|
-
ProfileJson: {
|
|
267
|
-
codecId: 'pg/jsonb@1',
|
|
268
|
-
nativeType: 'jsonb',
|
|
269
|
-
typeParams: {
|
|
270
|
-
schema: {
|
|
271
|
-
type: 'object',
|
|
272
|
-
properties: { displayName: { type: 'string' } },
|
|
273
|
-
required: ['displayName'],
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
tableColumns: {
|
|
279
|
-
id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false },
|
|
280
|
-
profile: {
|
|
281
|
-
nativeType: 'jsonb',
|
|
282
|
-
codecId: 'pg/jsonb@1',
|
|
283
|
-
nullable: true,
|
|
284
|
-
typeRef: 'ProfileJson',
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
});
|
|
288
|
-
const context = createTestContext(contract, createStubAdapter(), {
|
|
289
|
-
extensionPacks: [createJsonbExtensionDescriptor()],
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
expect(context.jsonSchemaValidators).toBeDefined();
|
|
293
|
-
expect(context.jsonSchemaValidators!.get('user.profile')).toBeDefined();
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('omits validator registry when no init hooks are defined', () => {
|
|
297
|
-
const registry = createCodecRegistry();
|
|
298
|
-
registry.register(
|
|
299
|
-
codec({
|
|
300
|
-
typeId: 'pg/jsonb@1',
|
|
301
|
-
targetTypes: ['jsonb'],
|
|
302
|
-
encode: (v: unknown) => JSON.stringify(v),
|
|
303
|
-
decode: (w: string) => (typeof w === 'string' ? JSON.parse(w) : w),
|
|
304
|
-
}),
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
const noInitExtension: SqlRuntimeExtensionDescriptor<'postgres'> = {
|
|
308
|
-
kind: 'extension' as const,
|
|
309
|
-
id: 'json-no-init',
|
|
310
|
-
version: '0.0.1',
|
|
311
|
-
familyId: 'sql' as const,
|
|
312
|
-
targetId: 'postgres' as const,
|
|
313
|
-
codecs: () => registry,
|
|
314
|
-
parameterizedCodecs: () => [{ codecId: 'pg/jsonb@1', paramsSchema: jsonTypeParamsSchema }],
|
|
315
|
-
create() {
|
|
316
|
-
return { familyId: 'sql' as const, targetId: 'postgres' as const };
|
|
317
|
-
},
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
const contract = createJsonSchemaContract();
|
|
321
|
-
const context = createTestContext(contract, createStubAdapter(), {
|
|
322
|
-
extensionPacks: [noInitExtension],
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
expect(context.jsonSchemaValidators).toBeUndefined();
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// =============================================================================
|
|
331
|
-
// Tests: Encoding validation
|
|
332
|
-
// =============================================================================
|
|
333
|
-
|
|
334
|
-
describe('JSON Schema encoding validation', () => {
|
|
335
|
-
const codecRegistry = createTestCodecRegistry();
|
|
336
|
-
|
|
337
|
-
it('encodes JSON values via codec', () => {
|
|
338
|
-
const plan = createTestPlan({
|
|
339
|
-
params: [{ name: 'Alice' }],
|
|
340
|
-
meta: {
|
|
341
|
-
target: 'postgres',
|
|
342
|
-
storageHash: 'sha256:test',
|
|
343
|
-
lane: 'dsl',
|
|
344
|
-
paramDescriptors: [
|
|
345
|
-
{
|
|
346
|
-
codecId: 'pg/jsonb@1',
|
|
347
|
-
source: 'dsl' as const,
|
|
348
|
-
},
|
|
349
|
-
],
|
|
350
|
-
},
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
const result = encodeParams(plan, codecRegistry);
|
|
354
|
-
expect(result[0]).toBe('{"name":"Alice"}');
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it('returns null for null values', () => {
|
|
358
|
-
const descriptor: ParamDescriptor = {
|
|
359
|
-
codecId: 'pg/jsonb@1',
|
|
360
|
-
source: 'dsl',
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
const result = encodeParam(null, descriptor, 0, codecRegistry);
|
|
364
|
-
expect(result).toBeNull();
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it('encodes when descriptor has name', () => {
|
|
368
|
-
const descriptor: ParamDescriptor = {
|
|
369
|
-
name: 'metadata',
|
|
370
|
-
codecId: 'pg/jsonb@1',
|
|
371
|
-
source: 'dsl',
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
const result = encodeParam({ age: 30 }, descriptor, 0, codecRegistry);
|
|
375
|
-
expect(result).toBe('{"age":30}');
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// =============================================================================
|
|
380
|
-
// Tests: Decoding validation
|
|
381
|
-
// =============================================================================
|
|
382
|
-
|
|
383
|
-
describe('JSON Schema decoding validation', () => {
|
|
384
|
-
const codecRegistry = createTestCodecRegistry();
|
|
385
|
-
|
|
386
|
-
it('passes valid decoded JSON values', () => {
|
|
387
|
-
const plan = createTestPlan({
|
|
388
|
-
meta: {
|
|
389
|
-
target: 'postgres',
|
|
390
|
-
storageHash: 'sha256:test',
|
|
391
|
-
lane: 'dsl',
|
|
392
|
-
paramDescriptors: [],
|
|
393
|
-
projectionTypes: { metadata: 'pg/jsonb@1' },
|
|
394
|
-
refs: { columns: [{ table: 'user', column: 'metadata' }] },
|
|
395
|
-
},
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
const row = { metadata: '{"name":"Alice"}' };
|
|
399
|
-
const result = decodeRow(row, plan, codecRegistry, createMetadataValidatorRegistry());
|
|
400
|
-
expect(result['metadata']).toEqual({ name: 'Alice' });
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
it('throws RUNTIME.JSON_SCHEMA_VALIDATION_FAILED for invalid decoded values', () => {
|
|
404
|
-
const plan = createTestPlan({
|
|
405
|
-
meta: {
|
|
406
|
-
target: 'postgres',
|
|
407
|
-
storageHash: 'sha256:test',
|
|
408
|
-
lane: 'dsl',
|
|
409
|
-
paramDescriptors: [],
|
|
410
|
-
projectionTypes: { metadata: 'pg/jsonb@1' },
|
|
411
|
-
refs: { columns: [{ table: 'user', column: 'metadata' }] },
|
|
412
|
-
},
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
const row = { metadata: '{"age":30}' };
|
|
416
|
-
expect(() => decodeRow(row, plan, codecRegistry, createMetadataValidatorRegistry())).toThrow(
|
|
417
|
-
expect.objectContaining({
|
|
418
|
-
code: 'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED',
|
|
419
|
-
category: 'RUNTIME',
|
|
420
|
-
severity: 'error',
|
|
421
|
-
details: expect.objectContaining({
|
|
422
|
-
table: 'user',
|
|
423
|
-
column: 'metadata',
|
|
424
|
-
direction: 'decode',
|
|
425
|
-
codecId: 'pg/jsonb@1',
|
|
426
|
-
}),
|
|
427
|
-
}),
|
|
428
|
-
);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
it('validates aliased projection columns using projection mapping', () => {
|
|
432
|
-
const plan = createTestPlan({
|
|
433
|
-
meta: {
|
|
434
|
-
target: 'postgres',
|
|
435
|
-
storageHash: 'sha256:test',
|
|
436
|
-
lane: 'dsl',
|
|
437
|
-
paramDescriptors: [],
|
|
438
|
-
projection: { userMeta: 'user.metadata' },
|
|
439
|
-
projectionTypes: { userMeta: 'pg/jsonb@1' },
|
|
440
|
-
refs: { columns: [{ table: 'user', column: 'metadata' }] },
|
|
441
|
-
},
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
const row = { userMeta: '{"age":30}' };
|
|
445
|
-
expect(() => decodeRow(row, plan, codecRegistry, createMetadataValidatorRegistry())).toThrow(
|
|
446
|
-
expect.objectContaining({
|
|
447
|
-
code: 'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED',
|
|
448
|
-
details: expect.objectContaining({
|
|
449
|
-
table: 'user',
|
|
450
|
-
column: 'metadata',
|
|
451
|
-
direction: 'decode',
|
|
452
|
-
}),
|
|
453
|
-
}),
|
|
454
|
-
);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
it('resolves join aliases with duplicate column names using projection mapping', () => {
|
|
458
|
-
const plan = createTestPlan({
|
|
459
|
-
meta: {
|
|
460
|
-
target: 'postgres',
|
|
461
|
-
storageHash: 'sha256:test',
|
|
462
|
-
lane: 'dsl',
|
|
463
|
-
paramDescriptors: [],
|
|
464
|
-
projection: {
|
|
465
|
-
userMeta: 'user.metadata',
|
|
466
|
-
postMeta: 'post.metadata',
|
|
467
|
-
},
|
|
468
|
-
projectionTypes: {
|
|
469
|
-
userMeta: 'pg/jsonb@1',
|
|
470
|
-
postMeta: 'pg/jsonb@1',
|
|
471
|
-
},
|
|
472
|
-
refs: {
|
|
473
|
-
columns: [
|
|
474
|
-
{ table: 'user', column: 'metadata' },
|
|
475
|
-
{ table: 'post', column: 'metadata' },
|
|
476
|
-
],
|
|
477
|
-
},
|
|
478
|
-
},
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
const row = {
|
|
482
|
-
userMeta: '{"userName":"Alice"}',
|
|
483
|
-
postMeta: '{"userName":"Alice"}',
|
|
484
|
-
};
|
|
485
|
-
expect(() =>
|
|
486
|
-
decodeRow(row, plan, codecRegistry, createJoinMetadataValidatorRegistry()),
|
|
487
|
-
).toThrow(
|
|
488
|
-
expect.objectContaining({
|
|
489
|
-
code: 'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED',
|
|
490
|
-
details: expect.objectContaining({
|
|
491
|
-
table: 'post',
|
|
492
|
-
column: 'metadata',
|
|
493
|
-
direction: 'decode',
|
|
494
|
-
}),
|
|
495
|
-
}),
|
|
496
|
-
);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
it('skips validation when column ref cannot be resolved', () => {
|
|
500
|
-
const plan = createTestPlan({
|
|
501
|
-
meta: {
|
|
502
|
-
target: 'postgres',
|
|
503
|
-
storageHash: 'sha256:test',
|
|
504
|
-
lane: 'dsl',
|
|
505
|
-
paramDescriptors: [],
|
|
506
|
-
projectionTypes: { data: 'pg/jsonb@1' },
|
|
507
|
-
},
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
const row = { data: '{"bad":"data"}' };
|
|
511
|
-
const result = decodeRow(row, plan, codecRegistry, createMetadataValidatorRegistry());
|
|
512
|
-
expect(result['data']).toEqual({ bad: 'data' });
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it('skips validation for null wire values', () => {
|
|
516
|
-
const plan = createTestPlan({
|
|
517
|
-
meta: {
|
|
518
|
-
target: 'postgres',
|
|
519
|
-
storageHash: 'sha256:test',
|
|
520
|
-
lane: 'dsl',
|
|
521
|
-
paramDescriptors: [],
|
|
522
|
-
projectionTypes: { metadata: 'pg/jsonb@1' },
|
|
523
|
-
refs: { columns: [{ table: 'user', column: 'metadata' }] },
|
|
524
|
-
},
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
const row = { metadata: null };
|
|
528
|
-
const result = decodeRow(row, plan, codecRegistry, createMetadataValidatorRegistry());
|
|
529
|
-
expect(result['metadata']).toBeNull();
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
it('skips validation when no registry is provided', () => {
|
|
533
|
-
const plan = createTestPlan({
|
|
534
|
-
meta: {
|
|
535
|
-
target: 'postgres',
|
|
536
|
-
storageHash: 'sha256:test',
|
|
537
|
-
lane: 'dsl',
|
|
538
|
-
paramDescriptors: [],
|
|
539
|
-
projectionTypes: { metadata: 'pg/jsonb@1' },
|
|
540
|
-
refs: { columns: [{ table: 'user', column: 'metadata' }] },
|
|
541
|
-
},
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
const row = { metadata: '{"bad":"data"}' };
|
|
545
|
-
const result = decodeRow(row, plan, codecRegistry);
|
|
546
|
-
expect(result['metadata']).toEqual({ bad: 'data' });
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
it('decodes non-JSON columns without validation', () => {
|
|
550
|
-
const plan = createTestPlan({
|
|
551
|
-
meta: {
|
|
552
|
-
target: 'postgres',
|
|
553
|
-
storageHash: 'sha256:test',
|
|
554
|
-
lane: 'dsl',
|
|
555
|
-
paramDescriptors: [],
|
|
556
|
-
projectionTypes: { id: 'pg/int4@1', metadata: 'pg/jsonb@1' },
|
|
557
|
-
refs: {
|
|
558
|
-
columns: [
|
|
559
|
-
{ table: 'user', column: 'id' },
|
|
560
|
-
{ table: 'user', column: 'metadata' },
|
|
561
|
-
],
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
const row = { id: 42, metadata: '{"name":"Alice"}' };
|
|
567
|
-
const result = decodeRow(row, plan, codecRegistry, createMetadataValidatorRegistry());
|
|
568
|
-
expect(result['id']).toBe(42);
|
|
569
|
-
expect(result['metadata']).toEqual({ name: 'Alice' });
|
|
570
|
-
});
|
|
571
|
-
});
|