@prisma-next/target-postgres 0.11.0-dev.9 → 0.12.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-types-CRlHq7Cz.d.mts.map +1 -1
- package/dist/codecs-Dud5KDNk.d.mts.map +1 -1
- package/dist/codecs.d.mts.map +1 -1
- package/dist/codecs.mjs.map +1 -1
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +16 -39
- package/dist/control.mjs.map +1 -1
- package/dist/data-transform-CdtGUWp2.mjs.map +1 -1
- package/dist/data-transform-bmOKkygi.d.mts.map +1 -1
- package/dist/{default-normalizer-DHCsbfjc.mjs → default-normalizer-CRscvhS5.mjs} +1 -1
- package/dist/{default-normalizer-DHCsbfjc.mjs.map → default-normalizer-CRscvhS5.mjs.map} +1 -1
- package/dist/default-normalizer.d.mts.map +1 -1
- package/dist/default-normalizer.mjs +1 -1
- package/dist/descriptor-meta-DLA2xV6B.mjs.map +1 -1
- package/dist/enum-planning-Dz0Ye3Lb.mjs +0 -0
- package/dist/enum-planning-Dz0Ye3Lb.mjs.map +1 -0
- package/dist/enum-planning.d.mts +41 -3
- package/dist/enum-planning.d.mts.map +1 -1
- package/dist/enum-planning.mjs +2 -2
- package/dist/errors--zafB5_n.mjs.map +1 -1
- package/dist/errors.d.mts.map +1 -1
- package/dist/{issue-planner-Ct9xNqbr.mjs → issue-planner-ByQhUzS4.mjs} +111 -54
- package/dist/issue-planner-ByQhUzS4.mjs.map +1 -0
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +1 -1
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +1 -1
- package/dist/migration.mjs.map +1 -1
- package/dist/{native-type-normalizer-DMikJJ1V.mjs → native-type-normalizer-ClNPq__-.mjs} +1 -1
- package/dist/{native-type-normalizer-DMikJJ1V.mjs.map → native-type-normalizer-ClNPq__-.mjs.map} +1 -1
- package/dist/native-type-normalizer.d.mts.map +1 -1
- package/dist/native-type-normalizer.mjs +1 -1
- package/dist/{op-factory-call-CB6tPJ3f.mjs → op-factory-call-B0WNg30h.mjs} +2 -2
- package/dist/{op-factory-call-CB6tPJ3f.mjs.map → op-factory-call-B0WNg30h.mjs.map} +1 -1
- package/dist/op-factory-call-Drccm_JD.d.mts.map +1 -1
- package/dist/op-factory-call.mjs +1 -1
- package/dist/pack.d.mts.map +1 -1
- package/dist/{planner-DlhK35aV.mjs → planner-ClF0y0YR.mjs} +17 -25
- package/dist/planner-ClF0y0YR.mjs.map +1 -0
- package/dist/{planner-ddl-builders--0TmW6Ey.mjs → planner-ddl-builders-BxRCSn_b.mjs} +3 -3
- package/dist/{planner-ddl-builders--0TmW6Ey.mjs.map → planner-ddl-builders-BxRCSn_b.mjs.map} +1 -1
- package/dist/planner-ddl-builders.d.mts.map +1 -1
- package/dist/planner-ddl-builders.mjs +1 -1
- package/dist/planner-identity-values-ojX-6cPV.mjs.map +1 -1
- package/dist/planner-identity-values.d.mts.map +1 -1
- package/dist/{planner-produced-postgres-migration-TJWH2m_x.mjs → planner-produced-postgres-migration-N1yqYg20.mjs} +3 -5
- package/dist/planner-produced-postgres-migration-N1yqYg20.mjs.map +1 -0
- package/dist/planner-produced-postgres-migration-p-VKkCia.d.mts.map +1 -1
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/planner-schema-lookup-BGyukuzG.mjs.map +1 -1
- package/dist/planner-schema-lookup.d.mts.map +1 -1
- package/dist/{planner-sql-checks-BDtJQ9WD.mjs → planner-sql-checks-D3H-xOO1.mjs} +3 -3
- package/dist/{planner-sql-checks-BDtJQ9WD.mjs.map → planner-sql-checks-D3H-xOO1.mjs.map} +1 -1
- package/dist/planner-sql-checks.d.mts.map +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/planner-target-details-CIj61DUj.d.mts.map +1 -1
- package/dist/planner.d.mts +1 -6
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +1 -1
- package/dist/{postgres-contract-serializer-CYct4Y7O.mjs → postgres-contract-serializer-YJvjKrmo.mjs} +2 -2
- package/dist/postgres-contract-serializer-YJvjKrmo.mjs.map +1 -0
- package/dist/postgres-enum-type-CNhPTDhy.d.mts.map +1 -1
- package/dist/postgres-enum-type-DS-KLVRH.mjs.map +1 -1
- package/dist/postgres-migration-uADmx0dW.mjs.map +1 -1
- package/dist/{postgres-schema-BL0RAvew.mjs → postgres-schema-Bm7vjlOv.mjs} +12 -19
- package/dist/postgres-schema-Bm7vjlOv.mjs.map +1 -0
- package/dist/render-ops-BC2PtCkj.mjs.map +1 -1
- package/dist/render-ops.d.mts.map +1 -1
- package/dist/{render-typescript-CI1wbgUc.mjs → render-typescript-CPk7hhWH.mjs} +2 -3
- package/dist/render-typescript-CPk7hhWH.mjs.map +1 -0
- package/dist/render-typescript.d.mts +0 -1
- package/dist/render-typescript.d.mts.map +1 -1
- package/dist/render-typescript.mjs +1 -1
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/runtime.mjs.map +1 -1
- package/dist/shared-ByhSooBS.d.mts.map +1 -1
- package/dist/{sql-utils-BewXAnsG.mjs → sql-utils-B_ruBD-M.mjs} +1 -1
- package/dist/{sql-utils-BewXAnsG.mjs.map → sql-utils-B_ruBD-M.mjs.map} +1 -1
- package/dist/sql-utils.d.mts.map +1 -1
- package/dist/sql-utils.mjs +1 -1
- package/dist/statement-builders-vImtdfmM.mjs.map +1 -1
- package/dist/statement-builders.d.mts +1 -1
- package/dist/statement-builders.d.mts.map +1 -1
- package/dist/{tables-Vhh4k5yz.mjs → tables-Dcb2q9zV.mjs} +3 -3
- package/dist/{tables-Vhh4k5yz.mjs.map → tables-Dcb2q9zV.mjs.map} +1 -1
- package/dist/types-D-XIpzHA.d.mts.map +1 -1
- package/dist/types.d.mts +10 -14
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +1 -1
- package/package.json +29 -18
- package/src/core/migrations/enum-planning.ts +134 -14
- package/src/core/migrations/issue-planner.ts +20 -11
- package/src/core/migrations/planner-produced-postgres-migration.ts +0 -2
- package/src/core/migrations/planner-strategies.ts +138 -40
- package/src/core/migrations/planner.ts +9 -21
- package/src/core/migrations/render-typescript.ts +1 -5
- package/src/core/migrations/runner.ts +20 -61
- package/src/core/migrations/statement-builders.ts +1 -1
- package/src/core/migrations/verify-postgres-namespaces.ts +6 -6
- package/src/core/postgres-contract-serializer.ts +4 -4
- package/src/core/postgres-schema.ts +10 -20
- package/src/exports/control.ts +16 -0
- package/src/exports/enum-planning.ts +5 -0
- package/dist/enum-planning-Bqp96iIw.mjs +0 -63
- package/dist/enum-planning-Bqp96iIw.mjs.map +0 -1
- package/dist/issue-planner-Ct9xNqbr.mjs.map +0 -1
- package/dist/planner-DlhK35aV.mjs.map +0 -1
- package/dist/planner-produced-postgres-migration-TJWH2m_x.mjs.map +0 -1
- package/dist/postgres-contract-serializer-CYct4Y7O.mjs.map +0 -1
- package/dist/postgres-schema-BL0RAvew.mjs.map +0 -1
- package/dist/render-typescript-CI1wbgUc.mjs.map +0 -1
|
@@ -37,7 +37,11 @@ import {
|
|
|
37
37
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
38
38
|
import { PostgresEnumType } from '../postgres-enum-type';
|
|
39
39
|
import { isPostgresSchema } from '../postgres-schema';
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
determineEnumDiff,
|
|
42
|
+
readExistingEnumValues,
|
|
43
|
+
resolveDdlSchemaForNamespaceStorage,
|
|
44
|
+
} from './enum-planning';
|
|
41
45
|
import {
|
|
42
46
|
AddColumnCall,
|
|
43
47
|
AddEnumValuesCall,
|
|
@@ -108,12 +112,12 @@ export function resolveNamespaceIdForIssue(issue: { readonly namespaceId?: strin
|
|
|
108
112
|
/**
|
|
109
113
|
* Resolve the DDL schema name for a namespace coordinate. Postgres-aware
|
|
110
114
|
* namespaces dispatch to their polymorphic `ddlSchemaName` override —
|
|
111
|
-
* named schemas return their own id
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
115
|
+
* named schemas return their own id; the unbound singleton returns
|
|
116
|
+
* `UNBOUND_NAMESPACE_ID`. Legacy single-namespace contracts whose
|
|
117
|
+
* `__unbound__` slot is the framework-default `SqlUnboundNamespace`
|
|
118
|
+
* (rather than the Postgres-aware `PostgresUnboundSchema`) flow the
|
|
119
|
+
* coordinate through unchanged so downstream `qualifyTableName`
|
|
120
|
+
* resolves polymorphically.
|
|
117
121
|
*/
|
|
118
122
|
export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId: string): string {
|
|
119
123
|
const namespace = ctx.toContract.storage.namespaces[namespaceId];
|
|
@@ -123,21 +127,57 @@ export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId:
|
|
|
123
127
|
return namespaceId;
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
/** Default Postgres enum landing namespace — where contract-level (`types:`)
|
|
131
|
+
* enums are placed by the authoring builder when no explicit namespace is
|
|
132
|
+
* given. Mirrors `POSTGRES_ENUM_NAMESPACE_ID` in the contract-ts builder. */
|
|
133
|
+
const DEFAULT_ENUM_NAMESPACE_ID = 'public';
|
|
134
|
+
|
|
135
|
+
function namespaceHasEnum(storage: SqlStorage, namespaceId: string, typeName: string): boolean {
|
|
136
|
+
const ns = storage.namespaces[namespaceId];
|
|
137
|
+
if (!ns || !('enum' in ns) || ns.enum == null) return false;
|
|
138
|
+
return (ns.enum as Record<string, PostgresEnumStorageEntry>)[typeName] !== undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
126
141
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* `
|
|
142
|
+
* Resolves which namespace's enum a column's bare `typeRef` binds to.
|
|
143
|
+
*
|
|
144
|
+
* Columns carry a bare (non-namespace-qualified) `typeRef`; the enum it names
|
|
145
|
+
* may live in a different namespace than the column's own (the authoring
|
|
146
|
+
* builder places contract-level `types:` enums in the default `public`
|
|
147
|
+
* namespace while a model's table may sit in the unbound namespace). The
|
|
148
|
+
* binding rule: an enum declared in the column's *own* namespace shadows
|
|
149
|
+
* everything; otherwise the column references the ambient enum — the sole
|
|
150
|
+
* namespace that defines `typeName`, preferring the default `public`
|
|
151
|
+
* namespace when several do. Returns `undefined` when no namespace defines it.
|
|
152
|
+
*/
|
|
153
|
+
function resolveColumnEnumNamespace(
|
|
154
|
+
storage: SqlStorage,
|
|
155
|
+
columnNamespaceId: string,
|
|
156
|
+
typeName: string,
|
|
157
|
+
): string | undefined {
|
|
158
|
+
if (namespaceHasEnum(storage, columnNamespaceId, typeName)) return columnNamespaceId;
|
|
159
|
+
const owners = Object.keys(storage.namespaces).filter((nsId) =>
|
|
160
|
+
namespaceHasEnum(storage, nsId, typeName),
|
|
161
|
+
);
|
|
162
|
+
if (owners.length === 1) return owners[0];
|
|
163
|
+
if (owners.includes(DEFAULT_ENUM_NAMESPACE_ID)) return DEFAULT_ENUM_NAMESPACE_ID;
|
|
164
|
+
return owners[0];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Finds a type entry by explicit namespace coordinate. Namespace types (e.g.
|
|
169
|
+
* Postgres enums) live under `storage.namespaces[nsId].enum`. Returns the
|
|
170
|
+
* entry from the named namespace only — never scans other namespaces, so two
|
|
171
|
+
* namespaces that hold an enum with the same name resolve independently.
|
|
130
172
|
*/
|
|
131
173
|
function locateNamespaceType(
|
|
132
174
|
storage: SqlStorage,
|
|
175
|
+
namespaceId: string,
|
|
133
176
|
typeName: string,
|
|
134
177
|
): PostgresEnumStorageEntry | undefined {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (entry !== undefined) return entry;
|
|
139
|
-
}
|
|
140
|
-
return undefined;
|
|
178
|
+
const ns = storage.namespaces[namespaceId];
|
|
179
|
+
if (!ns || !('enum' in ns) || ns.enum == null) return undefined;
|
|
180
|
+
return (ns.enum as Record<string, PostgresEnumStorageEntry>)[typeName];
|
|
141
181
|
}
|
|
142
182
|
|
|
143
183
|
// ============================================================================
|
|
@@ -369,10 +409,11 @@ export const nullableTighteningCallStrategy: CallMigrationStrategy = (issues, ct
|
|
|
369
409
|
};
|
|
370
410
|
|
|
371
411
|
function enumRebuildCallRecipe(
|
|
412
|
+
namespaceId: string,
|
|
372
413
|
typeName: string,
|
|
373
414
|
ctx: StrategyContext,
|
|
374
415
|
): readonly PostgresOpFactoryCall[] {
|
|
375
|
-
const toType = locateNamespaceType(ctx.toContract.storage, typeName);
|
|
416
|
+
const toType = locateNamespaceType(ctx.toContract.storage, namespaceId, typeName);
|
|
376
417
|
if (!toType) return [];
|
|
377
418
|
const isEnum = isPostgresEnumStorageEntry(toType);
|
|
378
419
|
const nativeType = toType.nativeType;
|
|
@@ -380,13 +421,28 @@ function enumRebuildCallRecipe(
|
|
|
380
421
|
? toType.values
|
|
381
422
|
: (((toType as StorageTypeInstance).typeParams['values'] ?? []) as readonly string[]);
|
|
382
423
|
const tempName = `${nativeType}${REBUILD_SUFFIX}`;
|
|
424
|
+
// Type DDL targets the enum's real schema — the unbound coordinate resolves
|
|
425
|
+
// to the introspected `current_schema()`, never the `__unbound__` sentinel.
|
|
426
|
+
const ddlSchema = resolveDdlSchemaForNamespaceStorage(
|
|
427
|
+
ctx.toContract.storage,
|
|
428
|
+
namespaceId,
|
|
429
|
+
ctx.schema,
|
|
430
|
+
);
|
|
383
431
|
|
|
432
|
+
// Migrate every column whose `typeRef` binds to *this* enum. The column's
|
|
433
|
+
// bare `typeRef` resolves to an enum namespace (own-namespace shadows;
|
|
434
|
+
// otherwise the ambient/default `public` enum), so a column in the unbound
|
|
435
|
+
// namespace correctly binds to a `public`-namespace enum, while two
|
|
436
|
+
// same-named enums in distinct namespaces keep their columns disjoint.
|
|
384
437
|
const columnRefs: { namespaceId: string; table: string; column: string }[] = [];
|
|
385
438
|
for (const [nsId, ns] of Object.entries(ctx.toContract.storage.namespaces)) {
|
|
386
439
|
for (const [tableName, tableNode] of Object.entries(ns.tables)) {
|
|
387
440
|
const table = tableNode as StorageTable;
|
|
388
441
|
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
389
|
-
if (
|
|
442
|
+
if (
|
|
443
|
+
column.typeRef === typeName &&
|
|
444
|
+
resolveColumnEnumNamespace(ctx.toContract.storage, nsId, typeName) === namespaceId
|
|
445
|
+
) {
|
|
390
446
|
columnRefs.push({ namespaceId: nsId, table: tableName, column: columnName });
|
|
391
447
|
}
|
|
392
448
|
}
|
|
@@ -394,7 +450,7 @@ function enumRebuildCallRecipe(
|
|
|
394
450
|
}
|
|
395
451
|
|
|
396
452
|
return [
|
|
397
|
-
new CreateEnumTypeCall(
|
|
453
|
+
new CreateEnumTypeCall(ddlSchema, tempName, desiredValues),
|
|
398
454
|
...columnRefs.map((ref) => {
|
|
399
455
|
const using = `${ref.column}::text::${tempName}`;
|
|
400
456
|
return new AlterColumnTypeCall(
|
|
@@ -409,8 +465,8 @@ function enumRebuildCallRecipe(
|
|
|
409
465
|
},
|
|
410
466
|
);
|
|
411
467
|
}),
|
|
412
|
-
new DropEnumTypeCall(
|
|
413
|
-
new RenameTypeCall(
|
|
468
|
+
new DropEnumTypeCall(ddlSchema, nativeType),
|
|
469
|
+
new RenameTypeCall(ddlSchema, tempName, nativeType),
|
|
414
470
|
];
|
|
415
471
|
}
|
|
416
472
|
|
|
@@ -450,6 +506,18 @@ function enumRebuildCallRecipe(
|
|
|
450
506
|
* into the `dep` bucket — i.e. `CREATE TYPE` runs before any
|
|
451
507
|
* `CreateTableCall` that references the new enum.
|
|
452
508
|
*/
|
|
509
|
+
/**
|
|
510
|
+
* Separator character for compound enum map keys (`namespaceId\u0000typeName`).
|
|
511
|
+
* NUL (`\u0000`) is invalid in both Postgres identifiers and TypeScript symbol
|
|
512
|
+
* names so it cannot appear in either component — unambiguous separator.
|
|
513
|
+
*/
|
|
514
|
+
const COMPOUND_KEY_SEP = '\u0000';
|
|
515
|
+
|
|
516
|
+
/** Builds the compound map key for a namespace-qualified enum entry. */
|
|
517
|
+
function enumCompoundKey(namespaceId: string, typeName: string): string {
|
|
518
|
+
return `${namespaceId}${COMPOUND_KEY_SEP}${typeName}`;
|
|
519
|
+
}
|
|
520
|
+
|
|
453
521
|
export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
454
522
|
const enumTypes = collectPostgresEnumTypes(ctx.toContract.storage);
|
|
455
523
|
if (enumTypes.size === 0) return { kind: 'no_match' };
|
|
@@ -457,28 +525,42 @@ export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) =
|
|
|
457
525
|
const dataAllowed = ctx.policy.allowedOperationClasses.includes('data');
|
|
458
526
|
|
|
459
527
|
const calls: PostgresOpFactoryCall[] = [];
|
|
460
|
-
const
|
|
461
|
-
const
|
|
462
|
-
const
|
|
528
|
+
const handledKeys = new Set<string>();
|
|
529
|
+
const introducedKeys = new Set<string>();
|
|
530
|
+
const rebuiltKeys = new Set<string>();
|
|
463
531
|
let emittedRebuildRecipe = false;
|
|
464
532
|
|
|
465
|
-
for (const [
|
|
533
|
+
for (const [key, enumType] of enumTypes) {
|
|
534
|
+
const sepIdx = key.indexOf(COMPOUND_KEY_SEP);
|
|
535
|
+
const enumNamespaceId = key.slice(0, sepIdx);
|
|
536
|
+
const typeName = key.slice(sepIdx + 1);
|
|
537
|
+
|
|
466
538
|
const desired = enumType.values;
|
|
467
|
-
|
|
539
|
+
// The enum's live schema: for the unbound coordinate this resolves to the
|
|
540
|
+
// introspected `current_schema()` (e.g. `public`), never the `__unbound__`
|
|
541
|
+
// DDL-emit sentinel — so both the existing-values lookup key and the
|
|
542
|
+
// emitted `CREATE TYPE` / `ALTER TYPE` target the real schema the type
|
|
543
|
+
// lives in. Named namespaces resolve to their own DDL schema.
|
|
544
|
+
const ddlSchema = resolveDdlSchemaForNamespaceStorage(
|
|
545
|
+
ctx.toContract.storage,
|
|
546
|
+
enumNamespaceId,
|
|
547
|
+
ctx.schema,
|
|
548
|
+
);
|
|
549
|
+
const existing = readExistingEnumValues(ctx.schema, ddlSchema, enumType.nativeType);
|
|
468
550
|
if (!existing) {
|
|
469
|
-
calls.push(new CreateEnumTypeCall(
|
|
470
|
-
|
|
471
|
-
|
|
551
|
+
calls.push(new CreateEnumTypeCall(ddlSchema, typeName, desired, enumType.nativeType));
|
|
552
|
+
handledKeys.add(key);
|
|
553
|
+
introducedKeys.add(key);
|
|
472
554
|
continue;
|
|
473
555
|
}
|
|
474
556
|
const diff = determineEnumDiff(existing, desired);
|
|
475
557
|
if (diff.kind === 'unchanged') {
|
|
476
|
-
|
|
558
|
+
handledKeys.add(key);
|
|
477
559
|
continue;
|
|
478
560
|
}
|
|
479
561
|
if (diff.kind === 'add_values') {
|
|
480
|
-
calls.push(new AddEnumValuesCall(
|
|
481
|
-
|
|
562
|
+
calls.push(new AddEnumValuesCall(ddlSchema, typeName, enumType.nativeType, diff.values));
|
|
563
|
+
handledKeys.add(key);
|
|
482
564
|
continue;
|
|
483
565
|
}
|
|
484
566
|
if (dataAllowed && diff.removedValues.length > 0) {
|
|
@@ -490,10 +572,10 @@ export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) =
|
|
|
490
572
|
),
|
|
491
573
|
);
|
|
492
574
|
}
|
|
493
|
-
calls.push(...enumRebuildCallRecipe(typeName, ctx));
|
|
575
|
+
calls.push(...enumRebuildCallRecipe(enumNamespaceId, typeName, ctx));
|
|
494
576
|
emittedRebuildRecipe = true;
|
|
495
|
-
|
|
496
|
-
|
|
577
|
+
handledKeys.add(key);
|
|
578
|
+
rebuiltKeys.add(key);
|
|
497
579
|
}
|
|
498
580
|
|
|
499
581
|
// The strategy emits a single `recipe` flag for the entire pass,
|
|
@@ -505,9 +587,17 @@ export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) =
|
|
|
505
587
|
// enum would fail at runtime with a confusing `type "X" does not
|
|
506
588
|
// exist` error. Surface the unrepresentable case here as a
|
|
507
589
|
// planner-time error so the failure mode is loud, not silent.
|
|
508
|
-
if (
|
|
590
|
+
if (introducedKeys.size > 0 && rebuiltKeys.size > 0) {
|
|
591
|
+
const introducedDisplay = [...introducedKeys]
|
|
592
|
+
.sort()
|
|
593
|
+
.map((k) => k.replace(COMPOUND_KEY_SEP, '.'))
|
|
594
|
+
.join(', ');
|
|
595
|
+
const rebuiltDisplay = [...rebuiltKeys]
|
|
596
|
+
.sort()
|
|
597
|
+
.map((k) => k.replace(COMPOUND_KEY_SEP, '.'))
|
|
598
|
+
.join(', ');
|
|
509
599
|
throw new Error(
|
|
510
|
-
`nativeEnumPlanCallStrategy: cannot emit both a brand-new enum and a rebuild on a different enum in the same plan; the single recipe flag cannot route them to different buckets. Introduced: [${
|
|
600
|
+
`nativeEnumPlanCallStrategy: cannot emit both a brand-new enum and a rebuild on a different enum in the same plan; the single recipe flag cannot route them to different buckets. Introduced: [${introducedDisplay}]; rebuilt: [${rebuiltDisplay}]. Split the strategy or grow the \`match\` return type before this case lands.`,
|
|
511
601
|
);
|
|
512
602
|
}
|
|
513
603
|
|
|
@@ -516,7 +606,7 @@ export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) =
|
|
|
516
606
|
!(
|
|
517
607
|
(issue.kind === 'type_missing' || issue.kind === 'enum_values_changed') &&
|
|
518
608
|
issue.typeName &&
|
|
519
|
-
|
|
609
|
+
handledKeys.has(enumCompoundKey(resolveNamespaceIdForIssue(issue), issue.typeName))
|
|
520
610
|
),
|
|
521
611
|
);
|
|
522
612
|
|
|
@@ -538,14 +628,22 @@ export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) =
|
|
|
538
628
|
return { kind: 'match', issues: remaining, calls, recipe: emittedRebuildRecipe };
|
|
539
629
|
};
|
|
540
630
|
|
|
631
|
+
/**
|
|
632
|
+
* Collects every `PostgresEnumType` instance across all declared namespaces,
|
|
633
|
+
* returning a compound-keyed map (`${namespaceId}\u0000${typeName}`). Two
|
|
634
|
+
* namespaces that declare an enum with the same name produce two distinct
|
|
635
|
+
* entries — no name collision, no last-write-wins.
|
|
636
|
+
*
|
|
637
|
+
* Entries within each namespace are sorted by name for deterministic ordering.
|
|
638
|
+
*/
|
|
541
639
|
function collectPostgresEnumTypes(storage: SqlStorage): ReadonlyMap<string, PostgresEnumType> {
|
|
542
640
|
const result = new Map<string, PostgresEnumType>();
|
|
543
|
-
for (const ns of Object.
|
|
641
|
+
for (const [nsId, ns] of Object.entries(storage.namespaces)) {
|
|
544
642
|
if (!('enum' in ns) || ns.enum == null) continue;
|
|
545
643
|
const nsEnums = ns.enum as Record<string, unknown>;
|
|
546
644
|
for (const [name, instance] of Object.entries(nsEnums).sort(([a], [b]) => a.localeCompare(b))) {
|
|
547
645
|
if (instance instanceof PostgresEnumType) {
|
|
548
|
-
result.set(name, instance);
|
|
646
|
+
result.set(enumCompoundKey(nsId, name), instance);
|
|
549
647
|
}
|
|
550
648
|
}
|
|
551
649
|
}
|
|
@@ -17,9 +17,10 @@ import type {
|
|
|
17
17
|
MigrationScaffoldContext,
|
|
18
18
|
SchemaIssue,
|
|
19
19
|
} from '@prisma-next/framework-components/control';
|
|
20
|
+
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
20
21
|
import { parsePostgresDefault } from '../default-normalizer';
|
|
21
22
|
import { normalizeSchemaNativeType } from '../native-type-normalizer';
|
|
22
|
-
import {
|
|
23
|
+
import { createResolveExistingEnumValues } from './enum-planning';
|
|
23
24
|
import { planIssues } from './issue-planner';
|
|
24
25
|
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
25
26
|
import { postgresPlannerStrategies } from './planner-strategies';
|
|
@@ -39,21 +40,8 @@ type VerifySqlSchemaOptionsWithComponents = Parameters<typeof verifySqlSchema>[0
|
|
|
39
40
|
readonly frameworkComponents: PlannerFrameworkComponents;
|
|
40
41
|
};
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const DEFAULT_PLANNER_CONFIG: PlannerConfig = {
|
|
47
|
-
defaultSchema: 'public',
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export function createPostgresMigrationPlanner(
|
|
51
|
-
config: Partial<PlannerConfig> = {},
|
|
52
|
-
): PostgresMigrationPlanner {
|
|
53
|
-
return new PostgresMigrationPlanner({
|
|
54
|
-
...DEFAULT_PLANNER_CONFIG,
|
|
55
|
-
...config,
|
|
56
|
-
});
|
|
43
|
+
export function createPostgresMigrationPlanner(): PostgresMigrationPlanner {
|
|
44
|
+
return new PostgresMigrationPlanner();
|
|
57
45
|
}
|
|
58
46
|
|
|
59
47
|
/**
|
|
@@ -84,8 +72,6 @@ export type PostgresPlanResult =
|
|
|
84
72
|
* authoring surface.
|
|
85
73
|
*/
|
|
86
74
|
export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgres'> {
|
|
87
|
-
constructor(private readonly config: PlannerConfig) {}
|
|
88
|
-
|
|
89
75
|
plan(options: {
|
|
90
76
|
readonly contract: unknown;
|
|
91
77
|
readonly schema: unknown;
|
|
@@ -132,7 +118,10 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
132
118
|
}
|
|
133
119
|
|
|
134
120
|
private planSql(options: SqlMigrationPlannerPlanOptions): PostgresPlanResult {
|
|
135
|
-
const schemaName =
|
|
121
|
+
const schemaName =
|
|
122
|
+
options.schemaName ??
|
|
123
|
+
Object.keys(options.contract.storage.namespaces).find((id) => id !== UNBOUND_NAMESPACE_ID) ??
|
|
124
|
+
UNBOUND_NAMESPACE_ID;
|
|
136
125
|
const policyResult = this.ensureAdditivePolicy(options.policy);
|
|
137
126
|
if (policyResult) {
|
|
138
127
|
return policyResult;
|
|
@@ -219,8 +208,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
219
208
|
frameworkComponents: options.frameworkComponents,
|
|
220
209
|
normalizeDefault: parsePostgresDefault,
|
|
221
210
|
normalizeNativeType: normalizeSchemaNativeType,
|
|
222
|
-
resolveExistingEnumValues: (
|
|
223
|
-
readExistingEnumValues(schema, enumType.nativeType),
|
|
211
|
+
resolveExistingEnumValues: createResolveExistingEnumValues(options.contract.storage),
|
|
224
212
|
};
|
|
225
213
|
const verifyResult = verifySqlSchema(verifyOptions);
|
|
226
214
|
// Schema presence is a Postgres-specific concern (no equivalent in
|
|
@@ -11,12 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
import type { OpFactoryCall } from '@prisma-next/framework-components/control';
|
|
13
13
|
import { detectScaffoldRuntime, shebangLineFor } from '@prisma-next/migration-tools/migration-ts';
|
|
14
|
-
import { type ImportRequirement,
|
|
14
|
+
import { type ImportRequirement, renderImports } from '@prisma-next/ts-render';
|
|
15
15
|
|
|
16
16
|
export interface RenderMigrationMeta {
|
|
17
17
|
readonly from: string | null;
|
|
18
18
|
readonly to: string;
|
|
19
|
-
readonly labels?: readonly string[];
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
/**
|
|
@@ -83,9 +82,6 @@ function buildDescribeMethod(meta: RenderMigrationMeta): string {
|
|
|
83
82
|
lines.push(' return {');
|
|
84
83
|
lines.push(` from: ${JSON.stringify(meta.from)},`);
|
|
85
84
|
lines.push(` to: ${JSON.stringify(meta.to)},`);
|
|
86
|
-
if (meta.labels && meta.labels.length > 0) {
|
|
87
|
-
lines.push(` labels: ${jsonToTsSource(meta.labels)},`);
|
|
88
|
-
}
|
|
89
85
|
lines.push(' };');
|
|
90
86
|
lines.push(' }');
|
|
91
87
|
lines.push('');
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ContractMarkerRecord } from '@prisma-next/contract/types';
|
|
2
2
|
import type {
|
|
3
3
|
MigrationOperationPolicy,
|
|
4
|
-
MultiSpaceRunnerResult,
|
|
5
4
|
SqlControlFamilyInstance,
|
|
6
5
|
SqlMigrationPlanContractInfo,
|
|
7
6
|
SqlMigrationPlanOperation,
|
|
@@ -14,15 +13,19 @@ import type {
|
|
|
14
13
|
} from '@prisma-next/family-sql/control';
|
|
15
14
|
import { runnerFailure, runnerSuccess } from '@prisma-next/family-sql/control';
|
|
16
15
|
import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
|
|
17
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
ControlDriverInstance,
|
|
18
|
+
MigrationRunnerResult,
|
|
19
|
+
} from '@prisma-next/framework-components/control';
|
|
18
20
|
import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
|
|
21
|
+
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
19
22
|
import { SqlQueryError } from '@prisma-next/sql-errors';
|
|
20
23
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
21
24
|
import type { Result } from '@prisma-next/utils/result';
|
|
22
25
|
import { notOk, ok, okVoid } from '@prisma-next/utils/result';
|
|
23
26
|
import { parsePostgresDefault } from '../default-normalizer';
|
|
24
27
|
import { normalizeSchemaNativeType } from '../native-type-normalizer';
|
|
25
|
-
import {
|
|
28
|
+
import { createResolveExistingEnumValues } from './enum-planning';
|
|
26
29
|
import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
27
30
|
import {
|
|
28
31
|
buildLedgerInsertStatement,
|
|
@@ -33,19 +36,11 @@ import {
|
|
|
33
36
|
type SqlStatement,
|
|
34
37
|
} from './statement-builders';
|
|
35
38
|
|
|
36
|
-
interface RunnerConfig {
|
|
37
|
-
readonly defaultSchema: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
39
|
interface ApplyPlanSuccessValue {
|
|
41
40
|
readonly operationsExecuted: number;
|
|
42
41
|
readonly executedOperations: readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[];
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
const DEFAULT_CONFIG: RunnerConfig = {
|
|
46
|
-
defaultSchema: 'public',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
44
|
const LOCK_DOMAIN = 'prisma_next.contract.marker';
|
|
50
45
|
|
|
51
46
|
/**
|
|
@@ -58,13 +53,10 @@ function cloneAndFreezeRecord<T extends Record<string, unknown>>(value: T): T {
|
|
|
58
53
|
if (val === null || val === undefined) {
|
|
59
54
|
cloned[key] = val;
|
|
60
55
|
} else if (Array.isArray(val)) {
|
|
61
|
-
// Clone array (shallow clone of array elements)
|
|
62
56
|
cloned[key] = Object.freeze([...val]);
|
|
63
57
|
} else if (typeof val === 'object') {
|
|
64
|
-
// Recursively clone nested objects
|
|
65
58
|
cloned[key] = cloneAndFreezeRecord(val as Record<string, unknown>);
|
|
66
59
|
} else {
|
|
67
|
-
// Primitives are copied as-is
|
|
68
60
|
cloned[key] = val;
|
|
69
61
|
}
|
|
70
62
|
}
|
|
@@ -73,61 +65,27 @@ function cloneAndFreezeRecord<T extends Record<string, unknown>>(value: T): T {
|
|
|
73
65
|
|
|
74
66
|
export function createPostgresMigrationRunner(
|
|
75
67
|
family: SqlControlFamilyInstance,
|
|
76
|
-
config: Partial<RunnerConfig> = {},
|
|
77
68
|
): SqlMigrationRunner<PostgresPlanTargetDetails> {
|
|
78
|
-
return new PostgresMigrationRunner(family
|
|
69
|
+
return new PostgresMigrationRunner(family);
|
|
79
70
|
}
|
|
80
71
|
|
|
81
72
|
class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDetails> {
|
|
82
|
-
constructor(
|
|
83
|
-
private readonly family: SqlControlFamilyInstance,
|
|
84
|
-
private readonly config: RunnerConfig,
|
|
85
|
-
) {}
|
|
86
|
-
|
|
87
|
-
async execute(
|
|
88
|
-
options: SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>,
|
|
89
|
-
): Promise<SqlMigrationRunnerResult> {
|
|
90
|
-
const driver = options.driver;
|
|
91
|
-
|
|
92
|
-
// Static checks fail fast before any transaction work — no point
|
|
93
|
-
// burning a BEGIN/ROLLBACK round-trip on a destination-contract
|
|
94
|
-
// mismatch the caller can fix locally.
|
|
95
|
-
const destinationCheck = this.ensurePlanMatchesDestinationContract(
|
|
96
|
-
options.plan.destination,
|
|
97
|
-
options.destinationContract,
|
|
98
|
-
);
|
|
99
|
-
if (!destinationCheck.ok) return destinationCheck;
|
|
100
|
-
|
|
101
|
-
const policyCheck = this.enforcePolicyCompatibility(options.policy, options.plan.operations);
|
|
102
|
-
if (!policyCheck.ok) return policyCheck;
|
|
103
|
-
|
|
104
|
-
await this.beginTransaction(driver);
|
|
105
|
-
let committed = false;
|
|
106
|
-
try {
|
|
107
|
-
const result = await this.executeOnConnection(options);
|
|
108
|
-
if (!result.ok) {
|
|
109
|
-
return result;
|
|
110
|
-
}
|
|
111
|
-
await this.commitTransaction(driver);
|
|
112
|
-
committed = true;
|
|
113
|
-
return result;
|
|
114
|
-
} finally {
|
|
115
|
-
if (!committed) {
|
|
116
|
-
await this.rollbackTransaction(driver);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
73
|
+
constructor(private readonly family: SqlControlFamilyInstance) {}
|
|
120
74
|
|
|
121
75
|
/**
|
|
122
76
|
* Body of the migration runner without transaction management. The
|
|
123
|
-
* caller (
|
|
124
|
-
* outer-tx orchestrator at the SQL family level) owns the
|
|
77
|
+
* caller ({@link PostgresMigrationRunner.execute}) owns the
|
|
125
78
|
* `BEGIN`/`COMMIT`/`ROLLBACK` lifecycle.
|
|
126
79
|
*/
|
|
127
80
|
async executeOnConnection(
|
|
128
81
|
options: SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>,
|
|
129
82
|
): Promise<SqlMigrationRunnerResult> {
|
|
130
|
-
const schema =
|
|
83
|
+
const schema =
|
|
84
|
+
options.schemaName ??
|
|
85
|
+
Object.keys(options.destinationContract.storage.namespaces).find(
|
|
86
|
+
(id) => id !== UNBOUND_NAMESPACE_ID,
|
|
87
|
+
) ??
|
|
88
|
+
UNBOUND_NAMESPACE_ID;
|
|
131
89
|
const driver = options.driver;
|
|
132
90
|
if (options.space !== undefined && options.space !== options.plan.spaceId) {
|
|
133
91
|
throw new Error(
|
|
@@ -188,8 +146,9 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
188
146
|
frameworkComponents: options.frameworkComponents,
|
|
189
147
|
normalizeDefault: parsePostgresDefault,
|
|
190
148
|
normalizeNativeType: normalizeSchemaNativeType,
|
|
191
|
-
resolveExistingEnumValues: (
|
|
192
|
-
|
|
149
|
+
resolveExistingEnumValues: createResolveExistingEnumValues(
|
|
150
|
+
options.destinationContract.storage,
|
|
151
|
+
),
|
|
193
152
|
});
|
|
194
153
|
if (!schemaVerifyResult.ok) {
|
|
195
154
|
return runnerFailure('SCHEMA_VERIFY_FAILED', schemaVerifyResult.summary, {
|
|
@@ -216,12 +175,12 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
216
175
|
});
|
|
217
176
|
}
|
|
218
177
|
|
|
219
|
-
async
|
|
178
|
+
async execute(options: {
|
|
220
179
|
readonly driver: ControlDriverInstance<'sql', string>;
|
|
221
180
|
readonly perSpaceOptions: ReadonlyArray<
|
|
222
181
|
SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>
|
|
223
182
|
>;
|
|
224
|
-
}): Promise<
|
|
183
|
+
}): Promise<MigrationRunnerResult> {
|
|
225
184
|
const driver = options.driver;
|
|
226
185
|
const perSpaceOptions = options.perSpaceOptions;
|
|
227
186
|
|
|
@@ -56,7 +56,7 @@ export interface MergeMarkerInput {
|
|
|
56
56
|
* Logical space identifier for this marker row. Required at every
|
|
57
57
|
* call site so the type system surfaces every place that needs to
|
|
58
58
|
* thread the value (rather than letting an `?? APP_SPACE_ID`
|
|
59
|
-
* fall-through silently collapse
|
|
59
|
+
* fall-through silently collapse per-space markers onto the
|
|
60
60
|
* `'app'` row). App-plan callers pass {@link APP_SPACE_ID}
|
|
61
61
|
* (`'app'`); per-extension callers (planner / runner / verifier
|
|
62
62
|
* extensions over contract spaces) pass the extension's space id.
|
|
@@ -52,12 +52,12 @@ function existingSchemasFromSchema(schema: SqlSchemaIR): readonly string[] {
|
|
|
52
52
|
*
|
|
53
53
|
* A namespace's live container is the schema returned by its
|
|
54
54
|
* polymorphic `ddlSchemaName(storage)` method — named schemas resolve
|
|
55
|
-
* to their own id
|
|
56
|
-
*
|
|
57
|
-
* emitted only when the resolved name is a real,
|
|
58
|
-
* (not the unbound sentinel) and is missing from the
|
|
59
|
-
* list. `public` is suppressed implicitly because the
|
|
60
|
-
* (or its sensible default) always carries it.
|
|
55
|
+
* to their own id; the unbound singleton returns `UNBOUND_NAMESPACE_ID`
|
|
56
|
+
* and is skipped explicitly (late-bound namespaces have no fixed DDL
|
|
57
|
+
* schema). Issues are emitted only when the resolved name is a real,
|
|
58
|
+
* creatable schema (not the unbound sentinel) and is missing from the
|
|
59
|
+
* introspected list. `public` is suppressed implicitly because the
|
|
60
|
+
* introspection (or its sensible default) always carries it.
|
|
61
61
|
*
|
|
62
62
|
* Each emitted issue stamps `namespaceId` with the contract namespace
|
|
63
63
|
* coordinate so the downstream `mapIssueToCall` re-resolves the DDL
|
|
@@ -117,10 +117,10 @@ export class PostgresContractSerializer extends SqlContractSerializerBase<Contra
|
|
|
117
117
|
if (isPostgresSchema(ns)) {
|
|
118
118
|
namespacesJson[nsId] = this.serializePostgresNamespace(ns, ns.id === UNBOUND_NAMESPACE_ID);
|
|
119
119
|
} else {
|
|
120
|
-
// Family-level
|
|
121
|
-
// been promoted to a PostgresSchema instance
|
|
122
|
-
// straight from the TS builder
|
|
123
|
-
//
|
|
120
|
+
// Family-level SqlUnboundNamespace or other family-built SQL
|
|
121
|
+
// namespaces haven't been promoted to a PostgresSchema instance
|
|
122
|
+
// yet (e.g. they came straight from the TS builder before a target
|
|
123
|
+
// `createNamespace` factory ran). Serialise them as postgres-schema /
|
|
124
124
|
// postgres-unbound-schema so the round-trip through
|
|
125
125
|
// deserializeContract hydrates them back into PostgresSchema
|
|
126
126
|
// instances.
|
|
@@ -120,10 +120,11 @@ export class PostgresSchema extends NamespaceBase {
|
|
|
120
120
|
* statements that need to identify this namespace in the live
|
|
121
121
|
* database (e.g. `CREATE TABLE "<ddlSchemaName>"."<table>" …`,
|
|
122
122
|
* catalog filters, planner conflict lookups). Named schemas resolve
|
|
123
|
-
* to their own id
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
123
|
+
* to their own id. The `PostgresUnboundSchema` singleton inherits
|
|
124
|
+
* this and returns `UNBOUND_NAMESPACE_ID` — callers that dispatch
|
|
125
|
+
* through `qualifyTableName` / `toRegclassLiteral` route through the
|
|
126
|
+
* polymorphic `PostgresUnboundSchema` overrides and produce
|
|
127
|
+
* unqualified (search-path-resolved) output automatically.
|
|
127
128
|
*/
|
|
128
129
|
ddlSchemaName(_storage: SqlStorage): string {
|
|
129
130
|
return this.id;
|
|
@@ -143,6 +144,11 @@ export class PostgresSchema extends NamespaceBase {
|
|
|
143
144
|
* sentinel; Postgres decides what late-bound means here (the table
|
|
144
145
|
* name, naked — the schema is supplied by the live connection's
|
|
145
146
|
* `search_path`).
|
|
147
|
+
*
|
|
148
|
+
* `ddlSchemaName` is inherited from `PostgresSchema` and returns
|
|
149
|
+
* `UNBOUND_NAMESPACE_ID`. Downstream helpers (`qualifyTableName`,
|
|
150
|
+
* `toRegclassLiteral`) route through the polymorphic factory and
|
|
151
|
+
* produce unqualified output automatically.
|
|
146
152
|
*/
|
|
147
153
|
export class PostgresUnboundSchema extends PostgresSchema {
|
|
148
154
|
static readonly instance: PostgresUnboundSchema = new PostgresUnboundSchema();
|
|
@@ -162,22 +168,6 @@ export class PostgresUnboundSchema extends PostgresSchema {
|
|
|
162
168
|
override schemaSqlExpression(): string {
|
|
163
169
|
return 'current_schema()';
|
|
164
170
|
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* The unbound slot has no schema name of its own, so DDL emission
|
|
168
|
-
* projects it onto a sibling when one is available: if the contract
|
|
169
|
-
* carries a `public` namespace, the late-bound slot resolves to
|
|
170
|
-
* `'public'` (the default Postgres landing schema); otherwise it
|
|
171
|
-
* resolves to the framework sentinel `UNBOUND_NAMESPACE_ID` so the
|
|
172
|
-
* planner can recognise the unprojected case and route accordingly
|
|
173
|
-
* (e.g. emit a conflict instead of silently picking a schema).
|
|
174
|
-
*/
|
|
175
|
-
override ddlSchemaName(storage: SqlStorage): string {
|
|
176
|
-
if (storage.namespaces['public'] !== undefined) {
|
|
177
|
-
return 'public';
|
|
178
|
-
}
|
|
179
|
-
return UNBOUND_NAMESPACE_ID;
|
|
180
|
-
}
|
|
181
171
|
}
|
|
182
172
|
|
|
183
173
|
PostgresSchema.unbound = PostgresUnboundSchema.instance;
|