@prisma-next/target-postgres 0.14.0-dev.2 → 0.14.0-dev.20

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.
Files changed (153) hide show
  1. package/dist/canonicalize-Bn3BM1Cn.mjs +46 -0
  2. package/dist/canonicalize-Bn3BM1Cn.mjs.map +1 -0
  3. package/dist/canonicalize-CAbXtVtB.d.mts +37 -0
  4. package/dist/canonicalize-CAbXtVtB.d.mts.map +1 -0
  5. package/dist/codecs.mjs.map +1 -1
  6. package/dist/contract-free.d.mts +8 -3
  7. package/dist/contract-free.d.mts.map +1 -1
  8. package/dist/contract-free.mjs +3 -3
  9. package/dist/control.d.mts +1 -1
  10. package/dist/control.mjs +6 -6
  11. package/dist/data-transform-BOWpliq8.mjs.map +1 -1
  12. package/dist/{data-transform-DDgWdB5o.d.mts → data-transform-qKy1U5V9.d.mts} +2 -2
  13. package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-qKy1U5V9.d.mts.map} +1 -1
  14. package/dist/data-transform.d.mts +1 -1
  15. package/dist/{ddl-QDyOSeLc.mjs → ddl-D1K9soOg.mjs} +62 -5
  16. package/dist/ddl-D1K9soOg.mjs.map +1 -0
  17. package/dist/ddl.d.mts +3 -2
  18. package/dist/ddl.mjs +2 -2
  19. package/dist/{default-normalizer-DyyCHQWs.mjs → default-normalizer-CzV8-QSu.mjs} +58 -2
  20. package/dist/default-normalizer-CzV8-QSu.mjs.map +1 -0
  21. package/dist/default-normalizer.d.mts +1 -1
  22. package/dist/default-normalizer.d.mts.map +1 -1
  23. package/dist/default-normalizer.mjs +1 -1
  24. package/dist/descriptor-meta-b_SzXwen.mjs +239 -0
  25. package/dist/descriptor-meta-b_SzXwen.mjs.map +1 -0
  26. package/dist/{issue-planner-DL6g3CmE.mjs → issue-planner-CUeQ7D83.mjs} +11 -9
  27. package/dist/issue-planner-CUeQ7D83.mjs.map +1 -0
  28. package/dist/issue-planner.d.mts +3 -3
  29. package/dist/issue-planner.d.mts.map +1 -1
  30. package/dist/issue-planner.mjs +1 -1
  31. package/dist/migration.d.mts +3 -3
  32. package/dist/migration.mjs +2 -2
  33. package/dist/nodes-CSkPX3rI.d.mts +120 -0
  34. package/dist/nodes-CSkPX3rI.d.mts.map +1 -0
  35. package/dist/{nodes-Bbhs2rwj.mjs → nodes-Dh__NS58.mjs} +56 -2
  36. package/dist/nodes-Dh__NS58.mjs.map +1 -0
  37. package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-CQ4P40QC.d.mts} +44 -13
  38. package/dist/op-factory-call-CQ4P40QC.d.mts.map +1 -0
  39. package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-CyqPjAwf.mjs} +199 -28
  40. package/dist/op-factory-call-CyqPjAwf.mjs.map +1 -0
  41. package/dist/op-factory-call.d.mts +1 -1
  42. package/dist/op-factory-call.mjs +1 -1
  43. package/dist/pack.d.mts +74 -1
  44. package/dist/pack.d.mts.map +1 -1
  45. package/dist/pack.mjs +1 -1
  46. package/dist/{planner-Bs_baQax.mjs → planner-D5zY__R3.mjs} +115 -23
  47. package/dist/planner-D5zY__R3.mjs.map +1 -0
  48. package/dist/{planner-ddl-builders-B2wOwLqI.mjs → planner-ddl-builders-B8Nn6nHN.mjs} +12 -27
  49. package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +1 -0
  50. package/dist/planner-ddl-builders.d.mts +2 -4
  51. package/dist/planner-ddl-builders.d.mts.map +1 -1
  52. package/dist/planner-ddl-builders.mjs +2 -2
  53. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-CNTSCMl7.d.mts} +3 -3
  54. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-CNTSCMl7.d.mts.map} +1 -1
  55. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-D8JHnu5j.mjs} +2 -2
  56. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-D8JHnu5j.mjs.map} +1 -1
  57. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  58. package/dist/planner-produced-postgres-migration.mjs +1 -1
  59. package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-CI6Z21mm.mjs} +2 -2
  60. package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-CI6Z21mm.mjs.map} +1 -1
  61. package/dist/planner-sql-checks.mjs +1 -1
  62. package/dist/{planner-target-details-CIY6tLeo.d.mts → planner-target-details-HkP4RrRO.d.mts} +2 -2
  63. package/dist/planner-target-details-HkP4RrRO.d.mts.map +1 -0
  64. package/dist/planner-target-details.d.mts +1 -1
  65. package/dist/planner.d.mts +35 -3
  66. package/dist/planner.d.mts.map +1 -1
  67. package/dist/planner.mjs +2 -2
  68. package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-serializer-k5eg5ZUZ.mjs} +29 -16
  69. package/dist/postgres-contract-serializer-k5eg5ZUZ.mjs.map +1 -0
  70. package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-BoqzWSkj.mjs} +2 -2
  71. package/dist/{postgres-migration-B5jKrXv3.mjs.map → postgres-migration-BoqzWSkj.mjs.map} +1 -1
  72. package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-DJNJHcdP.d.mts} +4 -4
  73. package/dist/{postgres-migration-Y4YBJqkS.d.mts.map → postgres-migration-DJNJHcdP.d.mts.map} +1 -1
  74. package/dist/postgres-rls-policy-D_z1osEq.d.mts +60 -0
  75. package/dist/postgres-rls-policy-D_z1osEq.d.mts.map +1 -0
  76. package/dist/postgres-role-_CCuyPCQ.d.mts +33 -0
  77. package/dist/postgres-role-_CCuyPCQ.d.mts.map +1 -0
  78. package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-BVDr_61a.mjs} +139 -3
  79. package/dist/postgres-schema-BVDr_61a.mjs.map +1 -0
  80. package/dist/postgres-schema-D0h4lsw2.d.mts +158 -0
  81. package/dist/postgres-schema-D0h4lsw2.d.mts.map +1 -0
  82. package/dist/postgres-schema-ir-BF0X8jit.mjs +66 -0
  83. package/dist/postgres-schema-ir-BF0X8jit.mjs.map +1 -0
  84. package/dist/postgres-schema-ir-BQ3RpZMz.d.mts +60 -0
  85. package/dist/postgres-schema-ir-BQ3RpZMz.d.mts.map +1 -0
  86. package/dist/postgres-schema-ir-annotations-El62Sywa.mjs +14 -0
  87. package/dist/postgres-schema-ir-annotations-El62Sywa.mjs.map +1 -0
  88. package/dist/render-ops.d.mts +1 -1
  89. package/dist/rls-canonicalize.d.mts +2 -0
  90. package/dist/rls-canonicalize.mjs +2 -0
  91. package/dist/runtime.d.mts +3 -2
  92. package/dist/runtime.d.mts.map +1 -1
  93. package/dist/runtime.mjs +2 -2
  94. package/dist/schema-ir-annotations.d.mts +8 -0
  95. package/dist/schema-ir-annotations.d.mts.map +1 -0
  96. package/dist/schema-ir-annotations.mjs +2 -0
  97. package/dist/types.d.mts +5 -140
  98. package/dist/types.mjs +3 -2
  99. package/package.json +21 -17
  100. package/src/contract-free/checks.ts +73 -0
  101. package/src/contract-free/ddl.ts +45 -1
  102. package/src/core/authoring.ts +115 -1
  103. package/src/core/codecs.ts +1 -1
  104. package/src/core/ddl/nodes.ts +94 -2
  105. package/src/core/default-normalizer.ts +60 -2
  106. package/src/core/descriptor-meta.ts +4 -0
  107. package/src/core/entity-kinds.ts +16 -0
  108. package/src/core/migrations/bound-schema.ts +10 -0
  109. package/src/core/migrations/control-policy.ts +3 -0
  110. package/src/core/migrations/issue-planner.ts +8 -0
  111. package/src/core/migrations/op-factory-call.ts +106 -21
  112. package/src/core/migrations/operations/columns.ts +24 -12
  113. package/src/core/migrations/operations/data-transform.ts +6 -0
  114. package/src/core/migrations/operations/rls.ts +106 -0
  115. package/src/core/migrations/operations/shared.ts +0 -30
  116. package/src/core/migrations/planner-ddl-builders.ts +15 -56
  117. package/src/core/migrations/planner-recipes.ts +31 -13
  118. package/src/core/migrations/planner-strategies.ts +3 -11
  119. package/src/core/migrations/planner-target-details.ts +3 -1
  120. package/src/core/migrations/planner.ts +99 -1
  121. package/src/core/migrations/verify-postgres-namespaces.ts +12 -15
  122. package/src/core/migrations/verify-postgres-rls-policies.ts +62 -0
  123. package/src/core/postgres-contract-serializer.ts +43 -19
  124. package/src/core/postgres-rls-policy.ts +103 -0
  125. package/src/core/postgres-role.ts +53 -0
  126. package/src/core/postgres-schema-ir-annotations.ts +22 -0
  127. package/src/core/postgres-schema-ir.ts +101 -0
  128. package/src/core/postgres-schema.ts +24 -4
  129. package/src/core/postgres-validators.ts +20 -0
  130. package/src/core/rls/canonicalize.ts +48 -0
  131. package/src/exports/contract-free.ts +7 -1
  132. package/src/exports/ddl.ts +4 -0
  133. package/src/exports/planner-ddl-builders.ts +0 -2
  134. package/src/exports/planner.ts +1 -0
  135. package/src/exports/rls-canonicalize.ts +6 -0
  136. package/src/exports/schema-ir-annotations.ts +1 -0
  137. package/src/exports/types.ts +12 -0
  138. package/dist/ddl-QDyOSeLc.mjs.map +0 -1
  139. package/dist/default-normalizer-DyyCHQWs.mjs.map +0 -1
  140. package/dist/descriptor-meta-CpGygXpI.mjs +0 -140
  141. package/dist/descriptor-meta-CpGygXpI.mjs.map +0 -1
  142. package/dist/issue-planner-DL6g3CmE.mjs.map +0 -1
  143. package/dist/nodes-Bbhs2rwj.mjs.map +0 -1
  144. package/dist/nodes-pLeLgdis.d.mts +0 -67
  145. package/dist/nodes-pLeLgdis.d.mts.map +0 -1
  146. package/dist/op-factory-call-D_p5vxwt.mjs.map +0 -1
  147. package/dist/op-factory-call-DmQEc3XV.d.mts.map +0 -1
  148. package/dist/planner-Bs_baQax.mjs.map +0 -1
  149. package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +0 -1
  150. package/dist/planner-target-details-CIY6tLeo.d.mts.map +0 -1
  151. package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +0 -1
  152. package/dist/postgres-schema-COGZ1ark.mjs.map +0 -1
  153. package/dist/types.d.mts.map +0 -1
