@prisma-next/adapter-postgres 0.12.0-dev.4 → 0.12.0-dev.41
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 +13 -20
- package/dist/{adapter-H8BiuXdq.mjs → adapter-CvkgqjE9.mjs} +13 -13
- package/dist/adapter-CvkgqjE9.mjs.map +1 -0
- package/dist/adapter.d.mts +2 -1
- package/dist/adapter.d.mts.map +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/control-adapter-5BSAQMQc.mjs +1364 -0
- package/dist/control-adapter-5BSAQMQc.mjs.map +1 -0
- package/dist/control.d.mts +62 -4
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +3 -659
- package/dist/control.mjs.map +1 -1
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.mjs +1 -1
- package/package.json +22 -22
- package/src/core/adapter.ts +28 -25
- package/src/core/control-adapter.ts +374 -90
- package/src/core/ddl-renderer.ts +73 -0
- package/src/core/marker-ledger.ts +124 -0
- package/src/core/sql-renderer.ts +66 -23
- package/dist/adapter-H8BiuXdq.mjs.map +0 -1
- package/dist/sql-renderer-DlZhVI9B.mjs +0 -457
- package/dist/sql-renderer-DlZhVI9B.mjs.map +0 -1
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
Contract,
|
|
3
|
+
ContractMarkerRecord,
|
|
4
|
+
LedgerEntryRecord,
|
|
5
|
+
} from '@prisma-next/contract/types';
|
|
6
|
+
import {
|
|
7
|
+
parseMarkerRowSafely,
|
|
8
|
+
rethrowMarkerReadError,
|
|
9
|
+
withMarkerReadErrorHandling,
|
|
10
|
+
} from '@prisma-next/errors/execution';
|
|
3
11
|
import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
|
|
4
12
|
import { parseContractMarkerRow } from '@prisma-next/family-sql/verify';
|
|
5
13
|
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
@@ -8,12 +16,16 @@ import {
|
|
|
8
16
|
type ControlDriverInstance,
|
|
9
17
|
} from '@prisma-next/framework-components/control';
|
|
10
18
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
19
|
+
import { ledgerOriginFromStored } from '@prisma-next/migration-tools/ledger-origin';
|
|
11
20
|
import type { PostgresEnumStorageEntry, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
12
21
|
import type {
|
|
13
22
|
AnyQueryAst,
|
|
23
|
+
DdlNode,
|
|
14
24
|
LoweredStatement,
|
|
15
25
|
LowererContext,
|
|
26
|
+
MarkerReadResult,
|
|
16
27
|
} from '@prisma-next/sql-relational-core/ast';
|
|
28
|
+
import { isDdlNode } from '@prisma-next/sql-relational-core/ast';
|
|
17
29
|
import type {
|
|
18
30
|
PrimaryKey,
|
|
19
31
|
SqlColumnIR,
|
|
@@ -24,6 +36,12 @@ import type {
|
|
|
24
36
|
SqlTableIR,
|
|
25
37
|
SqlUniqueIR,
|
|
26
38
|
} from '@prisma-next/sql-schema-ir/types';
|
|
39
|
+
import { postgresColumnsCompatible } from '@prisma-next/target-postgres/column-type-compatibility';
|
|
40
|
+
import {
|
|
41
|
+
buildControlTableBootstrapQueries,
|
|
42
|
+
buildSignMarkerBootstrapQueries,
|
|
43
|
+
} from '@prisma-next/target-postgres/contract-free';
|
|
44
|
+
import type { PostgresDdlNode } from '@prisma-next/target-postgres/ddl';
|
|
27
45
|
import { parsePostgresDefault } from '@prisma-next/target-postgres/default-normalizer';
|
|
28
46
|
import {
|
|
29
47
|
createResolveExistingEnumValues,
|
|
@@ -35,14 +53,35 @@ import { normalizeSchemaNativeType } from '@prisma-next/target-postgres/native-t
|
|
|
35
53
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
36
54
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
37
55
|
import { createPostgresBuiltinCodecLookup } from './codec-lookup';
|
|
56
|
+
import { renderLoweredDdl } from './ddl-renderer';
|
|
38
57
|
import {
|
|
39
58
|
introspectPostgresEnumTypes,
|
|
40
59
|
type PostgresEnumStorageTypeAnnotation,
|
|
41
60
|
} from './enum-control-hooks';
|
|
61
|
+
import {
|
|
62
|
+
execute,
|
|
63
|
+
infoSchemaTables,
|
|
64
|
+
ledger,
|
|
65
|
+
ledgerReadShape,
|
|
66
|
+
marker,
|
|
67
|
+
mergeInvariants,
|
|
68
|
+
NOW,
|
|
69
|
+
} from './marker-ledger';
|
|
42
70
|
import { renderLoweredSql } from './sql-renderer';
|
|
43
71
|
import type { PostgresContract } from './types';
|
|
44
72
|
|
|
45
73
|
const POSTGRES_MARKER_TABLE = 'prisma_contract.marker';
|
|
74
|
+
const POSTGRES_LEDGER_TABLE = 'prisma_contract.ledger';
|
|
75
|
+
|
|
76
|
+
type PostgresLedgerRow = {
|
|
77
|
+
readonly space: string;
|
|
78
|
+
readonly migration_name: string;
|
|
79
|
+
readonly migration_hash: string;
|
|
80
|
+
readonly origin_core_hash: string | null;
|
|
81
|
+
readonly destination_core_hash: string;
|
|
82
|
+
readonly operations: unknown;
|
|
83
|
+
readonly created_at: Date | string;
|
|
84
|
+
};
|
|
46
85
|
|
|
47
86
|
/**
|
|
48
87
|
* Postgres control plane adapter for control-plane operations like introspection.
|
|
@@ -78,6 +117,13 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
78
117
|
*/
|
|
79
118
|
readonly normalizeNativeType = normalizeSchemaNativeType;
|
|
80
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Target-supplied compatible-shape relation used under the `external`
|
|
122
|
+
* control policy. Threading the same relation the migration planner/runner
|
|
123
|
+
* use keeps runtime verify and migration verify in agreement.
|
|
124
|
+
*/
|
|
125
|
+
readonly columnsCompatible = postgresColumnsCompatible;
|
|
126
|
+
|
|
81
127
|
/**
|
|
82
128
|
* Bridges native `PostgresEnumStorageEntry` IR walks against the Postgres
|
|
83
129
|
* introspection shape (`schema.annotations.pg.storageTypes`). Lets
|
|
@@ -99,6 +145,14 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
99
145
|
readonly resolveExistingEnumValuesForContract = (contract: Contract<SqlStorage>) =>
|
|
100
146
|
createResolveExistingEnumValues(contract.storage);
|
|
101
147
|
|
|
148
|
+
bootstrapControlTableQueries(): readonly DdlNode[] {
|
|
149
|
+
return buildControlTableBootstrapQueries();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
bootstrapSignMarkerQueries(): readonly DdlNode[] {
|
|
153
|
+
return buildSignMarkerBootstrapQueries();
|
|
154
|
+
}
|
|
155
|
+
|
|
102
156
|
/**
|
|
103
157
|
* Lower a SQL query AST into a Postgres-flavored `{ sql, params }` payload.
|
|
104
158
|
*
|
|
@@ -107,7 +161,10 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
107
161
|
* and contract. Used at migration plan/emit time (e.g. by `dataTransform`)
|
|
108
162
|
* without instantiating the runtime adapter.
|
|
109
163
|
*/
|
|
110
|
-
lower(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement {
|
|
164
|
+
lower(ast: AnyQueryAst | PostgresDdlNode, context: LowererContext<unknown>): LoweredStatement {
|
|
165
|
+
if (isDdlNode(ast)) {
|
|
166
|
+
return renderLoweredDdl(ast);
|
|
167
|
+
}
|
|
111
168
|
return renderLoweredSql(ast, context.contract as PostgresContract, this.codecLookup);
|
|
112
169
|
}
|
|
113
170
|
|
|
@@ -123,52 +180,16 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
123
180
|
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
124
181
|
space: string,
|
|
125
182
|
): Promise<ContractMarkerRecord | null> {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
driver.query(
|
|
130
|
-
`select 1
|
|
131
|
-
from information_schema.tables
|
|
132
|
-
where table_schema = $1 and table_name = $2`,
|
|
133
|
-
['prisma_contract', 'marker'],
|
|
134
|
-
),
|
|
135
|
-
markerContext,
|
|
136
|
-
);
|
|
137
|
-
if (exists.rows.length === 0) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const result = await withMarkerReadErrorHandling(
|
|
142
|
-
() =>
|
|
143
|
-
driver.query<{
|
|
144
|
-
core_hash: string;
|
|
145
|
-
profile_hash: string;
|
|
146
|
-
contract_json: unknown | null;
|
|
147
|
-
canonical_version: number | null;
|
|
148
|
-
updated_at: Date | string;
|
|
149
|
-
app_tag: string | null;
|
|
150
|
-
meta: unknown | null;
|
|
151
|
-
invariants: readonly string[];
|
|
152
|
-
}>(
|
|
153
|
-
`select
|
|
154
|
-
core_hash,
|
|
155
|
-
profile_hash,
|
|
156
|
-
contract_json,
|
|
157
|
-
canonical_version,
|
|
158
|
-
updated_at,
|
|
159
|
-
app_tag,
|
|
160
|
-
meta,
|
|
161
|
-
invariants
|
|
162
|
-
from prisma_contract.marker
|
|
163
|
-
where space = $1`,
|
|
164
|
-
[space],
|
|
165
|
-
),
|
|
166
|
-
markerContext,
|
|
167
|
-
);
|
|
183
|
+
const result = await this.readMarkerDiscriminated(driver, space);
|
|
184
|
+
return result.kind === 'present' ? result.record : null;
|
|
185
|
+
}
|
|
168
186
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
187
|
+
async readMarkerDiscriminated(
|
|
188
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
189
|
+
space: string,
|
|
190
|
+
): Promise<MarkerReadResult> {
|
|
191
|
+
const markerContext = { space, markerLocation: POSTGRES_MARKER_TABLE };
|
|
192
|
+
return withMarkerReadErrorHandling(() => this.readMarkerResult(driver, space), markerContext);
|
|
172
193
|
}
|
|
173
194
|
|
|
174
195
|
/**
|
|
@@ -181,51 +202,50 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
181
202
|
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
182
203
|
): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
183
204
|
const markerContext = { space: APP_SPACE_ID, markerLocation: POSTGRES_MARKER_TABLE };
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
205
|
+
return withMarkerReadErrorHandling(() => this.readAllMarkersResult(driver), markerContext);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async readAllMarkersResult(
|
|
209
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
210
|
+
): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
211
|
+
const lower = (query: AnyQueryAst) => this.lower(query, { contract: undefined });
|
|
212
|
+
const probe = infoSchemaTables
|
|
213
|
+
.select(infoSchemaTables.table_schema)
|
|
214
|
+
.where(
|
|
215
|
+
infoSchemaTables.table_schema
|
|
216
|
+
.eq('prisma_contract')
|
|
217
|
+
.and(infoSchemaTables.table_name.eq('marker')),
|
|
218
|
+
)
|
|
219
|
+
.build();
|
|
220
|
+
const exists = await execute(lower, driver, probe);
|
|
221
|
+
if (exists.length === 0) {
|
|
195
222
|
return new Map();
|
|
196
223
|
}
|
|
197
224
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
app_tag,
|
|
219
|
-
meta,
|
|
220
|
-
invariants
|
|
221
|
-
from prisma_contract.marker`,
|
|
222
|
-
),
|
|
223
|
-
markerContext,
|
|
224
|
-
);
|
|
225
|
+
await this.assertMarkerTableHasSpaceColumn(driver, APP_SPACE_ID);
|
|
226
|
+
|
|
227
|
+
const fetch = marker
|
|
228
|
+
.select(
|
|
229
|
+
marker.space,
|
|
230
|
+
marker.core_hash,
|
|
231
|
+
marker.profile_hash,
|
|
232
|
+
marker.contract_json,
|
|
233
|
+
marker.canonical_version,
|
|
234
|
+
marker.updated_at,
|
|
235
|
+
marker.app_tag,
|
|
236
|
+
marker.meta,
|
|
237
|
+
marker.invariants,
|
|
238
|
+
)
|
|
239
|
+
.build();
|
|
240
|
+
const rawRows = await execute(lower, driver, fetch);
|
|
241
|
+
const rows = blindCast<
|
|
242
|
+
ReadonlyArray<{ space: string } & Record<string, unknown>>,
|
|
243
|
+
'Driver returns rows shaped by SELECT'
|
|
244
|
+
>(rawRows);
|
|
225
245
|
|
|
226
|
-
const
|
|
227
|
-
for (const row of
|
|
228
|
-
|
|
246
|
+
const out = new Map<string, ContractMarkerRecord>();
|
|
247
|
+
for (const row of rows) {
|
|
248
|
+
out.set(
|
|
229
249
|
row.space,
|
|
230
250
|
parseMarkerRowSafely(row, parseContractMarkerRow, {
|
|
231
251
|
space: row.space,
|
|
@@ -233,7 +253,271 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
233
253
|
}),
|
|
234
254
|
);
|
|
235
255
|
}
|
|
236
|
-
return
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Reads per-migration ledger rows from `prisma_contract.ledger` in apply
|
|
261
|
+
* order. Probes `information_schema.tables` first so a fresh database
|
|
262
|
+
* without the ledger table returns `[]` instead of raising "relation does
|
|
263
|
+
* not exist".
|
|
264
|
+
*/
|
|
265
|
+
async readLedger(
|
|
266
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
267
|
+
space?: string,
|
|
268
|
+
): Promise<readonly LedgerEntryRecord[]> {
|
|
269
|
+
const ledgerContext = { space: space ?? '*', markerLocation: POSTGRES_LEDGER_TABLE };
|
|
270
|
+
return withMarkerReadErrorHandling(() => this.readLedgerResult(driver, space), ledgerContext);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private async readLedgerResult(
|
|
274
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
275
|
+
space: string | undefined,
|
|
276
|
+
): Promise<readonly LedgerEntryRecord[]> {
|
|
277
|
+
const lower = (query: AnyQueryAst) => this.lower(query, { contract: undefined });
|
|
278
|
+
const probe = infoSchemaTables
|
|
279
|
+
.select(infoSchemaTables.table_schema)
|
|
280
|
+
.where(
|
|
281
|
+
infoSchemaTables.table_schema
|
|
282
|
+
.eq('prisma_contract')
|
|
283
|
+
.and(infoSchemaTables.table_name.eq('ledger')),
|
|
284
|
+
)
|
|
285
|
+
.build();
|
|
286
|
+
const exists = await execute(lower, driver, probe);
|
|
287
|
+
if (exists.length === 0) {
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const base = ledgerReadShape.select(
|
|
292
|
+
ledgerReadShape.space,
|
|
293
|
+
ledgerReadShape.migration_name,
|
|
294
|
+
ledgerReadShape.migration_hash,
|
|
295
|
+
ledgerReadShape.origin_core_hash,
|
|
296
|
+
ledgerReadShape.destination_core_hash,
|
|
297
|
+
ledgerReadShape.operations,
|
|
298
|
+
ledgerReadShape.created_at,
|
|
299
|
+
);
|
|
300
|
+
const filtered = space !== undefined ? base.where(ledgerReadShape.space.eq(space)) : base;
|
|
301
|
+
const rawRows = await execute(lower, driver, filtered.orderBy(ledgerReadShape.id).build());
|
|
302
|
+
const rows = blindCast<readonly PostgresLedgerRow[], 'Driver returns rows shaped by SELECT'>(
|
|
303
|
+
rawRows,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
return rows.map((row) => {
|
|
307
|
+
const appliedAt = row.created_at instanceof Date ? row.created_at : new Date(row.created_at);
|
|
308
|
+
return {
|
|
309
|
+
space: row.space,
|
|
310
|
+
migrationName: row.migration_name,
|
|
311
|
+
migrationHash: row.migration_hash,
|
|
312
|
+
from: ledgerOriginFromStored(row.origin_core_hash),
|
|
313
|
+
to: row.destination_core_hash,
|
|
314
|
+
appliedAt,
|
|
315
|
+
operationCount: Array.isArray(row.operations) ? row.operations.length : 0,
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Stamps the initial marker row for `space` via the shared contract-free DML
|
|
322
|
+
* builder, lowered through {@link lower} and executed on the driver. See the
|
|
323
|
+
* `SqlControlAdapter.initMarker` contract.
|
|
324
|
+
*/
|
|
325
|
+
async insertMarker(
|
|
326
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
327
|
+
space: string,
|
|
328
|
+
destination: {
|
|
329
|
+
readonly storageHash: string;
|
|
330
|
+
readonly profileHash: string;
|
|
331
|
+
readonly invariants?: readonly string[];
|
|
332
|
+
},
|
|
333
|
+
): Promise<void> {
|
|
334
|
+
await execute(
|
|
335
|
+
(query) => this.lower(query, { contract: undefined }),
|
|
336
|
+
driver,
|
|
337
|
+
marker
|
|
338
|
+
.insert({
|
|
339
|
+
space,
|
|
340
|
+
core_hash: destination.storageHash,
|
|
341
|
+
profile_hash: destination.profileHash,
|
|
342
|
+
contract_json: null,
|
|
343
|
+
canonical_version: null,
|
|
344
|
+
updated_at: NOW,
|
|
345
|
+
app_tag: null,
|
|
346
|
+
meta: {},
|
|
347
|
+
invariants: destination.invariants ?? [],
|
|
348
|
+
})
|
|
349
|
+
.build(),
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async initMarker(
|
|
354
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
355
|
+
space: string,
|
|
356
|
+
destination: {
|
|
357
|
+
readonly storageHash: string;
|
|
358
|
+
readonly profileHash: string;
|
|
359
|
+
readonly invariants?: readonly string[];
|
|
360
|
+
},
|
|
361
|
+
): Promise<void> {
|
|
362
|
+
await execute(
|
|
363
|
+
(query) => this.lower(query, { contract: undefined }),
|
|
364
|
+
driver,
|
|
365
|
+
marker
|
|
366
|
+
.upsert({
|
|
367
|
+
space,
|
|
368
|
+
core_hash: destination.storageHash,
|
|
369
|
+
profile_hash: destination.profileHash,
|
|
370
|
+
contract_json: null,
|
|
371
|
+
canonical_version: null,
|
|
372
|
+
updated_at: NOW,
|
|
373
|
+
app_tag: null,
|
|
374
|
+
meta: {},
|
|
375
|
+
invariants: destination.invariants ?? [],
|
|
376
|
+
})
|
|
377
|
+
.onConflict(marker.space)
|
|
378
|
+
.doUpdate((excluded) => ({
|
|
379
|
+
core_hash: excluded.core_hash,
|
|
380
|
+
profile_hash: excluded.profile_hash,
|
|
381
|
+
contract_json: excluded.contract_json,
|
|
382
|
+
canonical_version: excluded.canonical_version,
|
|
383
|
+
updated_at: NOW,
|
|
384
|
+
app_tag: excluded.app_tag,
|
|
385
|
+
meta: excluded.meta,
|
|
386
|
+
invariants: excluded.invariants,
|
|
387
|
+
}))
|
|
388
|
+
.build(),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Compare-and-swap advance of the marker row for `space`. See the
|
|
394
|
+
* `SqlControlAdapter.updateMarker` contract.
|
|
395
|
+
*/
|
|
396
|
+
async updateMarker(
|
|
397
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
398
|
+
space: string,
|
|
399
|
+
expectedFrom: string,
|
|
400
|
+
destination: {
|
|
401
|
+
readonly storageHash: string;
|
|
402
|
+
readonly profileHash: string;
|
|
403
|
+
readonly invariants?: readonly string[];
|
|
404
|
+
},
|
|
405
|
+
): Promise<boolean> {
|
|
406
|
+
const currentInvariants =
|
|
407
|
+
destination.invariants === undefined
|
|
408
|
+
? []
|
|
409
|
+
: ((await this.readMarker(driver, space))?.invariants ?? []);
|
|
410
|
+
const mergedInvariants =
|
|
411
|
+
destination.invariants === undefined
|
|
412
|
+
? undefined
|
|
413
|
+
: mergeInvariants(currentInvariants, destination.invariants);
|
|
414
|
+
|
|
415
|
+
const query = marker
|
|
416
|
+
.update()
|
|
417
|
+
.set({
|
|
418
|
+
core_hash: destination.storageHash,
|
|
419
|
+
profile_hash: destination.profileHash,
|
|
420
|
+
updated_at: NOW,
|
|
421
|
+
...(mergedInvariants !== undefined ? { invariants: mergedInvariants } : {}),
|
|
422
|
+
})
|
|
423
|
+
.where(marker.space.eq(space).and(marker.core_hash.eq(expectedFrom)))
|
|
424
|
+
.returning(marker.space)
|
|
425
|
+
.build();
|
|
426
|
+
|
|
427
|
+
const rows = await execute((q) => this.lower(q, { contract: undefined }), driver, query);
|
|
428
|
+
return rows.length > 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Appends a ledger entry for `space`. See the
|
|
433
|
+
* `SqlControlAdapter.writeLedgerEntry` contract.
|
|
434
|
+
*/
|
|
435
|
+
async writeLedgerEntry(
|
|
436
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
437
|
+
space: string,
|
|
438
|
+
entry: {
|
|
439
|
+
readonly edgeId: string;
|
|
440
|
+
readonly from: string;
|
|
441
|
+
readonly to: string;
|
|
442
|
+
readonly migrationName: string;
|
|
443
|
+
readonly migrationHash: string;
|
|
444
|
+
readonly operations: readonly unknown[];
|
|
445
|
+
},
|
|
446
|
+
): Promise<void> {
|
|
447
|
+
await execute(
|
|
448
|
+
(query) => this.lower(query, { contract: undefined }),
|
|
449
|
+
driver,
|
|
450
|
+
ledger
|
|
451
|
+
.insert({
|
|
452
|
+
space,
|
|
453
|
+
migration_name: entry.migrationName,
|
|
454
|
+
migration_hash: entry.migrationHash,
|
|
455
|
+
origin_core_hash: entry.from,
|
|
456
|
+
destination_core_hash: entry.to,
|
|
457
|
+
operations: entry.operations,
|
|
458
|
+
})
|
|
459
|
+
.build(),
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private async assertMarkerTableHasSpaceColumn(
|
|
464
|
+
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
465
|
+
space: string,
|
|
466
|
+
): Promise<void> {
|
|
467
|
+
const result = await driver.query<{ column_name: string }>(
|
|
468
|
+
`select column_name
|
|
469
|
+
from information_schema.columns
|
|
470
|
+
where table_schema = 'prisma_contract'
|
|
471
|
+
and table_name = 'marker'`,
|
|
472
|
+
);
|
|
473
|
+
const rows = result.rows;
|
|
474
|
+
if (rows.length === 0) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (!rows.every((row) => typeof row.column_name === 'string')) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (rows.some((row) => row.column_name === 'space')) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
rethrowMarkerReadError(new Error('column "space" does not exist'), {
|
|
484
|
+
space,
|
|
485
|
+
markerLocation: POSTGRES_MARKER_TABLE,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private async readMarkerResult(driver: ControlDriverInstance<'sql', 'postgres'>, space: string) {
|
|
490
|
+
const lower = (query: AnyQueryAst) => this.lower(query, { contract: undefined });
|
|
491
|
+
const probe = infoSchemaTables
|
|
492
|
+
.select(infoSchemaTables.table_schema)
|
|
493
|
+
.where(
|
|
494
|
+
infoSchemaTables.table_schema
|
|
495
|
+
.eq('prisma_contract')
|
|
496
|
+
.and(infoSchemaTables.table_name.eq('marker')),
|
|
497
|
+
)
|
|
498
|
+
.build();
|
|
499
|
+
const exists = await execute(lower, driver, probe);
|
|
500
|
+
if (exists.length === 0) return { kind: 'no-table' as const };
|
|
501
|
+
|
|
502
|
+
await this.assertMarkerTableHasSpaceColumn(driver, space);
|
|
503
|
+
|
|
504
|
+
const fetch = marker
|
|
505
|
+
.select(
|
|
506
|
+
marker.core_hash,
|
|
507
|
+
marker.profile_hash,
|
|
508
|
+
marker.contract_json,
|
|
509
|
+
marker.canonical_version,
|
|
510
|
+
marker.updated_at,
|
|
511
|
+
marker.app_tag,
|
|
512
|
+
marker.meta,
|
|
513
|
+
marker.invariants,
|
|
514
|
+
)
|
|
515
|
+
.where(marker.space.eq(space))
|
|
516
|
+
.build();
|
|
517
|
+
const result = await execute(lower, driver, fetch);
|
|
518
|
+
const row = result[0];
|
|
519
|
+
if (!row) return { kind: 'absent' as const };
|
|
520
|
+
return { kind: 'present' as const, record: parseContractMarkerRow(row) };
|
|
237
521
|
}
|
|
238
522
|
|
|
239
523
|
/**
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DdlColumn,
|
|
3
|
+
DdlColumnDefaultVisitor,
|
|
4
|
+
FunctionColumnDefault,
|
|
5
|
+
LiteralColumnDefault,
|
|
6
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
7
|
+
import type {
|
|
8
|
+
PostgresCreateSchema,
|
|
9
|
+
PostgresCreateTable,
|
|
10
|
+
PostgresDdlNode,
|
|
11
|
+
PostgresDdlVisitor,
|
|
12
|
+
} from '@prisma-next/target-postgres/ddl';
|
|
13
|
+
import { escapeLiteral } from '@prisma-next/target-postgres/sql-utils';
|
|
14
|
+
import type { PostgresLoweredStatement } from './types';
|
|
15
|
+
|
|
16
|
+
class PostgresDdlVisitorImpl implements PostgresDdlVisitor<string> {
|
|
17
|
+
createTable(node: PostgresCreateTable): string {
|
|
18
|
+
const ifNotExists = node.ifNotExists ? 'if not exists ' : '';
|
|
19
|
+
const tableRef = node.schema ? `${node.schema}.${node.table}` : node.table;
|
|
20
|
+
const columnDefs = node.columns.map((column) => renderColumn(column)).join(',\n ');
|
|
21
|
+
return `create table ${ifNotExists}${tableRef} (\n ${columnDefs}\n )`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
createSchema(node: PostgresCreateSchema): string {
|
|
25
|
+
const ifNotExists = node.ifNotExists ? 'if not exists ' : '';
|
|
26
|
+
return `create schema ${ifNotExists}${node.schema}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const defaultVisitor: DdlColumnDefaultVisitor<string> = {
|
|
31
|
+
literal(node: LiteralColumnDefault): string {
|
|
32
|
+
const { value } = node;
|
|
33
|
+
if (typeof value === 'string') {
|
|
34
|
+
return `default '${escapeLiteral(value)}'`;
|
|
35
|
+
}
|
|
36
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
37
|
+
return `default ${String(value)}`;
|
|
38
|
+
}
|
|
39
|
+
if (value === null) {
|
|
40
|
+
return 'default null';
|
|
41
|
+
}
|
|
42
|
+
return `default '${JSON.stringify(value)}'`;
|
|
43
|
+
},
|
|
44
|
+
function(node: FunctionColumnDefault): string {
|
|
45
|
+
if (node.expression === 'autoincrement()') {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
if (node.expression === 'now()') {
|
|
49
|
+
return 'default now()';
|
|
50
|
+
}
|
|
51
|
+
return `default (${node.expression})`;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function renderColumn(column: DdlColumn): string {
|
|
56
|
+
const parts = [column.name, column.type];
|
|
57
|
+
if (column.notNull) {
|
|
58
|
+
parts.push('not null');
|
|
59
|
+
}
|
|
60
|
+
if (column.primaryKey) {
|
|
61
|
+
parts.push('primary key');
|
|
62
|
+
}
|
|
63
|
+
const defaultClause = column.default ? column.default.accept(defaultVisitor) : '';
|
|
64
|
+
if (defaultClause.length > 0) {
|
|
65
|
+
parts.push(defaultClause);
|
|
66
|
+
}
|
|
67
|
+
return parts.join(' ');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function renderLoweredDdl(ast: PostgresDdlNode): PostgresLoweredStatement {
|
|
71
|
+
const sql = ast.accept(new PostgresDdlVisitorImpl());
|
|
72
|
+
return Object.freeze({ sql, params: Object.freeze([]) });
|
|
73
|
+
}
|