@prisma-next/adapter-postgres 0.12.0-dev.31 → 0.12.0-dev.33
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/dist/{adapter-Dr2W2Syq.mjs → adapter-DuD5XIwi.mjs} +11 -13
- package/dist/adapter-DuD5XIwi.mjs.map +1 -0
- package/dist/adapter.d.mts.map +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/control-adapter-CykaRJU9.mjs +1367 -0
- package/dist/control-adapter-CykaRJU9.mjs.map +1 -0
- package/dist/control.d.mts +43 -2
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +3 -719
- package/dist/control.mjs.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/package.json +22 -22
- package/src/core/adapter.ts +18 -24
- package/src/core/control-adapter.ts +320 -135
- package/src/core/marker-ledger.ts +124 -0
- package/src/core/sql-renderer.ts +14 -2
- package/dist/adapter-Dr2W2Syq.mjs.map +0 -1
- package/dist/sql-renderer-DqVeL4hP.mjs +0 -510
- package/dist/sql-renderer-DqVeL4hP.mjs.map +0 -1
|
@@ -3,7 +3,11 @@ import type {
|
|
|
3
3
|
ContractMarkerRecord,
|
|
4
4
|
LedgerEntryRecord,
|
|
5
5
|
} from '@prisma-next/contract/types';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
parseMarkerRowSafely,
|
|
8
|
+
rethrowMarkerReadError,
|
|
9
|
+
withMarkerReadErrorHandling,
|
|
10
|
+
} from '@prisma-next/errors/execution';
|
|
7
11
|
import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
|
|
8
12
|
import { parseContractMarkerRow } from '@prisma-next/family-sql/verify';
|
|
9
13
|
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
@@ -19,6 +23,7 @@ import type {
|
|
|
19
23
|
DdlNode,
|
|
20
24
|
LoweredStatement,
|
|
21
25
|
LowererContext,
|
|
26
|
+
MarkerReadResult,
|
|
22
27
|
} from '@prisma-next/sql-relational-core/ast';
|
|
23
28
|
import { isDdlNode } from '@prisma-next/sql-relational-core/ast';
|
|
24
29
|
import type {
|
|
@@ -53,12 +58,31 @@ import {
|
|
|
53
58
|
introspectPostgresEnumTypes,
|
|
54
59
|
type PostgresEnumStorageTypeAnnotation,
|
|
55
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';
|
|
56
70
|
import { renderLoweredSql } from './sql-renderer';
|
|
57
71
|
import type { PostgresContract } from './types';
|
|
58
72
|
|
|
59
73
|
const POSTGRES_MARKER_TABLE = 'prisma_contract.marker';
|
|
60
74
|
const POSTGRES_LEDGER_TABLE = 'prisma_contract.ledger';
|
|
61
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
|
+
};
|
|
85
|
+
|
|
62
86
|
/**
|
|
63
87
|
* Postgres control plane adapter for control-plane operations like introspection.
|
|
64
88
|
* Provides target-specific implementations for control-plane domain actions.
|
|
@@ -156,52 +180,16 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
156
180
|
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
157
181
|
space: string,
|
|
158
182
|
): Promise<ContractMarkerRecord | null> {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
driver.query(
|
|
163
|
-
`select 1
|
|
164
|
-
from information_schema.tables
|
|
165
|
-
where table_schema = $1 and table_name = $2`,
|
|
166
|
-
['prisma_contract', 'marker'],
|
|
167
|
-
),
|
|
168
|
-
markerContext,
|
|
169
|
-
);
|
|
170
|
-
if (exists.rows.length === 0) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const result = await withMarkerReadErrorHandling(
|
|
175
|
-
() =>
|
|
176
|
-
driver.query<{
|
|
177
|
-
core_hash: string;
|
|
178
|
-
profile_hash: string;
|
|
179
|
-
contract_json: unknown | null;
|
|
180
|
-
canonical_version: number | null;
|
|
181
|
-
updated_at: Date | string;
|
|
182
|
-
app_tag: string | null;
|
|
183
|
-
meta: unknown | null;
|
|
184
|
-
invariants: readonly string[];
|
|
185
|
-
}>(
|
|
186
|
-
`select
|
|
187
|
-
core_hash,
|
|
188
|
-
profile_hash,
|
|
189
|
-
contract_json,
|
|
190
|
-
canonical_version,
|
|
191
|
-
updated_at,
|
|
192
|
-
app_tag,
|
|
193
|
-
meta,
|
|
194
|
-
invariants
|
|
195
|
-
from prisma_contract.marker
|
|
196
|
-
where space = $1`,
|
|
197
|
-
[space],
|
|
198
|
-
),
|
|
199
|
-
markerContext,
|
|
200
|
-
);
|
|
183
|
+
const result = await this.readMarkerDiscriminated(driver, space);
|
|
184
|
+
return result.kind === 'present' ? result.record : null;
|
|
185
|
+
}
|
|
201
186
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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);
|
|
205
193
|
}
|
|
206
194
|
|
|
207
195
|
/**
|
|
@@ -214,51 +202,50 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
214
202
|
driver: ControlDriverInstance<'sql', 'postgres'>,
|
|
215
203
|
): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
216
204
|
const markerContext = { space: APP_SPACE_ID, markerLocation: POSTGRES_MARKER_TABLE };
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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) {
|
|
228
222
|
return new Map();
|
|
229
223
|
}
|
|
230
224
|
|
|
231
|
-
|
|
232
|
-
() =>
|
|
233
|
-
driver.query<{
|
|
234
|
-
space: string;
|
|
235
|
-
core_hash: string;
|
|
236
|
-
profile_hash: string;
|
|
237
|
-
contract_json: unknown | null;
|
|
238
|
-
canonical_version: number | null;
|
|
239
|
-
updated_at: Date | string;
|
|
240
|
-
app_tag: string | null;
|
|
241
|
-
meta: unknown | null;
|
|
242
|
-
invariants: readonly string[];
|
|
243
|
-
}>(
|
|
244
|
-
`select
|
|
245
|
-
space,
|
|
246
|
-
core_hash,
|
|
247
|
-
profile_hash,
|
|
248
|
-
contract_json,
|
|
249
|
-
canonical_version,
|
|
250
|
-
updated_at,
|
|
251
|
-
app_tag,
|
|
252
|
-
meta,
|
|
253
|
-
invariants
|
|
254
|
-
from prisma_contract.marker`,
|
|
255
|
-
),
|
|
256
|
-
markerContext,
|
|
257
|
-
);
|
|
225
|
+
await this.assertMarkerTableHasSpaceColumn(driver, APP_SPACE_ID);
|
|
258
226
|
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
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);
|
|
245
|
+
|
|
246
|
+
const out = new Map<string, ContractMarkerRecord>();
|
|
247
|
+
for (const row of rows) {
|
|
248
|
+
out.set(
|
|
262
249
|
row.space,
|
|
263
250
|
parseMarkerRowSafely(row, parseContractMarkerRow, {
|
|
264
251
|
space: row.space,
|
|
@@ -266,7 +253,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
266
253
|
}),
|
|
267
254
|
);
|
|
268
255
|
}
|
|
269
|
-
return
|
|
256
|
+
return out;
|
|
270
257
|
}
|
|
271
258
|
|
|
272
259
|
/**
|
|
@@ -280,59 +267,257 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
280
267
|
space?: string,
|
|
281
268
|
): Promise<readonly LedgerEntryRecord[]> {
|
|
282
269
|
const ledgerContext = { space: space ?? '*', markerLocation: POSTGRES_LEDGER_TABLE };
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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) {
|
|
294
288
|
return [];
|
|
295
289
|
}
|
|
296
290
|
|
|
297
|
-
|
|
298
|
-
space
|
|
299
|
-
migration_name
|
|
300
|
-
migration_hash
|
|
301
|
-
origin_core_hash
|
|
302
|
-
destination_core_hash
|
|
303
|
-
operations
|
|
304
|
-
created_at
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
destination_core_hash,
|
|
312
|
-
operations,
|
|
313
|
-
created_at
|
|
314
|
-
from prisma_contract.ledger`;
|
|
315
|
-
if (space !== undefined) {
|
|
316
|
-
sql += `
|
|
317
|
-
where space = $1`;
|
|
318
|
-
}
|
|
319
|
-
sql += `
|
|
320
|
-
order by id`;
|
|
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
|
+
);
|
|
321
305
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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(),
|
|
325
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);
|
|
326
503
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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) };
|
|
336
521
|
}
|
|
337
522
|
|
|
338
523
|
/**
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
|
|
2
|
+
import {
|
|
3
|
+
type AnyQueryAst,
|
|
4
|
+
type LoweredStatement,
|
|
5
|
+
RawExpr,
|
|
6
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
7
|
+
import {
|
|
8
|
+
createAstCodecRegistry,
|
|
9
|
+
deriveParamMetadata,
|
|
10
|
+
encodeParamsWithMetadata,
|
|
11
|
+
} from '@prisma-next/sql-runtime';
|
|
12
|
+
import { PG_TIMESTAMPTZ_CODEC_ID } from '@prisma-next/target-postgres/codec-ids';
|
|
13
|
+
import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
|
|
14
|
+
import {
|
|
15
|
+
int4,
|
|
16
|
+
int8,
|
|
17
|
+
jsonb,
|
|
18
|
+
pgTable,
|
|
19
|
+
text,
|
|
20
|
+
textArray,
|
|
21
|
+
timestamptz,
|
|
22
|
+
} from '@prisma-next/target-postgres/contract-free';
|
|
23
|
+
|
|
24
|
+
const CONTROL_CODECS = createAstCodecRegistry(postgresCodecRegistry);
|
|
25
|
+
|
|
26
|
+
export const marker = pgTable(
|
|
27
|
+
{ name: 'marker', schema: 'prisma_contract' },
|
|
28
|
+
{
|
|
29
|
+
space: text(),
|
|
30
|
+
core_hash: text(),
|
|
31
|
+
profile_hash: text(),
|
|
32
|
+
contract_json: jsonb({ nullable: true }),
|
|
33
|
+
canonical_version: int4({ nullable: true }),
|
|
34
|
+
updated_at: timestamptz(),
|
|
35
|
+
app_tag: text({ nullable: true }),
|
|
36
|
+
meta: jsonb({ nullable: true }),
|
|
37
|
+
invariants: textArray(),
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Writeable subset of the `prisma_contract.ledger` table. Omits the
|
|
43
|
+
* DB-generated `id` (bigserial) and `created_at` (default `now()`) so the
|
|
44
|
+
* insert path doesn't have to pass them.
|
|
45
|
+
*/
|
|
46
|
+
export const ledger = pgTable(
|
|
47
|
+
{ name: 'ledger', schema: 'prisma_contract' },
|
|
48
|
+
{
|
|
49
|
+
space: text(),
|
|
50
|
+
migration_name: text(),
|
|
51
|
+
migration_hash: text(),
|
|
52
|
+
origin_core_hash: text({ nullable: true }),
|
|
53
|
+
destination_core_hash: text(),
|
|
54
|
+
operations: jsonb(),
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read-side handle covering every column of `prisma_contract.ledger`,
|
|
60
|
+
* including the DB-generated `id` (for ORDER BY) and `created_at`.
|
|
61
|
+
*/
|
|
62
|
+
export const ledgerReadShape = pgTable(
|
|
63
|
+
{ name: 'ledger', schema: 'prisma_contract' },
|
|
64
|
+
{
|
|
65
|
+
id: int8(),
|
|
66
|
+
space: text(),
|
|
67
|
+
migration_name: text(),
|
|
68
|
+
migration_hash: text(),
|
|
69
|
+
origin_core_hash: text({ nullable: true }),
|
|
70
|
+
destination_core_hash: text(),
|
|
71
|
+
operations: jsonb(),
|
|
72
|
+
created_at: timestamptz(),
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
export const infoSchemaTables = pgTable(
|
|
77
|
+
{ name: 'tables', schema: 'information_schema' },
|
|
78
|
+
{ table_schema: text(), table_name: text() },
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export const NOW = new RawExpr({
|
|
82
|
+
parts: ['now()'],
|
|
83
|
+
returns: { codecId: PG_TIMESTAMPTZ_CODEC_ID, nullable: false },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
type Lower = (query: AnyQueryAst) => LoweredStatement;
|
|
87
|
+
|
|
88
|
+
type MarkerDriver = {
|
|
89
|
+
query<Row = Record<string, unknown>>(
|
|
90
|
+
sql: string,
|
|
91
|
+
params?: readonly unknown[],
|
|
92
|
+
): Promise<{ readonly rows: ReadonlyArray<Row> }>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export function mergeInvariants(
|
|
96
|
+
current: readonly string[],
|
|
97
|
+
incoming: readonly string[],
|
|
98
|
+
): readonly string[] {
|
|
99
|
+
return [...new Set([...current, ...incoming])].sort();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function execute(
|
|
103
|
+
lower: Lower,
|
|
104
|
+
driver: MarkerDriver,
|
|
105
|
+
query: AnyQueryAst,
|
|
106
|
+
): Promise<readonly Record<string, unknown>[]> {
|
|
107
|
+
const lowered = lower(query);
|
|
108
|
+
const values = lowered.params.map((slot) => {
|
|
109
|
+
if (slot.kind === 'literal') return slot.value;
|
|
110
|
+
throw new Error('Postgres control DML lowered to a bind parameter, which is unsupported');
|
|
111
|
+
});
|
|
112
|
+
const encoded = await encodeParamsWithMetadata(
|
|
113
|
+
values,
|
|
114
|
+
deriveParamMetadata(query),
|
|
115
|
+
{},
|
|
116
|
+
CONTROL_CODECS,
|
|
117
|
+
);
|
|
118
|
+
const result = await driver.query(lowered.sql, encoded);
|
|
119
|
+
return result.rows;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export type PostgresMarkerDriver = MarkerDriver;
|
|
123
|
+
export type PostgresMarkerLower = Lower;
|
|
124
|
+
export type PostgresMarkerWriteDriver = ControlDriverInstance<'sql', 'postgres'>;
|
package/src/core/sql-renderer.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type UpdateAst,
|
|
32
32
|
type WindowFuncExpr,
|
|
33
33
|
} from '@prisma-next/sql-relational-core/ast';
|
|
34
|
+
import { PostgresTableSource } from '@prisma-next/target-postgres/contract-free';
|
|
34
35
|
import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
|
|
35
36
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
36
37
|
import type { PostgresContract } from './types';
|
|
@@ -274,6 +275,9 @@ function qualifyTableFromNamespaceCoordinate(
|
|
|
274
275
|
table: Pick<TableSource, 'name' | 'namespaceId'>,
|
|
275
276
|
contract: PostgresContract,
|
|
276
277
|
): string {
|
|
278
|
+
if (table instanceof PostgresTableSource && table.schema !== undefined) {
|
|
279
|
+
return `${quoteIdentifier(table.schema)}.${quoteIdentifier(table.name)}`;
|
|
280
|
+
}
|
|
277
281
|
if (table.namespaceId === undefined) {
|
|
278
282
|
return quoteIdentifier(table.name);
|
|
279
283
|
}
|
|
@@ -730,7 +734,11 @@ function getInsertColumnOrder(
|
|
|
730
734
|
return Object.keys(table.columns);
|
|
731
735
|
}
|
|
732
736
|
|
|
733
|
-
function renderInsertValue(
|
|
737
|
+
function renderInsertValue(
|
|
738
|
+
value: InsertValue | undefined,
|
|
739
|
+
contract: PostgresContract,
|
|
740
|
+
pim: ParamIndexMap,
|
|
741
|
+
): string {
|
|
734
742
|
if (!value || value.kind === 'default-value') {
|
|
735
743
|
return 'DEFAULT';
|
|
736
744
|
}
|
|
@@ -741,6 +749,8 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
|
|
|
741
749
|
return renderParamRef(value, pim);
|
|
742
750
|
case 'column-ref':
|
|
743
751
|
return renderColumn(value);
|
|
752
|
+
case 'raw-expr':
|
|
753
|
+
return renderExpr(value, contract, pim);
|
|
744
754
|
// v8 ignore next 4
|
|
745
755
|
default:
|
|
746
756
|
throw new Error(
|
|
@@ -778,7 +788,9 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
|
|
|
778
788
|
const columns = columnOrder.map((column) => quoteIdentifier(column));
|
|
779
789
|
const values = rows
|
|
780
790
|
.map((row) => {
|
|
781
|
-
const renderedRow = columnOrder.map((column) =>
|
|
791
|
+
const renderedRow = columnOrder.map((column) =>
|
|
792
|
+
renderInsertValue(row[column], contract, pim),
|
|
793
|
+
);
|
|
782
794
|
return `(${renderedRow.join(', ')})`;
|
|
783
795
|
})
|
|
784
796
|
.join(', ');
|