@prisma-next/target-postgres 0.3.0-dev.113 → 0.3.0-dev.115
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/control.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { SQL_CHAR_CODEC_ID, SQL_FLOAT_CODEC_ID, SQL_INT_CODEC_ID, SQL_VARCHAR_CO
|
|
|
5
5
|
import { arraysEqual, verifySqlSchema } from "@prisma-next/family-sql/schema-verify";
|
|
6
6
|
import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
|
|
7
7
|
import { bigintJsonReplacer, isTaggedBigInt } from "@prisma-next/contract/types";
|
|
8
|
+
import { invariant } from "@prisma-next/utils/assertions";
|
|
8
9
|
import { readMarker } from "@prisma-next/family-sql/verify";
|
|
9
10
|
import { SqlQueryError } from "@prisma-next/sql-errors";
|
|
10
11
|
import { ok, okVoid } from "@prisma-next/utils/result";
|
|
@@ -393,7 +394,7 @@ function collectAllEnumColumns(contract, schema, typeName, nativeType) {
|
|
|
393
394
|
/**
|
|
394
395
|
* Builds a SQL check to verify a column's type matches an expected type.
|
|
395
396
|
*/
|
|
396
|
-
function columnTypeCheck(options) {
|
|
397
|
+
function columnTypeCheck$1(options) {
|
|
397
398
|
return `SELECT EXISTS (
|
|
398
399
|
SELECT 1
|
|
399
400
|
FROM information_schema.columns
|
|
@@ -480,7 +481,7 @@ USING ${quoteIdentifier(ref.column)}::text::${qualifiedTemp}`
|
|
|
480
481
|
},
|
|
481
482
|
...columnRefs.map((ref) => ({
|
|
482
483
|
description: `verify ${ref.table}.${ref.column} uses type "${options.nativeType}"`,
|
|
483
|
-
sql: columnTypeCheck({
|
|
484
|
+
sql: columnTypeCheck$1({
|
|
484
485
|
schemaName: options.schemaName,
|
|
485
486
|
tableName: ref.table,
|
|
486
487
|
columnName: ref.column,
|
|
@@ -2009,12 +2010,13 @@ function qualifyTableName(schema, table) {
|
|
|
2009
2010
|
function toRegclassLiteral(schema, name) {
|
|
2010
2011
|
return `'${escapeLiteral(`${quoteIdentifier(schema)}.${quoteIdentifier(name)}`)}'`;
|
|
2011
2012
|
}
|
|
2012
|
-
function constraintExistsCheck({ constraintName, schema, exists = true }) {
|
|
2013
|
+
function constraintExistsCheck({ constraintName, schema, table, exists = true }) {
|
|
2013
2014
|
return `SELECT ${exists ? "EXISTS" : "NOT EXISTS"} (
|
|
2014
2015
|
SELECT 1 FROM pg_constraint c
|
|
2015
2016
|
JOIN pg_namespace n ON c.connamespace = n.oid
|
|
2016
2017
|
WHERE c.conname = '${escapeLiteral(constraintName)}'
|
|
2017
2018
|
AND n.nspname = '${escapeLiteral(schema)}'
|
|
2019
|
+
AND c.conrelid = to_regclass(${toRegclassLiteral(schema, table)})
|
|
2018
2020
|
)`;
|
|
2019
2021
|
}
|
|
2020
2022
|
function columnExistsCheck({ schema, table, column, exists = true }) {
|
|
@@ -2037,6 +2039,70 @@ function columnNullabilityCheck({ schema, table, column, nullable }) {
|
|
|
2037
2039
|
AND is_nullable = '${expected}'
|
|
2038
2040
|
)`;
|
|
2039
2041
|
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Maps contract native type names to the display form returned by PostgreSQL's
|
|
2044
|
+
* `format_type()`. Base types use short names in the contract (e.g., `int4`)
|
|
2045
|
+
* but `format_type()` returns SQL-standard names (e.g., `integer`).
|
|
2046
|
+
*
|
|
2047
|
+
* NOTE: The inverse mapping lives in `normalizeFormattedType` in control-adapter.ts.
|
|
2048
|
+
* These two maps must stay in sync. A shared bidirectional map in
|
|
2049
|
+
* @prisma-next/adapter-postgres would eliminate the drift risk.
|
|
2050
|
+
*/
|
|
2051
|
+
const FORMAT_TYPE_DISPLAY = new Map([
|
|
2052
|
+
["int2", "smallint"],
|
|
2053
|
+
["int4", "integer"],
|
|
2054
|
+
["int8", "bigint"],
|
|
2055
|
+
["float4", "real"],
|
|
2056
|
+
["float8", "double precision"],
|
|
2057
|
+
["bool", "boolean"],
|
|
2058
|
+
["timestamp", "timestamp without time zone"],
|
|
2059
|
+
["timestamptz", "timestamp with time zone"],
|
|
2060
|
+
["time", "time without time zone"],
|
|
2061
|
+
["timetz", "time with time zone"]
|
|
2062
|
+
]);
|
|
2063
|
+
/**
|
|
2064
|
+
* Builds the string that `format_type(atttypid, atttypmod)` would return for a
|
|
2065
|
+
* contract column. Used for postchecks — separate from `buildColumnTypeSql` which
|
|
2066
|
+
* produces DDL-safe strings (e.g., quoted identifiers, SERIAL).
|
|
2067
|
+
*/
|
|
2068
|
+
function buildExpectedFormatType(column, codecHooks) {
|
|
2069
|
+
if (column.typeParams && column.codecId) {
|
|
2070
|
+
const hooks = codecHooks.get(column.codecId);
|
|
2071
|
+
if (hooks?.expandNativeType) return hooks.expandNativeType({
|
|
2072
|
+
nativeType: column.nativeType,
|
|
2073
|
+
codecId: column.codecId,
|
|
2074
|
+
typeParams: column.typeParams
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
if (column.typeRef) return column.nativeType !== column.nativeType.toLowerCase() ? `"${column.nativeType}"` : column.nativeType;
|
|
2078
|
+
return FORMAT_TYPE_DISPLAY.get(column.nativeType) ?? column.nativeType;
|
|
2079
|
+
}
|
|
2080
|
+
/** Checks that the column's full type (including typmods) matches the expected type via `format_type()`. */
|
|
2081
|
+
function columnTypeCheck({ schema, table, column, expectedType }) {
|
|
2082
|
+
return `SELECT EXISTS (
|
|
2083
|
+
SELECT 1
|
|
2084
|
+
FROM pg_attribute a
|
|
2085
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
2086
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
2087
|
+
WHERE n.nspname = '${escapeLiteral(schema)}'
|
|
2088
|
+
AND c.relname = '${escapeLiteral(table)}'
|
|
2089
|
+
AND a.attname = '${escapeLiteral(column)}'
|
|
2090
|
+
AND format_type(a.atttypid, a.atttypmod) = '${escapeLiteral(expectedType)}'
|
|
2091
|
+
AND NOT a.attisdropped
|
|
2092
|
+
)`;
|
|
2093
|
+
}
|
|
2094
|
+
/** Checks that a column default exists (or does not exist) via `information_schema.columns.column_default`. */
|
|
2095
|
+
function columnDefaultExistsCheck({ schema, table, column, exists = true }) {
|
|
2096
|
+
const nullCheck = exists ? "IS NOT NULL" : "IS NULL";
|
|
2097
|
+
return `SELECT EXISTS (
|
|
2098
|
+
SELECT 1
|
|
2099
|
+
FROM information_schema.columns
|
|
2100
|
+
WHERE table_schema = '${escapeLiteral(schema)}'
|
|
2101
|
+
AND table_name = '${escapeLiteral(table)}'
|
|
2102
|
+
AND column_name = '${escapeLiteral(column)}'
|
|
2103
|
+
AND column_default ${nullCheck}
|
|
2104
|
+
)`;
|
|
2105
|
+
}
|
|
2040
2106
|
function tableIsEmptyCheck(qualifiedTableName) {
|
|
2041
2107
|
return `SELECT NOT EXISTS (SELECT 1 FROM ${qualifiedTableName} LIMIT 1)`;
|
|
2042
2108
|
}
|
|
@@ -2243,6 +2309,25 @@ function buildReconciliationOperationFromIssue(options) {
|
|
|
2243
2309
|
if (!contractColumn) return null;
|
|
2244
2310
|
return buildAlterColumnTypeOperation(schemaName, issue.table, issue.column, contractColumn, codecHooks);
|
|
2245
2311
|
}
|
|
2312
|
+
case "default_missing": {
|
|
2313
|
+
if (!issue.table || !issue.column) return null;
|
|
2314
|
+
const contractColMissing = getContractColumn(contract, issue.table, issue.column);
|
|
2315
|
+
if (!contractColMissing) return null;
|
|
2316
|
+
invariant(contractColMissing.default !== void 0, `default_missing issue for "${issue.table}"."${issue.column}" but contract column has no default`);
|
|
2317
|
+
return buildDefaultOperation(schemaName, issue.table, issue.column, contractColMissing, contractColMissing.default, "additive", "Set");
|
|
2318
|
+
}
|
|
2319
|
+
case "default_mismatch": {
|
|
2320
|
+
if (!issue.table || !issue.column) return null;
|
|
2321
|
+
if (!mode.allowWidening) return null;
|
|
2322
|
+
const contractColMismatch = getContractColumn(contract, issue.table, issue.column);
|
|
2323
|
+
if (!contractColMismatch) return null;
|
|
2324
|
+
invariant(contractColMismatch.default !== void 0, `default_mismatch issue for "${issue.table}"."${issue.column}" but contract column has no default`);
|
|
2325
|
+
return buildDefaultOperation(schemaName, issue.table, issue.column, contractColMismatch, contractColMismatch.default, "widening", "Change");
|
|
2326
|
+
}
|
|
2327
|
+
case "extra_default":
|
|
2328
|
+
if (!issue.table || !issue.column) return null;
|
|
2329
|
+
if (!mode.allowDestructive) return null;
|
|
2330
|
+
return buildDropDefaultOperation(schemaName, issue.table, issue.column);
|
|
2246
2331
|
default: return null;
|
|
2247
2332
|
}
|
|
2248
2333
|
}
|
|
@@ -2346,7 +2431,8 @@ function buildDropConstraintOperation(schemaName, tableName, constraintName, con
|
|
|
2346
2431
|
description: `ensure constraint "${constraintName}" exists`,
|
|
2347
2432
|
sql: constraintExistsCheck({
|
|
2348
2433
|
constraintName,
|
|
2349
|
-
schema: schemaName
|
|
2434
|
+
schema: schemaName,
|
|
2435
|
+
table: tableName
|
|
2350
2436
|
})
|
|
2351
2437
|
}],
|
|
2352
2438
|
execute: [{
|
|
@@ -2359,6 +2445,7 @@ DROP CONSTRAINT ${quoteIdentifier(constraintName)}`
|
|
|
2359
2445
|
sql: constraintExistsCheck({
|
|
2360
2446
|
constraintName,
|
|
2361
2447
|
schema: schemaName,
|
|
2448
|
+
table: tableName,
|
|
2362
2449
|
exists: false
|
|
2363
2450
|
})
|
|
2364
2451
|
}]
|
|
@@ -2472,12 +2559,84 @@ TYPE ${expectedType}
|
|
|
2472
2559
|
USING ${quoteIdentifier(columnName)}::${expectedType}`
|
|
2473
2560
|
}],
|
|
2474
2561
|
postcheck: [{
|
|
2475
|
-
description: `verify column "${columnName}"
|
|
2562
|
+
description: `verify column "${columnName}" has type ${expectedType}`,
|
|
2563
|
+
sql: columnTypeCheck({
|
|
2564
|
+
schema: schemaName,
|
|
2565
|
+
table: tableName,
|
|
2566
|
+
column: columnName,
|
|
2567
|
+
expectedType: buildExpectedFormatType(column, codecHooks)
|
|
2568
|
+
})
|
|
2569
|
+
}]
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function buildDefaultOperation(schemaName, tableName, columnName, column, columnDefault, operationClass, verb) {
|
|
2573
|
+
const qualified = qualifyTableName(schemaName, tableName);
|
|
2574
|
+
const defaultClause = buildColumnDefaultSql(columnDefault, column);
|
|
2575
|
+
if (!defaultClause) return null;
|
|
2576
|
+
const verbLower = verb.toLowerCase();
|
|
2577
|
+
return {
|
|
2578
|
+
id: `setDefault.${tableName}.${columnName}`,
|
|
2579
|
+
label: `${verb} default for ${columnName} on ${tableName}`,
|
|
2580
|
+
summary: `${verb}s default on column ${columnName} of table ${tableName}`,
|
|
2581
|
+
operationClass,
|
|
2582
|
+
target: {
|
|
2583
|
+
id: "postgres",
|
|
2584
|
+
details: buildTargetDetails("column", columnName, schemaName, tableName)
|
|
2585
|
+
},
|
|
2586
|
+
precheck: [{
|
|
2587
|
+
description: `ensure column "${columnName}" exists`,
|
|
2588
|
+
sql: columnExistsCheck({
|
|
2589
|
+
schema: schemaName,
|
|
2590
|
+
table: tableName,
|
|
2591
|
+
column: columnName
|
|
2592
|
+
})
|
|
2593
|
+
}],
|
|
2594
|
+
execute: [{
|
|
2595
|
+
description: `${verbLower} default on "${columnName}"`,
|
|
2596
|
+
sql: `ALTER TABLE ${qualified}\nALTER COLUMN ${quoteIdentifier(columnName)} SET ${defaultClause}`
|
|
2597
|
+
}],
|
|
2598
|
+
postcheck: [{
|
|
2599
|
+
description: `verify column "${columnName}" has a default`,
|
|
2600
|
+
sql: columnDefaultExistsCheck({
|
|
2601
|
+
schema: schemaName,
|
|
2602
|
+
table: tableName,
|
|
2603
|
+
column: columnName,
|
|
2604
|
+
exists: true
|
|
2605
|
+
})
|
|
2606
|
+
}]
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
function buildDropDefaultOperation(schemaName, tableName, columnName) {
|
|
2610
|
+
const qualified = qualifyTableName(schemaName, tableName);
|
|
2611
|
+
return {
|
|
2612
|
+
id: `dropDefault.${tableName}.${columnName}`,
|
|
2613
|
+
label: `Drop default for ${columnName} on ${tableName}`,
|
|
2614
|
+
summary: `Drops default on column ${columnName} of table ${tableName}`,
|
|
2615
|
+
operationClass: "destructive",
|
|
2616
|
+
target: {
|
|
2617
|
+
id: "postgres",
|
|
2618
|
+
details: buildTargetDetails("column", columnName, schemaName, tableName)
|
|
2619
|
+
},
|
|
2620
|
+
precheck: [{
|
|
2621
|
+
description: `ensure column "${columnName}" exists`,
|
|
2476
2622
|
sql: columnExistsCheck({
|
|
2477
2623
|
schema: schemaName,
|
|
2478
2624
|
table: tableName,
|
|
2479
2625
|
column: columnName
|
|
2480
2626
|
})
|
|
2627
|
+
}],
|
|
2628
|
+
execute: [{
|
|
2629
|
+
description: `drop default on "${columnName}"`,
|
|
2630
|
+
sql: `ALTER TABLE ${qualified}\nALTER COLUMN ${quoteIdentifier(columnName)} DROP DEFAULT`
|
|
2631
|
+
}],
|
|
2632
|
+
postcheck: [{
|
|
2633
|
+
description: `verify column "${columnName}" has no default`,
|
|
2634
|
+
sql: columnDefaultExistsCheck({
|
|
2635
|
+
schema: schemaName,
|
|
2636
|
+
table: tableName,
|
|
2637
|
+
column: columnName,
|
|
2638
|
+
exists: false
|
|
2639
|
+
})
|
|
2481
2640
|
}]
|
|
2482
2641
|
};
|
|
2483
2642
|
}
|
|
@@ -2487,6 +2646,7 @@ function convertIssueToConflict(issue) {
|
|
|
2487
2646
|
case "nullability_mismatch": return buildConflict("nullabilityConflict", issue);
|
|
2488
2647
|
case "default_missing":
|
|
2489
2648
|
case "default_mismatch":
|
|
2649
|
+
case "extra_default":
|
|
2490
2650
|
case "extra_table":
|
|
2491
2651
|
case "extra_column":
|
|
2492
2652
|
case "extra_primary_key":
|
|
@@ -2832,6 +2992,7 @@ PRIMARY KEY (${table.primaryKey.columns.map(quoteIdentifier).join(", ")})`
|
|
|
2832
2992
|
sql: constraintExistsCheck({
|
|
2833
2993
|
constraintName,
|
|
2834
2994
|
schema: schemaName,
|
|
2995
|
+
table: tableName,
|
|
2835
2996
|
exists: false
|
|
2836
2997
|
})
|
|
2837
2998
|
}],
|
|
@@ -2845,7 +3006,8 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(", ")})`
|
|
|
2845
3006
|
description: `verify unique constraint "${constraintName}" exists`,
|
|
2846
3007
|
sql: constraintExistsCheck({
|
|
2847
3008
|
constraintName,
|
|
2848
|
-
schema: schemaName
|
|
3009
|
+
schema: schemaName,
|
|
3010
|
+
table: tableName
|
|
2849
3011
|
})
|
|
2850
3012
|
}]
|
|
2851
3013
|
});
|
|
@@ -2948,6 +3110,7 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(", ")})`
|
|
|
2948
3110
|
sql: constraintExistsCheck({
|
|
2949
3111
|
constraintName: fkName,
|
|
2950
3112
|
schema: schemaName,
|
|
3113
|
+
table: tableName,
|
|
2951
3114
|
exists: false
|
|
2952
3115
|
})
|
|
2953
3116
|
}],
|
|
@@ -2959,7 +3122,8 @@ UNIQUE (${unique.columns.map(quoteIdentifier).join(", ")})`
|
|
|
2959
3122
|
description: `verify foreign key "${fkName}" exists`,
|
|
2960
3123
|
sql: constraintExistsCheck({
|
|
2961
3124
|
constraintName: fkName,
|
|
2962
|
-
schema: schemaName
|
|
3125
|
+
schema: schemaName,
|
|
3126
|
+
table: tableName
|
|
2963
3127
|
})
|
|
2964
3128
|
}]
|
|
2965
3129
|
});
|