@prisma-next/adapter-postgres 0.3.0-pr.99.6 → 0.4.0-dev.1
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 +64 -2
- package/dist/adapter-7pXt8ej9.mjs +369 -0
- package/dist/adapter-7pXt8ej9.mjs.map +1 -0
- package/dist/adapter.d.mts +23 -0
- package/dist/adapter.d.mts.map +1 -0
- package/dist/adapter.mjs +3 -0
- package/dist/codec-ids-BwjcIf74.mjs +29 -0
- package/dist/codec-ids-BwjcIf74.mjs.map +1 -0
- package/dist/codec-types.d.mts +107 -0
- package/dist/codec-types.d.mts.map +1 -0
- package/dist/codec-types.mjs +3 -0
- package/dist/codecs-C3wlpdV7.mjs +385 -0
- package/dist/codecs-C3wlpdV7.mjs.map +1 -0
- package/dist/column-types.d.mts +122 -0
- package/dist/column-types.d.mts.map +1 -0
- package/dist/column-types.mjs +180 -0
- package/dist/column-types.mjs.map +1 -0
- package/dist/control.d.mts +77 -0
- package/dist/control.d.mts.map +1 -0
- package/dist/control.mjs +776 -0
- package/dist/control.mjs.map +1 -0
- package/dist/descriptor-meta-DemWrTfB.mjs +768 -0
- package/dist/descriptor-meta-DemWrTfB.mjs.map +1 -0
- package/dist/runtime.d.mts +19 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +98 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/sql-utils-CSfAGEwF.mjs +78 -0
- package/dist/sql-utils-CSfAGEwF.mjs.map +1 -0
- package/dist/types-DxaTd7aP.d.mts +20 -0
- package/dist/types-DxaTd7aP.d.mts.map +1 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +1 -0
- package/package.json +32 -41
- package/src/core/adapter.ts +535 -256
- package/src/core/codec-ids.ts +30 -0
- package/src/core/codecs.ts +487 -36
- package/src/core/control-adapter.ts +405 -184
- package/src/core/control-mutation-defaults.ts +335 -0
- package/src/core/default-normalizer.ts +145 -0
- package/src/core/descriptor-meta.ts +227 -9
- package/src/core/enum-control-hooks.ts +739 -0
- package/src/core/json-schema-type-expression.ts +131 -0
- package/src/core/json-schema-validator.ts +53 -0
- package/src/core/sql-utils.ts +111 -0
- package/src/core/standard-schema.ts +71 -0
- package/src/core/types.ts +8 -10
- package/src/exports/codec-types.ts +34 -1
- package/src/exports/column-types.ts +223 -27
- package/src/exports/control.ts +19 -9
- package/src/exports/runtime.ts +75 -19
- package/dist/chunk-HD5YISNQ.js +0 -47
- package/dist/chunk-HD5YISNQ.js.map +0 -1
- package/dist/chunk-J3XSOAM2.js +0 -162
- package/dist/chunk-J3XSOAM2.js.map +0 -1
- package/dist/chunk-T6S3A6VT.js +0 -301
- package/dist/chunk-T6S3A6VT.js.map +0 -1
- package/dist/core/adapter.d.ts +0 -19
- package/dist/core/adapter.d.ts.map +0 -1
- package/dist/core/codecs.d.ts +0 -110
- package/dist/core/codecs.d.ts.map +0 -1
- package/dist/core/control-adapter.d.ts +0 -33
- package/dist/core/control-adapter.d.ts.map +0 -1
- package/dist/core/descriptor-meta.d.ts +0 -72
- package/dist/core/descriptor-meta.d.ts.map +0 -1
- package/dist/core/types.d.ts +0 -16
- package/dist/core/types.d.ts.map +0 -1
- package/dist/exports/adapter.d.ts +0 -2
- package/dist/exports/adapter.d.ts.map +0 -1
- package/dist/exports/adapter.js +0 -8
- package/dist/exports/adapter.js.map +0 -1
- package/dist/exports/codec-types.d.ts +0 -11
- package/dist/exports/codec-types.d.ts.map +0 -1
- package/dist/exports/codec-types.js +0 -7
- package/dist/exports/codec-types.js.map +0 -1
- package/dist/exports/column-types.d.ts +0 -17
- package/dist/exports/column-types.d.ts.map +0 -1
- package/dist/exports/column-types.js +0 -49
- package/dist/exports/column-types.js.map +0 -1
- package/dist/exports/control.d.ts +0 -8
- package/dist/exports/control.d.ts.map +0 -1
- package/dist/exports/control.js +0 -279
- package/dist/exports/control.js.map +0 -1
- package/dist/exports/runtime.d.ts +0 -15
- package/dist/exports/runtime.d.ts.map +0 -1
- package/dist/exports/runtime.js +0 -20
- package/dist/exports/runtime.js.map +0 -1
- package/dist/exports/types.d.ts +0 -2
- package/dist/exports/types.d.ts.map +0 -1
- package/dist/exports/types.js +0 -1
- package/dist/exports/types.js.map +0 -1
package/src/core/adapter.ts
CHANGED
|
@@ -1,27 +1,61 @@
|
|
|
1
|
-
import
|
|
2
|
-
Adapter,
|
|
3
|
-
AdapterProfile,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import {
|
|
2
|
+
type Adapter,
|
|
3
|
+
type AdapterProfile,
|
|
4
|
+
type AggregateExpr,
|
|
5
|
+
type AnyExpression,
|
|
6
|
+
type AnyFromSource,
|
|
7
|
+
type AnyQueryAst,
|
|
8
|
+
type BinaryExpr,
|
|
9
|
+
type CodecParamsDescriptor,
|
|
10
|
+
type ColumnRef,
|
|
11
|
+
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,
|
|
11
20
|
LiteralExpr,
|
|
12
|
-
LowererContext,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
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,
|
|
18
30
|
} from '@prisma-next/sql-relational-core/ast';
|
|
19
|
-
import {
|
|
31
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
32
|
+
import { PG_JSON_CODEC_ID, PG_JSONB_CODEC_ID } from './codec-ids';
|
|
20
33
|
import { codecDefinitions } from './codecs';
|
|
34
|
+
import { escapeLiteral, quoteIdentifier } from './sql-utils';
|
|
21
35
|
import type { PostgresAdapterOptions, PostgresContract, PostgresLoweredStatement } from './types';
|
|
22
36
|
|
|
23
37
|
const VECTOR_CODEC_ID = 'pg/vector@1' as const;
|
|
24
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
|
+
|
|
25
59
|
const defaultCapabilities = Object.freeze({
|
|
26
60
|
postgres: {
|
|
27
61
|
orderBy: true,
|
|
@@ -30,9 +64,32 @@ const defaultCapabilities = Object.freeze({
|
|
|
30
64
|
jsonAgg: true,
|
|
31
65
|
returning: true,
|
|
32
66
|
},
|
|
67
|
+
sql: {
|
|
68
|
+
enums: true,
|
|
69
|
+
returning: true,
|
|
70
|
+
defaultInInsert: true,
|
|
71
|
+
},
|
|
33
72
|
});
|
|
34
73
|
|
|
35
|
-
|
|
74
|
+
type AdapterCodec = (typeof codecDefinitions)[keyof typeof codecDefinitions]['codec'];
|
|
75
|
+
type ParameterizedCodec = AdapterCodec & {
|
|
76
|
+
readonly paramsSchema: NonNullable<AdapterCodec['paramsSchema']>;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const parameterizedCodecs: ReadonlyArray<CodecParamsDescriptor> = Object.values(codecDefinitions)
|
|
80
|
+
.map((definition) => definition.codec)
|
|
81
|
+
.filter((codec): codec is ParameterizedCodec => codec.paramsSchema !== undefined)
|
|
82
|
+
.map((codec) =>
|
|
83
|
+
Object.freeze({
|
|
84
|
+
codecId: codec.id,
|
|
85
|
+
paramsSchema: codec.paramsSchema,
|
|
86
|
+
...ifDefined('init', codec.init),
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
class PostgresAdapterImpl
|
|
91
|
+
implements Adapter<AnyQueryAst, PostgresContract, PostgresLoweredStatement>
|
|
92
|
+
{
|
|
36
93
|
// These fields make the adapter instance structurally compatible with
|
|
37
94
|
// RuntimeAdapterInstance<'sql', 'postgres'> without introducing a runtime-plane dependency.
|
|
38
95
|
readonly familyId = 'sql' as const;
|
|
@@ -53,23 +110,50 @@ class PostgresAdapterImpl implements Adapter<QueryAst, PostgresContract, Postgre
|
|
|
53
110
|
target: 'postgres',
|
|
54
111
|
capabilities: defaultCapabilities,
|
|
55
112
|
codecs: () => this.codecRegistry,
|
|
113
|
+
readMarkerStatement: () => ({
|
|
114
|
+
sql: 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta from prisma_contract.marker where id = $1',
|
|
115
|
+
params: [1],
|
|
116
|
+
}),
|
|
56
117
|
});
|
|
57
118
|
}
|
|
58
119
|
|
|
59
|
-
|
|
120
|
+
parameterizedCodecs(): ReadonlyArray<CodecParamsDescriptor> {
|
|
121
|
+
return parameterizedCodecs;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
lower(ast: AnyQueryAst, context: LowererContext<PostgresContract>) {
|
|
125
|
+
const collectedParamRefs = ast.collectParamRefs();
|
|
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
|
+
|
|
60
136
|
let sql: string;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
);
|
|
73
157
|
}
|
|
74
158
|
|
|
75
159
|
return Object.freeze({
|
|
@@ -79,84 +163,175 @@ class PostgresAdapterImpl implements Adapter<QueryAst, PostgresContract, Postgre
|
|
|
79
163
|
}
|
|
80
164
|
}
|
|
81
165
|
|
|
82
|
-
function renderSelect(ast: SelectAst, contract?: PostgresContract): string {
|
|
83
|
-
const selectClause = `SELECT ${
|
|
84
|
-
|
|
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)}`;
|
|
85
173
|
|
|
86
174
|
const joinsClause = ast.joins?.length
|
|
87
|
-
? ast.joins.map((join) => renderJoin(join, contract)).join(' ')
|
|
88
|
-
: '';
|
|
89
|
-
const includesClause = ast.includes?.length
|
|
90
|
-
? ast.includes.map((include) => renderInclude(include, contract)).join(' ')
|
|
175
|
+
? ast.joins.map((join) => renderJoin(join, contract, pim)).join(' ')
|
|
91
176
|
: '';
|
|
92
177
|
|
|
93
|
-
const whereClause = ast.where ? `
|
|
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)}` : '';
|
|
94
183
|
const orderClause = ast.orderBy?.length
|
|
95
|
-
? `
|
|
184
|
+
? `ORDER BY ${ast.orderBy
|
|
96
185
|
.map((order) => {
|
|
97
|
-
const expr = renderExpr(order.expr
|
|
186
|
+
const expr = renderExpr(order.expr, contract, pim);
|
|
98
187
|
return `${expr} ${order.dir.toUpperCase()}`;
|
|
99
188
|
})
|
|
100
189
|
.join(', ')}`
|
|
101
190
|
: '';
|
|
102
|
-
const limitClause = typeof ast.limit === 'number' ? `
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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();
|
|
106
208
|
}
|
|
107
209
|
|
|
108
|
-
function renderProjection(
|
|
109
|
-
|
|
210
|
+
function renderProjection(
|
|
211
|
+
projection: ReadonlyArray<ProjectionItem>,
|
|
212
|
+
contract?: PostgresContract,
|
|
213
|
+
pim?: ParamIndexMap,
|
|
214
|
+
): string {
|
|
215
|
+
return projection
|
|
110
216
|
.map((item) => {
|
|
111
|
-
const expr = item.expr as ColumnRef | IncludeRef | OperationExpr | LiteralExpr;
|
|
112
|
-
if (expr.kind === 'includeRef') {
|
|
113
|
-
// For include references, select the column from the LATERAL join alias
|
|
114
|
-
// The LATERAL subquery returns a single column (the JSON array) with the alias
|
|
115
|
-
// The table is aliased as {alias}_lateral, and the column inside is aliased as the include alias
|
|
116
|
-
// We select it using table_alias.column_alias
|
|
117
|
-
const tableAlias = `${expr.alias}_lateral`;
|
|
118
|
-
return `${quoteIdentifier(tableAlias)}.${quoteIdentifier(expr.alias)} AS ${quoteIdentifier(item.alias)}`;
|
|
119
|
-
}
|
|
120
|
-
if (expr.kind === 'operation') {
|
|
121
|
-
const operation = renderOperation(expr, contract);
|
|
122
|
-
const alias = quoteIdentifier(item.alias);
|
|
123
|
-
return `${operation} AS ${alias}`;
|
|
124
|
-
}
|
|
125
|
-
if (expr.kind === 'literal') {
|
|
126
|
-
const literal = renderLiteral(expr);
|
|
127
|
-
const alias = quoteIdentifier(item.alias);
|
|
128
|
-
return `${literal} AS ${alias}`;
|
|
129
|
-
}
|
|
130
|
-
const column = renderColumn(expr as ColumnRef);
|
|
131
217
|
const alias = quoteIdentifier(item.alias);
|
|
132
|
-
|
|
218
|
+
if (item.expr.kind === 'literal') {
|
|
219
|
+
return `${renderLiteral(item.expr)} AS ${alias}`;
|
|
220
|
+
}
|
|
221
|
+
return `${renderExpr(item.expr, contract, pim)} AS ${alias}`;
|
|
133
222
|
})
|
|
134
223
|
.join(', ');
|
|
135
224
|
}
|
|
136
225
|
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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}) `;
|
|
142
235
|
}
|
|
143
|
-
|
|
236
|
+
if (distinct) {
|
|
237
|
+
return 'DISTINCT ';
|
|
238
|
+
}
|
|
239
|
+
return '';
|
|
144
240
|
}
|
|
145
241
|
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
+
|
|
160
335
|
const operatorMap: Record<BinaryExpr['op'], string> = {
|
|
161
336
|
eq: '=',
|
|
162
337
|
neq: '!=',
|
|
@@ -164,249 +339,353 @@ function renderBinary(expr: BinaryExpr, contract?: PostgresContract): string {
|
|
|
164
339
|
lt: '<',
|
|
165
340
|
gte: '>=',
|
|
166
341
|
lte: '<=',
|
|
342
|
+
like: 'LIKE',
|
|
343
|
+
ilike: 'ILIKE',
|
|
344
|
+
in: 'IN',
|
|
345
|
+
notIn: 'NOT IN',
|
|
167
346
|
};
|
|
168
347
|
|
|
169
348
|
return `${leftRendered} ${operatorMap[expr.op]} ${right}`;
|
|
170
349
|
}
|
|
171
350
|
|
|
351
|
+
function renderListLiteral(expr: ListExpression, pim?: ParamIndexMap): string {
|
|
352
|
+
if (expr.values.length === 0) {
|
|
353
|
+
return '(NULL)';
|
|
354
|
+
}
|
|
355
|
+
const values = expr.values
|
|
356
|
+
.map((v) => {
|
|
357
|
+
if (v.kind === 'param-ref') return renderParamRef(v, pim);
|
|
358
|
+
if (v.kind === 'literal') return renderLiteral(v);
|
|
359
|
+
return renderExpr(v, undefined, pim);
|
|
360
|
+
})
|
|
361
|
+
.join(', ');
|
|
362
|
+
return `(${values})`;
|
|
363
|
+
}
|
|
364
|
+
|
|
172
365
|
function renderColumn(ref: ColumnRef): string {
|
|
366
|
+
if (ref.table === 'excluded') {
|
|
367
|
+
return `excluded.${quoteIdentifier(ref.column)}`;
|
|
368
|
+
}
|
|
173
369
|
return `${quoteIdentifier(ref.table)}.${quoteIdentifier(ref.column)}`;
|
|
174
370
|
}
|
|
175
371
|
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
|
|
372
|
+
function renderAggregateExpr(
|
|
373
|
+
expr: AggregateExpr,
|
|
374
|
+
contract?: PostgresContract,
|
|
375
|
+
pim?: ParamIndexMap,
|
|
376
|
+
): string {
|
|
377
|
+
const fn = expr.fn.toUpperCase();
|
|
378
|
+
if (!expr.expr) {
|
|
379
|
+
return `${fn}(*)`;
|
|
179
380
|
}
|
|
180
|
-
return
|
|
381
|
+
return `${fn}(${renderExpr(expr.expr, contract, pim)})`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function renderJsonObjectExpr(
|
|
385
|
+
expr: JsonObjectExpr,
|
|
386
|
+
contract?: PostgresContract,
|
|
387
|
+
pim?: ParamIndexMap,
|
|
388
|
+
): string {
|
|
389
|
+
const args = expr.entries
|
|
390
|
+
.flatMap((entry): [string, string] => {
|
|
391
|
+
const key = `'${escapeLiteral(entry.key)}'`;
|
|
392
|
+
if (entry.value.kind === 'literal') {
|
|
393
|
+
return [key, renderLiteral(entry.value)];
|
|
394
|
+
}
|
|
395
|
+
return [key, renderExpr(entry.value, contract, pim)];
|
|
396
|
+
})
|
|
397
|
+
.join(', ');
|
|
398
|
+
return `json_build_object(${args})`;
|
|
181
399
|
}
|
|
182
400
|
|
|
183
|
-
function
|
|
184
|
-
|
|
401
|
+
function renderOrderByItems(
|
|
402
|
+
items: ReadonlyArray<OrderByItem>,
|
|
185
403
|
contract?: PostgresContract,
|
|
186
|
-
|
|
187
|
-
columnName?: string,
|
|
404
|
+
pim?: ParamIndexMap,
|
|
188
405
|
): string {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
406
|
+
return items
|
|
407
|
+
.map((item) => `${renderExpr(item.expr, contract, pim)} ${item.dir.toUpperCase()}`)
|
|
408
|
+
.join(', ');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function renderJsonArrayAggExpr(
|
|
412
|
+
expr: JsonArrayAggExpr,
|
|
413
|
+
contract?: PostgresContract,
|
|
414
|
+
pim?: ParamIndexMap,
|
|
415
|
+
): string {
|
|
416
|
+
const aggregateOrderBy =
|
|
417
|
+
expr.orderBy && expr.orderBy.length > 0
|
|
418
|
+
? ` ORDER BY ${renderOrderByItems(expr.orderBy, contract, pim)}`
|
|
419
|
+
: '';
|
|
420
|
+
const aggregated = `json_agg(${renderExpr(expr.expr, contract, pim)}${aggregateOrderBy})`;
|
|
421
|
+
if (expr.onEmpty === 'emptyArray') {
|
|
422
|
+
return `coalesce(${aggregated}, json_build_array())`;
|
|
423
|
+
}
|
|
424
|
+
return aggregated;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function renderExpr(expr: AnyExpression, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
428
|
+
const node = expr;
|
|
429
|
+
switch (node.kind) {
|
|
430
|
+
case 'column-ref':
|
|
431
|
+
return renderColumn(node);
|
|
432
|
+
case 'identifier-ref':
|
|
433
|
+
return quoteIdentifier(node.name);
|
|
434
|
+
case 'operation':
|
|
435
|
+
return renderOperation(node, contract, pim);
|
|
436
|
+
case 'subquery':
|
|
437
|
+
return renderSubqueryExpr(node, contract, pim);
|
|
438
|
+
case 'aggregate':
|
|
439
|
+
return renderAggregateExpr(node, contract, pim);
|
|
440
|
+
case 'json-object':
|
|
441
|
+
return renderJsonObjectExpr(node, contract, pim);
|
|
442
|
+
case 'json-array-agg':
|
|
443
|
+
return renderJsonArrayAggExpr(node, contract, pim);
|
|
444
|
+
case 'binary':
|
|
445
|
+
return renderBinary(node, contract, pim);
|
|
446
|
+
case 'and':
|
|
447
|
+
if (node.exprs.length === 0) {
|
|
448
|
+
return 'TRUE';
|
|
449
|
+
}
|
|
450
|
+
return `(${node.exprs.map((part) => renderExpr(part, contract, pim)).join(' AND ')})`;
|
|
451
|
+
case 'or':
|
|
452
|
+
if (node.exprs.length === 0) {
|
|
453
|
+
return 'FALSE';
|
|
454
|
+
}
|
|
455
|
+
return `(${node.exprs.map((part) => renderExpr(part, contract, pim)).join(' OR ')})`;
|
|
456
|
+
case 'exists': {
|
|
457
|
+
const notKeyword = node.notExists ? 'NOT ' : '';
|
|
458
|
+
const subquery = renderSelect(node.subquery, contract, pim);
|
|
459
|
+
return `${notKeyword}EXISTS (${subquery})`;
|
|
195
460
|
}
|
|
461
|
+
case 'null-check':
|
|
462
|
+
return renderNullCheck(node, contract, pim);
|
|
463
|
+
case 'not':
|
|
464
|
+
return `NOT (${renderExpr(node.expr, contract, pim)})`;
|
|
465
|
+
case 'param-ref':
|
|
466
|
+
return renderParamRef(node, pim);
|
|
467
|
+
case 'literal':
|
|
468
|
+
return renderLiteral(node);
|
|
469
|
+
case 'list':
|
|
470
|
+
return renderListLiteral(node, pim);
|
|
471
|
+
// v8 ignore next 4
|
|
472
|
+
default:
|
|
473
|
+
throw new Error(
|
|
474
|
+
`Unsupported expression node kind: ${(node satisfies never as { kind: string }).kind}`,
|
|
475
|
+
);
|
|
196
476
|
}
|
|
197
|
-
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function renderParamRef(ref: ParamRef, pim?: ParamIndexMap): string {
|
|
480
|
+
const index = pim?.get(ref);
|
|
481
|
+
if (index === undefined) {
|
|
482
|
+
throw new Error('ParamRef not found in index map');
|
|
483
|
+
}
|
|
484
|
+
return renderTypedParam(index, ref.codecId);
|
|
198
485
|
}
|
|
199
486
|
|
|
200
487
|
function renderLiteral(expr: LiteralExpr): string {
|
|
201
488
|
if (typeof expr.value === 'string') {
|
|
202
|
-
return `'${expr.value
|
|
489
|
+
return `'${escapeLiteral(expr.value)}'`;
|
|
203
490
|
}
|
|
204
491
|
if (typeof expr.value === 'number' || typeof expr.value === 'boolean') {
|
|
205
492
|
return String(expr.value);
|
|
206
493
|
}
|
|
494
|
+
if (typeof expr.value === 'bigint') {
|
|
495
|
+
return String(expr.value);
|
|
496
|
+
}
|
|
207
497
|
if (expr.value === null) {
|
|
208
498
|
return 'NULL';
|
|
209
499
|
}
|
|
500
|
+
if (expr.value === undefined) {
|
|
501
|
+
return 'NULL';
|
|
502
|
+
}
|
|
503
|
+
if (expr.value instanceof Date) {
|
|
504
|
+
return `'${escapeLiteral(expr.value.toISOString())}'`;
|
|
505
|
+
}
|
|
210
506
|
if (Array.isArray(expr.value)) {
|
|
211
|
-
return `ARRAY[${expr.value.map((v: unknown) => renderLiteral(
|
|
507
|
+
return `ARRAY[${expr.value.map((v: unknown) => renderLiteral(new LiteralExpr(v))).join(', ')}]`;
|
|
212
508
|
}
|
|
213
|
-
|
|
509
|
+
const json = JSON.stringify(expr.value);
|
|
510
|
+
if (json === undefined) {
|
|
511
|
+
return 'NULL';
|
|
512
|
+
}
|
|
513
|
+
return `'${escapeLiteral(json)}'`;
|
|
214
514
|
}
|
|
215
515
|
|
|
216
|
-
function renderOperation(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (arg.kind === 'param') {
|
|
225
|
-
// Cast vector operation parameters to vector type
|
|
226
|
-
return isVectorOperation ? `$${arg.index}::vector` : renderParam(arg, contract);
|
|
227
|
-
}
|
|
228
|
-
if (arg.kind === 'literal') {
|
|
229
|
-
return renderLiteral(arg);
|
|
230
|
-
}
|
|
231
|
-
if (arg.kind === 'operation') {
|
|
232
|
-
return renderOperation(arg, contract);
|
|
233
|
-
}
|
|
234
|
-
const _exhaustive: never = arg;
|
|
235
|
-
throw new Error(`Unsupported argument kind: ${(_exhaustive as { kind: string }).kind}`);
|
|
516
|
+
function renderOperation(
|
|
517
|
+
expr: OperationExpr,
|
|
518
|
+
contract?: PostgresContract,
|
|
519
|
+
pim?: ParamIndexMap,
|
|
520
|
+
): string {
|
|
521
|
+
const self = renderExpr(expr.self, contract, pim);
|
|
522
|
+
const args = expr.args.map((arg) => {
|
|
523
|
+
return renderExpr(arg, contract, pim);
|
|
236
524
|
});
|
|
237
525
|
|
|
238
526
|
let result = expr.lowering.template;
|
|
239
|
-
result = result.replace(
|
|
527
|
+
result = result.replace(/\{\{self\}\}/g, self);
|
|
240
528
|
for (let i = 0; i < args.length; i++) {
|
|
241
|
-
result = result.replace(new RegExp(
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (expr.lowering.strategy === 'function') {
|
|
245
|
-
return result;
|
|
529
|
+
result = result.replace(new RegExp(`\\{\\{arg${i}\\}\\}`, 'g'), args[i] ?? '');
|
|
246
530
|
}
|
|
247
531
|
|
|
248
532
|
return result;
|
|
249
533
|
}
|
|
250
534
|
|
|
251
|
-
function renderJoin(join: JoinAst,
|
|
535
|
+
function renderJoin(join: JoinAst, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
252
536
|
const joinType = join.joinType.toUpperCase();
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
|
|
537
|
+
const lateral = join.lateral ? 'LATERAL ' : '';
|
|
538
|
+
const source = renderSource(join.source, contract, pim);
|
|
539
|
+
const onClause = renderJoinOn(join.on, contract, pim);
|
|
540
|
+
return `${joinType} JOIN ${lateral}${source} ON ${onClause}`;
|
|
256
541
|
}
|
|
257
542
|
|
|
258
|
-
function renderJoinOn(on:
|
|
259
|
-
if (on.kind === '
|
|
543
|
+
function renderJoinOn(on: JoinOnExpr, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
544
|
+
if (on.kind === 'eq-col-join-on') {
|
|
260
545
|
const left = renderColumn(on.left);
|
|
261
546
|
const right = renderColumn(on.right);
|
|
262
547
|
return `${left} = ${right}`;
|
|
263
548
|
}
|
|
264
|
-
|
|
549
|
+
return renderWhere(on, contract, pim);
|
|
265
550
|
}
|
|
266
551
|
|
|
267
|
-
function
|
|
268
|
-
|
|
269
|
-
contract
|
|
270
|
-
|
|
271
|
-
|
|
552
|
+
function getInsertColumnOrder(
|
|
553
|
+
rows: ReadonlyArray<Record<string, InsertValue>>,
|
|
554
|
+
contract: PostgresContract,
|
|
555
|
+
tableName: string,
|
|
556
|
+
): string[] {
|
|
557
|
+
const orderedColumns: string[] = [];
|
|
558
|
+
const seenColumns = new Set<string>();
|
|
559
|
+
|
|
560
|
+
for (const row of rows) {
|
|
561
|
+
for (const column of Object.keys(row)) {
|
|
562
|
+
if (seenColumns.has(column)) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
seenColumns.add(column);
|
|
566
|
+
orderedColumns.push(column);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
272
569
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const expr = renderExpr(item.expr, contract);
|
|
277
|
-
return `'${item.alias}', ${expr}`;
|
|
278
|
-
})
|
|
279
|
-
.join(', ');
|
|
570
|
+
if (orderedColumns.length > 0) {
|
|
571
|
+
return orderedColumns;
|
|
572
|
+
}
|
|
280
573
|
|
|
281
|
-
|
|
574
|
+
return Object.keys(contract.storage.tables[tableName]?.columns ?? {});
|
|
575
|
+
}
|
|
282
576
|
|
|
283
|
-
|
|
284
|
-
|
|
577
|
+
function renderInsertValue(value: InsertValue | undefined, pim?: ParamIndexMap): string {
|
|
578
|
+
if (!value || value.kind === 'default-value') {
|
|
579
|
+
return 'DEFAULT';
|
|
580
|
+
}
|
|
285
581
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
582
|
+
switch (value.kind) {
|
|
583
|
+
case 'param-ref':
|
|
584
|
+
return renderParamRef(value, pim);
|
|
585
|
+
case 'column-ref':
|
|
586
|
+
return renderColumn(value);
|
|
587
|
+
// v8 ignore next 4
|
|
588
|
+
default:
|
|
589
|
+
throw new Error(
|
|
590
|
+
`Unsupported value node in INSERT: ${(value satisfies never as { kind: string }).kind}`,
|
|
591
|
+
);
|
|
290
592
|
}
|
|
593
|
+
}
|
|
291
594
|
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
595
|
+
function renderInsert(ast: InsertAst, contract: PostgresContract, pim?: ParamIndexMap): string {
|
|
596
|
+
const table = quoteIdentifier(ast.table.name);
|
|
597
|
+
const rows = ast.rows;
|
|
598
|
+
if (rows.length === 0) {
|
|
599
|
+
throw new Error('INSERT requires at least one row');
|
|
600
|
+
}
|
|
601
|
+
const hasExplicitValues = rows.some((row) => Object.keys(row).length > 0);
|
|
602
|
+
const insertClause = (() => {
|
|
603
|
+
if (!hasExplicitValues) {
|
|
604
|
+
if (rows.length === 1) {
|
|
605
|
+
return `INSERT INTO ${table} DEFAULT VALUES`;
|
|
606
|
+
}
|
|
301
607
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// Build the lateral subquery
|
|
306
|
-
// When ORDER BY is present without LIMIT, it goes inside json_agg() call: json_agg(expr ORDER BY ...)
|
|
307
|
-
// When LIMIT is present (with or without ORDER BY), we need to wrap in a subquery
|
|
308
|
-
const childTable = quoteIdentifier(include.child.table.name);
|
|
309
|
-
let subquery: string;
|
|
310
|
-
if (typeof include.child.limit === 'number') {
|
|
311
|
-
// With LIMIT, we need to wrap in a subquery
|
|
312
|
-
// Select individual columns in inner query, then aggregate
|
|
313
|
-
// Create a map of column references to their aliases for ORDER BY
|
|
314
|
-
// Only ColumnRef can be mapped (OperationExpr doesn't have table/column properties)
|
|
315
|
-
const columnAliasMap = new Map<string, string>();
|
|
316
|
-
for (const item of include.child.project) {
|
|
317
|
-
if (item.expr.kind === 'col') {
|
|
318
|
-
const columnKey = `${item.expr.table}.${item.expr.column}`;
|
|
319
|
-
columnAliasMap.set(columnKey, item.alias);
|
|
608
|
+
const defaultColumns = getInsertColumnOrder(rows, contract, ast.table.name);
|
|
609
|
+
if (defaultColumns.length === 0) {
|
|
610
|
+
return `INSERT INTO ${table} VALUES ${rows.map(() => '()').join(', ')}`;
|
|
320
611
|
}
|
|
612
|
+
|
|
613
|
+
const quotedColumns = defaultColumns.map((column) => quoteIdentifier(column));
|
|
614
|
+
const defaultRow = `(${defaultColumns.map(() => 'DEFAULT').join(', ')})`;
|
|
615
|
+
return `INSERT INTO ${table} (${quotedColumns.join(', ')}) VALUES ${rows
|
|
616
|
+
.map(() => defaultRow)
|
|
617
|
+
.join(', ')}`;
|
|
321
618
|
}
|
|
322
619
|
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
620
|
+
const columnOrder = getInsertColumnOrder(rows, contract, ast.table.name);
|
|
621
|
+
const columns = columnOrder.map((column) => quoteIdentifier(column));
|
|
622
|
+
const values = rows
|
|
623
|
+
.map((row) => {
|
|
624
|
+
const renderedRow = columnOrder.map((column) => renderInsertValue(row[column], pim));
|
|
625
|
+
return `(${renderedRow.join(', ')})`;
|
|
327
626
|
})
|
|
328
627
|
.join(', ');
|
|
329
628
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
629
|
+
return `INSERT INTO ${table} (${columns.join(', ')}) VALUES ${values}`;
|
|
630
|
+
})();
|
|
631
|
+
const onConflictClause = ast.onConflict
|
|
632
|
+
? (() => {
|
|
633
|
+
const conflictColumns = ast.onConflict.columns.map((col) => quoteIdentifier(col.column));
|
|
634
|
+
if (conflictColumns.length === 0) {
|
|
635
|
+
throw new Error('INSERT onConflict requires at least one conflict column');
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const action = ast.onConflict.action;
|
|
639
|
+
switch (action.kind) {
|
|
640
|
+
case 'do-nothing':
|
|
641
|
+
return ` ON CONFLICT (${conflictColumns.join(', ')}) DO NOTHING`;
|
|
642
|
+
case 'do-update-set': {
|
|
643
|
+
const updates = Object.entries(action.set).map(([colName, value]) => {
|
|
644
|
+
const target = quoteIdentifier(colName);
|
|
645
|
+
if (value.kind === 'param-ref') {
|
|
646
|
+
return `${target} = ${renderParamRef(value, pim)}`;
|
|
339
647
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
// No ORDER BY or LIMIT
|
|
353
|
-
subquery = `(SELECT json_agg(${jsonBuildObject}) AS ${quoteIdentifier(alias)} FROM ${childTable}${whereClause})`;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Return the LATERAL join with ON true (the condition is in the WHERE clause)
|
|
357
|
-
// The subquery returns a single column (the JSON array) with the alias
|
|
358
|
-
// We use a different alias for the table to avoid ambiguity when selecting the column
|
|
359
|
-
const tableAlias = `${alias}_lateral`;
|
|
360
|
-
return `LEFT JOIN LATERAL ${subquery} AS ${quoteIdentifier(tableAlias)} ON true`;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function quoteIdentifier(identifier: string): string {
|
|
364
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function renderInsert(ast: InsertAst, contract: PostgresContract): string {
|
|
368
|
-
const table = quoteIdentifier(ast.table.name);
|
|
369
|
-
const columns = Object.keys(ast.values).map((col) => quoteIdentifier(col));
|
|
370
|
-
const tableMeta = contract.storage.tables[ast.table.name];
|
|
371
|
-
const values = Object.entries(ast.values).map(([colName, val]) => {
|
|
372
|
-
if (val.kind === 'param') {
|
|
373
|
-
const columnMeta = tableMeta?.columns[colName];
|
|
374
|
-
const isVector = columnMeta?.codecId === VECTOR_CODEC_ID;
|
|
375
|
-
return isVector ? `$${val.index}::vector` : `$${val.index}`;
|
|
376
|
-
}
|
|
377
|
-
if (val.kind === 'col') {
|
|
378
|
-
return `${quoteIdentifier(val.table)}.${quoteIdentifier(val.column)}`;
|
|
379
|
-
}
|
|
380
|
-
throw new Error(`Unsupported value kind in INSERT: ${(val as { kind: string }).kind}`);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
const insertClause = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${values.join(', ')})`;
|
|
648
|
+
return `${target} = ${renderColumn(value)}`;
|
|
649
|
+
});
|
|
650
|
+
return ` ON CONFLICT (${conflictColumns.join(', ')}) DO UPDATE SET ${updates.join(', ')}`;
|
|
651
|
+
}
|
|
652
|
+
// v8 ignore next 4
|
|
653
|
+
default:
|
|
654
|
+
throw new Error(
|
|
655
|
+
`Unsupported onConflict action: ${(action satisfies never as { kind: string }).kind}`,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
})()
|
|
659
|
+
: '';
|
|
384
660
|
const returningClause = ast.returning?.length
|
|
385
661
|
? ` RETURNING ${ast.returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`
|
|
386
662
|
: '';
|
|
387
663
|
|
|
388
|
-
return `${insertClause}${returningClause}`;
|
|
664
|
+
return `${insertClause}${onConflictClause}${returningClause}`;
|
|
389
665
|
}
|
|
390
666
|
|
|
391
|
-
function renderUpdate(ast: UpdateAst, contract: PostgresContract): string {
|
|
667
|
+
function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim?: ParamIndexMap): string {
|
|
392
668
|
const table = quoteIdentifier(ast.table.name);
|
|
393
|
-
const tableMeta = contract.storage.tables[ast.table.name];
|
|
394
669
|
const setClauses = Object.entries(ast.set).map(([col, val]) => {
|
|
395
670
|
const column = quoteIdentifier(col);
|
|
396
671
|
let value: string;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
672
|
+
switch (val.kind) {
|
|
673
|
+
case 'param-ref':
|
|
674
|
+
value = renderParamRef(val, pim);
|
|
675
|
+
break;
|
|
676
|
+
case 'column-ref':
|
|
677
|
+
value = renderColumn(val);
|
|
678
|
+
break;
|
|
679
|
+
// v8 ignore next 4
|
|
680
|
+
default:
|
|
681
|
+
throw new Error(
|
|
682
|
+
`Unsupported value node in UPDATE: ${(val satisfies never as { kind: string }).kind}`,
|
|
683
|
+
);
|
|
405
684
|
}
|
|
406
685
|
return `${column} = ${value}`;
|
|
407
686
|
});
|
|
408
687
|
|
|
409
|
-
const whereClause = ` WHERE ${
|
|
688
|
+
const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
|
|
410
689
|
const returningClause = ast.returning?.length
|
|
411
690
|
? ` RETURNING ${ast.returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`
|
|
412
691
|
: '';
|
|
@@ -414,9 +693,9 @@ function renderUpdate(ast: UpdateAst, contract: PostgresContract): string {
|
|
|
414
693
|
return `UPDATE ${table} SET ${setClauses.join(', ')}${whereClause}${returningClause}`;
|
|
415
694
|
}
|
|
416
695
|
|
|
417
|
-
function renderDelete(ast: DeleteAst, contract?: PostgresContract): string {
|
|
696
|
+
function renderDelete(ast: DeleteAst, contract?: PostgresContract, pim?: ParamIndexMap): string {
|
|
418
697
|
const table = quoteIdentifier(ast.table.name);
|
|
419
|
-
const whereClause = ` WHERE ${
|
|
698
|
+
const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
|
|
420
699
|
const returningClause = ast.returning?.length
|
|
421
700
|
? ` RETURNING ${ast.returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`
|
|
422
701
|
: '';
|