@prisma-next/sql-runtime 0.3.0-pr.99.5 → 0.3.0
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/LICENSE +201 -0
- package/README.md +141 -24
- package/dist/exports-BO6Fl7yn.mjs +889 -0
- package/dist/exports-BO6Fl7yn.mjs.map +1 -0
- package/dist/index-n6z6trta.d.mts +186 -0
- package/dist/index-n6z6trta.d.mts.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +3 -0
- package/dist/test/utils.d.mts +77 -0
- package/dist/test/utils.d.mts.map +1 -0
- package/dist/test/utils.mjs +221 -0
- package/dist/test/utils.mjs.map +1 -0
- package/package.json +26 -20
- package/src/codecs/decoding.ts +84 -3
- package/src/codecs/encoding.ts +5 -15
- package/src/codecs/json-schema-validation.ts +61 -0
- package/src/codecs/validation.ts +7 -6
- package/src/exports/index.ts +20 -9
- package/src/lower-sql-plan.ts +9 -9
- package/src/middleware/budgets.ts +256 -0
- package/src/middleware/lints.ts +192 -0
- package/src/middleware/sql-middleware.ts +26 -0
- package/src/sql-context.ts +357 -257
- package/src/sql-family-adapter.ts +17 -23
- package/src/sql-marker.ts +2 -2
- package/src/sql-runtime.ts +136 -61
- package/test/async-iterable-result.test.ts +42 -37
- package/test/budgets.test.ts +431 -0
- package/test/context.types.test-d.ts +18 -20
- package/test/execution-stack.test.ts +164 -0
- package/test/json-schema-validation.test.ts +571 -0
- package/test/lints.test.ts +159 -0
- package/test/mutation-default-generators.test.ts +254 -0
- package/test/parameterized-types.test.ts +181 -205
- package/test/sql-context.test.ts +301 -134
- package/test/sql-family-adapter.test.ts +37 -20
- package/test/sql-runtime.test.ts +261 -49
- package/test/utils.ts +101 -67
- package/dist/accelerate-EEKAFGN3-P6A6XJWJ.js +0 -137863
- package/dist/accelerate-EEKAFGN3-P6A6XJWJ.js.map +0 -1
- package/dist/amcheck-24VY6X5V.js +0 -13
- package/dist/amcheck-24VY6X5V.js.map +0 -1
- package/dist/bloom-VS74NLHT.js +0 -13
- package/dist/bloom-VS74NLHT.js.map +0 -1
- package/dist/btree_gin-WBC4EAAI.js +0 -13
- package/dist/btree_gin-WBC4EAAI.js.map +0 -1
- package/dist/btree_gist-UNC6QD3M.js +0 -13
- package/dist/btree_gist-UNC6QD3M.js.map +0 -1
- package/dist/chunk-3KTOEDFX.js +0 -49
- package/dist/chunk-3KTOEDFX.js.map +0 -1
- package/dist/chunk-47DZBRQC.js +0 -1280
- package/dist/chunk-47DZBRQC.js.map +0 -1
- package/dist/chunk-52N6AFZM.js +0 -133
- package/dist/chunk-52N6AFZM.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-7D4SUZUM.js.map +0 -1
- package/dist/chunk-APA6GHYY.js +0 -537
- package/dist/chunk-APA6GHYY.js.map +0 -1
- package/dist/chunk-ECWIHLAT.js +0 -37
- package/dist/chunk-ECWIHLAT.js.map +0 -1
- package/dist/chunk-EI626SDC.js +0 -105
- package/dist/chunk-EI626SDC.js.map +0 -1
- package/dist/chunk-UKKOYUGL.js +0 -578
- package/dist/chunk-UKKOYUGL.js.map +0 -1
- package/dist/chunk-XPLNMXQV.js +0 -1537
- package/dist/chunk-XPLNMXQV.js.map +0 -1
- package/dist/citext-T7MXGUY7.js +0 -13
- package/dist/citext-T7MXGUY7.js.map +0 -1
- package/dist/client-5FENX6AW.js +0 -299
- package/dist/client-5FENX6AW.js.map +0 -1
- package/dist/cube-TFDQBZCI.js +0 -13
- package/dist/cube-TFDQBZCI.js.map +0 -1
- package/dist/dict_int-AEUOPGWP.js +0 -13
- package/dist/dict_int-AEUOPGWP.js.map +0 -1
- package/dist/dict_xsyn-DAAYX3FL.js +0 -13
- package/dist/dict_xsyn-DAAYX3FL.js.map +0 -1
- package/dist/dist-AQ3LWXOX.js +0 -570
- package/dist/dist-AQ3LWXOX.js.map +0 -1
- package/dist/dist-LBVX6BJW.js +0 -189
- package/dist/dist-LBVX6BJW.js.map +0 -1
- package/dist/dist-WLKUVDN2.js +0 -5127
- package/dist/dist-WLKUVDN2.js.map +0 -1
- package/dist/earthdistance-KIGTF4LE.js +0 -13
- package/dist/earthdistance-KIGTF4LE.js.map +0 -1
- package/dist/file_fdw-5N55UP6I.js +0 -13
- package/dist/file_fdw-5N55UP6I.js.map +0 -1
- package/dist/fuzzystrmatch-KN3YWBFP.js +0 -13
- package/dist/fuzzystrmatch-KN3YWBFP.js.map +0 -1
- package/dist/hstore-YX726NKN.js +0 -13
- package/dist/hstore-YX726NKN.js.map +0 -1
- package/dist/http-exception-FZY2H4OF.js +0 -8
- package/dist/http-exception-FZY2H4OF.js.map +0 -1
- package/dist/index.js +0 -30
- package/dist/index.js.map +0 -1
- package/dist/intarray-NKVXNO2D.js +0 -13
- package/dist/intarray-NKVXNO2D.js.map +0 -1
- package/dist/isn-FTEMJGEV.js +0 -13
- package/dist/isn-FTEMJGEV.js.map +0 -1
- package/dist/lo-DB7L4NGI.js +0 -13
- package/dist/lo-DB7L4NGI.js.map +0 -1
- package/dist/logger-WQ7SHNDD.js +0 -68
- package/dist/logger-WQ7SHNDD.js.map +0 -1
- package/dist/ltree-Z32TZT6W.js +0 -13
- package/dist/ltree-Z32TZT6W.js.map +0 -1
- package/dist/nodefs-NM46ACH7.js +0 -31
- package/dist/nodefs-NM46ACH7.js.map +0 -1
- package/dist/opfs-ahp-NJO33LVZ.js +0 -332
- package/dist/opfs-ahp-NJO33LVZ.js.map +0 -1
- package/dist/pageinspect-YP3IZR4X.js +0 -13
- package/dist/pageinspect-YP3IZR4X.js.map +0 -1
- package/dist/pg_buffercache-7TD5J2FB.js +0 -13
- package/dist/pg_buffercache-7TD5J2FB.js.map +0 -1
- package/dist/pg_dump-SG4KYBUB.js +0 -2492
- package/dist/pg_dump-SG4KYBUB.js.map +0 -1
- package/dist/pg_freespacemap-DZDNCPZK.js +0 -13
- package/dist/pg_freespacemap-DZDNCPZK.js.map +0 -1
- package/dist/pg_surgery-J2MUEWEP.js +0 -13
- package/dist/pg_surgery-J2MUEWEP.js.map +0 -1
- package/dist/pg_trgm-7VNQOYS6.js +0 -13
- package/dist/pg_trgm-7VNQOYS6.js.map +0 -1
- package/dist/pg_visibility-TTSIPHFL.js +0 -13
- package/dist/pg_visibility-TTSIPHFL.js.map +0 -1
- package/dist/pg_walinspect-KPFHSHRJ.js +0 -13
- package/dist/pg_walinspect-KPFHSHRJ.js.map +0 -1
- package/dist/proxy-signals-GUDAMDHV.js +0 -39
- package/dist/proxy-signals-GUDAMDHV.js.map +0 -1
- package/dist/seg-IYVDLE4O.js +0 -13
- package/dist/seg-IYVDLE4O.js.map +0 -1
- package/dist/src/codecs/decoding.d.ts +0 -4
- package/dist/src/codecs/decoding.d.ts.map +0 -1
- package/dist/src/codecs/encoding.d.ts +0 -5
- package/dist/src/codecs/encoding.d.ts.map +0 -1
- package/dist/src/codecs/validation.d.ts +0 -6
- package/dist/src/codecs/validation.d.ts.map +0 -1
- package/dist/src/exports/index.d.ts +0 -11
- package/dist/src/exports/index.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/lower-sql-plan.d.ts +0 -15
- package/dist/src/lower-sql-plan.d.ts.map +0 -1
- package/dist/src/sql-context.d.ts +0 -130
- package/dist/src/sql-context.d.ts.map +0 -1
- package/dist/src/sql-family-adapter.d.ts +0 -10
- package/dist/src/sql-family-adapter.d.ts.map +0 -1
- package/dist/src/sql-marker.d.ts +0 -22
- package/dist/src/sql-marker.d.ts.map +0 -1
- package/dist/src/sql-runtime.d.ts +0 -25
- package/dist/src/sql-runtime.d.ts.map +0 -1
- package/dist/tablefunc-EF4RCS7S.js +0 -13
- package/dist/tablefunc-EF4RCS7S.js.map +0 -1
- package/dist/tcn-3VT5BQYW.js +0 -13
- package/dist/tcn-3VT5BQYW.js.map +0 -1
- package/dist/test/utils.d.ts +0 -60
- package/dist/test/utils.d.ts.map +0 -1
- package/dist/test/utils.js +0 -24635
- package/dist/test/utils.js.map +0 -1
- package/dist/tiny-CW6F4GX6.js +0 -10
- package/dist/tiny-CW6F4GX6.js.map +0 -1
- package/dist/tsm_system_rows-ES7KNUQH.js +0 -13
- package/dist/tsm_system_rows-ES7KNUQH.js.map +0 -1
- package/dist/tsm_system_time-76WEIMBG.js +0 -13
- package/dist/tsm_system_time-76WEIMBG.js.map +0 -1
- package/dist/unaccent-7RYF3R64.js +0 -13
- package/dist/unaccent-7RYF3R64.js.map +0 -1
- package/dist/utility-Q5A254LJ-J4HTKZPT.js +0 -347
- package/dist/utility-Q5A254LJ-J4HTKZPT.js.map +0 -1
- package/dist/uuid_ossp-4ETE4FPE.js +0 -13
- package/dist/uuid_ossp-4ETE4FPE.js.map +0 -1
- package/dist/vector-74GPNV7V.js +0 -13
- package/dist/vector-74GPNV7V.js.map +0 -1
- package/src/index.ts +0 -1
package/test/sql-context.test.ts
CHANGED
|
@@ -1,103 +1,40 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
1
|
+
import { type Contract, coreHash, executionHash, profileHash } from '@prisma-next/contract/types';
|
|
2
|
+
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
3
|
+
import type { SqlOperationDescriptor } from '@prisma-next/sql-operations';
|
|
4
4
|
import { codec, createCodecRegistry } from '@prisma-next/sql-relational-core/ast';
|
|
5
5
|
import { describe, expect, it } from 'vitest';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
createExecutionContext,
|
|
8
|
+
type SqlExecutionStack,
|
|
8
9
|
type SqlRuntimeExtensionDescriptor,
|
|
9
|
-
type
|
|
10
|
+
type SqlRuntimeTargetDescriptor,
|
|
10
11
|
} from '../src/sql-context';
|
|
12
|
+
import {
|
|
13
|
+
createStubAdapter,
|
|
14
|
+
createTestAdapterDescriptor,
|
|
15
|
+
createTestTargetDescriptor,
|
|
16
|
+
} from './utils';
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
const testContract: SqlContract<SqlStorage> = {
|
|
14
|
-
schemaVersion: '1',
|
|
18
|
+
const testContract: Contract<SqlStorage> = {
|
|
15
19
|
targetFamily: 'sql',
|
|
16
20
|
target: 'postgres',
|
|
17
|
-
|
|
21
|
+
profileHash: profileHash('sha256:test'),
|
|
18
22
|
models: {},
|
|
19
|
-
|
|
20
|
-
storage: { tables: {} },
|
|
23
|
+
roots: {},
|
|
24
|
+
storage: { storageHash: coreHash('sha256:test'), tables: {} },
|
|
21
25
|
extensionPacks: {},
|
|
22
26
|
capabilities: {},
|
|
23
27
|
meta: {},
|
|
24
|
-
sources: {},
|
|
25
|
-
mappings: {
|
|
26
|
-
codecTypes: {},
|
|
27
|
-
operationTypes: {},
|
|
28
|
-
},
|
|
29
28
|
};
|
|
30
29
|
|
|
31
|
-
// Stub adapter codecs
|
|
32
|
-
function createStubCodecs(): CodecRegistry {
|
|
33
|
-
const registry = createCodecRegistry();
|
|
34
|
-
registry.register(
|
|
35
|
-
codec({
|
|
36
|
-
typeId: 'pg/int4@1',
|
|
37
|
-
targetTypes: ['int4'],
|
|
38
|
-
encode: (v: number) => v,
|
|
39
|
-
decode: (w: number) => w,
|
|
40
|
-
}),
|
|
41
|
-
);
|
|
42
|
-
return registry;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Create a test adapter descriptor
|
|
46
|
-
function createTestAdapterDescriptor() {
|
|
47
|
-
const codecs = createStubCodecs();
|
|
48
|
-
return {
|
|
49
|
-
kind: 'adapter' as const,
|
|
50
|
-
id: 'test-adapter',
|
|
51
|
-
version: '0.0.1',
|
|
52
|
-
familyId: 'sql' as const,
|
|
53
|
-
targetId: 'postgres' as const,
|
|
54
|
-
create() {
|
|
55
|
-
return {
|
|
56
|
-
familyId: 'sql' as const,
|
|
57
|
-
targetId: 'postgres' as const,
|
|
58
|
-
profile: {
|
|
59
|
-
id: 'test-profile',
|
|
60
|
-
target: 'postgres',
|
|
61
|
-
capabilities: {},
|
|
62
|
-
codecs() {
|
|
63
|
-
return codecs;
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
lower(ast: SelectAst) {
|
|
67
|
-
return {
|
|
68
|
-
profileId: 'test-profile',
|
|
69
|
-
body: Object.freeze({ sql: JSON.stringify(ast), params: [] }),
|
|
70
|
-
};
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Create a test target descriptor
|
|
78
|
-
function createTestTargetDescriptor() {
|
|
79
|
-
return {
|
|
80
|
-
kind: 'target' as const,
|
|
81
|
-
id: 'postgres',
|
|
82
|
-
version: '0.0.1',
|
|
83
|
-
familyId: 'sql' as const,
|
|
84
|
-
targetId: 'postgres' as const,
|
|
85
|
-
create() {
|
|
86
|
-
return { familyId: 'sql' as const, targetId: 'postgres' as const };
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Create a test extension descriptor
|
|
92
30
|
function createTestExtensionDescriptor(options?: {
|
|
93
31
|
hasCodecs?: boolean;
|
|
94
32
|
hasOperations?: boolean;
|
|
95
33
|
}): SqlRuntimeExtensionDescriptor<'postgres'> {
|
|
96
34
|
const { hasCodecs = false, hasOperations = false } = options ?? {};
|
|
97
35
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
? () => {
|
|
36
|
+
const codecRegistry = hasCodecs
|
|
37
|
+
? (() => {
|
|
101
38
|
const registry = createCodecRegistry();
|
|
102
39
|
registry.register(
|
|
103
40
|
codec({
|
|
@@ -108,21 +45,23 @@ function createTestExtensionDescriptor(options?: {
|
|
|
108
45
|
}),
|
|
109
46
|
);
|
|
110
47
|
return registry;
|
|
111
|
-
}
|
|
112
|
-
:
|
|
48
|
+
})()
|
|
49
|
+
: createCodecRegistry();
|
|
113
50
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
? (): ReadonlyArray<SqlOperationSignature> => [
|
|
51
|
+
const operationsArray: ReadonlyArray<SqlOperationDescriptor> = hasOperations
|
|
52
|
+
? [
|
|
117
53
|
{
|
|
118
|
-
forTypeId: 'test/ext@1',
|
|
119
54
|
method: 'testOp',
|
|
120
|
-
args: [],
|
|
121
|
-
returns: {
|
|
122
|
-
lowering: {
|
|
55
|
+
args: [{ codecId: 'test/ext@1', nullable: false }],
|
|
56
|
+
returns: { codecId: 'test/ext@1', nullable: false },
|
|
57
|
+
lowering: {
|
|
58
|
+
targetFamily: 'sql' as const,
|
|
59
|
+
strategy: 'function' as const,
|
|
60
|
+
template: 'test()',
|
|
61
|
+
},
|
|
123
62
|
},
|
|
124
63
|
]
|
|
125
|
-
:
|
|
64
|
+
: [];
|
|
126
65
|
|
|
127
66
|
return {
|
|
128
67
|
kind: 'extension' as const,
|
|
@@ -130,88 +69,316 @@ function createTestExtensionDescriptor(options?: {
|
|
|
130
69
|
version: '0.0.1',
|
|
131
70
|
familyId: 'sql' as const,
|
|
132
71
|
targetId: 'postgres' as const,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
72
|
+
codecs: () => codecRegistry,
|
|
73
|
+
queryOperations: () => operationsArray,
|
|
74
|
+
parameterizedCodecs: () => [],
|
|
75
|
+
create() {
|
|
76
|
+
return {
|
|
136
77
|
familyId: 'sql' as const,
|
|
137
78
|
targetId: 'postgres' as const,
|
|
138
79
|
};
|
|
139
|
-
if (codecsFn) {
|
|
140
|
-
(instance as { codecs?: () => CodecRegistry }).codecs = codecsFn;
|
|
141
|
-
}
|
|
142
|
-
if (operationsFn) {
|
|
143
|
-
(instance as { operations?: () => ReadonlyArray<SqlOperationSignature> }).operations =
|
|
144
|
-
operationsFn;
|
|
145
|
-
}
|
|
146
|
-
return instance;
|
|
147
80
|
},
|
|
148
81
|
};
|
|
149
82
|
}
|
|
150
83
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
84
|
+
function createStack(options?: {
|
|
85
|
+
extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<'postgres'>>;
|
|
86
|
+
}): SqlExecutionStack<'postgres'> {
|
|
87
|
+
return {
|
|
88
|
+
target: createTestTargetDescriptor(),
|
|
89
|
+
adapter: createTestAdapterDescriptor(createStubAdapter()),
|
|
90
|
+
extensionPacks: options?.extensionPacks ?? [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe('createExecutionContext', () => {
|
|
95
|
+
it('creates context with adapter codecs from descriptor', () => {
|
|
96
|
+
const context = createExecutionContext({
|
|
154
97
|
contract: testContract,
|
|
155
|
-
|
|
156
|
-
adapter: createTestAdapterDescriptor(),
|
|
98
|
+
stack: createStack(),
|
|
157
99
|
});
|
|
158
100
|
|
|
159
101
|
expect(context.contract).toBe(testContract);
|
|
160
|
-
expect(context.adapter).toBeDefined();
|
|
161
102
|
expect(context.codecs.has('pg/int4@1')).toBe(true);
|
|
162
|
-
expect(context.
|
|
103
|
+
expect(context.queryOperations).toBeDefined();
|
|
163
104
|
});
|
|
164
105
|
|
|
165
106
|
it('creates context with empty extension packs', () => {
|
|
166
|
-
const context =
|
|
107
|
+
const context = createExecutionContext({
|
|
167
108
|
contract: testContract,
|
|
168
|
-
|
|
169
|
-
adapter: createTestAdapterDescriptor(),
|
|
170
|
-
extensionPacks: [],
|
|
109
|
+
stack: createStack({ extensionPacks: [] }),
|
|
171
110
|
});
|
|
172
111
|
|
|
173
112
|
expect(context.codecs.has('pg/int4@1')).toBe(true);
|
|
174
|
-
// No extension codecs registered
|
|
175
113
|
expect(context.codecs.has('test/ext@1')).toBe(false);
|
|
176
114
|
});
|
|
177
115
|
|
|
178
|
-
it('registers extension codecs', () => {
|
|
179
|
-
const context =
|
|
116
|
+
it('registers extension codecs from descriptors', () => {
|
|
117
|
+
const context = createExecutionContext({
|
|
180
118
|
contract: testContract,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
119
|
+
stack: createStack({
|
|
120
|
+
extensionPacks: [createTestExtensionDescriptor({ hasCodecs: true })],
|
|
121
|
+
}),
|
|
184
122
|
});
|
|
185
123
|
|
|
186
|
-
// Adapter codec
|
|
187
124
|
expect(context.codecs.has('pg/int4@1')).toBe(true);
|
|
188
|
-
// Extension codec
|
|
189
125
|
expect(context.codecs.has('test/ext@1')).toBe(true);
|
|
190
126
|
});
|
|
191
127
|
|
|
192
|
-
it('registers extension operations', () => {
|
|
193
|
-
const context =
|
|
128
|
+
it('registers extension operations from descriptors', () => {
|
|
129
|
+
const context = createExecutionContext({
|
|
194
130
|
contract: testContract,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
131
|
+
stack: createStack({
|
|
132
|
+
extensionPacks: [createTestExtensionDescriptor({ hasOperations: true })],
|
|
133
|
+
}),
|
|
198
134
|
});
|
|
199
135
|
|
|
200
|
-
const
|
|
201
|
-
expect(
|
|
202
|
-
expect(ops[0]?.method).toBe('testOp');
|
|
136
|
+
const entries = context.queryOperations.entries();
|
|
137
|
+
expect(entries['testOp']).toBeDefined();
|
|
203
138
|
});
|
|
204
139
|
|
|
205
|
-
it('handles extension
|
|
206
|
-
const context =
|
|
140
|
+
it('handles extension with no contributions', () => {
|
|
141
|
+
const context = createExecutionContext({
|
|
207
142
|
contract: testContract,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
143
|
+
stack: createStack({
|
|
144
|
+
extensionPacks: [createTestExtensionDescriptor({ hasCodecs: false, hasOperations: false })],
|
|
145
|
+
}),
|
|
211
146
|
});
|
|
212
147
|
|
|
213
|
-
// Only adapter codec
|
|
214
148
|
expect(context.codecs.has('pg/int4@1')).toBe(true);
|
|
215
149
|
expect(context.codecs.has('test/ext@1')).toBe(false);
|
|
216
150
|
});
|
|
217
151
|
});
|
|
152
|
+
|
|
153
|
+
describe('comprehensive descriptor-based derivation', () => {
|
|
154
|
+
it('includes all expected codec IDs and operations from target, adapter, and extensions', () => {
|
|
155
|
+
const targetCodecRegistry = createCodecRegistry();
|
|
156
|
+
targetCodecRegistry.register(
|
|
157
|
+
codec({
|
|
158
|
+
typeId: 'target/special@1',
|
|
159
|
+
targetTypes: ['special'],
|
|
160
|
+
encode: (v: string) => v,
|
|
161
|
+
decode: (w: string) => w,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const targetOps: SqlOperationDescriptor[] = [
|
|
166
|
+
{
|
|
167
|
+
method: 'targetOp',
|
|
168
|
+
args: [{ codecId: 'target/special@1', nullable: false }],
|
|
169
|
+
returns: { codecId: 'target/special@1', nullable: false },
|
|
170
|
+
lowering: {
|
|
171
|
+
targetFamily: 'sql' as const,
|
|
172
|
+
strategy: 'function' as const,
|
|
173
|
+
template: 'target_fn()',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
const target: SqlRuntimeTargetDescriptor<'postgres'> = {
|
|
179
|
+
kind: 'target' as const,
|
|
180
|
+
id: 'postgres',
|
|
181
|
+
version: '0.0.1',
|
|
182
|
+
familyId: 'sql' as const,
|
|
183
|
+
targetId: 'postgres' as const,
|
|
184
|
+
codecs: () => targetCodecRegistry,
|
|
185
|
+
queryOperations: () => targetOps,
|
|
186
|
+
parameterizedCodecs: () => [],
|
|
187
|
+
create() {
|
|
188
|
+
return { familyId: 'sql' as const, targetId: 'postgres' as const };
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const stack: SqlExecutionStack<'postgres'> = {
|
|
193
|
+
target,
|
|
194
|
+
adapter: createTestAdapterDescriptor(createStubAdapter()),
|
|
195
|
+
extensionPacks: [createTestExtensionDescriptor({ hasCodecs: true, hasOperations: true })],
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const context = createExecutionContext({ contract: testContract, stack });
|
|
199
|
+
|
|
200
|
+
expect(context.codecs.has('target/special@1')).toBe(true);
|
|
201
|
+
expect(context.codecs.has('pg/int4@1')).toBe(true);
|
|
202
|
+
expect(context.codecs.has('test/ext@1')).toBe(true);
|
|
203
|
+
|
|
204
|
+
const entries = context.queryOperations.entries();
|
|
205
|
+
expect(entries['targetOp']).toBeDefined();
|
|
206
|
+
expect(entries['testOp']).toBeDefined();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('context.types presence', () => {
|
|
211
|
+
it('exists as empty object when no parameterized codecs are registered', () => {
|
|
212
|
+
const context = createExecutionContext({
|
|
213
|
+
contract: testContract,
|
|
214
|
+
stack: createStack(),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(context.types).toBeDefined();
|
|
218
|
+
expect(context.types).toEqual({});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('contract/stack validation errors', () => {
|
|
223
|
+
it('throws RUNTIME.CONTRACT_FAMILY_MISMATCH when contract targetFamily differs from stack', () => {
|
|
224
|
+
const mismatchedFamilyContract = {
|
|
225
|
+
...testContract,
|
|
226
|
+
targetFamily: 'document',
|
|
227
|
+
} as unknown as Contract<SqlStorage>;
|
|
228
|
+
|
|
229
|
+
expect(() =>
|
|
230
|
+
createExecutionContext({ contract: mismatchedFamilyContract, stack: createStack() }),
|
|
231
|
+
).toThrow(
|
|
232
|
+
expect.objectContaining({
|
|
233
|
+
code: 'RUNTIME.CONTRACT_FAMILY_MISMATCH',
|
|
234
|
+
category: 'RUNTIME',
|
|
235
|
+
severity: 'error',
|
|
236
|
+
details: {
|
|
237
|
+
actual: 'document',
|
|
238
|
+
expected: 'sql',
|
|
239
|
+
},
|
|
240
|
+
}),
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('throws RUNTIME.CONTRACT_TARGET_MISMATCH when contract target differs from stack', () => {
|
|
245
|
+
const mismatchedContract: Contract<SqlStorage> = {
|
|
246
|
+
...testContract,
|
|
247
|
+
target: 'mysql',
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
expect(() =>
|
|
251
|
+
createExecutionContext({ contract: mismatchedContract, stack: createStack() }),
|
|
252
|
+
).toThrow(
|
|
253
|
+
expect.objectContaining({
|
|
254
|
+
code: 'RUNTIME.CONTRACT_TARGET_MISMATCH',
|
|
255
|
+
category: 'RUNTIME',
|
|
256
|
+
severity: 'error',
|
|
257
|
+
details: {
|
|
258
|
+
actual: 'mysql',
|
|
259
|
+
expected: 'postgres',
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('throws RUNTIME.MISSING_EXTENSION_PACK when contract requires extension not in stack', () => {
|
|
266
|
+
const contractWithExtension: Contract<SqlStorage> = {
|
|
267
|
+
...testContract,
|
|
268
|
+
extensionPacks: {
|
|
269
|
+
'required-extension': { id: 'required-extension', version: '1.0.0', capabilities: {} },
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
expect(() =>
|
|
274
|
+
createExecutionContext({ contract: contractWithExtension, stack: createStack() }),
|
|
275
|
+
).toThrow(
|
|
276
|
+
expect.objectContaining({
|
|
277
|
+
code: 'RUNTIME.MISSING_EXTENSION_PACK',
|
|
278
|
+
category: 'RUNTIME',
|
|
279
|
+
severity: 'error',
|
|
280
|
+
details: {
|
|
281
|
+
packIds: ['required-extension'],
|
|
282
|
+
},
|
|
283
|
+
}),
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('lists all missing extension packs in a single error', () => {
|
|
288
|
+
const contractWithExtensions: Contract<SqlStorage> = {
|
|
289
|
+
...testContract,
|
|
290
|
+
extensionPacks: {
|
|
291
|
+
'ext-a': { id: 'ext-a', version: '1.0.0', capabilities: {} },
|
|
292
|
+
'ext-b': { id: 'ext-b', version: '1.0.0', capabilities: {} },
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
expect(() =>
|
|
297
|
+
createExecutionContext({ contract: contractWithExtensions, stack: createStack() }),
|
|
298
|
+
).toThrow(
|
|
299
|
+
expect.objectContaining({
|
|
300
|
+
code: 'RUNTIME.MISSING_EXTENSION_PACK',
|
|
301
|
+
details: {
|
|
302
|
+
packIds: expect.arrayContaining(['ext-a', 'ext-b']),
|
|
303
|
+
},
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('applyMutationDefaults', () => {
|
|
310
|
+
const contractWithDefaults: Contract<SqlStorage> = {
|
|
311
|
+
...testContract,
|
|
312
|
+
storage: {
|
|
313
|
+
storageHash: coreHash('sha256:test'),
|
|
314
|
+
tables: {
|
|
315
|
+
user: {
|
|
316
|
+
columns: {
|
|
317
|
+
id: { nativeType: 'text', codecId: 'pg/text@1', nullable: false },
|
|
318
|
+
slug: { nativeType: 'text', codecId: 'pg/text@1', nullable: false },
|
|
319
|
+
},
|
|
320
|
+
uniques: [],
|
|
321
|
+
indexes: [],
|
|
322
|
+
foreignKeys: [],
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
execution: {
|
|
327
|
+
executionHash: executionHash('sha256:test'),
|
|
328
|
+
mutations: {
|
|
329
|
+
defaults: [
|
|
330
|
+
{
|
|
331
|
+
ref: { table: 'user', column: 'id' },
|
|
332
|
+
onCreate: { kind: 'generator', id: 'nanoid', params: { size: 8 } },
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
ref: { table: 'user', column: 'slug' },
|
|
336
|
+
onUpdate: { kind: 'generator', id: 'nanoid', params: { size: 6 } },
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
it('applies create defaults with generator params', () => {
|
|
344
|
+
const context = createExecutionContext({
|
|
345
|
+
contract: contractWithDefaults,
|
|
346
|
+
stack: createStack(),
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const applied = context.applyMutationDefaults({
|
|
350
|
+
op: 'create',
|
|
351
|
+
table: 'user',
|
|
352
|
+
values: {},
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
expect(applied).toEqual([
|
|
356
|
+
{
|
|
357
|
+
column: 'id',
|
|
358
|
+
value: expect.any(String),
|
|
359
|
+
},
|
|
360
|
+
]);
|
|
361
|
+
expect((applied[0]?.value as string).length).toBe(8);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('applies update defaults from onUpdate', () => {
|
|
365
|
+
const context = createExecutionContext({
|
|
366
|
+
contract: contractWithDefaults,
|
|
367
|
+
stack: createStack(),
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const applied = context.applyMutationDefaults({
|
|
371
|
+
op: 'update',
|
|
372
|
+
table: 'user',
|
|
373
|
+
values: {},
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
expect(applied).toEqual([
|
|
377
|
+
{
|
|
378
|
+
column: 'slug',
|
|
379
|
+
value: expect.any(String),
|
|
380
|
+
},
|
|
381
|
+
]);
|
|
382
|
+
expect((applied[0]?.value as string).length).toBe(6);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
@@ -1,42 +1,59 @@
|
|
|
1
|
-
import type { ExecutionPlan } from '@prisma-next/contract/types';
|
|
2
|
-
import
|
|
1
|
+
import type { Contract, ExecutionPlan } from '@prisma-next/contract/types';
|
|
2
|
+
import { coreHash, profileHash } from '@prisma-next/contract/types';
|
|
3
|
+
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
4
|
+
import type { AdapterProfile } from '@prisma-next/sql-relational-core/ast';
|
|
3
5
|
import { describe, expect, it } from 'vitest';
|
|
4
6
|
import { SqlFamilyAdapter } from '../src/sql-family-adapter';
|
|
5
7
|
|
|
6
8
|
// Minimal test contract
|
|
7
|
-
const testContract:
|
|
8
|
-
schemaVersion: '1',
|
|
9
|
+
const testContract: Contract<SqlStorage> = {
|
|
9
10
|
targetFamily: 'sql',
|
|
10
11
|
target: 'postgres',
|
|
11
|
-
|
|
12
|
+
profileHash: profileHash('sha256:test-hash'),
|
|
12
13
|
models: {},
|
|
13
|
-
|
|
14
|
-
storage: { tables: {} },
|
|
14
|
+
roots: {},
|
|
15
|
+
storage: { storageHash: coreHash('sha256:test-hash'), tables: {} },
|
|
15
16
|
extensionPacks: {},
|
|
16
17
|
capabilities: {},
|
|
17
18
|
meta: {},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const testProfile: AdapterProfile = {
|
|
22
|
+
id: 'test/default@1',
|
|
23
|
+
target: 'postgres',
|
|
24
|
+
capabilities: {},
|
|
25
|
+
codecs: () => {
|
|
26
|
+
throw new Error('not needed in test');
|
|
22
27
|
},
|
|
28
|
+
readMarkerStatement: () => ({
|
|
29
|
+
sql: 'SELECT core_hash, profile_hash FROM prisma_contract.marker WHERE id = $1',
|
|
30
|
+
params: [1],
|
|
31
|
+
}),
|
|
23
32
|
};
|
|
24
33
|
|
|
25
34
|
describe('SqlFamilyAdapter', () => {
|
|
26
35
|
it('creates adapter with contract and marker reader', () => {
|
|
27
|
-
const adapter = new SqlFamilyAdapter(testContract);
|
|
36
|
+
const adapter = new SqlFamilyAdapter(testContract, testProfile);
|
|
28
37
|
|
|
29
38
|
expect(adapter.contract).toBe(testContract);
|
|
30
39
|
expect(adapter.markerReader).toBeDefined();
|
|
31
40
|
expect(adapter.markerReader.readMarkerStatement).toBeDefined();
|
|
32
41
|
});
|
|
33
42
|
|
|
43
|
+
it('delegates readMarkerStatement to adapter profile', () => {
|
|
44
|
+
const adapter = new SqlFamilyAdapter(testContract, testProfile);
|
|
45
|
+
const stmt = adapter.markerReader.readMarkerStatement();
|
|
46
|
+
|
|
47
|
+
expect(stmt.sql).toContain('prisma_contract.marker');
|
|
48
|
+
expect(stmt.params).toEqual([1]);
|
|
49
|
+
});
|
|
50
|
+
|
|
34
51
|
it('validates plan with matching target and hash', () => {
|
|
35
|
-
const adapter = new SqlFamilyAdapter(testContract);
|
|
52
|
+
const adapter = new SqlFamilyAdapter(testContract, testProfile);
|
|
36
53
|
const plan: ExecutionPlan = {
|
|
37
54
|
meta: {
|
|
38
55
|
target: 'postgres',
|
|
39
|
-
|
|
56
|
+
storageHash: 'sha256:test-hash',
|
|
40
57
|
lane: 'sql',
|
|
41
58
|
paramDescriptors: [],
|
|
42
59
|
},
|
|
@@ -49,11 +66,11 @@ describe('SqlFamilyAdapter', () => {
|
|
|
49
66
|
});
|
|
50
67
|
|
|
51
68
|
it('throws on plan target mismatch', () => {
|
|
52
|
-
const adapter = new SqlFamilyAdapter(testContract);
|
|
69
|
+
const adapter = new SqlFamilyAdapter(testContract, testProfile);
|
|
53
70
|
const plan: ExecutionPlan = {
|
|
54
71
|
meta: {
|
|
55
72
|
target: 'mysql', // Wrong target
|
|
56
|
-
|
|
73
|
+
storageHash: 'sha256:test-hash',
|
|
57
74
|
lane: 'sql',
|
|
58
75
|
paramDescriptors: [],
|
|
59
76
|
},
|
|
@@ -66,12 +83,12 @@ describe('SqlFamilyAdapter', () => {
|
|
|
66
83
|
);
|
|
67
84
|
});
|
|
68
85
|
|
|
69
|
-
it('throws on plan
|
|
70
|
-
const adapter = new SqlFamilyAdapter(testContract);
|
|
86
|
+
it('throws on plan storageHash mismatch', () => {
|
|
87
|
+
const adapter = new SqlFamilyAdapter(testContract, testProfile);
|
|
71
88
|
const plan: ExecutionPlan = {
|
|
72
89
|
meta: {
|
|
73
90
|
target: 'postgres',
|
|
74
|
-
|
|
91
|
+
storageHash: 'sha256:different-hash', // Wrong hash
|
|
75
92
|
lane: 'sql',
|
|
76
93
|
paramDescriptors: [],
|
|
77
94
|
},
|
|
@@ -80,7 +97,7 @@ describe('SqlFamilyAdapter', () => {
|
|
|
80
97
|
};
|
|
81
98
|
|
|
82
99
|
expect(() => adapter.validatePlan(plan, testContract)).toThrow(
|
|
83
|
-
'Plan
|
|
100
|
+
'Plan storage hash does not match runtime contract',
|
|
84
101
|
);
|
|
85
102
|
});
|
|
86
103
|
});
|