@prisma-next/adapter-postgres 0.5.0-dev.6 → 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 +14 -16
- package/dist/adapter-D0MBaNqv.mjs +47 -0
- package/dist/adapter-D0MBaNqv.mjs.map +1 -0
- package/dist/adapter.d.mts +3 -4
- package/dist/adapter.d.mts.map +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/column-types.d.mts +20 -24
- package/dist/column-types.d.mts.map +1 -1
- package/dist/column-types.mjs +19 -58
- package/dist/column-types.mjs.map +1 -1
- package/dist/control.d.mts +76 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +48 -9
- package/dist/control.mjs.map +1 -1
- package/dist/{descriptor-meta-RTDzyrae.mjs → descriptor-meta-D8znZhXl.mjs} +35 -24
- package/dist/descriptor-meta-D8znZhXl.mjs.map +1 -0
- package/dist/operation-types.d.mts +11 -10
- package/dist/operation-types.d.mts.map +1 -1
- package/dist/runtime.d.mts +3 -11
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +17 -77
- package/dist/runtime.mjs.map +1 -1
- package/dist/{sql-renderer-pEaSP82_.mjs → sql-renderer-Qt6yk5Qj.mjs} +89 -46
- package/dist/sql-renderer-Qt6yk5Qj.mjs.map +1 -0
- package/dist/{types-CfRPdAk8.d.mts → types-tLtmYqCO.d.mts} +12 -1
- package/dist/types-tLtmYqCO.d.mts.map +1 -0
- package/dist/types.d.mts +1 -1
- package/package.json +21 -21
- package/src/core/adapter.ts +15 -41
- package/src/core/codec-lookup.ts +19 -0
- package/src/core/control-adapter.ts +68 -1
- package/src/core/control-mutation-defaults.ts +24 -18
- package/src/core/descriptor-meta.ts +39 -19
- package/src/core/sql-renderer.ts +111 -66
- package/src/core/types.ts +11 -0
- package/src/exports/column-types.ts +21 -61
- package/src/exports/control.ts +3 -2
- package/src/exports/runtime.ts +27 -66
- package/src/types/operation-types.ts +19 -9
- package/dist/adapter-hNElNHo4.mjs +0 -60
- package/dist/adapter-hNElNHo4.mjs.map +0 -1
- package/dist/descriptor-meta-RTDzyrae.mjs.map +0 -1
- package/dist/sql-renderer-pEaSP82_.mjs.map +0 -1
- package/dist/types-CfRPdAk8.d.mts.map +0 -1
- package/src/core/json-schema-validator.ts +0 -54
- package/src/core/standard-schema.ts +0 -71
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
2
|
+
import { extractCodecLookup } from '@prisma-next/framework-components/control';
|
|
3
|
+
import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build a {@link CodecLookup} populated with the Postgres-builtin codec definitions only.
|
|
7
|
+
*
|
|
8
|
+
* This is the default lookup used by `createPostgresAdapter()` and `new PostgresControlAdapter()` when called without a stack-derived lookup (e.g. from tests, or one-off scripts that don't compose a full stack).
|
|
9
|
+
*
|
|
10
|
+
* Extension codecs (e.g. `pg/vector@1` from `@prisma-next/extension-pgvector`) are intentionally NOT included here: a bare adapter cannot see extensions. Stack-composed paths (`SqlControlAdapterDescriptor.create(stack)` / `SqlRuntimeAdapterDescriptor.create(stack)`) supply the broader, extension-inclusive lookup at construction time.
|
|
11
|
+
*/
|
|
12
|
+
export function createPostgresBuiltinCodecLookup(): CodecLookup {
|
|
13
|
+
return extractCodecLookup([
|
|
14
|
+
{
|
|
15
|
+
id: 'postgres-builtin-codecs',
|
|
16
|
+
types: { codecTypes: { codecDescriptors: Array.from(postgresCodecRegistry.values()) } },
|
|
17
|
+
},
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { ContractMarkerRecord } from '@prisma-next/contract/types';
|
|
1
2
|
import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
|
|
3
|
+
import { parseContractMarkerRow } from '@prisma-next/family-sql/verify';
|
|
4
|
+
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
2
5
|
import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
|
|
3
6
|
import type {
|
|
4
7
|
AnyQueryAst,
|
|
@@ -19,6 +22,7 @@ import type {
|
|
|
19
22
|
import { parsePostgresDefault } from '@prisma-next/target-postgres/default-normalizer';
|
|
20
23
|
import { normalizeSchemaNativeType } from '@prisma-next/target-postgres/native-type-normalizer';
|
|
21
24
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
25
|
+
import { createPostgresBuiltinCodecLookup } from './codec-lookup';
|
|
22
26
|
import { pgEnumControlHooks } from './enum-control-hooks';
|
|
23
27
|
import { renderLoweredSql } from './sql-renderer';
|
|
24
28
|
import type { PostgresContract } from './types';
|
|
@@ -31,6 +35,19 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
31
35
|
readonly familyId = 'sql' as const;
|
|
32
36
|
readonly targetId = 'postgres' as const;
|
|
33
37
|
|
|
38
|
+
private readonly codecLookup: CodecLookup;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param codecLookup - Codec lookup used by the SQL renderer to resolve
|
|
42
|
+
* per-codec metadata at lower-time. Defaults to a Postgres-builtins-only
|
|
43
|
+
* lookup when omitted. Stack-aware callers
|
|
44
|
+
* (`SqlControlAdapterDescriptor.create(stack)`) supply
|
|
45
|
+
* `stack.codecLookup` so extension codecs are visible to the renderer.
|
|
46
|
+
*/
|
|
47
|
+
constructor(codecLookup?: CodecLookup) {
|
|
48
|
+
this.codecLookup = codecLookup ?? createPostgresBuiltinCodecLookup();
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
/**
|
|
35
52
|
* Target-specific normalizer for raw Postgres default expressions.
|
|
36
53
|
* Used by schema verification to normalize raw defaults before comparison.
|
|
@@ -53,7 +70,57 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
53
70
|
* without instantiating the runtime adapter.
|
|
54
71
|
*/
|
|
55
72
|
lower(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement {
|
|
56
|
-
return renderLoweredSql(ast, context.contract as PostgresContract);
|
|
73
|
+
return renderLoweredSql(ast, context.contract as PostgresContract, this.codecLookup);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Reads the contract marker from `prisma_contract.marker`. Probes
|
|
78
|
+
* `information_schema.tables` first so a fresh database (where the
|
|
79
|
+
* `prisma_contract` schema doesn't yet exist) returns `null` instead of a
|
|
80
|
+
* "relation does not exist" error — some Postgres wire-protocol clients
|
|
81
|
+
* (e.g. PGlite's TCP proxy) don't fully recover from extended-protocol
|
|
82
|
+
* parse errors, so we probe before reading.
|
|
83
|
+
*/
|
|
84
|
+
async readMarker(
|
|
85
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
86
|
+
): Promise<ContractMarkerRecord | null> {
|
|
87
|
+
const exists = await driver.query(
|
|
88
|
+
`select 1
|
|
89
|
+
from information_schema.tables
|
|
90
|
+
where table_schema = $1 and table_name = $2`,
|
|
91
|
+
['prisma_contract', 'marker'],
|
|
92
|
+
);
|
|
93
|
+
if (exists.rows.length === 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = await driver.query<{
|
|
98
|
+
core_hash: string;
|
|
99
|
+
profile_hash: string;
|
|
100
|
+
contract_json: unknown | null;
|
|
101
|
+
canonical_version: number | null;
|
|
102
|
+
updated_at: Date | string;
|
|
103
|
+
app_tag: string | null;
|
|
104
|
+
meta: unknown | null;
|
|
105
|
+
invariants: readonly string[];
|
|
106
|
+
}>(
|
|
107
|
+
`select
|
|
108
|
+
core_hash,
|
|
109
|
+
profile_hash,
|
|
110
|
+
contract_json,
|
|
111
|
+
canonical_version,
|
|
112
|
+
updated_at,
|
|
113
|
+
app_tag,
|
|
114
|
+
meta,
|
|
115
|
+
invariants
|
|
116
|
+
from prisma_contract.marker
|
|
117
|
+
where id = $1`,
|
|
118
|
+
[1],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const row = result.rows[0];
|
|
122
|
+
if (!row) return null;
|
|
123
|
+
return parseContractMarkerRow(row);
|
|
57
124
|
}
|
|
58
125
|
|
|
59
126
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExecutionMutationDefaultValue } from '@prisma-next/contract/types';
|
|
2
|
+
import { timestampNowControlDescriptor } from '@prisma-next/family-sql/control';
|
|
2
3
|
import type {
|
|
3
4
|
ControlMutationDefaultEntry,
|
|
4
5
|
DefaultFunctionLoweringContext,
|
|
@@ -301,25 +302,30 @@ export function createPostgresDefaultFunctionRegistry(): ReadonlyMap<
|
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
export function createPostgresMutationDefaultGeneratorDescriptors(): readonly MutationDefaultGeneratorDescriptor[] {
|
|
304
|
-
return
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
resolveGeneratedColumnDescriptor: ({ generated }) => {
|
|
308
|
-
if (generated.kind !== 'generator' || generated.id !== id) {
|
|
309
|
-
return undefined;
|
|
310
|
-
}
|
|
311
|
-
const descriptor = resolveBuiltinGeneratedColumnDescriptor({
|
|
305
|
+
return [
|
|
306
|
+
...builtinGeneratorRegistryMetadata.map(
|
|
307
|
+
({ id, applicableCodecIds }): MutationDefaultGeneratorDescriptor => ({
|
|
312
308
|
id,
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
309
|
+
applicableCodecIds,
|
|
310
|
+
resolveGeneratedColumnDescriptor: ({ generated }) => {
|
|
311
|
+
if (generated.kind !== 'generator' || generated.id !== id) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
const descriptor = resolveBuiltinGeneratedColumnDescriptor({
|
|
315
|
+
id,
|
|
316
|
+
...(generated.params ? { params: generated.params } : {}),
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
codecId: descriptor.type.codecId,
|
|
320
|
+
nativeType: descriptor.type.nativeType,
|
|
321
|
+
...(descriptor.type.typeRef ? { typeRef: descriptor.type.typeRef } : {}),
|
|
322
|
+
...(descriptor.typeParams ? { typeParams: descriptor.typeParams } : {}),
|
|
323
|
+
};
|
|
324
|
+
},
|
|
325
|
+
}),
|
|
326
|
+
),
|
|
327
|
+
timestampNowControlDescriptor(),
|
|
328
|
+
];
|
|
323
329
|
}
|
|
324
330
|
|
|
325
331
|
export function createPostgresScalarTypeDescriptors(): ReadonlyMap<string, string> {
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import type { CodecControlHooks, ExpandNativeTypeInput } from '@prisma-next/family-sql/control';
|
|
2
2
|
import type { SqlOperationDescriptor } from '@prisma-next/sql-operations';
|
|
3
|
+
import {
|
|
4
|
+
buildOperation,
|
|
5
|
+
type CodecExpression,
|
|
6
|
+
type Expression,
|
|
7
|
+
refsOf,
|
|
8
|
+
type TraitExpression,
|
|
9
|
+
toExpr,
|
|
10
|
+
} from '@prisma-next/sql-relational-core/expression';
|
|
3
11
|
import {
|
|
4
12
|
PG_BIT_CODEC_ID,
|
|
5
13
|
PG_BOOL_CODEC_ID,
|
|
14
|
+
PG_BYTEA_CODEC_ID,
|
|
6
15
|
PG_CHAR_CODEC_ID,
|
|
7
16
|
PG_ENUM_CODEC_ID,
|
|
8
17
|
PG_FLOAT_CODEC_ID,
|
|
@@ -30,12 +39,10 @@ import {
|
|
|
30
39
|
SQL_TIMESTAMP_CODEC_ID,
|
|
31
40
|
SQL_VARCHAR_CODEC_ID,
|
|
32
41
|
} from '@prisma-next/target-postgres/codec-ids';
|
|
33
|
-
import {
|
|
42
|
+
import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
|
|
34
43
|
import { pgEnumControlHooks } from './enum-control-hooks';
|
|
35
44
|
|
|
36
|
-
// ============================================================================
|
|
37
|
-
// Helper functions for reducing boilerplate
|
|
38
|
-
// ============================================================================
|
|
45
|
+
// ============================================================================ Helper functions for reducing boilerplate ============================================================================
|
|
39
46
|
|
|
40
47
|
/** Creates a type import spec for codec types */
|
|
41
48
|
const codecTypeImport = (named: string) =>
|
|
@@ -124,21 +131,32 @@ const precisionHooks: CodecControlHooks = { expandNativeType: expandPrecision };
|
|
|
124
131
|
const numericHooks: CodecControlHooks = { expandNativeType: expandNumeric };
|
|
125
132
|
const identityHooks: CodecControlHooks = { expandNativeType: ({ nativeType }) => nativeType };
|
|
126
133
|
|
|
127
|
-
// ============================================================================
|
|
128
|
-
// Descriptor metadata
|
|
129
|
-
// ============================================================================
|
|
134
|
+
// ============================================================================ Descriptor metadata ============================================================================
|
|
130
135
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
]
|
|
136
|
+
type CodecTypesBase = Record<string, { readonly input: unknown; readonly output: unknown }>;
|
|
137
|
+
|
|
138
|
+
export function postgresQueryOperations<
|
|
139
|
+
CT extends CodecTypesBase,
|
|
140
|
+
>(): readonly SqlOperationDescriptor[] {
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
method: 'ilike',
|
|
144
|
+
self: { traits: ['textual'] },
|
|
145
|
+
impl: (
|
|
146
|
+
self: TraitExpression<readonly ['textual'], false, CT>,
|
|
147
|
+
pattern: CodecExpression<'pg/text@1', false, CT>,
|
|
148
|
+
): Expression<{ codecId: 'pg/bool@1'; nullable: false }> => {
|
|
149
|
+
const selfRefs = refsOf(self);
|
|
150
|
+
return buildOperation({
|
|
151
|
+
method: 'ilike',
|
|
152
|
+
args: [toExpr(self), toExpr(pattern, PG_TEXT_CODEC_ID, selfRefs)],
|
|
153
|
+
returns: { codecId: PG_BOOL_CODEC_ID, nullable: false },
|
|
154
|
+
lowering: { targetFamily: 'sql', strategy: 'infix', template: '{{self}} ILIKE {{arg0}}' },
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
}
|
|
142
160
|
|
|
143
161
|
export const postgresAdapterDescriptorMeta = {
|
|
144
162
|
kind: 'adapter',
|
|
@@ -162,7 +180,7 @@ export const postgresAdapterDescriptorMeta = {
|
|
|
162
180
|
},
|
|
163
181
|
types: {
|
|
164
182
|
codecTypes: {
|
|
165
|
-
|
|
183
|
+
codecDescriptors: Array.from(postgresCodecRegistry.values()),
|
|
166
184
|
import: {
|
|
167
185
|
package: '@prisma-next/target-postgres/codec-types',
|
|
168
186
|
named: 'CodecTypes',
|
|
@@ -202,6 +220,7 @@ export const postgresAdapterDescriptorMeta = {
|
|
|
202
220
|
[PG_ENUM_CODEC_ID]: pgEnumControlHooks,
|
|
203
221
|
[PG_JSON_CODEC_ID]: identityHooks,
|
|
204
222
|
[PG_JSONB_CODEC_ID]: identityHooks,
|
|
223
|
+
[PG_BYTEA_CODEC_ID]: identityHooks,
|
|
205
224
|
},
|
|
206
225
|
},
|
|
207
226
|
storage: [
|
|
@@ -267,6 +286,7 @@ export const postgresAdapterDescriptorMeta = {
|
|
|
267
286
|
},
|
|
268
287
|
{ typeId: PG_JSON_CODEC_ID, familyId: 'sql', targetId: 'postgres', nativeType: 'json' },
|
|
269
288
|
{ typeId: PG_JSONB_CODEC_ID, familyId: 'sql', targetId: 'postgres', nativeType: 'jsonb' },
|
|
289
|
+
{ typeId: PG_BYTEA_CODEC_ID, familyId: 'sql', targetId: 'postgres', nativeType: 'bytea' },
|
|
270
290
|
],
|
|
271
291
|
queryOperationTypes: {
|
|
272
292
|
import: {
|
package/src/core/sql-renderer.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
1
2
|
import {
|
|
2
3
|
type AggregateExpr,
|
|
3
4
|
type AnyExpression,
|
|
@@ -5,6 +6,7 @@ import {
|
|
|
5
6
|
type AnyQueryAst,
|
|
6
7
|
type BinaryExpr,
|
|
7
8
|
type ColumnRef,
|
|
9
|
+
collectOrderedParamRefs,
|
|
8
10
|
type DeleteAst,
|
|
9
11
|
type InsertAst,
|
|
10
12
|
type InsertValue,
|
|
@@ -23,83 +25,116 @@ import {
|
|
|
23
25
|
type SubqueryExpr,
|
|
24
26
|
type UpdateAst,
|
|
25
27
|
} from '@prisma-next/sql-relational-core/ast';
|
|
26
|
-
import { PG_JSON_CODEC_ID, PG_JSONB_CODEC_ID } from '@prisma-next/target-postgres/codec-ids';
|
|
27
28
|
import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
|
|
28
29
|
import type { PostgresContract } from './types';
|
|
29
30
|
|
|
30
|
-
// Mirrors `VECTOR_CODEC_ID` in `@prisma-next/extension-pgvector/core/constants`.
|
|
31
|
-
// Duplicated here rather than imported because the canonical export is not
|
|
32
|
-
// part of the extension's public subpath surface, and `@prisma-next/adapter-postgres`
|
|
33
|
-
// does not (and should not) take a runtime dependency on the extension package
|
|
34
|
-
// just for one constant. The whole `getCodecParamCast` switch is slated for
|
|
35
|
-
// removal under TML-2310 ("Move SQL param-cast metadata onto codec descriptors"),
|
|
36
|
-
// at which point this and the JSON/JSONB IDs below also disappear.
|
|
37
|
-
const VECTOR_CODEC_ID = 'pg/vector@1' as const;
|
|
38
|
-
|
|
39
31
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
32
|
+
* Postgres native types whose unknown-OID parameter inference is reliable in arbitrary expression positions. Parameters bound to a codec whose `meta.db.sql.postgres.nativeType` falls in this set are emitted as plain `$N`; everything else (including `json`, `jsonb`, extension types like `vector`, and unknown user types) is emitted as `$N::<nativeType>` so the planner picks an unambiguous overload.
|
|
33
|
+
*
|
|
34
|
+
* `json` / `jsonb` are intentionally excluded despite being Postgres builtins: their operator overloads make context inference unreliable in expression positions (e.g. `$1 -> 'key'` is ambiguous between the two).
|
|
42
35
|
*
|
|
43
|
-
*
|
|
44
|
-
* TML-2310 ("Move SQL param-cast metadata onto codec descriptors").
|
|
45
|
-
* Until that lands the cast lives on the renderer rather than the codec.
|
|
36
|
+
* Spellings match the on-disk `meta.db.sql.postgres.nativeType` values in `@prisma-next/target-postgres`'s codec definitions, not the `udt_name` abbreviations that ADR 205 used as illustrative shorthand. The lookup-based cast policy compares against these strings directly.
|
|
46
37
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
38
|
+
const POSTGRES_INFERRABLE_NATIVE_TYPES: ReadonlySet<string> = new Set([
|
|
39
|
+
// Numeric
|
|
40
|
+
'integer',
|
|
41
|
+
'smallint',
|
|
42
|
+
'bigint',
|
|
43
|
+
'real',
|
|
44
|
+
'double precision',
|
|
45
|
+
'numeric',
|
|
46
|
+
// Boolean
|
|
47
|
+
'boolean',
|
|
48
|
+
// Strings
|
|
49
|
+
'text',
|
|
50
|
+
'character',
|
|
51
|
+
'character varying',
|
|
52
|
+
// Temporal
|
|
53
|
+
'timestamp',
|
|
54
|
+
'timestamp without time zone',
|
|
55
|
+
'timestamp with time zone',
|
|
56
|
+
'time',
|
|
57
|
+
'timetz',
|
|
58
|
+
'interval',
|
|
59
|
+
// Bit strings
|
|
60
|
+
'bit',
|
|
61
|
+
'bit varying',
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
function renderTypedParam(
|
|
65
|
+
index: number,
|
|
66
|
+
codecId: string | undefined,
|
|
67
|
+
codecLookup: CodecLookup,
|
|
68
|
+
): string {
|
|
69
|
+
if (codecId === undefined) {
|
|
70
|
+
return `$${index}`;
|
|
71
|
+
}
|
|
72
|
+
if (codecLookup.get(codecId) === undefined) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Postgres lowering: ParamRef carries codecId "${codecId}" but the ` +
|
|
75
|
+
'assembled codec lookup has no entry for it. This usually indicates ' +
|
|
76
|
+
'a missing extension pack in the runtime stack — register the pack ' +
|
|
77
|
+
'that contributes this codec (e.g. `extensionPacks: [pgvectorRuntime]`), ' +
|
|
78
|
+
'or use the codec directly from `@prisma-next/target-postgres/codecs` ' +
|
|
79
|
+
"if it's a builtin.",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
// The framework `CodecLookup.metaFor` returns the family-agnostic `CodecMeta` whose `db` is `Record<string, unknown>`. The SQL family populates a narrower shape with `db.sql.<dialect>.nativeType: string`; navigate that path defensively and string-check the leaf.
|
|
83
|
+
const meta = codecLookup.metaFor(codecId);
|
|
84
|
+
const dbRecord = meta?.db;
|
|
85
|
+
const sqlBlock = isRecord(dbRecord) ? dbRecord['sql'] : undefined;
|
|
86
|
+
const dialectBlock = isRecord(sqlBlock) ? sqlBlock['postgres'] : undefined;
|
|
87
|
+
const nativeType = isRecord(dialectBlock) ? dialectBlock['nativeType'] : undefined;
|
|
88
|
+
if (typeof nativeType === 'string' && !POSTGRES_INFERRABLE_NATIVE_TYPES.has(nativeType)) {
|
|
89
|
+
return `$${index}::${nativeType}`;
|
|
90
|
+
}
|
|
91
|
+
return `$${index}`;
|
|
58
92
|
}
|
|
59
93
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
return cast ? `$${index}::${cast}` : `$${index}`;
|
|
94
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
95
|
+
return typeof value === 'object' && value !== null;
|
|
63
96
|
}
|
|
64
97
|
|
|
65
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Per-render carrier threaded through every helper. Bundles the param-index map (for `$N` numbering) and the assembled-stack `codecLookup` (for cast policy at the `renderTypedParam` chokepoint). Carrying both on a single value keeps helper signatures stable.
|
|
100
|
+
*/
|
|
101
|
+
interface ParamIndexMap {
|
|
102
|
+
readonly indexMap: Map<ParamRef, number>;
|
|
103
|
+
readonly codecLookup: CodecLookup;
|
|
104
|
+
}
|
|
66
105
|
|
|
67
106
|
/**
|
|
68
107
|
* Render a SQL query AST to a Postgres-flavored `{ sql, params }` payload.
|
|
69
108
|
*
|
|
70
|
-
* Shared between the runtime (`PostgresAdapterImpl.lower`) and control
|
|
71
|
-
* (`PostgresControlAdapter.lower`) entrypoints so emit-time and run-time
|
|
72
|
-
* paths produce byte-identical output for the same AST.
|
|
109
|
+
* Shared between the runtime (`PostgresAdapterImpl.lower`) and control (`PostgresControlAdapter.lower`) entrypoints so emit-time and run-time paths produce byte-identical output for the same AST.
|
|
73
110
|
*/
|
|
74
111
|
export function renderLoweredSql(
|
|
75
112
|
ast: AnyQueryAst,
|
|
76
113
|
contract: PostgresContract,
|
|
114
|
+
codecLookup: CodecLookup,
|
|
77
115
|
): { readonly sql: string; readonly params: readonly unknown[] } {
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const params: unknown[] =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
paramIndexMap.set(ref, params.length + 1);
|
|
86
|
-
params.push(ref.value);
|
|
87
|
-
}
|
|
116
|
+
const orderedRefs = collectOrderedParamRefs(ast);
|
|
117
|
+
const indexMap = new Map<ParamRef, number>();
|
|
118
|
+
const params: unknown[] = orderedRefs.map((ref, i) => {
|
|
119
|
+
indexMap.set(ref, i + 1);
|
|
120
|
+
return ref.value;
|
|
121
|
+
});
|
|
122
|
+
const pim: ParamIndexMap = { indexMap, codecLookup };
|
|
88
123
|
|
|
89
124
|
const node = ast;
|
|
90
125
|
let sql: string;
|
|
91
126
|
switch (node.kind) {
|
|
92
127
|
case 'select':
|
|
93
|
-
sql = renderSelect(node, contract,
|
|
128
|
+
sql = renderSelect(node, contract, pim);
|
|
94
129
|
break;
|
|
95
130
|
case 'insert':
|
|
96
|
-
sql = renderInsert(node, contract,
|
|
131
|
+
sql = renderInsert(node, contract, pim);
|
|
97
132
|
break;
|
|
98
133
|
case 'update':
|
|
99
|
-
sql = renderUpdate(node, contract,
|
|
134
|
+
sql = renderUpdate(node, contract, pim);
|
|
100
135
|
break;
|
|
101
136
|
case 'delete':
|
|
102
|
-
sql = renderDelete(node, contract,
|
|
137
|
+
sql = renderDelete(node, contract, pim);
|
|
103
138
|
break;
|
|
104
139
|
// v8 ignore next 4
|
|
105
140
|
default:
|
|
@@ -171,6 +206,27 @@ function renderProjection(
|
|
|
171
206
|
.join(', ');
|
|
172
207
|
}
|
|
173
208
|
|
|
209
|
+
function renderReturning(
|
|
210
|
+
items: ReadonlyArray<ProjectionItem>,
|
|
211
|
+
contract: PostgresContract,
|
|
212
|
+
pim: ParamIndexMap,
|
|
213
|
+
): string {
|
|
214
|
+
return items
|
|
215
|
+
.map((item) => {
|
|
216
|
+
if (item.expr.kind === 'column-ref') {
|
|
217
|
+
const rendered = renderColumn(item.expr);
|
|
218
|
+
return item.expr.column === item.alias
|
|
219
|
+
? rendered
|
|
220
|
+
: `${rendered} AS ${quoteIdentifier(item.alias)}`;
|
|
221
|
+
}
|
|
222
|
+
if (item.expr.kind === 'literal') {
|
|
223
|
+
return `${renderLiteral(item.expr)} AS ${quoteIdentifier(item.alias)}`;
|
|
224
|
+
}
|
|
225
|
+
return `${renderExpr(item.expr, contract, pim)} AS ${quoteIdentifier(item.alias)}`;
|
|
226
|
+
})
|
|
227
|
+
.join(', ');
|
|
228
|
+
}
|
|
229
|
+
|
|
174
230
|
function renderDistinctPrefix(
|
|
175
231
|
distinct: true | undefined,
|
|
176
232
|
distinctOn: ReadonlyArray<AnyExpression> | undefined,
|
|
@@ -241,15 +297,9 @@ function renderNullCheck(
|
|
|
241
297
|
}
|
|
242
298
|
|
|
243
299
|
/**
|
|
244
|
-
* Atomic expression kinds whose rendered SQL is already self-delimited
|
|
245
|
-
* (a column reference, parameter, literal, function call, aggregate, etc.)
|
|
246
|
-
* and therefore does not need surrounding parentheses when used as the
|
|
247
|
-
* left operand of a postfix predicate like `IS NULL` or `IS NOT NULL`,
|
|
248
|
-
* or as either operand of a binary infix operator.
|
|
300
|
+
* Atomic expression kinds whose rendered SQL is already self-delimited (a column reference, parameter, literal, function call, aggregate, etc.) and therefore does not need surrounding parentheses when used as the left operand of a postfix predicate like `IS NULL` or `IS NOT NULL`, or as either operand of a binary infix operator.
|
|
249
301
|
*
|
|
250
|
-
* Anything not in this set is treated as composite (binary, AND/OR/NOT,
|
|
251
|
-
* EXISTS, nested IS NULL, subqueries, operation templates) and gets
|
|
252
|
-
* wrapped to preserve grouping.
|
|
302
|
+
* Anything not in this set is treated as composite (binary, AND/OR/NOT, EXISTS, nested IS NULL, subqueries, operation templates) and gets wrapped to preserve grouping.
|
|
253
303
|
*/
|
|
254
304
|
function isAtomicExpressionKind(kind: AnyExpression['kind']): boolean {
|
|
255
305
|
switch (kind) {
|
|
@@ -457,11 +507,11 @@ function renderExpr(expr: AnyExpression, contract: PostgresContract, pim: ParamI
|
|
|
457
507
|
}
|
|
458
508
|
|
|
459
509
|
function renderParamRef(ref: ParamRef, pim: ParamIndexMap): string {
|
|
460
|
-
const index = pim.get(ref);
|
|
510
|
+
const index = pim.indexMap.get(ref);
|
|
461
511
|
if (index === undefined) {
|
|
462
512
|
throw new Error('ParamRef not found in index map');
|
|
463
513
|
}
|
|
464
|
-
return renderTypedParam(index, ref.codecId);
|
|
514
|
+
return renderTypedParam(index, ref.codecId, pim.codecLookup);
|
|
465
515
|
}
|
|
466
516
|
|
|
467
517
|
function renderLiteral(expr: LiteralExpr): string {
|
|
@@ -503,12 +553,7 @@ function renderOperation(
|
|
|
503
553
|
return renderExpr(arg, contract, pim);
|
|
504
554
|
});
|
|
505
555
|
|
|
506
|
-
// Resolve `{{self}}` and `{{argN}}` from the original template in a single
|
|
507
|
-
// pass. Doing this with sequential `String.prototype.replace` calls is
|
|
508
|
-
// unsafe: a substituted fragment can itself contain text that matches a
|
|
509
|
-
// later token (e.g. an arg literal containing the substring `{{arg1}}`),
|
|
510
|
-
// and the next iteration would corrupt it. A single regex callback never
|
|
511
|
-
// re-scans already-substituted output.
|
|
556
|
+
// Resolve `{{self}}` and `{{argN}}` from the original template in a single pass. Doing this with sequential `String.prototype.replace` calls is unsafe: a substituted fragment can itself contain text that matches a later token (e.g. an arg literal containing the substring `{{arg1}}`), and the next iteration would corrupt it. A single regex callback never re-scans already-substituted output.
|
|
512
557
|
return expr.lowering.template.replace(
|
|
513
558
|
/\{\{self\}\}|\{\{arg(\d+)\}\}/g,
|
|
514
559
|
(token, argIndex: string | undefined) => {
|
|
@@ -660,7 +705,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
|
|
|
660
705
|
})()
|
|
661
706
|
: '';
|
|
662
707
|
const returningClause = ast.returning?.length
|
|
663
|
-
? ` RETURNING ${ast.returning
|
|
708
|
+
? ` RETURNING ${renderReturning(ast.returning, contract, pim)}`
|
|
664
709
|
: '';
|
|
665
710
|
|
|
666
711
|
return `${insertClause}${onConflictClause}${returningClause}`;
|
|
@@ -693,7 +738,7 @@ function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamInde
|
|
|
693
738
|
|
|
694
739
|
const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
|
|
695
740
|
const returningClause = ast.returning?.length
|
|
696
|
-
? ` RETURNING ${ast.returning
|
|
741
|
+
? ` RETURNING ${renderReturning(ast.returning, contract, pim)}`
|
|
697
742
|
: '';
|
|
698
743
|
|
|
699
744
|
return `UPDATE ${table} SET ${setClauses.join(', ')}${whereClause}${returningClause}`;
|
|
@@ -703,7 +748,7 @@ function renderDelete(ast: DeleteAst, contract: PostgresContract, pim: ParamInde
|
|
|
703
748
|
const table = quoteIdentifier(ast.table.name);
|
|
704
749
|
const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
|
|
705
750
|
const returningClause = ast.returning?.length
|
|
706
|
-
? ` RETURNING ${ast.returning
|
|
751
|
+
? ` RETURNING ${renderReturning(ast.returning, contract, pim)}`
|
|
707
752
|
: '';
|
|
708
753
|
|
|
709
754
|
return `DELETE FROM ${table}${whereClause}${returningClause}`;
|
package/src/core/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
2
3
|
import type { SqlStorage, StorageColumn, StorageTable } from '@prisma-next/sql-contract/types';
|
|
3
4
|
import type {
|
|
4
5
|
AnyQueryAst,
|
|
@@ -19,6 +20,16 @@ import type {
|
|
|
19
20
|
|
|
20
21
|
export interface PostgresAdapterOptions {
|
|
21
22
|
readonly profileId?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Codec lookup used by the SQL renderer to resolve per-codec metadata at
|
|
25
|
+
* lower-time. Defaults to a Postgres-builtins-only lookup when omitted —
|
|
26
|
+
* see {@link createPostgresBuiltinCodecLookup} in `./codec-lookup`.
|
|
27
|
+
*
|
|
28
|
+
* Stack-aware callers (`SqlRuntimeAdapterDescriptor.create(stack)` /
|
|
29
|
+
* `SqlControlAdapterDescriptor.create(stack)`) supply the assembled stack
|
|
30
|
+
* lookup so extension codecs are visible to the renderer.
|
|
31
|
+
*/
|
|
32
|
+
readonly codecLookup?: CodecLookup;
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
export type PostgresContract = Contract<SqlStorage> & { readonly target: 'postgres' };
|