@prisma-next/adapter-postgres 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 +21 -15
- package/dist/adapter-_L4wXA4O.mjs +64 -0
- package/dist/adapter-_L4wXA4O.mjs.map +1 -0
- package/dist/adapter.d.mts +3 -8
- package/dist/adapter.d.mts.map +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/column-types.d.mts +15 -23
- package/dist/column-types.d.mts.map +1 -1
- package/dist/column-types.mjs +15 -58
- package/dist/column-types.mjs.map +1 -1
- package/dist/control.d.mts +73 -67
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +59 -162
- package/dist/control.mjs.map +1 -1
- package/dist/{descriptor-meta-BB9XPAFi.mjs → descriptor-meta-Dxnoq_rr.mjs} +27 -26
- package/dist/descriptor-meta-Dxnoq_rr.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 +23 -66
- package/dist/runtime.mjs.map +1 -1
- package/dist/{adapter-Du9Hr9Rl.mjs → sql-renderer-DLwYpnxz.mjs} +176 -113
- package/dist/sql-renderer-DLwYpnxz.mjs.map +1 -0
- package/dist/{types-TyL62f9Y.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 +22 -18
- package/src/core/adapter.ts +13 -626
- package/src/core/codec-lookup.ts +24 -0
- package/src/core/control-adapter.ts +88 -47
- package/src/core/descriptor-meta.ts +34 -16
- package/src/core/enum-control-hooks.ts +7 -2
- package/src/core/sql-renderer.ts +778 -0
- package/src/core/types.ts +11 -0
- package/src/exports/column-types.ts +14 -59
- package/src/exports/control.ts +11 -5
- package/src/exports/runtime.ts +29 -42
- package/src/types/operation-types.ts +19 -9
- package/dist/adapter-Du9Hr9Rl.mjs.map +0 -1
- package/dist/codec-ids-5g4Gwrgm.mjs +0 -29
- package/dist/codec-ids-5g4Gwrgm.mjs.map +0 -1
- package/dist/codec-types.d.mts +0 -107
- package/dist/codec-types.d.mts.map +0 -1
- package/dist/codec-types.mjs +0 -3
- package/dist/codecs-DiPlMi3-.mjs +0 -385
- package/dist/codecs-DiPlMi3-.mjs.map +0 -1
- package/dist/descriptor-meta-BB9XPAFi.mjs.map +0 -1
- package/dist/sql-utils-DkUJyZmA.mjs +0 -78
- package/dist/sql-utils-DkUJyZmA.mjs.map +0 -1
- package/dist/types-TyL62f9Y.d.mts.map +0 -1
- package/src/core/codec-ids.ts +0 -30
- package/src/core/codecs.ts +0 -645
- package/src/core/default-normalizer.ts +0 -145
- package/src/core/json-schema-type-expression.ts +0 -131
- package/src/core/json-schema-validator.ts +0 -53
- package/src/core/sql-utils.ts +0 -111
- package/src/core/standard-schema.ts +0 -71
- package/src/exports/codec-types.ts +0 -44
package/src/core/adapter.ts
CHANGED
|
@@ -1,61 +1,19 @@
|
|
|
1
|
+
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
1
2
|
import {
|
|
2
3
|
type Adapter,
|
|
3
4
|
type AdapterProfile,
|
|
4
|
-
type AggregateExpr,
|
|
5
|
-
type AnyExpression,
|
|
6
|
-
type AnyFromSource,
|
|
7
5
|
type AnyQueryAst,
|
|
8
|
-
type BinaryExpr,
|
|
9
6
|
type CodecParamsDescriptor,
|
|
10
|
-
type ColumnRef,
|
|
11
7
|
createCodecRegistry,
|
|
12
|
-
type DeleteAst,
|
|
13
|
-
type InsertAst,
|
|
14
|
-
type InsertValue,
|
|
15
|
-
type JoinAst,
|
|
16
|
-
type JoinOnExpr,
|
|
17
|
-
type JsonArrayAggExpr,
|
|
18
|
-
type JsonObjectExpr,
|
|
19
|
-
type ListExpression,
|
|
20
|
-
LiteralExpr,
|
|
21
8
|
type LowererContext,
|
|
22
|
-
type NullCheckExpr,
|
|
23
|
-
type OperationExpr,
|
|
24
|
-
type OrderByItem,
|
|
25
|
-
type ParamRef,
|
|
26
|
-
type ProjectionItem,
|
|
27
|
-
type SelectAst,
|
|
28
|
-
type SubqueryExpr,
|
|
29
|
-
type UpdateAst,
|
|
30
9
|
} from '@prisma-next/sql-relational-core/ast';
|
|
10
|
+
import { parseContractMarkerRow } from '@prisma-next/sql-runtime';
|
|
11
|
+
import { codecDefinitions } from '@prisma-next/target-postgres/codecs';
|
|
31
12
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import { escapeLiteral, quoteIdentifier } from './sql-utils';
|
|
13
|
+
import { createPostgresBuiltinCodecLookup } from './codec-lookup';
|
|
14
|
+
import { renderLoweredSql } from './sql-renderer';
|
|
35
15
|
import type { PostgresAdapterOptions, PostgresContract, PostgresLoweredStatement } from './types';
|
|
36
16
|
|
|
37
|
-
const VECTOR_CODEC_ID = 'pg/vector@1' as const;
|
|
38
|
-
|
|
39
|
-
function getCodecParamCast(codecId: string | undefined): string | undefined {
|
|
40
|
-
if (codecId === VECTOR_CODEC_ID) {
|
|
41
|
-
return 'vector';
|
|
42
|
-
}
|
|
43
|
-
if (codecId === PG_JSON_CODEC_ID) {
|
|
44
|
-
return 'json';
|
|
45
|
-
}
|
|
46
|
-
if (codecId === PG_JSONB_CODEC_ID) {
|
|
47
|
-
return 'jsonb';
|
|
48
|
-
}
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function renderTypedParam(index: number, codecId: string | undefined): string {
|
|
53
|
-
const cast = getCodecParamCast(codecId);
|
|
54
|
-
return cast ? `$${index}::${cast}` : `$${index}`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
type ParamIndexMap = Map<ParamRef, number>;
|
|
58
|
-
|
|
59
17
|
const defaultCapabilities = Object.freeze({
|
|
60
18
|
postgres: {
|
|
61
19
|
orderBy: true,
|
|
@@ -103,17 +61,22 @@ class PostgresAdapterImpl
|
|
|
103
61
|
}
|
|
104
62
|
return registry;
|
|
105
63
|
})();
|
|
64
|
+
private readonly codecLookup: CodecLookup;
|
|
106
65
|
|
|
107
66
|
constructor(options?: PostgresAdapterOptions) {
|
|
67
|
+
this.codecLookup = options?.codecLookup ?? createPostgresBuiltinCodecLookup();
|
|
108
68
|
this.profile = Object.freeze({
|
|
109
69
|
id: options?.profileId ?? 'postgres/default@1',
|
|
110
70
|
target: 'postgres',
|
|
111
71
|
capabilities: defaultCapabilities,
|
|
112
72
|
codecs: () => this.codecRegistry,
|
|
113
73
|
readMarkerStatement: () => ({
|
|
114
|
-
sql: 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta from prisma_contract.marker where id = $1',
|
|
74
|
+
sql: 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta, invariants from prisma_contract.marker where id = $1',
|
|
115
75
|
params: [1],
|
|
116
76
|
}),
|
|
77
|
+
// Postgres' driver hydrates `text[]` columns as native JS arrays, so
|
|
78
|
+
// the row is already in the shape the shared parser expects.
|
|
79
|
+
parseMarkerRow: (row: unknown) => parseContractMarkerRow(row),
|
|
117
80
|
});
|
|
118
81
|
}
|
|
119
82
|
|
|
@@ -121,587 +84,11 @@ class PostgresAdapterImpl
|
|
|
121
84
|
return parameterizedCodecs;
|
|
122
85
|
}
|
|
123
86
|
|
|
124
|
-
lower(ast: AnyQueryAst, context: LowererContext<PostgresContract>) {
|
|
125
|
-
|
|
126
|
-
const paramIndexMap: ParamIndexMap = new Map();
|
|
127
|
-
const params: unknown[] = [];
|
|
128
|
-
for (const ref of collectedParamRefs) {
|
|
129
|
-
if (paramIndexMap.has(ref)) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
paramIndexMap.set(ref, params.length + 1);
|
|
133
|
-
params.push(ref.value);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
let sql: string;
|
|
137
|
-
|
|
138
|
-
const node = ast;
|
|
139
|
-
switch (node.kind) {
|
|
140
|
-
case 'select':
|
|
141
|
-
sql = renderSelect(node, context.contract, paramIndexMap);
|
|
142
|
-
break;
|
|
143
|
-
case 'insert':
|
|
144
|
-
sql = renderInsert(node, context.contract, paramIndexMap);
|
|
145
|
-
break;
|
|
146
|
-
case 'update':
|
|
147
|
-
sql = renderUpdate(node, context.contract, paramIndexMap);
|
|
148
|
-
break;
|
|
149
|
-
case 'delete':
|
|
150
|
-
sql = renderDelete(node, context.contract, paramIndexMap);
|
|
151
|
-
break;
|
|
152
|
-
// v8 ignore next 4
|
|
153
|
-
default:
|
|
154
|
-
throw new Error(
|
|
155
|
-
`Unsupported AST node kind: ${(node satisfies never as { kind: string }).kind}`,
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return Object.freeze({
|
|
160
|
-
profileId: this.profile.id,
|
|
161
|
-
body: Object.freeze({ sql, params }),
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function renderSelect(ast: SelectAst, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
167
|
-
const selectClause = `SELECT ${renderDistinctPrefix(ast.distinct, ast.distinctOn, contract, pim)}${renderProjection(
|
|
168
|
-
ast.projection,
|
|
169
|
-
contract,
|
|
170
|
-
pim,
|
|
171
|
-
)}`;
|
|
172
|
-
const fromClause = `FROM ${renderSource(ast.from, contract, pim)}`;
|
|
173
|
-
|
|
174
|
-
const joinsClause = ast.joins?.length
|
|
175
|
-
? ast.joins.map((join) => renderJoin(join, contract, pim)).join(' ')
|
|
176
|
-
: '';
|
|
177
|
-
|
|
178
|
-
const whereClause = ast.where ? `WHERE ${renderWhere(ast.where, contract, pim)}` : '';
|
|
179
|
-
const groupByClause = ast.groupBy?.length
|
|
180
|
-
? `GROUP BY ${ast.groupBy.map((expr) => renderExpr(expr, contract, pim)).join(', ')}`
|
|
181
|
-
: '';
|
|
182
|
-
const havingClause = ast.having ? `HAVING ${renderWhere(ast.having, contract, pim)}` : '';
|
|
183
|
-
const orderClause = ast.orderBy?.length
|
|
184
|
-
? `ORDER BY ${ast.orderBy
|
|
185
|
-
.map((order) => {
|
|
186
|
-
const expr = renderExpr(order.expr, contract, pim);
|
|
187
|
-
return `${expr} ${order.dir.toUpperCase()}`;
|
|
188
|
-
})
|
|
189
|
-
.join(', ')}`
|
|
190
|
-
: '';
|
|
191
|
-
const limitClause = typeof ast.limit === 'number' ? `LIMIT ${ast.limit}` : '';
|
|
192
|
-
const offsetClause = typeof ast.offset === 'number' ? `OFFSET ${ast.offset}` : '';
|
|
193
|
-
|
|
194
|
-
const clauses = [
|
|
195
|
-
selectClause,
|
|
196
|
-
fromClause,
|
|
197
|
-
joinsClause,
|
|
198
|
-
whereClause,
|
|
199
|
-
groupByClause,
|
|
200
|
-
havingClause,
|
|
201
|
-
orderClause,
|
|
202
|
-
limitClause,
|
|
203
|
-
offsetClause,
|
|
204
|
-
]
|
|
205
|
-
.filter((part) => part.length > 0)
|
|
206
|
-
.join(' ');
|
|
207
|
-
return clauses.trim();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function renderProjection(
|
|
211
|
-
projection: ReadonlyArray<ProjectionItem>,
|
|
212
|
-
contract?: PostgresContract,
|
|
213
|
-
pim?: ParamIndexMap,
|
|
214
|
-
): string {
|
|
215
|
-
return projection
|
|
216
|
-
.map((item) => {
|
|
217
|
-
const alias = quoteIdentifier(item.alias);
|
|
218
|
-
if (item.expr.kind === 'literal') {
|
|
219
|
-
return `${renderLiteral(item.expr)} AS ${alias}`;
|
|
220
|
-
}
|
|
221
|
-
return `${renderExpr(item.expr, contract, pim)} AS ${alias}`;
|
|
222
|
-
})
|
|
223
|
-
.join(', ');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function renderDistinctPrefix(
|
|
227
|
-
distinct: true | undefined,
|
|
228
|
-
distinctOn: ReadonlyArray<AnyExpression> | undefined,
|
|
229
|
-
contract?: PostgresContract,
|
|
230
|
-
pim?: ParamIndexMap,
|
|
231
|
-
): string {
|
|
232
|
-
if (distinctOn && distinctOn.length > 0) {
|
|
233
|
-
const rendered = distinctOn.map((expr) => renderExpr(expr, contract, pim)).join(', ');
|
|
234
|
-
return `DISTINCT ON (${rendered}) `;
|
|
235
|
-
}
|
|
236
|
-
if (distinct) {
|
|
237
|
-
return 'DISTINCT ';
|
|
238
|
-
}
|
|
239
|
-
return '';
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function renderSource(
|
|
243
|
-
source: AnyFromSource,
|
|
244
|
-
contract?: PostgresContract,
|
|
245
|
-
pim?: ParamIndexMap,
|
|
246
|
-
): string {
|
|
247
|
-
const node = source;
|
|
248
|
-
switch (node.kind) {
|
|
249
|
-
case 'table-source': {
|
|
250
|
-
const table = quoteIdentifier(node.name);
|
|
251
|
-
if (!node.alias) {
|
|
252
|
-
return table;
|
|
253
|
-
}
|
|
254
|
-
return `${table} AS ${quoteIdentifier(node.alias)}`;
|
|
255
|
-
}
|
|
256
|
-
case 'derived-table-source':
|
|
257
|
-
return `(${renderSelect(node.query, contract, pim)}) AS ${quoteIdentifier(node.alias)}`;
|
|
258
|
-
// v8 ignore next 4
|
|
259
|
-
default:
|
|
260
|
-
throw new Error(
|
|
261
|
-
`Unsupported source node kind: ${(node satisfies never as { kind: string }).kind}`,
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function assertScalarSubquery(query: SelectAst): void {
|
|
267
|
-
if (query.projection.length !== 1) {
|
|
268
|
-
throw new Error('Subquery expressions must project exactly one column');
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function renderSubqueryExpr(
|
|
273
|
-
expr: SubqueryExpr,
|
|
274
|
-
contract?: PostgresContract,
|
|
275
|
-
pim?: ParamIndexMap,
|
|
276
|
-
): string {
|
|
277
|
-
assertScalarSubquery(expr.query);
|
|
278
|
-
return `(${renderSelect(expr.query, contract, pim)})`;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function renderWhere(
|
|
282
|
-
expr: AnyExpression,
|
|
283
|
-
contract?: PostgresContract,
|
|
284
|
-
pim?: ParamIndexMap,
|
|
285
|
-
): string {
|
|
286
|
-
return renderExpr(expr, contract, pim);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function renderNullCheck(
|
|
290
|
-
expr: NullCheckExpr,
|
|
291
|
-
contract?: PostgresContract,
|
|
292
|
-
pim?: ParamIndexMap,
|
|
293
|
-
): string {
|
|
294
|
-
const rendered = renderExpr(expr.expr, contract, pim);
|
|
295
|
-
const renderedExpr =
|
|
296
|
-
expr.expr.kind === 'operation' || expr.expr.kind === 'subquery' ? `(${rendered})` : rendered;
|
|
297
|
-
return expr.isNull ? `${renderedExpr} IS NULL` : `${renderedExpr} IS NOT NULL`;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function renderBinary(expr: BinaryExpr, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
301
|
-
if (expr.right.kind === 'list' && expr.right.values.length === 0) {
|
|
302
|
-
if (expr.op === 'in') {
|
|
303
|
-
return 'FALSE';
|
|
304
|
-
}
|
|
305
|
-
if (expr.op === 'notIn') {
|
|
306
|
-
return 'TRUE';
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const leftExpr = expr.left;
|
|
311
|
-
const left = renderExpr(leftExpr, contract, pim);
|
|
312
|
-
const leftRendered =
|
|
313
|
-
leftExpr.kind === 'operation' || leftExpr.kind === 'subquery' ? `(${left})` : left;
|
|
314
|
-
|
|
315
|
-
const rightNode = expr.right;
|
|
316
|
-
let right: string;
|
|
317
|
-
switch (rightNode.kind) {
|
|
318
|
-
case 'list':
|
|
319
|
-
right = renderListLiteral(rightNode, pim);
|
|
320
|
-
break;
|
|
321
|
-
case 'literal':
|
|
322
|
-
right = renderLiteral(rightNode);
|
|
323
|
-
break;
|
|
324
|
-
case 'column-ref':
|
|
325
|
-
right = renderColumn(rightNode);
|
|
326
|
-
break;
|
|
327
|
-
case 'param-ref':
|
|
328
|
-
right = renderParamRef(rightNode, pim);
|
|
329
|
-
break;
|
|
330
|
-
default:
|
|
331
|
-
right = renderExpr(rightNode, contract, pim);
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const operatorMap: Record<BinaryExpr['op'], string> = {
|
|
336
|
-
eq: '=',
|
|
337
|
-
neq: '!=',
|
|
338
|
-
gt: '>',
|
|
339
|
-
lt: '<',
|
|
340
|
-
gte: '>=',
|
|
341
|
-
lte: '<=',
|
|
342
|
-
like: 'LIKE',
|
|
343
|
-
in: 'IN',
|
|
344
|
-
notIn: 'NOT IN',
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
return `${leftRendered} ${operatorMap[expr.op]} ${right}`;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function renderListLiteral(expr: ListExpression, pim?: ParamIndexMap): string {
|
|
351
|
-
if (expr.values.length === 0) {
|
|
352
|
-
return '(NULL)';
|
|
353
|
-
}
|
|
354
|
-
const values = expr.values
|
|
355
|
-
.map((v) => {
|
|
356
|
-
if (v.kind === 'param-ref') return renderParamRef(v, pim);
|
|
357
|
-
if (v.kind === 'literal') return renderLiteral(v);
|
|
358
|
-
return renderExpr(v, undefined, pim);
|
|
359
|
-
})
|
|
360
|
-
.join(', ');
|
|
361
|
-
return `(${values})`;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function renderColumn(ref: ColumnRef): string {
|
|
365
|
-
if (ref.table === 'excluded') {
|
|
366
|
-
return `excluded.${quoteIdentifier(ref.column)}`;
|
|
367
|
-
}
|
|
368
|
-
return `${quoteIdentifier(ref.table)}.${quoteIdentifier(ref.column)}`;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function renderAggregateExpr(
|
|
372
|
-
expr: AggregateExpr,
|
|
373
|
-
contract?: PostgresContract,
|
|
374
|
-
pim?: ParamIndexMap,
|
|
375
|
-
): string {
|
|
376
|
-
const fn = expr.fn.toUpperCase();
|
|
377
|
-
if (!expr.expr) {
|
|
378
|
-
return `${fn}(*)`;
|
|
379
|
-
}
|
|
380
|
-
return `${fn}(${renderExpr(expr.expr, contract, pim)})`;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function renderJsonObjectExpr(
|
|
384
|
-
expr: JsonObjectExpr,
|
|
385
|
-
contract?: PostgresContract,
|
|
386
|
-
pim?: ParamIndexMap,
|
|
387
|
-
): string {
|
|
388
|
-
const args = expr.entries
|
|
389
|
-
.flatMap((entry): [string, string] => {
|
|
390
|
-
const key = `'${escapeLiteral(entry.key)}'`;
|
|
391
|
-
if (entry.value.kind === 'literal') {
|
|
392
|
-
return [key, renderLiteral(entry.value)];
|
|
393
|
-
}
|
|
394
|
-
return [key, renderExpr(entry.value, contract, pim)];
|
|
395
|
-
})
|
|
396
|
-
.join(', ');
|
|
397
|
-
return `json_build_object(${args})`;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function renderOrderByItems(
|
|
401
|
-
items: ReadonlyArray<OrderByItem>,
|
|
402
|
-
contract?: PostgresContract,
|
|
403
|
-
pim?: ParamIndexMap,
|
|
404
|
-
): string {
|
|
405
|
-
return items
|
|
406
|
-
.map((item) => `${renderExpr(item.expr, contract, pim)} ${item.dir.toUpperCase()}`)
|
|
407
|
-
.join(', ');
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function renderJsonArrayAggExpr(
|
|
411
|
-
expr: JsonArrayAggExpr,
|
|
412
|
-
contract?: PostgresContract,
|
|
413
|
-
pim?: ParamIndexMap,
|
|
414
|
-
): string {
|
|
415
|
-
const aggregateOrderBy =
|
|
416
|
-
expr.orderBy && expr.orderBy.length > 0
|
|
417
|
-
? ` ORDER BY ${renderOrderByItems(expr.orderBy, contract, pim)}`
|
|
418
|
-
: '';
|
|
419
|
-
const aggregated = `json_agg(${renderExpr(expr.expr, contract, pim)}${aggregateOrderBy})`;
|
|
420
|
-
if (expr.onEmpty === 'emptyArray') {
|
|
421
|
-
return `coalesce(${aggregated}, json_build_array())`;
|
|
422
|
-
}
|
|
423
|
-
return aggregated;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function renderExpr(expr: AnyExpression, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
427
|
-
const node = expr;
|
|
428
|
-
switch (node.kind) {
|
|
429
|
-
case 'column-ref':
|
|
430
|
-
return renderColumn(node);
|
|
431
|
-
case 'identifier-ref':
|
|
432
|
-
return quoteIdentifier(node.name);
|
|
433
|
-
case 'operation':
|
|
434
|
-
return renderOperation(node, contract, pim);
|
|
435
|
-
case 'subquery':
|
|
436
|
-
return renderSubqueryExpr(node, contract, pim);
|
|
437
|
-
case 'aggregate':
|
|
438
|
-
return renderAggregateExpr(node, contract, pim);
|
|
439
|
-
case 'json-object':
|
|
440
|
-
return renderJsonObjectExpr(node, contract, pim);
|
|
441
|
-
case 'json-array-agg':
|
|
442
|
-
return renderJsonArrayAggExpr(node, contract, pim);
|
|
443
|
-
case 'binary':
|
|
444
|
-
return renderBinary(node, contract, pim);
|
|
445
|
-
case 'and':
|
|
446
|
-
if (node.exprs.length === 0) {
|
|
447
|
-
return 'TRUE';
|
|
448
|
-
}
|
|
449
|
-
return `(${node.exprs.map((part) => renderExpr(part, contract, pim)).join(' AND ')})`;
|
|
450
|
-
case 'or':
|
|
451
|
-
if (node.exprs.length === 0) {
|
|
452
|
-
return 'FALSE';
|
|
453
|
-
}
|
|
454
|
-
return `(${node.exprs.map((part) => renderExpr(part, contract, pim)).join(' OR ')})`;
|
|
455
|
-
case 'exists': {
|
|
456
|
-
const notKeyword = node.notExists ? 'NOT ' : '';
|
|
457
|
-
const subquery = renderSelect(node.subquery, contract, pim);
|
|
458
|
-
return `${notKeyword}EXISTS (${subquery})`;
|
|
459
|
-
}
|
|
460
|
-
case 'null-check':
|
|
461
|
-
return renderNullCheck(node, contract, pim);
|
|
462
|
-
case 'not':
|
|
463
|
-
return `NOT (${renderExpr(node.expr, contract, pim)})`;
|
|
464
|
-
case 'param-ref':
|
|
465
|
-
return renderParamRef(node, pim);
|
|
466
|
-
case 'literal':
|
|
467
|
-
return renderLiteral(node);
|
|
468
|
-
case 'list':
|
|
469
|
-
return renderListLiteral(node, pim);
|
|
470
|
-
// v8 ignore next 4
|
|
471
|
-
default:
|
|
472
|
-
throw new Error(
|
|
473
|
-
`Unsupported expression node kind: ${(node satisfies never as { kind: string }).kind}`,
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function renderParamRef(ref: ParamRef, pim?: ParamIndexMap): string {
|
|
479
|
-
const index = pim?.get(ref);
|
|
480
|
-
if (index === undefined) {
|
|
481
|
-
throw new Error('ParamRef not found in index map');
|
|
482
|
-
}
|
|
483
|
-
return renderTypedParam(index, ref.codecId);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
function renderLiteral(expr: LiteralExpr): string {
|
|
487
|
-
if (typeof expr.value === 'string') {
|
|
488
|
-
return `'${escapeLiteral(expr.value)}'`;
|
|
489
|
-
}
|
|
490
|
-
if (typeof expr.value === 'number' || typeof expr.value === 'boolean') {
|
|
491
|
-
return String(expr.value);
|
|
492
|
-
}
|
|
493
|
-
if (typeof expr.value === 'bigint') {
|
|
494
|
-
return String(expr.value);
|
|
495
|
-
}
|
|
496
|
-
if (expr.value === null) {
|
|
497
|
-
return 'NULL';
|
|
498
|
-
}
|
|
499
|
-
if (expr.value === undefined) {
|
|
500
|
-
return 'NULL';
|
|
501
|
-
}
|
|
502
|
-
if (expr.value instanceof Date) {
|
|
503
|
-
return `'${escapeLiteral(expr.value.toISOString())}'`;
|
|
504
|
-
}
|
|
505
|
-
if (Array.isArray(expr.value)) {
|
|
506
|
-
return `ARRAY[${expr.value.map((v: unknown) => renderLiteral(new LiteralExpr(v))).join(', ')}]`;
|
|
507
|
-
}
|
|
508
|
-
const json = JSON.stringify(expr.value);
|
|
509
|
-
if (json === undefined) {
|
|
510
|
-
return 'NULL';
|
|
511
|
-
}
|
|
512
|
-
return `'${escapeLiteral(json)}'`;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function renderOperation(
|
|
516
|
-
expr: OperationExpr,
|
|
517
|
-
contract?: PostgresContract,
|
|
518
|
-
pim?: ParamIndexMap,
|
|
519
|
-
): string {
|
|
520
|
-
const self = renderExpr(expr.self, contract, pim);
|
|
521
|
-
const args = expr.args.map((arg) => {
|
|
522
|
-
return renderExpr(arg, contract, pim);
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
let result = expr.lowering.template;
|
|
526
|
-
result = result.replace(/\{\{self\}\}/g, self);
|
|
527
|
-
for (let i = 0; i < args.length; i++) {
|
|
528
|
-
result = result.replace(new RegExp(`\\{\\{arg${i}\\}\\}`, 'g'), args[i] ?? '');
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
return result;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function renderJoin(join: JoinAst, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
535
|
-
const joinType = join.joinType.toUpperCase();
|
|
536
|
-
const lateral = join.lateral ? 'LATERAL ' : '';
|
|
537
|
-
const source = renderSource(join.source, contract, pim);
|
|
538
|
-
const onClause = renderJoinOn(join.on, contract, pim);
|
|
539
|
-
return `${joinType} JOIN ${lateral}${source} ON ${onClause}`;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function renderJoinOn(on: JoinOnExpr, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
543
|
-
if (on.kind === 'eq-col-join-on') {
|
|
544
|
-
const left = renderColumn(on.left);
|
|
545
|
-
const right = renderColumn(on.right);
|
|
546
|
-
return `${left} = ${right}`;
|
|
547
|
-
}
|
|
548
|
-
return renderWhere(on, contract, pim);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function getInsertColumnOrder(
|
|
552
|
-
rows: ReadonlyArray<Record<string, InsertValue>>,
|
|
553
|
-
contract: PostgresContract,
|
|
554
|
-
tableName: string,
|
|
555
|
-
): string[] {
|
|
556
|
-
const orderedColumns: string[] = [];
|
|
557
|
-
const seenColumns = new Set<string>();
|
|
558
|
-
|
|
559
|
-
for (const row of rows) {
|
|
560
|
-
for (const column of Object.keys(row)) {
|
|
561
|
-
if (seenColumns.has(column)) {
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
seenColumns.add(column);
|
|
565
|
-
orderedColumns.push(column);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if (orderedColumns.length > 0) {
|
|
570
|
-
return orderedColumns;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
return Object.keys(contract.storage.tables[tableName]?.columns ?? {});
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function renderInsertValue(value: InsertValue | undefined, pim?: ParamIndexMap): string {
|
|
577
|
-
if (!value || value.kind === 'default-value') {
|
|
578
|
-
return 'DEFAULT';
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
switch (value.kind) {
|
|
582
|
-
case 'param-ref':
|
|
583
|
-
return renderParamRef(value, pim);
|
|
584
|
-
case 'column-ref':
|
|
585
|
-
return renderColumn(value);
|
|
586
|
-
// v8 ignore next 4
|
|
587
|
-
default:
|
|
588
|
-
throw new Error(
|
|
589
|
-
`Unsupported value node in INSERT: ${(value satisfies never as { kind: string }).kind}`,
|
|
590
|
-
);
|
|
87
|
+
lower(ast: AnyQueryAst, context: LowererContext<PostgresContract>): PostgresLoweredStatement {
|
|
88
|
+
return renderLoweredSql(ast, context.contract, this.codecLookup);
|
|
591
89
|
}
|
|
592
90
|
}
|
|
593
91
|
|
|
594
|
-
function renderInsert(ast: InsertAst, contract: PostgresContract, pim?: ParamIndexMap): string {
|
|
595
|
-
const table = quoteIdentifier(ast.table.name);
|
|
596
|
-
const rows = ast.rows;
|
|
597
|
-
if (rows.length === 0) {
|
|
598
|
-
throw new Error('INSERT requires at least one row');
|
|
599
|
-
}
|
|
600
|
-
const hasExplicitValues = rows.some((row) => Object.keys(row).length > 0);
|
|
601
|
-
const insertClause = (() => {
|
|
602
|
-
if (!hasExplicitValues) {
|
|
603
|
-
if (rows.length === 1) {
|
|
604
|
-
return `INSERT INTO ${table} DEFAULT VALUES`;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const defaultColumns = getInsertColumnOrder(rows, contract, ast.table.name);
|
|
608
|
-
if (defaultColumns.length === 0) {
|
|
609
|
-
return `INSERT INTO ${table} VALUES ${rows.map(() => '()').join(', ')}`;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const quotedColumns = defaultColumns.map((column) => quoteIdentifier(column));
|
|
613
|
-
const defaultRow = `(${defaultColumns.map(() => 'DEFAULT').join(', ')})`;
|
|
614
|
-
return `INSERT INTO ${table} (${quotedColumns.join(', ')}) VALUES ${rows
|
|
615
|
-
.map(() => defaultRow)
|
|
616
|
-
.join(', ')}`;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
const columnOrder = getInsertColumnOrder(rows, contract, ast.table.name);
|
|
620
|
-
const columns = columnOrder.map((column) => quoteIdentifier(column));
|
|
621
|
-
const values = rows
|
|
622
|
-
.map((row) => {
|
|
623
|
-
const renderedRow = columnOrder.map((column) => renderInsertValue(row[column], pim));
|
|
624
|
-
return `(${renderedRow.join(', ')})`;
|
|
625
|
-
})
|
|
626
|
-
.join(', ');
|
|
627
|
-
|
|
628
|
-
return `INSERT INTO ${table} (${columns.join(', ')}) VALUES ${values}`;
|
|
629
|
-
})();
|
|
630
|
-
const onConflictClause = ast.onConflict
|
|
631
|
-
? (() => {
|
|
632
|
-
const conflictColumns = ast.onConflict.columns.map((col) => quoteIdentifier(col.column));
|
|
633
|
-
if (conflictColumns.length === 0) {
|
|
634
|
-
throw new Error('INSERT onConflict requires at least one conflict column');
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const action = ast.onConflict.action;
|
|
638
|
-
switch (action.kind) {
|
|
639
|
-
case 'do-nothing':
|
|
640
|
-
return ` ON CONFLICT (${conflictColumns.join(', ')}) DO NOTHING`;
|
|
641
|
-
case 'do-update-set': {
|
|
642
|
-
const updates = Object.entries(action.set).map(([colName, value]) => {
|
|
643
|
-
const target = quoteIdentifier(colName);
|
|
644
|
-
if (value.kind === 'param-ref') {
|
|
645
|
-
return `${target} = ${renderParamRef(value, pim)}`;
|
|
646
|
-
}
|
|
647
|
-
return `${target} = ${renderColumn(value)}`;
|
|
648
|
-
});
|
|
649
|
-
return ` ON CONFLICT (${conflictColumns.join(', ')}) DO UPDATE SET ${updates.join(', ')}`;
|
|
650
|
-
}
|
|
651
|
-
// v8 ignore next 4
|
|
652
|
-
default:
|
|
653
|
-
throw new Error(
|
|
654
|
-
`Unsupported onConflict action: ${(action satisfies never as { kind: string }).kind}`,
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
})()
|
|
658
|
-
: '';
|
|
659
|
-
const returningClause = ast.returning?.length
|
|
660
|
-
? ` RETURNING ${ast.returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`
|
|
661
|
-
: '';
|
|
662
|
-
|
|
663
|
-
return `${insertClause}${onConflictClause}${returningClause}`;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim?: ParamIndexMap): string {
|
|
667
|
-
const table = quoteIdentifier(ast.table.name);
|
|
668
|
-
const setClauses = Object.entries(ast.set).map(([col, val]) => {
|
|
669
|
-
const column = quoteIdentifier(col);
|
|
670
|
-
let value: string;
|
|
671
|
-
switch (val.kind) {
|
|
672
|
-
case 'param-ref':
|
|
673
|
-
value = renderParamRef(val, pim);
|
|
674
|
-
break;
|
|
675
|
-
case 'column-ref':
|
|
676
|
-
value = renderColumn(val);
|
|
677
|
-
break;
|
|
678
|
-
// v8 ignore next 4
|
|
679
|
-
default:
|
|
680
|
-
throw new Error(
|
|
681
|
-
`Unsupported value node in UPDATE: ${(val satisfies never as { kind: string }).kind}`,
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
return `${column} = ${value}`;
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
|
|
688
|
-
const returningClause = ast.returning?.length
|
|
689
|
-
? ` RETURNING ${ast.returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`
|
|
690
|
-
: '';
|
|
691
|
-
|
|
692
|
-
return `UPDATE ${table} SET ${setClauses.join(', ')}${whereClause}${returningClause}`;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function renderDelete(ast: DeleteAst, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
696
|
-
const table = quoteIdentifier(ast.table.name);
|
|
697
|
-
const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
|
|
698
|
-
const returningClause = ast.returning?.length
|
|
699
|
-
? ` RETURNING ${ast.returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`
|
|
700
|
-
: '';
|
|
701
|
-
|
|
702
|
-
return `DELETE FROM ${table}${whereClause}${returningClause}`;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
92
|
export function createPostgresAdapter(options?: PostgresAdapterOptions) {
|
|
706
93
|
return Object.freeze(new PostgresAdapterImpl(options));
|
|
707
94
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Codec, CodecLookup } from '@prisma-next/framework-components/codec';
|
|
2
|
+
import { codecDefinitions } from '@prisma-next/target-postgres/codecs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build a {@link CodecLookup} populated with the Postgres-builtin codec
|
|
6
|
+
* definitions only.
|
|
7
|
+
*
|
|
8
|
+
* This is the default lookup used by `createPostgresAdapter()` and
|
|
9
|
+
* `new PostgresControlAdapter()` when called without a stack-derived lookup
|
|
10
|
+
* (e.g. from tests, or one-off scripts that don't compose a full stack).
|
|
11
|
+
*
|
|
12
|
+
* Extension codecs (e.g. `pg/vector@1` from `@prisma-next/extension-pgvector`)
|
|
13
|
+
* are intentionally NOT included here: a bare adapter cannot see extensions.
|
|
14
|
+
* Stack-composed paths (`SqlControlAdapterDescriptor.create(stack)` /
|
|
15
|
+
* `SqlRuntimeAdapterDescriptor.create(stack)`) supply the broader,
|
|
16
|
+
* extension-inclusive lookup at construction time.
|
|
17
|
+
*/
|
|
18
|
+
export function createPostgresBuiltinCodecLookup(): CodecLookup {
|
|
19
|
+
const byId = new Map<string, Codec>();
|
|
20
|
+
for (const definition of Object.values(codecDefinitions)) {
|
|
21
|
+
byId.set(definition.codec.id, definition.codec);
|
|
22
|
+
}
|
|
23
|
+
return { get: (id) => byId.get(id) };
|
|
24
|
+
}
|