@prisma-next/target-postgres 0.3.0-dev.34 → 0.3.0-dev.37
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 +6 -1
- package/dist/control.d.mts +16 -0
- package/dist/control.d.mts.map +1 -0
- package/dist/control.mjs +2494 -0
- package/dist/control.mjs.map +1 -0
- package/dist/descriptor-meta-DxB8oZzB.mjs +13 -0
- package/dist/descriptor-meta-DxB8oZzB.mjs.map +1 -0
- package/dist/pack.d.mts +7 -0
- package/dist/pack.d.mts.map +1 -0
- package/dist/pack.mjs +9 -0
- package/dist/pack.mjs.map +1 -0
- package/dist/runtime.d.mts +9 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +21 -0
- package/dist/runtime.mjs.map +1 -0
- package/package.json +28 -28
- package/src/core/migrations/planner.ts +237 -22
- package/src/core/migrations/runner.ts +27 -20
- package/src/core/migrations/statement-builders.ts +6 -6
- package/src/core/types.ts +5 -0
- package/src/exports/control.ts +1 -3
- package/src/exports/runtime.ts +7 -12
- package/dist/chunk-RKEXRSSI.js +0 -14
- package/dist/chunk-RKEXRSSI.js.map +0 -1
- package/dist/core/descriptor-meta.d.ts +0 -9
- package/dist/core/descriptor-meta.d.ts.map +0 -1
- package/dist/core/migrations/planner.d.ts +0 -14
- package/dist/core/migrations/planner.d.ts.map +0 -1
- package/dist/core/migrations/runner.d.ts +0 -8
- package/dist/core/migrations/runner.d.ts.map +0 -1
- package/dist/core/migrations/statement-builders.d.ts +0 -30
- package/dist/core/migrations/statement-builders.d.ts.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 -1260
- package/dist/exports/control.js.map +0 -1
- package/dist/exports/pack.d.ts +0 -4
- package/dist/exports/pack.d.ts.map +0 -1
- package/dist/exports/pack.js +0 -11
- package/dist/exports/pack.js.map +0 -1
- package/dist/exports/runtime.d.ts +0 -12
- package/dist/exports/runtime.d.ts.map +0 -1
- package/dist/exports/runtime.js +0 -19
- package/dist/exports/runtime.js.map +0 -1
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeLiteral,
|
|
3
|
+
expandParameterizedNativeType,
|
|
4
|
+
normalizeSchemaNativeType,
|
|
5
|
+
parsePostgresDefault,
|
|
6
|
+
quoteIdentifier,
|
|
7
|
+
} from '@prisma-next/adapter-postgres/control';
|
|
1
8
|
import type { SchemaIssue } from '@prisma-next/core-control-plane/types';
|
|
2
9
|
import type {
|
|
10
|
+
CodecControlHooks,
|
|
3
11
|
MigrationOperationPolicy,
|
|
4
12
|
SqlMigrationPlanner,
|
|
5
13
|
SqlMigrationPlannerPlanOptions,
|
|
@@ -8,6 +16,7 @@ import type {
|
|
|
8
16
|
} from '@prisma-next/family-sql/control';
|
|
9
17
|
import {
|
|
10
18
|
createMigrationPlan,
|
|
19
|
+
extractCodecControlHooks,
|
|
11
20
|
plannerFailure,
|
|
12
21
|
plannerSuccess,
|
|
13
22
|
} from '@prisma-next/family-sql/control';
|
|
@@ -25,8 +34,10 @@ import type {
|
|
|
25
34
|
StorageTable,
|
|
26
35
|
} from '@prisma-next/sql-contract/types';
|
|
27
36
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
37
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
38
|
+
import type { PostgresColumnDefault } from '../types';
|
|
28
39
|
|
|
29
|
-
type OperationClass = 'extension' | 'table' | 'unique' | 'index' | 'foreignKey';
|
|
40
|
+
type OperationClass = 'extension' | 'type' | 'table' | 'unique' | 'index' | 'foreignKey';
|
|
30
41
|
|
|
31
42
|
type PlannerFrameworkComponents = SqlMigrationPlannerPlanOptions extends {
|
|
32
43
|
readonly frameworkComponents: infer T;
|
|
@@ -88,11 +99,21 @@ class PostgresMigrationPlanner implements SqlMigrationPlanner<PostgresPlanTarget
|
|
|
88
99
|
return plannerFailure(classification.conflicts);
|
|
89
100
|
}
|
|
90
101
|
|
|
102
|
+
// Extract codec control hooks once at entry point for reuse across all operations.
|
|
103
|
+
// This avoids repeated iteration over frameworkComponents for each method that needs hooks.
|
|
104
|
+
const codecHooks = extractCodecControlHooks(options.frameworkComponents);
|
|
105
|
+
|
|
91
106
|
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
92
107
|
|
|
108
|
+
const storageTypePlan = this.buildStorageTypeOperations(options, schemaName, codecHooks);
|
|
109
|
+
if (storageTypePlan.conflicts.length > 0) {
|
|
110
|
+
return plannerFailure(storageTypePlan.conflicts);
|
|
111
|
+
}
|
|
112
|
+
|
|
93
113
|
// Build extension operations from component-owned database dependencies
|
|
94
114
|
operations.push(
|
|
95
115
|
...this.buildDatabaseDependencyOperations(options),
|
|
116
|
+
...storageTypePlan.operations,
|
|
96
117
|
...this.buildTableOperations(options.contract.storage.tables, options.schema, schemaName),
|
|
97
118
|
...this.buildColumnOperations(options.contract.storage.tables, options.schema, schemaName),
|
|
98
119
|
...this.buildPrimaryKeyOperations(
|
|
@@ -102,6 +123,11 @@ class PostgresMigrationPlanner implements SqlMigrationPlanner<PostgresPlanTarget
|
|
|
102
123
|
),
|
|
103
124
|
...this.buildUniqueOperations(options.contract.storage.tables, options.schema, schemaName),
|
|
104
125
|
...this.buildIndexOperations(options.contract.storage.tables, options.schema, schemaName),
|
|
126
|
+
...this.buildFkBackingIndexOperations(
|
|
127
|
+
options.contract.storage.tables,
|
|
128
|
+
options.schema,
|
|
129
|
+
schemaName,
|
|
130
|
+
),
|
|
105
131
|
...this.buildForeignKeyOperations(
|
|
106
132
|
options.contract.storage.tables,
|
|
107
133
|
options.schema,
|
|
@@ -113,8 +139,8 @@ class PostgresMigrationPlanner implements SqlMigrationPlanner<PostgresPlanTarget
|
|
|
113
139
|
targetId: 'postgres',
|
|
114
140
|
origin: null,
|
|
115
141
|
destination: {
|
|
116
|
-
|
|
117
|
-
...(
|
|
142
|
+
storageHash: options.contract.storageHash,
|
|
143
|
+
...ifDefined('profileHash', options.contract.profileHash),
|
|
118
144
|
},
|
|
119
145
|
operations,
|
|
120
146
|
});
|
|
@@ -171,6 +197,55 @@ class PostgresMigrationPlanner implements SqlMigrationPlanner<PostgresPlanTarget
|
|
|
171
197
|
|
|
172
198
|
return operations;
|
|
173
199
|
}
|
|
200
|
+
|
|
201
|
+
private buildStorageTypeOperations(
|
|
202
|
+
options: PlannerOptionsWithComponents,
|
|
203
|
+
schemaName: string,
|
|
204
|
+
codecHooks: Map<string, CodecControlHooks>,
|
|
205
|
+
): {
|
|
206
|
+
readonly operations: readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[];
|
|
207
|
+
readonly conflicts: readonly SqlPlannerConflict[];
|
|
208
|
+
} {
|
|
209
|
+
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
210
|
+
const conflicts: SqlPlannerConflict[] = [];
|
|
211
|
+
const storageTypes = options.contract.storage.types ?? {};
|
|
212
|
+
|
|
213
|
+
for (const [typeName, typeInstance] of sortedEntries(storageTypes)) {
|
|
214
|
+
const hook = codecHooks.get(typeInstance.codecId);
|
|
215
|
+
const planResult = hook?.planTypeOperations?.({
|
|
216
|
+
typeName,
|
|
217
|
+
typeInstance,
|
|
218
|
+
contract: options.contract,
|
|
219
|
+
schema: options.schema,
|
|
220
|
+
schemaName,
|
|
221
|
+
policy: options.policy,
|
|
222
|
+
});
|
|
223
|
+
if (!planResult) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
for (const operation of planResult.operations) {
|
|
227
|
+
if (!options.policy.allowedOperationClasses.includes(operation.operationClass)) {
|
|
228
|
+
conflicts.push({
|
|
229
|
+
kind: 'missingButNonAdditive',
|
|
230
|
+
summary: `Storage type "${typeName}" requires "${operation.operationClass}" operation "${operation.id}"`,
|
|
231
|
+
location: {
|
|
232
|
+
type: typeName,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
operations.push({
|
|
238
|
+
...operation,
|
|
239
|
+
target: {
|
|
240
|
+
id: operation.target.id,
|
|
241
|
+
details: this.buildTargetDetails('type', typeName, schemaName),
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { operations, conflicts };
|
|
248
|
+
}
|
|
174
249
|
private collectDependencies(
|
|
175
250
|
options: PlannerOptionsWithComponents,
|
|
176
251
|
): ReadonlyArray<PlannerDatabaseDependency> {
|
|
@@ -263,15 +338,20 @@ class PostgresMigrationPlanner implements SqlMigrationPlanner<PostgresPlanTarget
|
|
|
263
338
|
): SqlMigrationPlanOperation<PostgresPlanTargetDetails> {
|
|
264
339
|
const qualified = qualifyTableName(schema, tableName);
|
|
265
340
|
const notNull = column.nullable === false;
|
|
341
|
+
const hasDefault = column.default !== undefined;
|
|
342
|
+
// Only require empty table for NOT NULL columns WITHOUT defaults.
|
|
343
|
+
// PostgreSQL allows adding NOT NULL columns with defaults to non-empty tables
|
|
344
|
+
// because the default value is applied to existing rows.
|
|
345
|
+
const requiresEmptyTable = notNull && !hasDefault;
|
|
266
346
|
const precheck = [
|
|
267
347
|
{
|
|
268
348
|
description: `ensure column "${columnName}" is missing`,
|
|
269
349
|
sql: columnExistsCheck({ schema, table: tableName, column: columnName, exists: false }),
|
|
270
350
|
},
|
|
271
|
-
...(
|
|
351
|
+
...(requiresEmptyTable
|
|
272
352
|
? [
|
|
273
353
|
{
|
|
274
|
-
description: `ensure table "${tableName}" is empty before adding NOT NULL column`,
|
|
354
|
+
description: `ensure table "${tableName}" is empty before adding NOT NULL column without default`,
|
|
275
355
|
sql: tableIsEmptyCheck(qualified),
|
|
276
356
|
},
|
|
277
357
|
]
|
|
@@ -459,6 +539,65 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(', ')})`,
|
|
|
459
539
|
return operations;
|
|
460
540
|
}
|
|
461
541
|
|
|
542
|
+
/**
|
|
543
|
+
* Generates FK-backing index operations for FKs with `index: true`,
|
|
544
|
+
* but only when no matching user-declared index exists in `contractTable.indexes`.
|
|
545
|
+
*/
|
|
546
|
+
private buildFkBackingIndexOperations(
|
|
547
|
+
tables: SqlContract<SqlStorage>['storage']['tables'],
|
|
548
|
+
schema: SqlSchemaIR,
|
|
549
|
+
schemaName: string,
|
|
550
|
+
): readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] {
|
|
551
|
+
const operations: SqlMigrationPlanOperation<PostgresPlanTargetDetails>[] = [];
|
|
552
|
+
for (const [tableName, table] of sortedEntries(tables)) {
|
|
553
|
+
const schemaTable = schema.tables[tableName];
|
|
554
|
+
// Collect column sets of user-declared indexes to avoid duplicates
|
|
555
|
+
const declaredIndexColumns = new Set(table.indexes.map((idx) => idx.columns.join(',')));
|
|
556
|
+
|
|
557
|
+
for (const fk of table.foreignKeys) {
|
|
558
|
+
if (fk.index === false) continue;
|
|
559
|
+
// Skip if user already declared an index with these columns
|
|
560
|
+
if (declaredIndexColumns.has(fk.columns.join(','))) continue;
|
|
561
|
+
// Skip if the index already exists in the database
|
|
562
|
+
if (schemaTable && hasIndex(schemaTable, fk.columns)) continue;
|
|
563
|
+
|
|
564
|
+
const indexName = `${tableName}_${fk.columns.join('_')}_idx`;
|
|
565
|
+
operations.push({
|
|
566
|
+
id: `index.${tableName}.${indexName}`,
|
|
567
|
+
label: `Create FK-backing index ${indexName} on ${tableName}`,
|
|
568
|
+
summary: `Creates FK-backing index ${indexName} on ${tableName}`,
|
|
569
|
+
operationClass: 'additive',
|
|
570
|
+
target: {
|
|
571
|
+
id: 'postgres',
|
|
572
|
+
details: this.buildTargetDetails('index', indexName, schemaName, tableName),
|
|
573
|
+
},
|
|
574
|
+
precheck: [
|
|
575
|
+
{
|
|
576
|
+
description: `ensure index "${indexName}" is missing`,
|
|
577
|
+
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NULL`,
|
|
578
|
+
},
|
|
579
|
+
],
|
|
580
|
+
execute: [
|
|
581
|
+
{
|
|
582
|
+
description: `create FK-backing index "${indexName}"`,
|
|
583
|
+
sql: `CREATE INDEX ${quoteIdentifier(indexName)} ON ${qualifyTableName(
|
|
584
|
+
schemaName,
|
|
585
|
+
tableName,
|
|
586
|
+
)} (${fk.columns.map(quoteIdentifier).join(', ')})`,
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
postcheck: [
|
|
590
|
+
{
|
|
591
|
+
description: `verify index "${indexName}" exists`,
|
|
592
|
+
sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NOT NULL`,
|
|
593
|
+
},
|
|
594
|
+
],
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return operations;
|
|
599
|
+
}
|
|
600
|
+
|
|
462
601
|
private buildForeignKeyOperations(
|
|
463
602
|
tables: SqlContract<SqlStorage>['storage']['tables'],
|
|
464
603
|
schema: SqlSchemaIR,
|
|
@@ -468,6 +607,7 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(', ')})`,
|
|
|
468
607
|
for (const [tableName, table] of sortedEntries(tables)) {
|
|
469
608
|
const schemaTable = schema.tables[tableName];
|
|
470
609
|
for (const foreignKey of table.foreignKeys) {
|
|
610
|
+
if (foreignKey.constraint === false) continue;
|
|
471
611
|
if (schemaTable && hasForeignKey(schemaTable, foreignKey)) {
|
|
472
612
|
continue;
|
|
473
613
|
}
|
|
@@ -524,7 +664,7 @@ REFERENCES ${qualifyTableName(schemaName, foreignKey.references.table)} (${forei
|
|
|
524
664
|
schema,
|
|
525
665
|
objectType,
|
|
526
666
|
name,
|
|
527
|
-
...(table
|
|
667
|
+
...ifDefined('table', table),
|
|
528
668
|
};
|
|
529
669
|
}
|
|
530
670
|
|
|
@@ -540,6 +680,8 @@ REFERENCES ${qualifyTableName(schemaName, foreignKey.references.table)} (${forei
|
|
|
540
680
|
strict: false,
|
|
541
681
|
typeMetadataRegistry: new Map(),
|
|
542
682
|
frameworkComponents: options.frameworkComponents,
|
|
683
|
+
normalizeDefault: parsePostgresDefault,
|
|
684
|
+
normalizeNativeType: normalizeSchemaNativeType,
|
|
543
685
|
};
|
|
544
686
|
const verifyResult = verifySqlSchema(verifyOptions);
|
|
545
687
|
|
|
@@ -588,16 +730,16 @@ REFERENCES ${qualifyTableName(schemaName, foreignKey.references.table)} (${forei
|
|
|
588
730
|
const meta =
|
|
589
731
|
issue.expected || issue.actual
|
|
590
732
|
? Object.freeze({
|
|
591
|
-
...(
|
|
592
|
-
...(
|
|
733
|
+
...ifDefined('expected', issue.expected),
|
|
734
|
+
...ifDefined('actual', issue.actual),
|
|
593
735
|
})
|
|
594
736
|
: undefined;
|
|
595
737
|
|
|
596
738
|
return {
|
|
597
739
|
kind,
|
|
598
740
|
summary: issue.message,
|
|
599
|
-
...(location
|
|
600
|
-
...(meta
|
|
741
|
+
...ifDefined('location', location),
|
|
742
|
+
...ifDefined('meta', meta),
|
|
601
743
|
};
|
|
602
744
|
}
|
|
603
745
|
}
|
|
@@ -638,7 +780,8 @@ function buildCreateTableSql(qualifiedTableName: string, table: StorageTable): s
|
|
|
638
780
|
([columnName, column]: [string, StorageColumn]) => {
|
|
639
781
|
const parts = [
|
|
640
782
|
quoteIdentifier(columnName),
|
|
641
|
-
column
|
|
783
|
+
buildColumnTypeSql(column),
|
|
784
|
+
buildColumnDefaultSql(column.default),
|
|
642
785
|
column.nullable ? '' : 'NOT NULL',
|
|
643
786
|
].filter(Boolean);
|
|
644
787
|
return parts.join(' ');
|
|
@@ -656,6 +799,83 @@ function buildCreateTableSql(qualifiedTableName: string, table: StorageTable): s
|
|
|
656
799
|
return `CREATE TABLE ${qualifiedTableName} (\n ${allDefinitions.join(',\n ')}\n)`;
|
|
657
800
|
}
|
|
658
801
|
|
|
802
|
+
/**
|
|
803
|
+
* Builds the column type SQL, handling autoincrement as a special case.
|
|
804
|
+
* For autoincrement on int4/int8, we use SERIAL/BIGSERIAL types.
|
|
805
|
+
*/
|
|
806
|
+
function buildColumnTypeSql(column: StorageColumn): string {
|
|
807
|
+
const columnDefault = column.default;
|
|
808
|
+
|
|
809
|
+
// For autoincrement, use SERIAL/BIGSERIAL types instead of int4/int8
|
|
810
|
+
if (columnDefault?.kind === 'function' && columnDefault.expression === 'autoincrement()') {
|
|
811
|
+
if (column.nativeType === 'int4' || column.nativeType === 'integer') {
|
|
812
|
+
return 'SERIAL';
|
|
813
|
+
}
|
|
814
|
+
if (column.nativeType === 'int8' || column.nativeType === 'bigint') {
|
|
815
|
+
return 'BIGSERIAL';
|
|
816
|
+
}
|
|
817
|
+
if (column.nativeType === 'int2' || column.nativeType === 'smallint') {
|
|
818
|
+
return 'SMALLSERIAL';
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (column.typeRef) {
|
|
823
|
+
return quoteIdentifier(column.nativeType);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return renderParameterizedTypeSql(column) ?? column.nativeType;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Renders parameterized type SQL for a column, returning null if no expansion is needed.
|
|
831
|
+
*
|
|
832
|
+
* Uses the shared expandParameterizedNativeType utility from the postgres adapter.
|
|
833
|
+
* Returns null when the column has no typeParams, allowing the caller to fall back
|
|
834
|
+
* to the base nativeType.
|
|
835
|
+
*/
|
|
836
|
+
function renderParameterizedTypeSql(column: StorageColumn): string | null {
|
|
837
|
+
if (!column.typeParams) {
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const expanded = expandParameterizedNativeType({
|
|
842
|
+
nativeType: column.nativeType,
|
|
843
|
+
codecId: column.codecId,
|
|
844
|
+
typeParams: column.typeParams,
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// If no expansion happened (returned the same base type), return null
|
|
848
|
+
// so caller can decide whether to use nativeType directly
|
|
849
|
+
return expanded !== column.nativeType ? expanded : null;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Builds the DEFAULT clause for a column definition.
|
|
854
|
+
* Returns empty string if no default is defined.
|
|
855
|
+
*
|
|
856
|
+
* Note: autoincrement is handled specially via SERIAL types, so we skip it here.
|
|
857
|
+
*/
|
|
858
|
+
function buildColumnDefaultSql(columnDefault: PostgresColumnDefault | undefined): string {
|
|
859
|
+
if (!columnDefault) {
|
|
860
|
+
return '';
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
switch (columnDefault.kind) {
|
|
864
|
+
case 'literal':
|
|
865
|
+
return `DEFAULT ${columnDefault.expression}`;
|
|
866
|
+
case 'function': {
|
|
867
|
+
// autoincrement is handled by SERIAL type, no explicit DEFAULT needed
|
|
868
|
+
if (columnDefault.expression === 'autoincrement()') {
|
|
869
|
+
return '';
|
|
870
|
+
}
|
|
871
|
+
return `DEFAULT ${columnDefault.expression}`;
|
|
872
|
+
}
|
|
873
|
+
case 'sequence':
|
|
874
|
+
// Sequence names use quoteIdentifier for safe identifier handling
|
|
875
|
+
return `DEFAULT nextval(${quoteIdentifier(columnDefault.name)}::regclass)`;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
659
879
|
function qualifyTableName(schema: string, table: string): string {
|
|
660
880
|
return `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
|
|
661
881
|
}
|
|
@@ -665,16 +885,6 @@ function toRegclassLiteral(schema: string, name: string): string {
|
|
|
665
885
|
return `'${escapeLiteral(regclass)}'`;
|
|
666
886
|
}
|
|
667
887
|
|
|
668
|
-
/** Escapes and quotes a SQL identifier (table, column, schema name). */
|
|
669
|
-
function quoteIdentifier(identifier: string): string {
|
|
670
|
-
// TypeScript enforces string type - no runtime check needed for internal callers
|
|
671
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
function escapeLiteral(value: string): string {
|
|
675
|
-
return value.replace(/'/g, "''");
|
|
676
|
-
}
|
|
677
|
-
|
|
678
888
|
function sortedEntries<V>(record: Readonly<Record<string, V>>): Array<[string, V]> {
|
|
679
889
|
return Object.entries(record).sort(([a], [b]) => a.localeCompare(b)) as Array<[string, V]>;
|
|
680
890
|
}
|
|
@@ -746,9 +956,12 @@ function buildAddColumnSql(
|
|
|
746
956
|
columnName: string,
|
|
747
957
|
column: StorageColumn,
|
|
748
958
|
): string {
|
|
959
|
+
const typeSql = buildColumnTypeSql(column);
|
|
960
|
+
const defaultSql = buildColumnDefaultSql(column.default);
|
|
749
961
|
const parts = [
|
|
750
962
|
`ALTER TABLE ${qualifiedTableName}`,
|
|
751
|
-
`ADD COLUMN ${quoteIdentifier(columnName)} ${
|
|
963
|
+
`ADD COLUMN ${quoteIdentifier(columnName)} ${typeSql}`,
|
|
964
|
+
defaultSql,
|
|
752
965
|
column.nullable ? '' : 'NOT NULL',
|
|
753
966
|
].filter(Boolean);
|
|
754
967
|
return parts.join(' ');
|
|
@@ -807,6 +1020,8 @@ function hasForeignKey(table: SqlSchemaIR['tables'][string], fk: ForeignKey): bo
|
|
|
807
1020
|
|
|
808
1021
|
function isAdditiveIssue(issue: SchemaIssue): boolean {
|
|
809
1022
|
switch (issue.kind) {
|
|
1023
|
+
case 'type_missing':
|
|
1024
|
+
case 'type_values_mismatch':
|
|
810
1025
|
case 'missing_table':
|
|
811
1026
|
case 'missing_column':
|
|
812
1027
|
case 'extension_missing':
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeSchemaNativeType,
|
|
3
|
+
parsePostgresDefault,
|
|
4
|
+
} from '@prisma-next/adapter-postgres/control';
|
|
1
5
|
import type { ContractMarkerRecord } from '@prisma-next/contract/types';
|
|
2
6
|
import type {
|
|
3
7
|
MigrationOperationPolicy,
|
|
@@ -14,6 +18,7 @@ import { runnerFailure, runnerSuccess } from '@prisma-next/family-sql/control';
|
|
|
14
18
|
import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
|
|
15
19
|
import { readMarker } from '@prisma-next/family-sql/verify';
|
|
16
20
|
import { SqlQueryError } from '@prisma-next/sql-errors';
|
|
21
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
17
22
|
import type { Result } from '@prisma-next/utils/result';
|
|
18
23
|
import { ok, okVoid } from '@prisma-next/utils/result';
|
|
19
24
|
import type { PostgresPlanTargetDetails } from './planner';
|
|
@@ -141,6 +146,8 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
141
146
|
context: options.context ?? {},
|
|
142
147
|
typeMetadataRegistry: this.family.typeMetadataRegistry,
|
|
143
148
|
frameworkComponents: options.frameworkComponents,
|
|
149
|
+
normalizeDefault: parsePostgresDefault,
|
|
150
|
+
normalizeNativeType: normalizeSchemaNativeType,
|
|
144
151
|
});
|
|
145
152
|
if (!schemaVerifyResult.ok) {
|
|
146
153
|
return runnerFailure('SCHEMA_VERIFY_FAILED', schemaVerifyResult.summary, {
|
|
@@ -371,13 +378,13 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
371
378
|
return Object.freeze({
|
|
372
379
|
id: operation.id,
|
|
373
380
|
label: operation.label,
|
|
374
|
-
...(
|
|
381
|
+
...ifDefined('summary', operation.summary),
|
|
375
382
|
operationClass: operation.operationClass,
|
|
376
383
|
target: operation.target, // Already frozen from plan creation
|
|
377
384
|
precheck: Object.freeze([]),
|
|
378
385
|
execute: Object.freeze([]),
|
|
379
386
|
postcheck: frozenPostcheck,
|
|
380
|
-
...(operation.meta || mergedMeta ?
|
|
387
|
+
...ifDefined('meta', operation.meta || mergedMeta ? mergedMeta : undefined),
|
|
381
388
|
});
|
|
382
389
|
}
|
|
383
390
|
|
|
@@ -388,7 +395,7 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
388
395
|
if (!marker) {
|
|
389
396
|
return false;
|
|
390
397
|
}
|
|
391
|
-
if (marker.
|
|
398
|
+
if (marker.storageHash !== plan.destination.storageHash) {
|
|
392
399
|
return false;
|
|
393
400
|
}
|
|
394
401
|
if (plan.destination.profileHash && marker.profileHash !== plan.destination.profileHash) {
|
|
@@ -435,10 +442,10 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
435
442
|
}
|
|
436
443
|
return runnerFailure(
|
|
437
444
|
'MARKER_ORIGIN_MISMATCH',
|
|
438
|
-
`Existing contract marker (${marker.
|
|
445
|
+
`Existing contract marker (${marker.storageHash}) does not match plan origin (no marker expected).`,
|
|
439
446
|
{
|
|
440
447
|
meta: {
|
|
441
|
-
|
|
448
|
+
markerStorageHash: marker.storageHash,
|
|
442
449
|
expectedOrigin: null,
|
|
443
450
|
},
|
|
444
451
|
},
|
|
@@ -448,22 +455,22 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
448
455
|
if (!marker) {
|
|
449
456
|
return runnerFailure(
|
|
450
457
|
'MARKER_ORIGIN_MISMATCH',
|
|
451
|
-
`Missing contract marker: expected origin
|
|
458
|
+
`Missing contract marker: expected origin storage hash ${origin.storageHash}.`,
|
|
452
459
|
{
|
|
453
460
|
meta: {
|
|
454
|
-
|
|
461
|
+
expectedOriginStorageHash: origin.storageHash,
|
|
455
462
|
},
|
|
456
463
|
},
|
|
457
464
|
);
|
|
458
465
|
}
|
|
459
|
-
if (marker.
|
|
466
|
+
if (marker.storageHash !== origin.storageHash) {
|
|
460
467
|
return runnerFailure(
|
|
461
468
|
'MARKER_ORIGIN_MISMATCH',
|
|
462
|
-
`Existing contract marker (${marker.
|
|
469
|
+
`Existing contract marker (${marker.storageHash}) does not match plan origin (${origin.storageHash}).`,
|
|
463
470
|
{
|
|
464
471
|
meta: {
|
|
465
|
-
|
|
466
|
-
|
|
472
|
+
markerStorageHash: marker.storageHash,
|
|
473
|
+
expectedOriginStorageHash: origin.storageHash,
|
|
467
474
|
},
|
|
468
475
|
},
|
|
469
476
|
);
|
|
@@ -487,14 +494,14 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
487
494
|
destination: SqlMigrationPlanContractInfo,
|
|
488
495
|
contract: SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>['destinationContract'],
|
|
489
496
|
): Result<void, SqlMigrationRunnerFailure> {
|
|
490
|
-
if (destination.
|
|
497
|
+
if (destination.storageHash !== contract.storageHash) {
|
|
491
498
|
return runnerFailure(
|
|
492
499
|
'DESTINATION_CONTRACT_MISMATCH',
|
|
493
|
-
`Plan destination
|
|
500
|
+
`Plan destination storage hash (${destination.storageHash}) does not match provided contract storage hash (${contract.storageHash}).`,
|
|
494
501
|
{
|
|
495
502
|
meta: {
|
|
496
|
-
|
|
497
|
-
|
|
503
|
+
planStorageHash: destination.storageHash,
|
|
504
|
+
contractStorageHash: contract.storageHash,
|
|
498
505
|
},
|
|
499
506
|
},
|
|
500
507
|
);
|
|
@@ -524,11 +531,11 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
524
531
|
existingMarker: ContractMarkerRecord | null,
|
|
525
532
|
): Promise<void> {
|
|
526
533
|
const writeStatements = buildWriteMarkerStatements({
|
|
527
|
-
|
|
534
|
+
storageHash: options.plan.destination.storageHash,
|
|
528
535
|
profileHash:
|
|
529
536
|
options.plan.destination.profileHash ??
|
|
530
537
|
options.destinationContract.profileHash ??
|
|
531
|
-
options.plan.destination.
|
|
538
|
+
options.plan.destination.storageHash,
|
|
532
539
|
contractJson: options.destinationContract,
|
|
533
540
|
canonicalVersion: null,
|
|
534
541
|
meta: {},
|
|
@@ -544,13 +551,13 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
544
551
|
executedOperations: readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[],
|
|
545
552
|
): Promise<void> {
|
|
546
553
|
const ledgerStatement = buildLedgerInsertStatement({
|
|
547
|
-
|
|
554
|
+
originStorageHash: existingMarker?.storageHash ?? null,
|
|
548
555
|
originProfileHash: existingMarker?.profileHash ?? null,
|
|
549
|
-
|
|
556
|
+
destinationStorageHash: options.plan.destination.storageHash,
|
|
550
557
|
destinationProfileHash:
|
|
551
558
|
options.plan.destination.profileHash ??
|
|
552
559
|
options.destinationContract.profileHash ??
|
|
553
|
-
options.plan.destination.
|
|
560
|
+
options.plan.destination.storageHash,
|
|
554
561
|
contractJsonBefore: existingMarker?.contractJson ?? null,
|
|
555
562
|
contractJsonAfter: options.destinationContract,
|
|
556
563
|
operations: executedOperations,
|
|
@@ -38,7 +38,7 @@ export const ensureLedgerTableStatement: SqlStatement = {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
export interface WriteMarkerInput {
|
|
41
|
-
readonly
|
|
41
|
+
readonly storageHash: string;
|
|
42
42
|
readonly profileHash: string;
|
|
43
43
|
readonly contractJson?: unknown;
|
|
44
44
|
readonly canonicalVersion?: number | null;
|
|
@@ -52,7 +52,7 @@ export function buildWriteMarkerStatements(input: WriteMarkerInput): {
|
|
|
52
52
|
} {
|
|
53
53
|
const params: readonly unknown[] = [
|
|
54
54
|
1,
|
|
55
|
-
input.
|
|
55
|
+
input.storageHash,
|
|
56
56
|
input.profileHash,
|
|
57
57
|
jsonParam(input.contractJson),
|
|
58
58
|
input.canonicalVersion ?? null,
|
|
@@ -99,9 +99,9 @@ export function buildWriteMarkerStatements(input: WriteMarkerInput): {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export interface LedgerInsertInput {
|
|
102
|
-
readonly
|
|
102
|
+
readonly originStorageHash?: string | null;
|
|
103
103
|
readonly originProfileHash?: string | null;
|
|
104
|
-
readonly
|
|
104
|
+
readonly destinationStorageHash: string;
|
|
105
105
|
readonly destinationProfileHash?: string | null;
|
|
106
106
|
readonly contractJsonBefore?: unknown;
|
|
107
107
|
readonly contractJsonAfter?: unknown;
|
|
@@ -128,9 +128,9 @@ export function buildLedgerInsertStatement(input: LedgerInsertInput): SqlStateme
|
|
|
128
128
|
$7::jsonb
|
|
129
129
|
)`,
|
|
130
130
|
params: [
|
|
131
|
-
input.
|
|
131
|
+
input.originStorageHash ?? null,
|
|
132
132
|
input.originProfileHash ?? null,
|
|
133
|
-
input.
|
|
133
|
+
input.destinationStorageHash,
|
|
134
134
|
input.destinationProfileHash ?? null,
|
|
135
135
|
jsonParam(input.contractJsonBefore),
|
|
136
136
|
jsonParam(input.contractJsonAfter),
|
package/src/exports/control.ts
CHANGED
|
@@ -12,12 +12,10 @@ import type { PostgresPlanTargetDetails } from '../core/migrations/planner';
|
|
|
12
12
|
import { createPostgresMigrationPlanner } from '../core/migrations/planner';
|
|
13
13
|
import { createPostgresMigrationRunner } from '../core/migrations/runner';
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Postgres target descriptor for CLI config.
|
|
17
|
-
*/
|
|
18
15
|
const postgresTargetDescriptor: SqlControlTargetDescriptor<'postgres', PostgresPlanTargetDetails> =
|
|
19
16
|
{
|
|
20
17
|
...postgresTargetDescriptorMeta,
|
|
18
|
+
operationSignatures: () => [],
|
|
21
19
|
/**
|
|
22
20
|
* Migrations capability for CLI to access planner/runner via core types.
|
|
23
21
|
* The SQL-specific planner/runner types are compatible with the generic
|
package/src/exports/runtime.ts
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from '@prisma-next/core-execution-plane/types';
|
|
1
|
+
import type { RuntimeTargetInstance } from '@prisma-next/core-execution-plane/types';
|
|
2
|
+
import { createCodecRegistry } from '@prisma-next/sql-relational-core/ast';
|
|
3
|
+
import type { SqlRuntimeTargetDescriptor } from '@prisma-next/sql-runtime';
|
|
5
4
|
import { postgresTargetDescriptorMeta } from '../core/descriptor-meta';
|
|
6
5
|
|
|
7
|
-
/**
|
|
8
|
-
* Postgres runtime target instance interface.
|
|
9
|
-
*/
|
|
10
6
|
export interface PostgresRuntimeTargetInstance extends RuntimeTargetInstance<'sql', 'postgres'> {}
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
* Postgres target descriptor for runtime plane.
|
|
14
|
-
*/
|
|
15
|
-
const postgresRuntimeTargetDescriptor: RuntimeTargetDescriptor<
|
|
16
|
-
'sql',
|
|
8
|
+
const postgresRuntimeTargetDescriptor: SqlRuntimeTargetDescriptor<
|
|
17
9
|
'postgres',
|
|
18
10
|
PostgresRuntimeTargetInstance
|
|
19
11
|
> = {
|
|
20
12
|
...postgresTargetDescriptorMeta,
|
|
13
|
+
codecs: () => createCodecRegistry(),
|
|
14
|
+
operationSignatures: () => [],
|
|
15
|
+
parameterizedCodecs: () => [],
|
|
21
16
|
create(): PostgresRuntimeTargetInstance {
|
|
22
17
|
return {
|
|
23
18
|
familyId: 'sql',
|
package/dist/chunk-RKEXRSSI.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// src/core/descriptor-meta.ts
|
|
2
|
-
var postgresTargetDescriptorMeta = {
|
|
3
|
-
kind: "target",
|
|
4
|
-
familyId: "sql",
|
|
5
|
-
targetId: "postgres",
|
|
6
|
-
id: "postgres",
|
|
7
|
-
version: "0.0.1",
|
|
8
|
-
capabilities: {}
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export {
|
|
12
|
-
postgresTargetDescriptorMeta
|
|
13
|
-
};
|
|
14
|
-
//# sourceMappingURL=chunk-RKEXRSSI.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/descriptor-meta.ts"],"sourcesContent":["export const postgresTargetDescriptorMeta = {\n kind: 'target',\n familyId: 'sql',\n targetId: 'postgres',\n id: 'postgres',\n version: '0.0.1',\n capabilities: {},\n} as const;\n"],"mappings":";AAAO,IAAM,+BAA+B;AAAA,EAC1C,MAAM;AAAA,EACN,UAAU;AAAA,EACV,UAAU;AAAA,EACV,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,cAAc,CAAC;AACjB;","names":[]}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare const postgresTargetDescriptorMeta: {
|
|
2
|
-
readonly kind: "target";
|
|
3
|
-
readonly familyId: "sql";
|
|
4
|
-
readonly targetId: "postgres";
|
|
5
|
-
readonly id: "postgres";
|
|
6
|
-
readonly version: "0.0.1";
|
|
7
|
-
readonly capabilities: {};
|
|
8
|
-
};
|
|
9
|
-
//# sourceMappingURL=descriptor-meta.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"descriptor-meta.d.ts","sourceRoot":"","sources":["../../src/core/descriptor-meta.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B;;;;;;;CAO/B,CAAC"}
|