@prisma-next/target-postgres 0.5.0-dev.9 → 0.5.0
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/{codec-ids-CojIXVf9.mjs → codec-ids-C5qzBqus.mjs} +4 -4
- package/dist/{codec-ids-CojIXVf9.mjs.map → codec-ids-C5qzBqus.mjs.map} +1 -1
- package/dist/codec-ids-CplrEfmx.d.mts +29 -0
- package/dist/codec-ids-CplrEfmx.d.mts.map +1 -0
- package/dist/codec-ids.d.mts +2 -28
- package/dist/codec-ids.mjs +2 -3
- package/dist/codec-types-lrsb3N07.d.mts +79 -0
- package/dist/codec-types-lrsb3N07.d.mts.map +1 -0
- package/dist/codec-types.d.mts +2 -42
- package/dist/codec-types.mjs +1 -3
- package/dist/codecs-Cue97Xqf.d.mts +558 -0
- package/dist/codecs-Cue97Xqf.d.mts.map +1 -0
- package/dist/codecs.d.mts +13 -2
- package/dist/codecs.d.mts.map +1 -0
- package/dist/codecs.mjs +738 -2
- package/dist/codecs.mjs.map +1 -0
- package/dist/control.d.mts +1 -1
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +131 -94
- package/dist/control.mjs.map +1 -1
- package/dist/{data-transform-VfEGzXWt.mjs → data-transform-DKWXdHuZ.mjs} +25 -7
- package/dist/data-transform-DKWXdHuZ.mjs.map +1 -0
- package/dist/data-transform-bIeAcZIJ.d.mts +38 -0
- package/dist/data-transform-bIeAcZIJ.d.mts.map +1 -0
- package/dist/data-transform.d.mts +1 -1
- package/dist/data-transform.mjs +2 -3
- package/dist/{default-normalizer-DNOpRoOF.mjs → default-normalizer-C8XyZj85.mjs} +2 -2
- package/dist/{default-normalizer-DNOpRoOF.mjs.map → default-normalizer-C8XyZj85.mjs.map} +1 -1
- package/dist/default-normalizer.d.mts +0 -1
- package/dist/default-normalizer.d.mts.map +1 -1
- package/dist/default-normalizer.mjs +2 -3
- package/dist/descriptor-meta-Dde_BS3K.mjs +99 -0
- package/dist/descriptor-meta-Dde_BS3K.mjs.map +1 -0
- package/dist/{errors-AFvEPZ1R.mjs → errors-Chm2bKcS.mjs} +2 -3
- package/dist/{errors-AFvEPZ1R.mjs.map → errors-Chm2bKcS.mjs.map} +1 -1
- package/dist/errors.d.mts +0 -1
- package/dist/errors.d.mts.map +1 -1
- package/dist/errors.mjs +2 -3
- package/dist/{issue-planner-CFjB0_oO.mjs → issue-planner-CiNmA4ls.mjs} +20 -16
- package/dist/issue-planner-CiNmA4ls.mjs.map +1 -0
- package/dist/issue-planner.d.mts +2 -4
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +2 -3
- package/dist/migration.d.mts +8 -4
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +4 -5
- package/dist/migration.mjs.map +1 -1
- package/dist/{native-type-normalizer-CInai_oY.mjs → native-type-normalizer-Cry4QoLf.mjs} +2 -2
- package/dist/native-type-normalizer-Cry4QoLf.mjs.map +1 -0
- package/dist/native-type-normalizer.d.mts.map +1 -1
- package/dist/native-type-normalizer.mjs +2 -3
- package/dist/{op-factory-call-C3bWXKSP.d.mts → op-factory-call-BUhxZuUA.d.mts} +9 -5
- package/dist/op-factory-call-BUhxZuUA.d.mts.map +1 -0
- package/dist/{op-factory-call-BKlruaiC.mjs → op-factory-call-Bs3HWhvG.mjs} +25 -7
- package/dist/op-factory-call-Bs3HWhvG.mjs.map +1 -0
- package/dist/op-factory-call.d.mts +1 -2
- package/dist/op-factory-call.mjs +2 -3
- package/dist/pack.d.mts +28 -9
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +2 -3
- package/dist/{planner-CLUvVhUN.mjs → planner-Lhacw3uU.mjs} +22 -16
- package/dist/planner-Lhacw3uU.mjs.map +1 -0
- package/dist/{planner-ddl-builders-Dxvw1LHw.mjs → planner-ddl-builders-CLB7Umhh.mjs} +4 -5
- package/dist/planner-ddl-builders-CLB7Umhh.mjs.map +1 -0
- package/dist/planner-ddl-builders.d.mts +1 -1
- package/dist/planner-ddl-builders.d.mts.map +1 -1
- package/dist/planner-ddl-builders.mjs +2 -3
- package/dist/{planner-identity-values-Dju-o5GF.mjs → planner-identity-values-DTx0gePL.mjs} +2 -3
- package/dist/{planner-identity-values-Dju-o5GF.mjs.map → planner-identity-values-DTx0gePL.mjs.map} +1 -1
- package/dist/planner-identity-values.d.mts +0 -1
- package/dist/planner-identity-values.d.mts.map +1 -1
- package/dist/planner-identity-values.mjs +2 -3
- package/dist/{planner-produced-postgres-migration-CRRTno6Z.d.mts → planner-produced-postgres-migration-CjxWIVgh.d.mts} +11 -7
- package/dist/planner-produced-postgres-migration-CjxWIVgh.d.mts.map +1 -0
- package/dist/{planner-produced-postgres-migration-DSSPq8QS.mjs → planner-produced-postgres-migration-DphktB2N.mjs} +16 -8
- package/dist/planner-produced-postgres-migration-DphktB2N.mjs.map +1 -0
- package/dist/planner-produced-postgres-migration.d.mts +1 -4
- package/dist/planner-produced-postgres-migration.mjs +2 -3
- package/dist/{planner-schema-lookup-B7lkypwn.mjs → planner-schema-lookup-B1ags8ys.mjs} +2 -2
- package/dist/{planner-schema-lookup-B7lkypwn.mjs.map → planner-schema-lookup-B1ags8ys.mjs.map} +1 -1
- package/dist/planner-schema-lookup.d.mts +0 -1
- package/dist/planner-schema-lookup.d.mts.map +1 -1
- package/dist/planner-schema-lookup.mjs +2 -3
- package/dist/{planner-sql-checks-7jkgm9TX.mjs → planner-sql-checks-DwZvGlV4.mjs} +3 -5
- package/dist/planner-sql-checks-DwZvGlV4.mjs.map +1 -0
- package/dist/planner-sql-checks.d.mts.map +1 -1
- package/dist/planner-sql-checks.mjs +2 -3
- package/dist/{planner-target-details-DH-azLu-.d.mts → planner-target-details-bVVcanWh.d.mts} +1 -1
- package/dist/planner-target-details-bVVcanWh.d.mts.map +1 -0
- package/dist/planner-target-details.d.mts +1 -1
- package/dist/planner-target-details.mjs +1 -1
- package/dist/planner.d.mts +21 -12
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +2 -4
- package/dist/{postgres-migration-qtmtbONe.mjs → postgres-migration-Bkv140RW.mjs} +4 -5
- package/dist/postgres-migration-Bkv140RW.mjs.map +1 -0
- package/dist/{postgres-migration-BjA3Zmts.d.mts → postgres-migration-UkcHfZAA.d.mts} +6 -6
- package/dist/postgres-migration-UkcHfZAA.d.mts.map +1 -0
- package/dist/render-ops--1nnfNus.mjs +23 -0
- package/dist/render-ops--1nnfNus.mjs.map +1 -0
- package/dist/render-ops.d.mts +3 -4
- package/dist/render-ops.d.mts.map +1 -1
- package/dist/render-ops.mjs +2 -3
- package/dist/{render-typescript-1rF_SB4g.mjs → render-typescript-D3doH-vX.mjs} +2 -14
- package/dist/render-typescript-D3doH-vX.mjs.map +1 -0
- package/dist/render-typescript.d.mts +3 -6
- package/dist/render-typescript.d.mts.map +1 -1
- package/dist/render-typescript.mjs +2 -3
- package/dist/runtime.d.mts +5 -9
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +7 -14
- package/dist/runtime.mjs.map +1 -1
- package/dist/{shared-Bxkt8pNO.d.mts → shared-MpwjwAjM.d.mts} +2 -2
- package/dist/shared-MpwjwAjM.d.mts.map +1 -0
- package/dist/{sql-utils-r-Lw535w.mjs → sql-utils-CggjWNij.mjs} +4 -2
- package/dist/sql-utils-CggjWNij.mjs.map +1 -0
- package/dist/sql-utils.d.mts.map +1 -1
- package/dist/sql-utils.mjs +2 -3
- package/dist/{statement-builders-BPnmt6wx.mjs → statement-builders-BT889jV0.mjs} +28 -13
- package/dist/statement-builders-BT889jV0.mjs.map +1 -0
- package/dist/statement-builders.d.mts +31 -3
- package/dist/statement-builders.d.mts.map +1 -1
- package/dist/statement-builders.mjs +2 -3
- package/dist/{tables-BmdW_FWO.mjs → tables-r9Zk1y-Y.mjs} +18 -13
- package/dist/tables-r9Zk1y-Y.mjs.map +1 -0
- package/dist/{types-ClK03Ojd.d.mts → types-CTqpysRY.d.mts} +1 -1
- package/dist/types-CTqpysRY.d.mts.map +1 -0
- package/dist/types.d.mts +1 -1
- package/dist/types.mjs +1 -1
- package/package.json +21 -19
- package/src/core/authoring.ts +5 -11
- package/src/core/codec-helpers.ts +135 -0
- package/src/core/codec-ids.ts +1 -0
- package/src/core/codec-type-map.ts +81 -0
- package/src/core/codecs.ts +941 -547
- package/src/core/descriptor-meta.ts +1 -1
- package/src/core/migrations/issue-planner.ts +15 -3
- package/src/core/migrations/op-factory-call.ts +24 -2
- package/src/core/migrations/operations/data-transform.ts +86 -21
- package/src/core/migrations/operations/indexes.ts +27 -2
- package/src/core/migrations/planner-produced-postgres-migration.ts +17 -5
- package/src/core/migrations/planner.ts +62 -20
- package/src/core/migrations/postgres-migration.ts +3 -6
- package/src/core/migrations/render-ops.ts +26 -3
- package/src/core/migrations/render-typescript.ts +5 -9
- package/src/core/migrations/runner.ts +172 -151
- package/src/core/migrations/statement-builders.ts +49 -10
- package/src/core/registry.ts +11 -0
- package/src/exports/codec-types.ts +4 -13
- package/src/exports/codecs.ts +49 -2
- package/src/exports/runtime.ts +6 -11
- package/src/exports/statement-builders.ts +2 -1
- package/dist/codec-ids.d.mts.map +0 -1
- package/dist/codec-types.d.mts.map +0 -1
- package/dist/codecs-BQEm9_oo.d.mts +0 -319
- package/dist/codecs-BQEm9_oo.d.mts.map +0 -1
- package/dist/codecs-BoahtY_Q.mjs +0 -385
- package/dist/codecs-BoahtY_Q.mjs.map +0 -1
- package/dist/data-transform-CxFRBIUp.d.mts +0 -32
- package/dist/data-transform-CxFRBIUp.d.mts.map +0 -1
- package/dist/data-transform-VfEGzXWt.mjs.map +0 -1
- package/dist/descriptor-meta-BVoVtyp-.mjs +0 -120
- package/dist/descriptor-meta-BVoVtyp-.mjs.map +0 -1
- package/dist/issue-planner-CFjB0_oO.mjs.map +0 -1
- package/dist/native-type-normalizer-CInai_oY.mjs.map +0 -1
- package/dist/op-factory-call-BKlruaiC.mjs.map +0 -1
- package/dist/op-factory-call-C3bWXKSP.d.mts.map +0 -1
- package/dist/planner-CLUvVhUN.mjs.map +0 -1
- package/dist/planner-ddl-builders-Dxvw1LHw.mjs.map +0 -1
- package/dist/planner-produced-postgres-migration-CRRTno6Z.d.mts.map +0 -1
- package/dist/planner-produced-postgres-migration-DSSPq8QS.mjs.map +0 -1
- package/dist/planner-sql-checks-7jkgm9TX.mjs.map +0 -1
- package/dist/planner-target-details-DH-azLu-.d.mts.map +0 -1
- package/dist/postgres-migration-BjA3Zmts.d.mts.map +0 -1
- package/dist/postgres-migration-qtmtbONe.mjs.map +0 -1
- package/dist/render-ops-D6_DHdOK.mjs +0 -8
- package/dist/render-ops-D6_DHdOK.mjs.map +0 -1
- package/dist/render-typescript-1rF_SB4g.mjs.map +0 -1
- package/dist/shared-Bxkt8pNO.d.mts.map +0 -1
- package/dist/sql-utils-r-Lw535w.mjs.map +0 -1
- package/dist/statement-builders-BPnmt6wx.mjs.map +0 -1
- package/dist/tables-BmdW_FWO.mjs.map +0 -1
- package/dist/types-ClK03Ojd.d.mts.map +0 -1
- package/src/core/json-schema-type-expression.ts +0 -131
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
SqlPlannerConflict,
|
|
16
16
|
SqlPlannerConflictLocation,
|
|
17
17
|
} from '@prisma-next/family-sql/control';
|
|
18
|
+
import { arraysEqual } from '@prisma-next/family-sql/schema-verify';
|
|
18
19
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
19
20
|
import type { SchemaIssue } from '@prisma-next/framework-components/control';
|
|
20
21
|
import type {
|
|
@@ -211,7 +212,12 @@ function mapIssueToCall(
|
|
|
211
212
|
];
|
|
212
213
|
for (const index of contractTable.indexes) {
|
|
213
214
|
const indexName = index.name ?? `${issue.table}_${index.columns.join('_')}_idx`;
|
|
214
|
-
|
|
215
|
+
const extras: { type?: string; options?: Record<string, unknown> } = {};
|
|
216
|
+
if (index.type !== undefined) extras.type = index.type;
|
|
217
|
+
if (index.options !== undefined) extras.options = index.options;
|
|
218
|
+
calls.push(
|
|
219
|
+
new CreateIndexCall(schemaName, issue.table, indexName, [...index.columns], extras),
|
|
220
|
+
);
|
|
215
221
|
}
|
|
216
222
|
const explicitIndexColumnSets = new Set(
|
|
217
223
|
contractTable.indexes.map((idx) => idx.columns.join(',')),
|
|
@@ -446,8 +452,14 @@ function mapIssueToCall(
|
|
|
446
452
|
return notOk(issueConflict('indexIncompatible', 'Index issue has no table name'));
|
|
447
453
|
if (isMissing(issue) && issue.expected) {
|
|
448
454
|
const columns = issue.expected.split(', ');
|
|
449
|
-
const
|
|
450
|
-
|
|
455
|
+
const contractIndex = ctx.toContract.storage.tables[issue.table]?.indexes.find((idx) =>
|
|
456
|
+
arraysEqual(idx.columns, columns),
|
|
457
|
+
);
|
|
458
|
+
const indexName = contractIndex?.name ?? `${issue.table}_${columns.join('_')}_idx`;
|
|
459
|
+
const extras: { type?: string; options?: Record<string, unknown> } = {};
|
|
460
|
+
if (contractIndex?.type !== undefined) extras.type = contractIndex.type;
|
|
461
|
+
if (contractIndex?.options !== undefined) extras.options = contractIndex.options;
|
|
462
|
+
return ok([new CreateIndexCall(schemaName, issue.table, indexName, columns, extras)]);
|
|
451
463
|
}
|
|
452
464
|
return notOk(
|
|
453
465
|
issueConflict(
|
|
@@ -506,6 +506,10 @@ export class CreateIndexCall extends PostgresOpFactoryCallNode {
|
|
|
506
506
|
readonly tableName: string;
|
|
507
507
|
readonly indexName: string;
|
|
508
508
|
readonly columns: readonly string[];
|
|
509
|
+
// Named indexType (not typeName) to avoid collision with CreateEnumTypeCall.typeName,
|
|
510
|
+
// which identifies a CREATE TYPE target and is read by `locationForCall` in issue-planner.ts.
|
|
511
|
+
readonly indexType: string | undefined;
|
|
512
|
+
readonly options: Record<string, unknown> | undefined;
|
|
509
513
|
readonly label: string;
|
|
510
514
|
|
|
511
515
|
constructor(
|
|
@@ -513,22 +517,40 @@ export class CreateIndexCall extends PostgresOpFactoryCallNode {
|
|
|
513
517
|
tableName: string,
|
|
514
518
|
indexName: string,
|
|
515
519
|
columns: readonly string[],
|
|
520
|
+
extras?: { readonly type?: string; readonly options?: Record<string, unknown> },
|
|
516
521
|
) {
|
|
517
522
|
super();
|
|
518
523
|
this.schemaName = schemaName;
|
|
519
524
|
this.tableName = tableName;
|
|
520
525
|
this.indexName = indexName;
|
|
521
526
|
this.columns = columns;
|
|
527
|
+
this.indexType = extras?.type;
|
|
528
|
+
this.options = extras?.options;
|
|
522
529
|
this.label = `Create index "${indexName}" on "${tableName}"`;
|
|
523
530
|
this.freeze();
|
|
524
531
|
}
|
|
525
532
|
|
|
526
533
|
toOp(): Op {
|
|
527
|
-
|
|
534
|
+
const extras: { type?: string; options?: Record<string, unknown> } = {};
|
|
535
|
+
if (this.indexType !== undefined) extras.type = this.indexType;
|
|
536
|
+
if (this.options !== undefined) extras.options = this.options;
|
|
537
|
+
return createIndex(this.schemaName, this.tableName, this.indexName, this.columns, extras);
|
|
528
538
|
}
|
|
529
539
|
|
|
530
540
|
renderTypeScript(): string {
|
|
531
|
-
|
|
541
|
+
const args = [
|
|
542
|
+
jsonToTsSource(this.schemaName),
|
|
543
|
+
jsonToTsSource(this.tableName),
|
|
544
|
+
jsonToTsSource(this.indexName),
|
|
545
|
+
jsonToTsSource(this.columns),
|
|
546
|
+
];
|
|
547
|
+
if (this.indexType !== undefined || this.options !== undefined) {
|
|
548
|
+
const extrasParts: string[] = [];
|
|
549
|
+
if (this.indexType !== undefined) extrasParts.push(`type: ${jsonToTsSource(this.indexType)}`);
|
|
550
|
+
if (this.options !== undefined) extrasParts.push(`options: ${jsonToTsSource(this.options)}`);
|
|
551
|
+
args.push(`{ ${extrasParts.join(', ')} }`);
|
|
552
|
+
}
|
|
553
|
+
return `createIndex(${args.join(', ')})`;
|
|
532
554
|
}
|
|
533
555
|
}
|
|
534
556
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* override get operations() {
|
|
12
12
|
* return [
|
|
13
13
|
* this.dataTransform(endContract, 'backfill emails', {
|
|
14
|
-
* check: () => db.users.
|
|
14
|
+
* check: () => db.users.select('id').where(({ email }) => email.isNull()).limit(1),
|
|
15
15
|
* run: () => db.users.update({ email: '' }).where(({ email }) => email.isNull()),
|
|
16
16
|
* }),
|
|
17
17
|
* ];
|
|
@@ -23,20 +23,49 @@
|
|
|
23
23
|
* invokes each one, asserts that its `meta.storageHash` matches the
|
|
24
24
|
* `contract` it was handed (→ `PN-MIG-2005` on mismatch), and lowers the
|
|
25
25
|
* plan via the supplied control adapter to a serialized `{sql, params}`
|
|
26
|
-
* payload
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* payload.
|
|
27
|
+
*
|
|
28
|
+
* The factory then lowers the data transform to the unified migration-op
|
|
29
|
+
* shape `{ precheck, execute, postcheck }`. The user's `check` plan is
|
|
30
|
+
* wrapped twice with opposite truth values:
|
|
31
|
+
*
|
|
32
|
+
* - precheck `SELECT EXISTS (<check>) AS ok` asserts there is work to do
|
|
33
|
+
* (precheck is short-circuited by the runner's pre-satisfied-skip path
|
|
34
|
+
* when nothing remains to backfill).
|
|
35
|
+
* - postcheck `SELECT NOT EXISTS (<check>) AS ok` asserts the work is
|
|
36
|
+
* complete after the run steps execute.
|
|
37
|
+
*
|
|
38
|
+
* The `check` plan is therefore expected to be a **rowset query whose
|
|
39
|
+
* presence of any row signals "work remains"** — typically `select('id')
|
|
40
|
+
* .where(<violation predicate>).limit(1)`. Scalar/aggregate shapes
|
|
41
|
+
* (`count(*)`, `bool_and(...)`) do not work under this contract: they
|
|
42
|
+
* always return exactly one row, so `EXISTS` is always true and
|
|
43
|
+
* `NOT EXISTS` is always false. (This is the same row-presence contract
|
|
44
|
+
* the pre-unification runner relied on; the wrapping is just lifting it
|
|
45
|
+
* into SQL.)
|
|
46
|
+
*
|
|
47
|
+
* Each `run` plan becomes an execute step. Because the `Step.params`
|
|
48
|
+
* field threads through `driver.query(sql, params)`, the user's bound
|
|
49
|
+
* values flow through the driver's parameter binder rather than being
|
|
50
|
+
* inlined into the SQL text.
|
|
51
|
+
*
|
|
52
|
+
* The free factory remains usable standalone (tests, ad-hoc tooling,
|
|
53
|
+
* non-class contexts) by passing the adapter explicitly as the fourth
|
|
54
|
+
* argument.
|
|
29
55
|
*/
|
|
30
56
|
|
|
31
57
|
import type { Contract } from '@prisma-next/contract/types';
|
|
32
58
|
import { errorDataTransformContractMismatch } from '@prisma-next/errors/migration';
|
|
33
|
-
import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
|
|
34
59
|
import type {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} from '@prisma-next/
|
|
60
|
+
SqlMigrationPlanOperation,
|
|
61
|
+
SqlMigrationPlanOperationStep,
|
|
62
|
+
} from '@prisma-next/family-sql/control';
|
|
63
|
+
import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
|
|
64
|
+
import type { SerializedQueryPlan } from '@prisma-next/framework-components/control';
|
|
38
65
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
39
66
|
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
67
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
68
|
+
import type { PostgresPlanTargetDetails } from '../planner-target-details';
|
|
40
69
|
|
|
41
70
|
interface Buildable<R = unknown> {
|
|
42
71
|
build(): SqlQueryPlan<R>;
|
|
@@ -49,36 +78,72 @@ interface Buildable<R = unknown> {
|
|
|
49
78
|
export type DataTransformClosure = () => SqlQueryPlan | Buildable;
|
|
50
79
|
|
|
51
80
|
export interface DataTransformOptions {
|
|
52
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Optional opt-in routing identity. Presence opts the transform into
|
|
83
|
+
* invariant-aware routing; absence means it is path-dependent and
|
|
84
|
+
* not referenceable from refs.
|
|
85
|
+
*/
|
|
86
|
+
readonly invariantId?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Optional pre-flight query. `undefined` means "no check". When
|
|
89
|
+
* supplied, the closure must return a **rowset query** whose
|
|
90
|
+
* presence of any row signals "violations remain". Conventional
|
|
91
|
+
* shape: `db.<table>.select('id').where(<violation>).limit(1)`.
|
|
92
|
+
* Scalar/aggregate shapes do not satisfy this contract.
|
|
93
|
+
*/
|
|
53
94
|
readonly check?: DataTransformClosure;
|
|
54
95
|
/** One or more mutation queries to execute. */
|
|
55
96
|
readonly run: DataTransformClosure | readonly DataTransformClosure[];
|
|
56
97
|
}
|
|
57
98
|
|
|
58
|
-
/**
|
|
59
|
-
* Concrete Postgres flavor of `DataTransformOperation`, re-exported so the
|
|
60
|
-
* `PostgresMigration.dataTransform` instance method can name it without
|
|
61
|
-
* leaking the framework-components symbol into call sites.
|
|
62
|
-
*/
|
|
63
|
-
export type PostgresDataTransformOperation = DataTransformOperation;
|
|
64
|
-
|
|
65
99
|
export function dataTransform<TContract extends Contract<SqlStorage>>(
|
|
66
100
|
contract: TContract,
|
|
67
101
|
name: string,
|
|
68
102
|
options: DataTransformOptions,
|
|
69
103
|
adapter: SqlControlAdapter<'postgres'>,
|
|
70
|
-
):
|
|
104
|
+
): SqlMigrationPlanOperation<PostgresPlanTargetDetails> {
|
|
71
105
|
const runClosures: readonly DataTransformClosure[] = Array.isArray(options.run)
|
|
72
106
|
? options.run
|
|
73
107
|
: [options.run as DataTransformClosure];
|
|
108
|
+
|
|
109
|
+
const checkPlan = options.check ? invokeAndLower(options.check, contract, adapter, name) : null;
|
|
110
|
+
const runPlans = runClosures.map((closure) => invokeAndLower(closure, contract, adapter, name));
|
|
111
|
+
|
|
112
|
+
const precheck: readonly SqlMigrationPlanOperationStep[] = checkPlan
|
|
113
|
+
? [
|
|
114
|
+
{
|
|
115
|
+
description: `Check ${name} has work to do`,
|
|
116
|
+
sql: `SELECT EXISTS (${checkPlan.sql}) AS ok`,
|
|
117
|
+
params: checkPlan.params,
|
|
118
|
+
},
|
|
119
|
+
]
|
|
120
|
+
: [];
|
|
121
|
+
|
|
122
|
+
const execute: readonly SqlMigrationPlanOperationStep[] = runPlans.map((plan) => ({
|
|
123
|
+
description: `Run ${name}`,
|
|
124
|
+
sql: plan.sql,
|
|
125
|
+
params: plan.params,
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
const postcheck: readonly SqlMigrationPlanOperationStep[] = checkPlan
|
|
129
|
+
? [
|
|
130
|
+
{
|
|
131
|
+
description: `Verify ${name} resolved all violations`,
|
|
132
|
+
sql: `SELECT NOT EXISTS (${checkPlan.sql}) AS ok`,
|
|
133
|
+
params: checkPlan.params,
|
|
134
|
+
},
|
|
135
|
+
]
|
|
136
|
+
: [];
|
|
137
|
+
|
|
74
138
|
return {
|
|
75
139
|
id: `data_migration.${name}`,
|
|
76
140
|
label: `Data transform: ${name}`,
|
|
77
141
|
operationClass: 'data',
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
142
|
+
...ifDefined('invariantId', options.invariantId),
|
|
143
|
+
target: { id: 'postgres' },
|
|
144
|
+
precheck,
|
|
145
|
+
execute,
|
|
146
|
+
postcheck,
|
|
82
147
|
};
|
|
83
148
|
}
|
|
84
149
|
|
|
@@ -1,15 +1,40 @@
|
|
|
1
|
-
import { quoteIdentifier } from '../../sql-utils';
|
|
1
|
+
import { escapeLiteral, quoteIdentifier } from '../../sql-utils';
|
|
2
2
|
import { qualifyTableName, toRegclassLiteral } from '../planner-sql-checks';
|
|
3
3
|
import { type Op, step, targetDetails } from './shared';
|
|
4
4
|
|
|
5
|
+
export interface CreateIndexExtras {
|
|
6
|
+
readonly type?: string;
|
|
7
|
+
readonly options?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function renderIndexOptionValue(key: string, value: unknown): string {
|
|
11
|
+
if (typeof value === 'string') return `'${escapeLiteral(value)}'`;
|
|
12
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
13
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Index option "${key}" must be a string, finite number, or boolean; got ${typeof value}`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function renderIndexOptions(options: Record<string, unknown>): string {
|
|
20
|
+
return Object.entries(options)
|
|
21
|
+
.map(([key, value]) => `${quoteIdentifier(key)} = ${renderIndexOptionValue(key, value)}`)
|
|
22
|
+
.join(', ');
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
export function createIndex(
|
|
6
26
|
schemaName: string,
|
|
7
27
|
tableName: string,
|
|
8
28
|
indexName: string,
|
|
9
29
|
columns: readonly string[],
|
|
30
|
+
extras?: CreateIndexExtras,
|
|
10
31
|
): Op {
|
|
11
32
|
const qualified = qualifyTableName(schemaName, tableName);
|
|
12
33
|
const columnList = columns.map(quoteIdentifier).join(', ');
|
|
34
|
+
const using = extras?.type ? ` USING ${quoteIdentifier(extras.type)}` : '';
|
|
35
|
+
const options = extras?.options;
|
|
36
|
+
const withClause =
|
|
37
|
+
options && Object.keys(options).length > 0 ? ` WITH (${renderIndexOptions(options)})` : '';
|
|
13
38
|
return {
|
|
14
39
|
id: `index.${tableName}.${indexName}`,
|
|
15
40
|
label: `Create index "${indexName}" on "${tableName}"`,
|
|
@@ -24,7 +49,7 @@ export function createIndex(
|
|
|
24
49
|
execute: [
|
|
25
50
|
step(
|
|
26
51
|
`create index "${indexName}"`,
|
|
27
|
-
`CREATE INDEX ${quoteIdentifier(indexName)} ON ${qualified} (${columnList})`,
|
|
52
|
+
`CREATE INDEX ${quoteIdentifier(indexName)} ON ${qualified}${using} (${columnList})${withClause}`,
|
|
28
53
|
),
|
|
29
54
|
],
|
|
30
55
|
postcheck: [
|
|
@@ -24,10 +24,12 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
|
|
27
|
-
import type {
|
|
27
|
+
import type {
|
|
28
|
+
MigrationPlanWithAuthoringSurface,
|
|
29
|
+
OpFactoryCall,
|
|
30
|
+
} from '@prisma-next/framework-components/control';
|
|
28
31
|
import type { MigrationMeta } from '@prisma-next/migration-tools/migration';
|
|
29
32
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
30
|
-
import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
31
33
|
import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
32
34
|
import { PostgresMigration } from './postgres-migration';
|
|
33
35
|
import { renderOps } from './render-ops';
|
|
@@ -39,13 +41,15 @@ export class TypeScriptRenderablePostgresMigration
|
|
|
39
41
|
extends PostgresMigration
|
|
40
42
|
implements MigrationPlanWithAuthoringSurface
|
|
41
43
|
{
|
|
42
|
-
readonly #calls: readonly
|
|
44
|
+
readonly #calls: readonly OpFactoryCall[];
|
|
43
45
|
readonly #meta: MigrationMeta;
|
|
46
|
+
readonly #spaceId: string;
|
|
44
47
|
|
|
45
|
-
constructor(calls: readonly
|
|
48
|
+
constructor(calls: readonly OpFactoryCall[], meta: MigrationMeta, spaceId: string) {
|
|
46
49
|
super();
|
|
47
50
|
this.#calls = calls;
|
|
48
51
|
this.#meta = meta;
|
|
52
|
+
this.#spaceId = spaceId;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
override get operations(): readonly Op[] {
|
|
@@ -56,11 +60,19 @@ export class TypeScriptRenderablePostgresMigration
|
|
|
56
60
|
return this.#meta;
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Contract space this planner-produced plan applies to. Threaded
|
|
65
|
+
* from the planner options so the runner keys the marker row by
|
|
66
|
+
* the right space when executing the plan.
|
|
67
|
+
*/
|
|
68
|
+
get spaceId(): string {
|
|
69
|
+
return this.#spaceId;
|
|
70
|
+
}
|
|
71
|
+
|
|
59
72
|
renderTypeScript(): string {
|
|
60
73
|
return renderCallsToTypeScript(this.#calls, {
|
|
61
74
|
from: this.#meta.from,
|
|
62
75
|
to: this.#meta.to,
|
|
63
|
-
...ifDefined('kind', this.#meta.kind),
|
|
64
76
|
...ifDefined('labels', this.#meta.labels),
|
|
65
77
|
});
|
|
66
78
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
1
2
|
import type {
|
|
2
3
|
MigrationOperationPolicy,
|
|
3
4
|
SqlMigrationPlannerPlanOptions,
|
|
4
5
|
SqlPlannerFailureResult,
|
|
5
6
|
} from '@prisma-next/family-sql/control';
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
extractCodecControlHooks,
|
|
9
|
+
planFieldEventOperations,
|
|
10
|
+
plannerFailure,
|
|
11
|
+
} from '@prisma-next/family-sql/control';
|
|
7
12
|
import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
|
|
8
13
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
9
14
|
import type {
|
|
@@ -83,30 +88,48 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
83
88
|
readonly contract: unknown;
|
|
84
89
|
readonly schema: unknown;
|
|
85
90
|
readonly policy: MigrationOperationPolicy;
|
|
86
|
-
readonly fromHash?: string;
|
|
87
91
|
/**
|
|
88
92
|
* The "from" contract (state the planner assumes the database starts
|
|
89
|
-
* at)
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* activate.
|
|
93
|
+
* at), or `null` for reconciliation flows. Only `migration plan` ever
|
|
94
|
+
* supplies a non-null value; `db update` / `db init` reconcile against
|
|
95
|
+
* the live schema and pass `null`. When present alongside the
|
|
96
|
+
* `'data'` operation class, strategies that need from/to column-shape
|
|
97
|
+
* comparisons (unsafe type change, nullability tightening) activate.
|
|
98
|
+
*
|
|
99
|
+
* Typed as the framework `Contract | null` to satisfy the
|
|
100
|
+
* `MigrationPlanner` interface contract; `planSql` narrows to the SQL
|
|
101
|
+
* shape via `SqlMigrationPlannerPlanOptions`. Used to populate
|
|
102
|
+
* `describe().from` on the produced plan as
|
|
103
|
+
* `fromContract?.storage.storageHash ?? null`.
|
|
94
104
|
*/
|
|
95
|
-
readonly fromContract
|
|
105
|
+
readonly fromContract: Contract | null;
|
|
96
106
|
readonly schemaName?: string;
|
|
97
107
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
108
|
+
/**
|
|
109
|
+
* Contract space this plan applies to. Stamped onto the produced
|
|
110
|
+
* {@link TypeScriptRenderablePostgresMigration.spaceId} so the runner keys
|
|
111
|
+
* the marker row by the right space.
|
|
112
|
+
*/
|
|
113
|
+
readonly spaceId: string;
|
|
98
114
|
}): PostgresPlanResult {
|
|
99
|
-
return this.planSql(options as SqlMigrationPlannerPlanOptions
|
|
115
|
+
return this.planSql(options as SqlMigrationPlannerPlanOptions);
|
|
100
116
|
}
|
|
101
117
|
|
|
102
|
-
emptyMigration(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
emptyMigration(
|
|
119
|
+
context: MigrationScaffoldContext,
|
|
120
|
+
spaceId: string,
|
|
121
|
+
): MigrationPlanWithAuthoringSurface {
|
|
122
|
+
return new TypeScriptRenderablePostgresMigration(
|
|
123
|
+
[],
|
|
124
|
+
{
|
|
125
|
+
from: context.fromHash,
|
|
126
|
+
to: context.toHash,
|
|
127
|
+
},
|
|
128
|
+
spaceId,
|
|
129
|
+
);
|
|
107
130
|
}
|
|
108
131
|
|
|
109
|
-
private planSql(options: SqlMigrationPlannerPlanOptions
|
|
132
|
+
private planSql(options: SqlMigrationPlannerPlanOptions): PostgresPlanResult {
|
|
110
133
|
const schemaName = options.schemaName ?? this.config.defaultSchema;
|
|
111
134
|
const policyResult = this.ensureAdditivePolicy(options.policy);
|
|
112
135
|
if (policyResult) {
|
|
@@ -125,7 +148,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
125
148
|
// from/to comparisons (unsafe type change, nullable tightening) are
|
|
126
149
|
// inapplicable there — reconciliation falls through to
|
|
127
150
|
// `mapIssueToCall`'s direct destructive handlers.
|
|
128
|
-
fromContract: options.fromContract
|
|
151
|
+
fromContract: options.fromContract,
|
|
129
152
|
schemaName,
|
|
130
153
|
codecHooks,
|
|
131
154
|
storageTypes,
|
|
@@ -139,12 +162,31 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
139
162
|
return plannerFailure(result.failure);
|
|
140
163
|
}
|
|
141
164
|
|
|
165
|
+
// Inline `onFieldEvent`-emitted ops after structural DDL. The fixed
|
|
166
|
+
// ordering is `structural → added → dropped → altered`, with
|
|
167
|
+
// within-group sorting by `(tableName, fieldName)` so re-emits are
|
|
168
|
+
// byte-stable. The hook fires only at the application emitter —
|
|
169
|
+
// extension-space planning never reaches this helper.
|
|
170
|
+
const fieldEventOps = planFieldEventOperations({
|
|
171
|
+
priorContract: options.fromContract,
|
|
172
|
+
newContract: options.contract,
|
|
173
|
+
codecHooks,
|
|
174
|
+
});
|
|
175
|
+
// Codec-emitted calls already conform to `OpFactoryCall` — render +
|
|
176
|
+
// toOp + importRequirements ride directly through the same emit path
|
|
177
|
+
// as structural ops, no `RawSqlCall` wrap.
|
|
178
|
+
const calls = [...result.value.calls, ...fieldEventOps];
|
|
179
|
+
|
|
142
180
|
return Object.freeze({
|
|
143
181
|
kind: 'success' as const,
|
|
144
|
-
plan: new TypeScriptRenderablePostgresMigration(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
182
|
+
plan: new TypeScriptRenderablePostgresMigration(
|
|
183
|
+
calls,
|
|
184
|
+
{
|
|
185
|
+
from: options.fromContract?.storage.storageHash ?? null,
|
|
186
|
+
to: options.contract.storage.storageHash,
|
|
187
|
+
},
|
|
188
|
+
options.spaceId,
|
|
189
|
+
),
|
|
148
190
|
});
|
|
149
191
|
}
|
|
150
192
|
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
|
|
2
3
|
import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
|
|
3
4
|
import { Migration as SqlMigration } from '@prisma-next/family-sql/migration';
|
|
4
5
|
import type { ControlStack } from '@prisma-next/framework-components/control';
|
|
5
6
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
6
7
|
import { errorPostgresMigrationStackMissing } from '../errors';
|
|
7
|
-
import {
|
|
8
|
-
type DataTransformOptions,
|
|
9
|
-
dataTransform,
|
|
10
|
-
type PostgresDataTransformOperation,
|
|
11
|
-
} from './operations/data-transform';
|
|
8
|
+
import { type DataTransformOptions, dataTransform } from './operations/data-transform';
|
|
12
9
|
import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
13
10
|
|
|
14
11
|
/**
|
|
@@ -64,7 +61,7 @@ export abstract class PostgresMigration extends SqlMigration<
|
|
|
64
61
|
contract: TContract,
|
|
65
62
|
name: string,
|
|
66
63
|
options: DataTransformOptions,
|
|
67
|
-
):
|
|
64
|
+
): SqlMigrationPlanOperation<PostgresPlanTargetDetails> {
|
|
68
65
|
if (!this.controlAdapter) {
|
|
69
66
|
throw errorPostgresMigrationStackMissing();
|
|
70
67
|
}
|
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
|
|
2
|
-
import type {
|
|
2
|
+
import type { OpFactoryCall } from '@prisma-next/framework-components/control';
|
|
3
3
|
import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
4
4
|
|
|
5
5
|
type Op = SqlMigrationPlanOperation<PostgresPlanTargetDetails>;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Asserts an op materialised by an `OpFactoryCall` targets postgres. The
|
|
9
|
+
* extension surface lets any contributor emit calls, so this is the
|
|
10
|
+
* integration boundary where a stray non-postgres op would otherwise
|
|
11
|
+
* silently flow through to postgres-shaped renderers — exactly the
|
|
12
|
+
* place to fail loudly with op metadata (`id` + `target.id`).
|
|
13
|
+
*/
|
|
14
|
+
function assertPostgresOp(
|
|
15
|
+
op: ReturnType<OpFactoryCall['toOp']>,
|
|
16
|
+
callFactoryName: string,
|
|
17
|
+
): asserts op is Op {
|
|
18
|
+
const targetId = (op as Partial<Op>).target?.id;
|
|
19
|
+
if (targetId !== 'postgres') {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`renderOps: expected postgres op but got target.id="${String(targetId)}" for op.id="${op.id}" (factoryName="${callFactoryName}"). An OpFactoryCall produced an op for a different target on the postgres planner path; check the call's target binding.`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function renderOps(calls: readonly OpFactoryCall[]): Op[] {
|
|
27
|
+
return calls.map((c) => {
|
|
28
|
+
const op = c.toOp();
|
|
29
|
+
assertPostgresOp(op, c.factoryName);
|
|
30
|
+
return op;
|
|
31
|
+
});
|
|
9
32
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Polymorphic TypeScript emitter for the Postgres migration IR.
|
|
3
3
|
*
|
|
4
|
-
* Each `
|
|
4
|
+
* Each `OpFactoryCall` renders itself via `renderTypeScript()` and
|
|
5
5
|
* declares its own `importRequirements()`; this file just composes the module
|
|
6
6
|
* source around those contributions. The design mirrors the Mongo target's
|
|
7
7
|
* `render-typescript.ts` deliberately — byte-for-byte alignment isn't required
|
|
@@ -9,14 +9,13 @@
|
|
|
9
9
|
* shape is, so future consolidation to a framework-level helper is mechanical.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import type { OpFactoryCall } from '@prisma-next/framework-components/control';
|
|
12
13
|
import { detectScaffoldRuntime, shebangLineFor } from '@prisma-next/migration-tools/migration-ts';
|
|
13
14
|
import { type ImportRequirement, jsonToTsSource, renderImports } from '@prisma-next/ts-render';
|
|
14
|
-
import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
15
15
|
|
|
16
16
|
export interface RenderMigrationMeta {
|
|
17
|
-
readonly from: string;
|
|
17
|
+
readonly from: string | null;
|
|
18
18
|
readonly to: string;
|
|
19
|
-
readonly kind?: string;
|
|
20
19
|
readonly labels?: readonly string[];
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -44,7 +43,7 @@ const BASE_IMPORTS: readonly ImportRequirement[] = [
|
|
|
44
43
|
];
|
|
45
44
|
|
|
46
45
|
export function renderCallsToTypeScript(
|
|
47
|
-
calls: ReadonlyArray<
|
|
46
|
+
calls: ReadonlyArray<OpFactoryCall>,
|
|
48
47
|
meta: RenderMigrationMeta,
|
|
49
48
|
): string {
|
|
50
49
|
const imports = buildImports(calls);
|
|
@@ -68,7 +67,7 @@ export function renderCallsToTypeScript(
|
|
|
68
67
|
].join('\n');
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
function buildImports(calls: ReadonlyArray<
|
|
70
|
+
function buildImports(calls: ReadonlyArray<OpFactoryCall>): string {
|
|
72
71
|
const requirements: ImportRequirement[] = [...BASE_IMPORTS];
|
|
73
72
|
for (const call of calls) {
|
|
74
73
|
for (const req of call.importRequirements()) {
|
|
@@ -84,9 +83,6 @@ function buildDescribeMethod(meta: RenderMigrationMeta): string {
|
|
|
84
83
|
lines.push(' return {');
|
|
85
84
|
lines.push(` from: ${JSON.stringify(meta.from)},`);
|
|
86
85
|
lines.push(` to: ${JSON.stringify(meta.to)},`);
|
|
87
|
-
if (meta.kind) {
|
|
88
|
-
lines.push(` kind: ${JSON.stringify(meta.kind)},`);
|
|
89
|
-
}
|
|
90
86
|
if (meta.labels && meta.labels.length > 0) {
|
|
91
87
|
lines.push(` labels: ${jsonToTsSource(meta.labels)},`);
|
|
92
88
|
}
|