@@ -40,8 +40,10 @@ import { blindCast } from '@prisma-next/utils/casts';
40
40
  import { ifDefined } from '@prisma-next/utils/defined';
41
41
  import { columnExistsAst, tableExistsAst } from '../../contract-free/checks';
42
42
  import * as contractFreeDdl from '../../contract-free/ddl';
43
+ import type { PostgresRlsPolicy } from '../postgres-rls-policy';
43
44
  import { escapeLiteral, quoteIdentifier } from '../sql-utils';
44
45
  import type { PostgresColumnDefault } from '../types';
46
+ import { boundSchema } from './bound-schema';
45
47
  import {
46
48
  addNotNullColumnDirect,
47
49
  alterColumnType,
@@ -61,6 +63,7 @@ import {
61
63
  } from './operations/constraints';
62
64
  import { createExtension } from './operations/dependencies';
63
65
  import { createIndex, dropIndex } from './operations/indexes';
66
+ import { createRlsPolicy, dropRlsPolicy, enableRowLevelSecurity } from './operations/rls';
64
67
  import type { ForeignKeySpec } from './operations/shared';
65
68
  import { step, targetDetails } from './operations/shared';
66
69
  import { dropTable } from './operations/tables';
@@ -83,10 +86,6 @@ type Op = SqlMigrationPlanOperation<PostgresPlanTargetDetails>;
83
86
  // transitive package on disk.
84
87
  const POSTGRES_MIGRATION_FACADE = '@prisma-next/postgres/migration';
85
88
 
86
- function boundSchema(schemaName: string): string | undefined {
87
- return schemaName === UNBOUND_NAMESPACE_ID ? undefined : schemaName;
88
- }
89
-
90
89
  abstract class PostgresOpFactoryCallNode extends TsExpression implements FrameworkOpFactoryCall {
91
90
  abstract readonly factoryName: string;
92
91
  abstract readonly operationClass: MigrationOperationClass;
@@ -684,10 +683,8 @@ export class DropDefaultCall extends PostgresOpFactoryCallNode {
684
683
 
685
684
  /**
686
685
  * Planner-internal call for adding a NOT NULL column (no contract default) to
687
- * a table that must be empty at migration time. Carries the raw ADD COLUMN SQL
688
- * (produced by `buildAddColumnSql` at plan time deferred from slice-7 typed
689
- * DDL) plus the parameters needed to lower the typed pre/postchecks at render
690
- * time.
686
+ * a table that must be empty at migration time. Carries the typed `DdlColumn`
687
+ * and lowers it to an ADD COLUMN execute step via the adapter at `toOp` time.
691
688
  *
692
689
  * No authored `PostgresMigration` method: this call is only emitted by the
693
690
  * planner, never hand-written by migration authors.
@@ -698,15 +695,15 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
698
695
  readonly schemaName: string;
699
696
  readonly tableName: string;
700
697
  readonly columnName: string;
701
- readonly executeStepSql: string;
698
+ readonly column: DdlColumn;
702
699
  readonly label: string;
703
700
 
704
- constructor(schemaName: string, tableName: string, columnName: string, executeStepSql: string) {
701
+ constructor(schemaName: string, tableName: string, columnName: string, column: DdlColumn) {
705
702
  super();
706
703
  this.schemaName = schemaName;
707
704
  this.tableName = tableName;
708
705
  this.columnName = columnName;
709
- this.executeStepSql = executeStepSql;
706
+ this.column = column;
710
707
  this.label = `Add column ${columnName} to ${tableName}`;
711
708
  this.freeze();
712
709
  }
@@ -717,13 +714,7 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
717
714
  `AddNotNullColumnDirectCall.toOp: a lowerer is required on the Postgres planner path (column "${this.columnName}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
718
715
  );
719
716
  }
720
- return addNotNullColumnDirect(
721
- this.schemaName,
722
- this.tableName,
723
- this.columnName,
724
- this.executeStepSql,
725
- lowerer,
726
- );
717
+ return addNotNullColumnDirect(this.schemaName, this.tableName, this.column, lowerer);
727
718
  }
728
719
 
729
720
  renderTypeScript(): string {
@@ -738,9 +729,9 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
738
729
  /**
739
730
  * Planner-internal call for adding a NOT NULL column (no contract default)
740
731
  * using a temporary default value for non-empty tables. Carries all parameters
741
- * needed for `buildAddNotNullColumnWithTemporaryDefaultOperation`; the typed
742
- * pre/postchecks are lowered at render time via `toOp(lowerer)`. The execute
743
- * SQL (`buildAddColumnSql`) is still raw per slice-7 deferral.
732
+ * needed for `buildAddNotNullColumnWithTemporaryDefaultOperation`; both the
733
+ * typed ADD COLUMN execute step and the pre/postchecks are lowered via the
734
+ * adapter at `toOp` time.
744
735
  *
745
736
  * No authored `PostgresMigration` method: this call is only emitted by the
746
737
  * planner, never hand-written by migration authors.
@@ -1376,6 +1367,97 @@ export class DataTransformCall extends PostgresOpFactoryCallNode {
1376
1367
  }
1377
1368
  }
1378
1369
 
1370
+ export class CreatePostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
1371
+ readonly factoryName = 'createRlsPolicy' as const;
1372
+ readonly operationClass = 'additive' as const;
1373
+ readonly schemaName: string;
1374
+ readonly tableName: string;
1375
+ readonly policy: PostgresRlsPolicy;
1376
+ readonly label: string;
1377
+
1378
+ constructor(schemaName: string, tableName: string, policy: PostgresRlsPolicy) {
1379
+ super();
1380
+ this.schemaName = schemaName;
1381
+ this.tableName = tableName;
1382
+ this.policy = policy;
1383
+ this.label = `Create RLS policy "${policy.name}" on "${tableName}"`;
1384
+ this.freeze();
1385
+ }
1386
+
1387
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
1388
+ if (lowerer === undefined) {
1389
+ throw new Error(
1390
+ `CreatePostgresRlsPolicyCall.toOp: a lowerer is required on the Postgres planner path (policy "${this.policy.name}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
1391
+ );
1392
+ }
1393
+ return createRlsPolicy(this.schemaName, this.tableName, this.policy, lowerer);
1394
+ }
1395
+
1396
+ renderTypeScript(): string {
1397
+ return `createRlsPolicy(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.policy)})`;
1398
+ }
1399
+ }
1400
+
1401
+ export class DropPostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
1402
+ readonly factoryName = 'dropRlsPolicy' as const;
1403
+ readonly operationClass = 'destructive' as const;
1404
+ readonly schemaName: string;
1405
+ readonly tableName: string;
1406
+ readonly policyName: string;
1407
+ readonly label: string;
1408
+
1409
+ constructor(schemaName: string, tableName: string, policyName: string) {
1410
+ super();
1411
+ this.schemaName = schemaName;
1412
+ this.tableName = tableName;
1413
+ this.policyName = policyName;
1414
+ this.label = `Drop RLS policy "${policyName}" on "${tableName}"`;
1415
+ this.freeze();
1416
+ }
1417
+
1418
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
1419
+ if (lowerer === undefined) {
1420
+ throw new Error(
1421
+ `DropPostgresRlsPolicyCall.toOp: a lowerer is required on the Postgres planner path (policy "${this.policyName}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
1422
+ );
1423
+ }
1424
+ return dropRlsPolicy(this.schemaName, this.tableName, this.policyName, lowerer);
1425
+ }
1426
+
1427
+ renderTypeScript(): string {
1428
+ return `dropRlsPolicy(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.policyName)})`;
1429
+ }
1430
+ }
1431
+
1432
+ export class EnableRowLevelSecurityCall extends PostgresOpFactoryCallNode {
1433
+ readonly factoryName = 'enableRowLevelSecurity' as const;
1434
+ readonly operationClass = 'additive' as const;
1435
+ readonly schemaName: string;
1436
+ readonly tableName: string;
1437
+ readonly label: string;
1438
+
1439
+ constructor(schemaName: string, tableName: string) {
1440
+ super();
1441
+ this.schemaName = schemaName;
1442
+ this.tableName = tableName;
1443
+ this.label = `Enable row-level security on "${tableName}"`;
1444
+ this.freeze();
1445
+ }
1446
+
1447
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
1448
+ if (lowerer === undefined) {
1449
+ throw new Error(
1450
+ `EnableRowLevelSecurityCall.toOp: a lowerer is required on the Postgres planner path (table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
1451
+ );
1452
+ }
1453
+ return enableRowLevelSecurity(this.schemaName, this.tableName, lowerer);
1454
+ }
1455
+
1456
+ renderTypeScript(): string {
1457
+ return `enableRowLevelSecurity(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)})`;
1458
+ }
1459
+ }
1460
+
1379
1461
  export type PostgresOpFactoryCall =
1380
1462
  | CreateTableCall
1381
1463
  | DropTableCall
@@ -1399,4 +1481,7 @@ export type PostgresOpFactoryCall =
1399
1481
  | RawSqlCall
1400
1482
  | CreateExtensionCall
1401
1483
  | CreateSchemaCall
1484
+ | CreatePostgresRlsPolicyCall
1485
+ | DropPostgresRlsPolicyCall
1486
+ | EnableRowLevelSecurityCall
1402
1487
  | DataTransformCall;
@@ -1,4 +1,6 @@
1
1
  import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
2
+ import type { DdlColumn } from '@prisma-next/sql-relational-core/ast';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
2
4
  import {
3
5
  columnDefaultAst,
4
6
  columnExistsAst,
@@ -7,7 +9,9 @@ import {
7
9
  noNullValuesAst,
8
10
  tableIsEmptyAst,
9
11
  } from '../../../contract-free/checks';
12
+ import * as contractFreeDdl from '../../../contract-free/ddl';
10
13
  import { quoteIdentifier } from '../../sql-utils';
14
+ import { boundSchema } from '../bound-schema';
11
15
  import { qualifyTableName } from '../planner-sql-checks';
12
16
  import { type Op, step, targetDetails } from './shared';
13
17
 
@@ -242,12 +246,18 @@ export async function dropDefault(
242
246
  columnName: string,
243
247
  lowerer: ExecuteRequestLowerer,
244
248
  ): Promise<Op> {
245
- const qualified = qualifyTableName(schemaName, tableName);
246
249
  const { present } = await columnExistsSteps(lowerer, {
247
250
  schema: schemaName,
248
251
  table: tableName,
249
252
  column: columnName,
250
253
  });
254
+ const dropDefaultExec = await lowerer.lowerToExecuteRequest(
255
+ contractFreeDdl.alterTable({
256
+ ...ifDefined('schema', boundSchema(schemaName)),
257
+ table: tableName,
258
+ actions: [contractFreeDdl.dropDefaultAction(columnName)],
259
+ }),
260
+ );
251
261
  const noDefault = await lowerer.lowerToExecuteRequest(
252
262
  columnDefaultAst({ schema: schemaName, table: tableName, column: columnName }).defaultAbsent(),
253
263
  );
@@ -257,12 +267,7 @@ export async function dropDefault(
257
267
  operationClass: 'destructive',
258
268
  target: targetDetails('column', columnName, schemaName, tableName),
259
269
  precheck: [step(`ensure column "${columnName}" exists`, present.sql, present.params)],
260
- execute: [
261
- step(
262
- `drop default on "${columnName}"`,
263
- `ALTER TABLE ${qualified} ALTER COLUMN ${quoteIdentifier(columnName)} DROP DEFAULT`,
264
- ),
265
- ],
270
+ execute: [step(`drop default on "${columnName}"`, dropDefaultExec.sql)],
266
271
  postcheck: [
267
272
  step(`verify column "${columnName}" has no default`, noDefault.sql, noDefault.params),
268
273
  ],
@@ -272,16 +277,23 @@ export async function dropDefault(
272
277
  /**
273
278
  * Builds the op for adding a NOT NULL column (no contract default) to a
274
279
  * non-empty table. Prechecks assert the column is absent and the table is
275
- * empty; the execute step is the raw ADD COLUMN SQL passed by the caller
276
- * (slice-7 deferred); postchecks assert the column exists and is NOT NULL.
280
+ * empty; the execute step lowers the typed `AddColumn` DDL node through the
281
+ * adapter; postchecks assert the column exists and is NOT NULL.
277
282
  */
278
283
  export async function addNotNullColumnDirect(
279
284
  schemaName: string,
280
285
  tableName: string,
281
- columnName: string,
282
- executeStepSql: string,
286
+ column: DdlColumn,
283
287
  lowerer: ExecuteRequestLowerer,
284
288
  ): Promise<Op> {
289
+ const columnName = column.name;
290
+ const addColumn = await lowerer.lowerToExecuteRequest(
291
+ contractFreeDdl.alterTable({
292
+ ...ifDefined('schema', boundSchema(schemaName)),
293
+ table: tableName,
294
+ actions: [contractFreeDdl.addColumnAction(column)],
295
+ }),
296
+ );
285
297
  const absent = await lowerer.lowerToExecuteRequest(
286
298
  columnExistsAst({ schema: schemaName, table: tableName, column: columnName }).columnAbsent(),
287
299
  );
@@ -311,7 +323,7 @@ export async function addNotNullColumnDirect(
311
323
  tableEmpty.params,
312
324
  ),
313
325
  ],
314
- execute: [step(`add column "${columnName}"`, executeStepSql)],
326
+ execute: [step(`add column "${columnName}"`, addColumn.sql)],
315
327
  postcheck: [
316
328
  step(`verify column "${columnName}" exists`, present.sql, present.params),
317
329
  step(`verify column "${columnName}" is NOT NULL`, notNullable.sql, notNullable.params),
@@ -113,6 +113,12 @@ export async function dataTransform<TContract extends Contract<SqlStorage>>(
113
113
  runClosures.map((closure) => invokeAndLower(closure, contract, adapter, name)),
114
114
  );
115
115
 
116
+ // Raw remnant: the factory lowered the user's `check` plan above before
117
+ // we get a chance to wrap it, so the EXISTS lifting only has text by the
118
+ // time it runs. The fix is to rewrite the factory to defer lowering until
119
+ // after wrapping — so `cfExpr.exists(checkPlan.ast)` can produce the
120
+ // EXISTS query, then lower once at the boundary — not to grow new AST
121
+ // substrate. Out of scope for the slice that introduced this comment.
116
122
  const precheck: readonly SqlMigrationPlanOperationStep[] = checkPlan
117
123
  ? [
118
124
  {
@@ -0,0 +1,106 @@
1
+ import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
2
+ import { ifDefined } from '@prisma-next/utils/defined';
3
+ import { rlsEnabledAst, rlsPolicyExistsAst } from '../../../contract-free/checks';
4
+ import { createPolicy, dropPolicy } from '../../../contract-free/ddl';
5
+ import type { PostgresRlsPolicy } from '../../postgres-rls-policy';
6
+ import { qualifyTableName } from '../planner-sql-checks';
7
+ import { type Op, step, targetDetails } from './shared';
8
+
9
+ const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]*$/;
10
+
11
+ function validateRoleName(role: string): string {
12
+ if (!PLAIN_IDENTIFIER.test(role)) {
13
+ throw new Error(
14
+ `Invalid role name ${JSON.stringify(role)}: role names must be plain SQL identifiers matching ^[A-Za-z_][A-Za-z0-9_$]*$`,
15
+ );
16
+ }
17
+ return role;
18
+ }
19
+
20
+ export async function createRlsPolicy(
21
+ schemaName: string,
22
+ tableName: string,
23
+ policy: PostgresRlsPolicy,
24
+ lowerer: ExecuteRequestLowerer,
25
+ ): Promise<Op> {
26
+ const validatedRoles = policy.roles.map(validateRoleName);
27
+ const ddlNode = createPolicy({
28
+ schema: schemaName,
29
+ table: tableName,
30
+ name: policy.name,
31
+ permissive: policy.permissive,
32
+ operation: policy.operation,
33
+ roles: validatedRoles,
34
+ ...ifDefined('using', policy.using),
35
+ ...ifDefined('withCheck', policy.withCheck),
36
+ });
37
+ const checks = rlsPolicyExistsAst({
38
+ schema: schemaName,
39
+ table: tableName,
40
+ policyName: policy.name,
41
+ });
42
+ const absent = await lowerer.lowerToExecuteRequest(checks.policyAbsent());
43
+ const execute = await lowerer.lowerToExecuteRequest(ddlNode);
44
+ const present = await lowerer.lowerToExecuteRequest(checks.policyPresent());
45
+ return {
46
+ id: `rlsPolicy.${schemaName}.${tableName}.${policy.name}`,
47
+ label: `Create RLS policy "${policy.name}" on "${tableName}"`,
48
+ operationClass: 'additive',
49
+ target: targetDetails('rlsPolicy', policy.name, schemaName, tableName),
50
+ precheck: [
51
+ step(`ensure RLS policy "${policy.name}" does not exist`, absent.sql, absent.params),
52
+ ],
53
+ execute: [step(`create RLS policy "${policy.name}"`, execute.sql, execute.params)],
54
+ postcheck: [step(`verify RLS policy "${policy.name}" exists`, present.sql, present.params)],
55
+ };
56
+ }
57
+
58
+ export async function dropRlsPolicy(
59
+ schemaName: string,
60
+ tableName: string,
61
+ policyName: string,
62
+ lowerer: ExecuteRequestLowerer,
63
+ ): Promise<Op> {
64
+ const ddlNode = dropPolicy({ schema: schemaName, table: tableName, name: policyName });
65
+ const checks = rlsPolicyExistsAst({ schema: schemaName, table: tableName, policyName });
66
+ const present = await lowerer.lowerToExecuteRequest(checks.policyPresent());
67
+ const execute = await lowerer.lowerToExecuteRequest(ddlNode);
68
+ const absent = await lowerer.lowerToExecuteRequest(checks.policyAbsent());
69
+ return {
70
+ id: `rlsPolicy.${schemaName}.${tableName}.${policyName}.drop`,
71
+ label: `Drop RLS policy "${policyName}" on "${tableName}"`,
72
+ operationClass: 'destructive',
73
+ target: targetDetails('rlsPolicy', policyName, schemaName, tableName),
74
+ precheck: [step(`ensure RLS policy "${policyName}" exists`, present.sql, present.params)],
75
+ execute: [step(`drop RLS policy "${policyName}"`, execute.sql, execute.params)],
76
+ postcheck: [step(`verify RLS policy "${policyName}" is absent`, absent.sql, absent.params)],
77
+ };
78
+ }
79
+
80
+ export async function enableRowLevelSecurity(
81
+ schemaName: string,
82
+ tableName: string,
83
+ lowerer: ExecuteRequestLowerer,
84
+ ): Promise<Op> {
85
+ const checks = rlsEnabledAst(schemaName, tableName);
86
+ const disabled = await lowerer.lowerToExecuteRequest(checks.rlsDisabled());
87
+ const enabled = await lowerer.lowerToExecuteRequest(checks.rlsEnabled());
88
+ return {
89
+ id: `rowLevelSecurity.${schemaName}.${tableName}`,
90
+ label: `Enable row-level security on "${tableName}"`,
91
+ operationClass: 'additive',
92
+ target: targetDetails('rowLevelSecurity', tableName, schemaName),
93
+ precheck: [
94
+ step(`check RLS is not already enabled on "${tableName}"`, disabled.sql, disabled.params),
95
+ ],
96
+ execute: [
97
+ step(
98
+ `enable row-level security on "${tableName}"`,
99
+ `ALTER TABLE ${qualifyTableName(schemaName, tableName)} ENABLE ROW LEVEL SECURITY`,
100
+ ),
101
+ ],
102
+ postcheck: [
103
+ step(`verify row-level security is enabled on "${tableName}"`, enabled.sql, enabled.params),
104
+ ],
105
+ };
106
+ }
@@ -1,30 +1,10 @@
1
1
  import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
2
  import type { ReferentialAction } from '@prisma-next/sql-contract/types';
3
3
  import { ifDefined } from '@prisma-next/utils/defined';
4
- import { quoteIdentifier } from '../../sql-utils';
5
4
  import type { OperationClass, PostgresPlanTargetDetails } from '../planner-target-details';
6
5
 
7
6
  export type Op = SqlMigrationPlanOperation<PostgresPlanTargetDetails>;
8
7
 
9
- /**
10
- * Literal-args shape for a column definition consumed by `addColumn` and
11
- * similar operations. Fully materialized: codec expansion and default
12
- * rendering have already happened in the wrapper.
13
- *
14
- * - `typeSql` is the column's DDL type string (e.g. `"integer"`, `"SERIAL"`,
15
- * `"varchar(100)"`), already produced by `buildColumnTypeSql` in the
16
- * call-factory wrapper.
17
- * - `defaultSql` is the full `DEFAULT …` clause (e.g. `"DEFAULT 42"`) or an
18
- * empty string when the column has no default, matching
19
- * `buildColumnDefaultSql`'s output.
20
- */
21
- export interface ColumnSpec {
22
- readonly name: string;
23
- readonly typeSql: string;
24
- readonly defaultSql: string;
25
- readonly nullable: boolean;
26
- }
27
-
28
8
  /**
29
9
  * Literal-args shape for a foreign key definition. `references.schema`
30
10
  * carries the target table's namespace (schema) coordinate so the rendered
@@ -57,13 +37,3 @@ export function targetDetails(
57
37
  details: { schema, objectType, name, ...ifDefined('table', table) },
58
38
  };
59
39
  }
60
-
61
- export function renderColumnDefinition(column: ColumnSpec): string {
62
- const parts = [
63
- quoteIdentifier(column.name),
64
- column.typeSql,
65
- column.defaultSql,
66
- column.nullable ? '' : 'NOT NULL',
67
- ].filter(Boolean);
68
- return parts.join(' ');
69
- }
@@ -1,43 +1,10 @@
1
1
  import type { CodecControlHooks } from '@prisma-next/family-sql/control';
2
- import type {
3
- StorageColumn,
4
- StorageTable,
5
- StorageTypeInstance,
6
- } from '@prisma-next/sql-contract/types';
2
+ import type { StorageColumn, StorageTypeInstance } from '@prisma-next/sql-contract/types';
7
3
  import { ifDefined } from '@prisma-next/utils/defined';
8
4
  import { escapeLiteral, quoteIdentifier } from '../sql-utils';
9
5
  import type { PostgresColumnDefault } from '../types';
10
6
  import { resolveColumnTypeMetadata } from './planner-type-resolution';
11
7
 
12
- export function buildCreateTableSql(
13
- qualifiedTableName: string,
14
- table: StorageTable,
15
- codecHooks: ReadonlyMap<string, CodecControlHooks>,
16
- storageTypes: Record<string, StorageTypeInstance> = {},
17
- ): string {
18
- const columnDefinitions = Object.entries(table.columns).map(
19
- ([columnName, column]: [string, StorageColumn]) => {
20
- const parts = [
21
- quoteIdentifier(columnName),
22
- buildColumnTypeSql(column, codecHooks, storageTypes),
23
- buildColumnDefaultSql(column.default, column),
24
- column.nullable ? '' : 'NOT NULL',
25
- ].filter(Boolean);
26
- return parts.join(' ');
27
- },
28
- );
29
-
30
- const constraintDefinitions: string[] = [];
31
- if (table.primaryKey) {
32
- constraintDefinitions.push(
33
- `PRIMARY KEY (${table.primaryKey.columns.map(quoteIdentifier).join(', ')})`,
34
- );
35
- }
36
-
37
- const allDefinitions = [...columnDefinitions, ...constraintDefinitions];
38
- return `CREATE TABLE ${qualifiedTableName} (\n ${allDefinitions.join(',\n ')}\n)`;
39
- }
40
-
41
8
  /**
42
9
  * Pattern for safe PostgreSQL type names.
43
10
  * Allows letters, digits, underscores, spaces (for "double precision", "character varying"),
@@ -100,15 +67,16 @@ export function buildColumnTypeSql(
100
67
 
101
68
  const expanded = expandParameterizedTypeSql(resolved, codecHooks);
102
69
  if (expanded !== null) {
103
- return expanded;
70
+ return column.many ? `${expanded}[]` : expanded;
104
71
  }
105
72
 
106
73
  if (column.typeRef) {
107
- return quoteIdentifier(resolved.nativeType);
74
+ const base = quoteIdentifier(resolved.nativeType);
75
+ return column.many ? `${base}[]` : base;
108
76
  }
109
77
 
110
78
  assertSafeNativeType(resolved.nativeType);
111
- return resolved.nativeType;
79
+ return column.many ? `${resolved.nativeType}[]` : resolved.nativeType;
112
80
  }
113
81
 
114
82
  function expandParameterizedTypeSql(
@@ -174,6 +142,10 @@ export function buildColumnDefaultSql(
174
142
  export function renderDefaultLiteral(value: unknown, column?: StorageColumn): string {
175
143
  const isJsonColumn = column?.nativeType === 'json' || column?.nativeType === 'jsonb';
176
144
 
145
+ if (column?.many && Array.isArray(value)) {
146
+ return renderArrayLiteralDefault(value);
147
+ }
148
+
177
149
  if (value instanceof Date) {
178
150
  return `'${escapeLiteral(value.toISOString())}'`;
179
151
  }
@@ -193,23 +165,10 @@ export function renderDefaultLiteral(value: unknown, column?: StorageColumn): st
193
165
  return `'${escapeLiteral(json)}'`;
194
166
  }
195
167
 
196
- export function buildAddColumnSql(
197
- qualifiedTableName: string,
198
- columnName: string,
199
- column: StorageColumn,
200
- codecHooks: ReadonlyMap<string, CodecControlHooks>,
201
- temporaryDefault?: string | null,
202
- storageTypes: Record<string, StorageTypeInstance> = {},
203
- ): string {
204
- const typeSql = buildColumnTypeSql(column, codecHooks, storageTypes);
205
- const defaultSql =
206
- buildColumnDefaultSql(column.default, column) ||
207
- (temporaryDefault ? `DEFAULT ${temporaryDefault}` : '');
208
- const parts = [
209
- `ALTER TABLE ${qualifiedTableName}`,
210
- `ADD COLUMN ${quoteIdentifier(columnName)} ${typeSql}`,
211
- defaultSql,
212
- column.nullable ? '' : 'NOT NULL',
213
- ].filter(Boolean);
214
- return parts.join(' ');
168
+ function renderArrayLiteralDefault(elements: unknown[]): string {
169
+ if (elements.length === 0) {
170
+ return "'{}'";
171
+ }
172
+ const rendered = elements.map((el) => renderDefaultLiteral(el)).join(', ');
173
+ return `ARRAY[${rendered}]`;
215
174
  }
@@ -1,15 +1,18 @@
1
1
  import type { CodecControlHooks, SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
2
  import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
3
3
  import type { StorageColumn, StorageTypeInstance } from '@prisma-next/sql-contract/types';
4
+ import { FunctionColumnDefault } from '@prisma-next/sql-relational-core/ast';
5
+ import { col } from '@prisma-next/sql-relational-core/contract-free';
6
+ import { ifDefined } from '@prisma-next/utils/defined';
4
7
  import {
5
8
  columnDefaultAst,
6
9
  columnExistsAst,
7
10
  columnNullabilityAst,
8
11
  } from '../../contract-free/checks';
9
- import { quoteIdentifier } from '../sql-utils';
12
+ import * as contractFreeDdl from '../../contract-free/ddl';
13
+ import { boundSchema } from './bound-schema';
10
14
  import { step } from './operations/shared';
11
- import { buildAddColumnSql } from './planner-ddl-builders';
12
- import { qualifyTableName } from './planner-sql-checks';
15
+ import { buildColumnTypeSql } from './planner-ddl-builders';
13
16
  import { buildTargetDetails, type PostgresPlanTargetDetails } from './planner-target-details';
14
17
 
15
18
  export function buildAddColumnOperationIdentity(
@@ -51,7 +54,29 @@ export async function buildAddNotNullColumnWithTemporaryDefaultOperation(options
51
54
  temporaryDefault,
52
55
  lowerer,
53
56
  } = options;
54
- const qualified = qualifyTableName(schema, tableName);
57
+
58
+ // The recipe handles NOT NULL columns that carry no contract default, so the
59
+ // temporary backfill value is the only default. It is a pre-rendered SQL
60
+ // fragment (e.g. `''`, `0`, `'{}'::jsonb`), carried verbatim as a
61
+ // `FunctionColumnDefault` so the adapter emits it as a `DEFAULT (...)` clause.
62
+ const ddlColumn = col(columnName, buildColumnTypeSql(column, codecHooks, storageTypes), {
63
+ notNull: true,
64
+ default: new FunctionColumnDefault(temporaryDefault),
65
+ });
66
+ const addColumn = await lowerer.lowerToExecuteRequest(
67
+ contractFreeDdl.alterTable({
68
+ ...ifDefined('schema', boundSchema(schema)),
69
+ table: tableName,
70
+ actions: [contractFreeDdl.addColumnAction(ddlColumn)],
71
+ }),
72
+ );
73
+ const dropTempDefault = await lowerer.lowerToExecuteRequest(
74
+ contractFreeDdl.alterTable({
75
+ ...ifDefined('schema', boundSchema(schema)),
76
+ table: tableName,
77
+ actions: [contractFreeDdl.dropDefaultAction(columnName)],
78
+ }),
79
+ );
55
80
 
56
81
  const absent = await lowerer.lowerToExecuteRequest(
57
82
  columnExistsAst({ schema, table: tableName, column: columnName }).columnAbsent(),
@@ -73,18 +98,11 @@ export async function buildAddNotNullColumnWithTemporaryDefaultOperation(options
73
98
  execute: [
74
99
  {
75
100
  description: `add column "${columnName}"`,
76
- sql: buildAddColumnSql(
77
- qualified,
78
- columnName,
79
- column,
80
- codecHooks,
81
- temporaryDefault,
82
- storageTypes,
83
- ),
101
+ sql: addColumn.sql,
84
102
  },
85
103
  {
86
104
  description: `drop temporary default from column "${columnName}"`,
87
- sql: `ALTER TABLE ${qualified} ALTER COLUMN ${quoteIdentifier(columnName)} DROP DEFAULT`,
105
+ sql: dropTempDefault.sql,
88
106
  },
89
107
  ],
90
108
  postcheck: [
@@ -53,10 +53,10 @@ import {
53
53
  RawSqlCall,
54
54
  SetNotNullCall,
55
55
  } from './op-factory-call';
56
- import { buildAddColumnSql, buildColumnTypeSql } from './planner-ddl-builders';
56
+ import { buildColumnTypeSql } from './planner-ddl-builders';
57
57
  import { resolveIdentityValue } from './planner-identity-values';
58
58
  import { buildSchemaLookupMap, hasForeignKey, hasUniqueConstraint } from './planner-schema-lookup';
59
- import { buildExpectedFormatType, qualifyTableName } from './planner-sql-checks';
59
+ import { buildExpectedFormatType } from './planner-sql-checks';
60
60
  import { buildTargetDetails, type PostgresPlanTargetDetails } from './planner-target-details';
61
61
  import { resolveColumnTypeMetadata } from './planner-type-resolution';
62
62
 
@@ -600,20 +600,12 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
600
600
  continue;
601
601
  }
602
602
 
603
- const qualified = qualifyTableName(schemaForTable, issue.table);
604
603
  calls.push(
605
604
  new AddNotNullColumnDirectCall(
606
605
  schemaForTable,
607
606
  issue.table,
608
607
  issue.column,
609
- buildAddColumnSql(
610
- qualified,
611
- issue.column,
612
- column,
613
- mutableCodecHooks,
614
- undefined,
615
- mutableStorageTypes,
616
- ),
608
+ buildColumnSpec(namespaceId, issue.table, issue.column, ctx),
617
609
  ),
618
610
  );
619
611
  }
@@ -9,7 +9,9 @@ export type OperationClass =
9
9
  | 'unique'
10
10
  | 'index'
11
11
  | 'foreignKey'
12
- | 'checkConstraint';
12
+ | 'checkConstraint'
13
+ | 'rlsPolicy'
14
+ | 'rowLevelSecurity';
13
15
 
14
16
  export interface PostgresPlanTargetDetails {
15
17
  readonly schema: string;