@prisma/orm-target-postgres 0.16.0-dev.36
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/LICENSE +201 -0
- package/README.md +23 -0
- package/dist/adapter-CWvVo0PV.d.mts +22 -0
- package/dist/adapter-CWvVo0PV.d.mts.map +1 -0
- package/dist/adapter-DhqcO3YO-jzwDVcCv.mjs +70 -0
- package/dist/adapter-DhqcO3YO-jzwDVcCv.mjs.map +1 -0
- package/dist/adapter.d.mts +12 -0
- package/dist/adapter.mjs +7 -0
- package/dist/adapter__adapter.d.mts +2 -0
- package/dist/adapter__adapter.mjs +2 -0
- package/dist/adapter__column-types.d.mts +2 -0
- package/dist/adapter__column-types.mjs +2 -0
- package/dist/adapter__control.d.mts +5 -0
- package/dist/adapter__control.mjs +441 -0
- package/dist/adapter__control.mjs.map +1 -0
- package/dist/adapter__operation-types.d.mts +2 -0
- package/dist/adapter__operation-types.mjs +1 -0
- package/dist/adapter__runtime.d.mts +2 -0
- package/dist/adapter__runtime.mjs +38 -0
- package/dist/adapter__runtime.mjs.map +1 -0
- package/dist/adapter__types.d.mts +4 -0
- package/dist/adapter__types.mjs +1 -0
- package/dist/authoring-03FwIL2k-kPjZAp3s.mjs +757 -0
- package/dist/authoring-03FwIL2k-kPjZAp3s.mjs.map +1 -0
- package/dist/canonicalize-CQlxSR4D-MnrT5gtW.d.mts +34 -0
- package/dist/canonicalize-CQlxSR4D-MnrT5gtW.d.mts.map +1 -0
- package/dist/canonicalize-CrN3u_xM-D4MrOqcB.mjs +59 -0
- package/dist/canonicalize-CrN3u_xM-D4MrOqcB.mjs.map +1 -0
- package/dist/codec-descriptor-Bec_zukr-BfvtUqhm.d.mts +37 -0
- package/dist/codec-descriptor-Bec_zukr-BfvtUqhm.d.mts.map +1 -0
- package/dist/codec-descriptor-Bg-_412t.d.mts +1 -0
- package/dist/codec-descriptor-CB6x0xEu-C-Zn2IEz.mjs +135 -0
- package/dist/codec-descriptor-CB6x0xEu-C-Zn2IEz.mjs.map +1 -0
- package/dist/codec-ids-CsOU3xDv-mPI0INiF.d.mts +33 -0
- package/dist/codec-ids-CsOU3xDv-mPI0INiF.d.mts.map +1 -0
- package/dist/codec-ids-fgafMLR7.d.mts +1 -0
- package/dist/codec-ids-tX7Svtz--BuOfRaVU.mjs +33 -0
- package/dist/codec-ids-tX7Svtz--BuOfRaVU.mjs.map +1 -0
- package/dist/codec-types-BHwY9Ff-.d.mts +2 -0
- package/dist/codec-types-mWhnDEAf-CAQzGFnF.d.mts +82 -0
- package/dist/codec-types-mWhnDEAf-CAQzGFnF.d.mts.map +1 -0
- package/dist/codecs-CBl0nD44-CaWPc1bd.mjs +1486 -0
- package/dist/codecs-CBl0nD44-CaWPc1bd.mjs.map +1 -0
- package/dist/codecs-CHIVTNZs.mjs +15 -0
- package/dist/codecs-CHIVTNZs.mjs.map +1 -0
- package/dist/codecs-Cvqvb9pV-Cdc51FKY.d.mts +589 -0
- package/dist/codecs-Cvqvb9pV-Cdc51FKY.d.mts.map +1 -0
- package/dist/codecs-DBn-uIv-.d.mts +15 -0
- package/dist/codecs-DBn-uIv-.d.mts.map +1 -0
- package/dist/column-types-BH-MU6uU.d.mts +108 -0
- package/dist/column-types-BH-MU6uU.d.mts.map +1 -0
- package/dist/column-types-CwAyCgp2.mjs +126 -0
- package/dist/column-types-CwAyCgp2.mjs.map +1 -0
- package/dist/contract-free-BZUS18FU.mjs +118 -0
- package/dist/contract-free-BZUS18FU.mjs.map +1 -0
- package/dist/contract-free-DzpGxLfb.d.mts +258 -0
- package/dist/contract-free-DzpGxLfb.d.mts.map +1 -0
- package/dist/control-2F195jJk.d.mts +11 -0
- package/dist/control-2F195jJk.d.mts.map +1 -0
- package/dist/control-6WFTtLAM.mjs +60 -0
- package/dist/control-6WFTtLAM.mjs.map +1 -0
- package/dist/control-B-FTbEnE.d.mts +22 -0
- package/dist/control-B-FTbEnE.d.mts.map +1 -0
- package/dist/control-D85zv_aY.d.mts +199 -0
- package/dist/control-D85zv_aY.d.mts.map +1 -0
- package/dist/control-DRMWptiJ.mjs +1355 -0
- package/dist/control-DRMWptiJ.mjs.map +1 -0
- package/dist/control-adapter-B9t0g7Pf-HrCxlM65.mjs +1901 -0
- package/dist/control-adapter-B9t0g7Pf-HrCxlM65.mjs.map +1 -0
- package/dist/control-policy-Cl3RRSPg-CT3QGpR3.mjs +1257 -0
- package/dist/control-policy-Cl3RRSPg-CT3QGpR3.mjs.map +1 -0
- package/dist/data-transform-BSM7TQpE.d.mts +1 -0
- package/dist/data-transform-ChZzy2dP-CxVCat_8.mjs +53 -0
- package/dist/data-transform-ChZzy2dP-CxVCat_8.mjs.map +1 -0
- package/dist/data-transform-Ck5FbyNO-BNE7QlAo.d.mts +38 -0
- package/dist/data-transform-Ck5FbyNO-BNE7QlAo.d.mts.map +1 -0
- package/dist/ddl-B4Rh8ntj.d.mts +1 -0
- package/dist/ddl-BdP4q3KY-Duef4uHw.mjs +395 -0
- package/dist/ddl-BdP4q3KY-Duef4uHw.mjs.map +1 -0
- package/dist/default-normalizer-B36TOul4.d.mts +31 -0
- package/dist/default-normalizer-B36TOul4.d.mts.map +1 -0
- package/dist/default-normalizer-Ba5ZebHM-isV86rjy.mjs +253 -0
- package/dist/default-normalizer-Ba5ZebHM-isV86rjy.mjs.map +1 -0
- package/dist/descriptor-meta-2IlVC6_d-DnV00Qd_.mjs +351 -0
- package/dist/descriptor-meta-2IlVC6_d-DnV00Qd_.mjs.map +1 -0
- package/dist/descriptor-meta-DnHabAS3-C53XXPQw.mjs +28 -0
- package/dist/descriptor-meta-DnHabAS3-C53XXPQw.mjs.map +1 -0
- package/dist/descriptor-meta-runtime-UwYdV8fD-B_m_Ha8r.mjs +13 -0
- package/dist/descriptor-meta-runtime-UwYdV8fD-B_m_Ha8r.mjs.map +1 -0
- package/dist/diff-database-schema-BfRs2vrG-D2o4KII0.mjs +390 -0
- package/dist/diff-database-schema-BfRs2vrG-D2o4KII0.mjs.map +1 -0
- package/dist/diff-database-schema-DsoNznGC.d.mts +38 -0
- package/dist/diff-database-schema-DsoNznGC.d.mts.map +1 -0
- package/dist/driver.d.mts +3 -0
- package/dist/driver.mjs +2 -0
- package/dist/driver__control.d.mts +2 -0
- package/dist/driver__control.mjs +2 -0
- package/dist/driver__runtime.d.mts +2 -0
- package/dist/driver__runtime.mjs +597 -0
- package/dist/driver__runtime.mjs.map +1 -0
- package/dist/errors-DCcVX4j1.d.mts +32 -0
- package/dist/errors-DCcVX4j1.d.mts.map +1 -0
- package/dist/errors-kYxOQjdD-D4T3rbVp.mjs +39 -0
- package/dist/errors-kYxOQjdD-D4T3rbVp.mjs.map +1 -0
- package/dist/issue-planner-jkY6p-Ca.d.mts +105 -0
- package/dist/issue-planner-jkY6p-Ca.d.mts.map +1 -0
- package/dist/migration-CBSAPE8S.d.mts +25 -0
- package/dist/migration-CBSAPE8S.d.mts.map +1 -0
- package/dist/migration-PTHAY9Cv.mjs +21 -0
- package/dist/migration-PTHAY9Cv.mjs.map +1 -0
- package/dist/namespace-ids-xtp_ZY86-Cpp8PTxD.mjs +7 -0
- package/dist/namespace-ids-xtp_ZY86-Cpp8PTxD.mjs.map +1 -0
- package/dist/native-type-normalizer-DqJlOTeX.d.mts +19 -0
- package/dist/native-type-normalizer-DqJlOTeX.d.mts.map +1 -0
- package/dist/native-type-normalizer-yvd3tqv-.mjs +38 -0
- package/dist/native-type-normalizer-yvd3tqv-.mjs.map +1 -0
- package/dist/nodes-BU-V-aZA-RqwPH68u.d.mts +235 -0
- package/dist/nodes-BU-V-aZA-RqwPH68u.d.mts.map +1 -0
- package/dist/nodes-DAvDJLvJ-BBdvFXNa.mjs +255 -0
- package/dist/nodes-DAvDJLvJ-BBdvFXNa.mjs.map +1 -0
- package/dist/normalize-error-CNjgVO71-CLlZ3CuJ.mjs +121 -0
- package/dist/normalize-error-CNjgVO71-CLlZ3CuJ.mjs.map +1 -0
- package/dist/op-factory-call-DifL68KT-Bo6pxs-R.mjs +2009 -0
- package/dist/op-factory-call-DifL68KT-Bo6pxs-R.mjs.map +1 -0
- package/dist/op-factory-call-FEA5mnWH.d.mts +1 -0
- package/dist/op-factory-call-jd4cgfjA-CyNT4YVn.d.mts +521 -0
- package/dist/op-factory-call-jd4cgfjA-CyNT4YVn.d.mts.map +1 -0
- package/dist/operation-types-BfRocNI8.d.mts +22 -0
- package/dist/operation-types-BfRocNI8.d.mts.map +1 -0
- package/dist/pack-DFM4tmob.d.mts +610 -0
- package/dist/pack-DFM4tmob.d.mts.map +1 -0
- package/dist/planner-Bmje9ii8-B99fEsNM.mjs +588 -0
- package/dist/planner-Bmje9ii8-B99fEsNM.mjs.map +1 -0
- package/dist/planner-DL4Bh5Nk.d.mts +163 -0
- package/dist/planner-DL4Bh5Nk.d.mts.map +1 -0
- package/dist/planner-ddl-builders-CbfICQfm-CxYiLrhX.mjs +102 -0
- package/dist/planner-ddl-builders-CbfICQfm-CxYiLrhX.mjs.map +1 -0
- package/dist/planner-ddl-builders-ijp60-JO.d.mts +19 -0
- package/dist/planner-ddl-builders-ijp60-JO.d.mts.map +1 -0
- package/dist/planner-identity-values-BmHn5C6U-BYBbUAyR.mjs +90 -0
- package/dist/planner-identity-values-BmHn5C6U-BYBbUAyR.mjs.map +1 -0
- package/dist/planner-identity-values-TfEhnoWa.d.mts +25 -0
- package/dist/planner-identity-values-TfEhnoWa.d.mts.map +1 -0
- package/dist/planner-produced-postgres-migration-B0PQLLUb-qsS6cQqX.d.mts +25 -0
- package/dist/planner-produced-postgres-migration-B0PQLLUb-qsS6cQqX.d.mts.map +1 -0
- package/dist/planner-produced-postgres-migration-C3GPZWeC.d.mts +1 -0
- package/dist/planner-produced-postgres-migration-Cicg2sXg-Ck9iDxGI.mjs +47 -0
- package/dist/planner-produced-postgres-migration-Cicg2sXg-Ck9iDxGI.mjs.map +1 -0
- package/dist/planner-schema-lookup-C4DHAddq.d.mts +21 -0
- package/dist/planner-schema-lookup-C4DHAddq.d.mts.map +1 -0
- package/dist/planner-schema-lookup-kMIVP2y--CdO4VlnF.mjs +57 -0
- package/dist/planner-schema-lookup-kMIVP2y--CdO4VlnF.mjs.map +1 -0
- package/dist/planner-sql-checks-CFgbRTJw.d.mts +20 -0
- package/dist/planner-sql-checks-CFgbRTJw.d.mts.map +1 -0
- package/dist/planner-sql-checks-DRc_78n8-C9nHOrvU.mjs +152 -0
- package/dist/planner-sql-checks-DRc_78n8-C9nHOrvU.mjs.map +1 -0
- package/dist/planner-target-details-HkP4RrRO-_xPQVPF0.d.mts +12 -0
- package/dist/planner-target-details-HkP4RrRO-_xPQVPF0.d.mts.map +1 -0
- package/dist/planner-type-resolution-Bt2f_q-F-C4T6YTae.mjs +15 -0
- package/dist/planner-type-resolution-Bt2f_q-F-C4T6YTae.mjs.map +1 -0
- package/dist/postgres-contract-view-2OP5JnXl-D2fpy4-8.d.mts +36 -0
- package/dist/postgres-contract-view-2OP5JnXl-D2fpy4-8.d.mts.map +1 -0
- package/dist/postgres-contract-view-FdLX7dV9-Cat1VKV9.mjs +144 -0
- package/dist/postgres-contract-view-FdLX7dV9-Cat1VKV9.mjs.map +1 -0
- package/dist/postgres-database-schema-node-CyfpHXDr-BqyozkQE.d.mts +285 -0
- package/dist/postgres-database-schema-node-CyfpHXDr-BqyozkQE.d.mts.map +1 -0
- package/dist/postgres-migration-CDJj7CvD-Bsqs_fVo.d.mts +260 -0
- package/dist/postgres-migration-CDJj7CvD-Bsqs_fVo.d.mts.map +1 -0
- package/dist/postgres-migration-CJshdIZn-DYYjgdD0.mjs +201 -0
- package/dist/postgres-migration-CJshdIZn-DYYjgdD0.mjs.map +1 -0
- package/dist/postgres-native-enum-CdP3b_-R-Cgz9KEKx.mjs +45 -0
- package/dist/postgres-native-enum-CdP3b_-R-Cgz9KEKx.mjs.map +1 -0
- package/dist/postgres-rls-policy-CnDxf3z9-C3LIHAIG.d.mts +61 -0
- package/dist/postgres-rls-policy-CnDxf3z9-C3LIHAIG.d.mts.map +1 -0
- package/dist/postgres-role-schema-node-B8Z5i5D--PObwlU2Z.mjs +204 -0
- package/dist/postgres-role-schema-node-B8Z5i5D--PObwlU2Z.mjs.map +1 -0
- package/dist/postgres-schema-KSzLDONj-CsXGoY8b.mjs +421 -0
- package/dist/postgres-schema-KSzLDONj-CsXGoY8b.mjs.map +1 -0
- package/dist/postgres-schema-Xsjt3RKO-Buv2X8ai.d.mts +269 -0
- package/dist/postgres-schema-Xsjt3RKO-Buv2X8ai.d.mts.map +1 -0
- package/dist/postgres-table-schema-node-BBkitBQh-DT1T5VWU.mjs +212 -0
- package/dist/postgres-table-schema-node-BBkitBQh-DT1T5VWU.mjs.map +1 -0
- package/dist/render-ops-DPwhZt2W.d.mts +11 -0
- package/dist/render-ops-DPwhZt2W.d.mts.map +1 -0
- package/dist/render-ops-DxzTxSsv-Bz85uUHk.mjs +34 -0
- package/dist/render-ops-DxzTxSsv-Bz85uUHk.mjs.map +1 -0
- package/dist/render-typescript-6sCFvMBh.d.mts +21 -0
- package/dist/render-typescript-6sCFvMBh.d.mts.map +1 -0
- package/dist/render-typescript-Cw3lFO28-Cx0lXWmv.mjs +117 -0
- package/dist/render-typescript-Cw3lFO28-Cx0lXWmv.mjs.map +1 -0
- package/dist/resolve-ddl-schema-CxqlLW47-0GiHRziS.mjs +22 -0
- package/dist/resolve-ddl-schema-CxqlLW47-0GiHRziS.mjs.map +1 -0
- package/dist/runtime-BbSRVwdd.d.mts +12 -0
- package/dist/runtime-BbSRVwdd.d.mts.map +1 -0
- package/dist/runtime-CDewySU9.d.mts +55 -0
- package/dist/runtime-CDewySU9.d.mts.map +1 -0
- package/dist/runtime-_TfO66h5.d.mts +32 -0
- package/dist/runtime-_TfO66h5.d.mts.map +1 -0
- package/dist/schema-ir-annotations-DuuYh0gl.d.mts +15 -0
- package/dist/schema-ir-annotations-DuuYh0gl.d.mts.map +1 -0
- package/dist/schema-node-kinds-qlnURWJy-Bo30pwTM.d.mts +30 -0
- package/dist/schema-node-kinds-qlnURWJy-Bo30pwTM.d.mts.map +1 -0
- package/dist/sql-utils-BnBlQxyh-DaX9Nb7Z.mjs +90 -0
- package/dist/sql-utils-BnBlQxyh-DaX9Nb7Z.mjs.map +1 -0
- package/dist/sql-utils-DW6fkczF.d.mts +62 -0
- package/dist/sql-utils-DW6fkczF.d.mts.map +1 -0
- package/dist/table-source-ikHOZmhD-DriY1Lcr.d.mts +15 -0
- package/dist/table-source-ikHOZmhD-DriY1Lcr.d.mts.map +1 -0
- package/dist/target.d.mts +47 -0
- package/dist/target.mjs +34 -0
- package/dist/target__codec-descriptor.d.mts +3 -0
- package/dist/target__codec-descriptor.mjs +2 -0
- package/dist/target__codec-ids.d.mts +3 -0
- package/dist/target__codec-ids.mjs +2 -0
- package/dist/target__codec-types.d.mts +4 -0
- package/dist/target__codec-types.mjs +1 -0
- package/dist/target__codecs.d.mts +3 -0
- package/dist/target__codecs.mjs +3 -0
- package/dist/target__contract-free.d.mts +3 -0
- package/dist/target__contract-free.mjs +4 -0
- package/dist/target__control.d.mts +2 -0
- package/dist/target__control.mjs +2 -0
- package/dist/target__data-transform.d.mts +3 -0
- package/dist/target__data-transform.mjs +2 -0
- package/dist/target__ddl.d.mts +4 -0
- package/dist/target__ddl.mjs +2 -0
- package/dist/target__default-normalizer.d.mts +2 -0
- package/dist/target__default-normalizer.mjs +2 -0
- package/dist/target__diff-database-schema.d.mts +2 -0
- package/dist/target__diff-database-schema.mjs +2 -0
- package/dist/target__errors.d.mts +2 -0
- package/dist/target__errors.mjs +2 -0
- package/dist/target__issue-planner.d.mts +2 -0
- package/dist/target__issue-planner.mjs +2 -0
- package/dist/target__migration.d.mts +4 -0
- package/dist/target__migration.mjs +5 -0
- package/dist/target__native-type-normalizer.d.mts +2 -0
- package/dist/target__native-type-normalizer.mjs +2 -0
- package/dist/target__op-factory-call.d.mts +3 -0
- package/dist/target__op-factory-call.mjs +2 -0
- package/dist/target__pack.d.mts +2 -0
- package/dist/target__pack.mjs +2 -0
- package/dist/target__planner-ddl-builders.d.mts +2 -0
- package/dist/target__planner-ddl-builders.mjs +2 -0
- package/dist/target__planner-identity-values.d.mts +2 -0
- package/dist/target__planner-identity-values.mjs +2 -0
- package/dist/target__planner-produced-postgres-migration.d.mts +3 -0
- package/dist/target__planner-produced-postgres-migration.mjs +2 -0
- package/dist/target__planner-schema-lookup.d.mts +2 -0
- package/dist/target__planner-schema-lookup.mjs +2 -0
- package/dist/target__planner-sql-checks.d.mts +2 -0
- package/dist/target__planner-sql-checks.mjs +2 -0
- package/dist/target__planner-target-details.d.mts +2 -0
- package/dist/target__planner-target-details.mjs +1 -0
- package/dist/target__planner.d.mts +2 -0
- package/dist/target__planner.mjs +3 -0
- package/dist/target__render-ops.d.mts +2 -0
- package/dist/target__render-ops.mjs +2 -0
- package/dist/target__render-typescript.d.mts +2 -0
- package/dist/target__render-typescript.mjs +2 -0
- package/dist/target__rls-canonicalize.d.mts +2 -0
- package/dist/target__rls-canonicalize.mjs +2 -0
- package/dist/target__runtime.d.mts +3 -0
- package/dist/target__runtime.mjs +23 -0
- package/dist/target__runtime.mjs.map +1 -0
- package/dist/target__schema-ir-annotations.d.mts +2 -0
- package/dist/target__schema-ir-annotations.mjs +2 -0
- package/dist/target__sql-utils.d.mts +2 -0
- package/dist/target__sql-utils.mjs +2 -0
- package/dist/target__types.d.mts +7 -0
- package/dist/target__types.mjs +5 -0
- package/dist/types-CkryN1AS-6MNZLjLY.d.mts +27 -0
- package/dist/types-CkryN1AS-6MNZLjLY.d.mts.map +1 -0
- package/dist/types-Cp_Jf2tk.d.mts +1 -0
- package/dist/types-ue6jkJyF-BQJzjx1P.d.mts +10 -0
- package/dist/types-ue6jkJyF-BQJzjx1P.d.mts.map +1 -0
- package/dist/types-xa86TkcV.d.mts +32 -0
- package/dist/types-xa86TkcV.d.mts.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,1355 @@
|
|
|
1
|
+
import { n as postgresError } from "./errors-kYxOQjdD-D4T3rbVp.mjs";
|
|
2
|
+
import { n as postgresResolveDefault } from "./default-normalizer-Ba5ZebHM-isV86rjy.mjs";
|
|
3
|
+
import { t as postgresTargetDescriptorMeta } from "./descriptor-meta-DnHabAS3-C53XXPQw.mjs";
|
|
4
|
+
import { a as postgresDiffSubjectGranularity, i as postgresDiffSubjectEntityKind } from "./postgres-table-schema-node-BBkitBQh-DT1T5VWU.mjs";
|
|
5
|
+
import { t as PostgresDatabaseSchemaNode } from "./postgres-role-schema-node-B8Z5i5D--PObwlU2Z.mjs";
|
|
6
|
+
import { r as renderDefaultLiteral } from "./planner-ddl-builders-CbfICQfm-CxYiLrhX.mjs";
|
|
7
|
+
import { n as contractToPostgresDatabaseSchemaNode, r as diffPostgresSchema } from "./diff-database-schema-BfRs2vrG-D2o4KII0.mjs";
|
|
8
|
+
import { t as PostgresContractSerializer } from "./postgres-contract-view-FdLX7dV9-Cat1VKV9.mjs";
|
|
9
|
+
import { t as createPostgresMigrationPlanner } from "./planner-Bmje9ii8-B99fEsNM.mjs";
|
|
10
|
+
import { blindCast } from "@prisma/orm-framework/utils/casts";
|
|
11
|
+
import { UNBOUND_NAMESPACE_ID, coordinateKey, elementCoordinates } from "@prisma/orm-framework/components/ir";
|
|
12
|
+
import { ifDefined } from "@prisma/orm-framework/utils/defined";
|
|
13
|
+
import { computeIndexContentHash, parseWireName } from "@prisma/orm-family-sql/schema-ir/naming";
|
|
14
|
+
import { assertDefined } from "@prisma/orm-framework/utils/assertions";
|
|
15
|
+
import { APP_SPACE_ID } from "@prisma/orm-framework/components/control";
|
|
16
|
+
import { buildNativeTypeExpander, runnerFailure, runnerSuccess } from "@prisma/orm-family-sql/family/control";
|
|
17
|
+
import { SqlSchemaIR, SqlTableIR } from "@prisma/orm-family-sql/schema-ir/types";
|
|
18
|
+
import { InternalError } from "@prisma/orm-framework/utils/internal-error";
|
|
19
|
+
import { notOk, ok, okVoid } from "@prisma/orm-framework/utils/result";
|
|
20
|
+
import { SqlSchemaVerifierBase } from "@prisma/orm-family-sql/family/ir";
|
|
21
|
+
import { SqlQueryError } from "@prisma/orm-family-sql/errors";
|
|
22
|
+
import { buildChildRelationField, deriveRelationFieldName, inferRelations, mapDefault, parseRawDefault, toEnumMemberName, toEnumName, toFieldName, toModelName } from "@prisma/orm-family-sql/family/psl-infer";
|
|
23
|
+
import { UNSPECIFIED_PSL_NAMESPACE_ID, makePslNamespace, makePslNamespaceEntries } from "@prisma/orm-framework/components/psl-ast";
|
|
24
|
+
import { isColumnDefault } from "@prisma/orm-framework/contract/types";
|
|
25
|
+
//#region ../../../3-targets/3-targets/postgres/dist/control.mjs
|
|
26
|
+
const LOCK_DOMAIN = "prisma_next.contract.marker";
|
|
27
|
+
/**
|
|
28
|
+
* Deep clones and freezes a record object to prevent mutation.
|
|
29
|
+
* Recursively clones nested objects and arrays to ensure complete isolation.
|
|
30
|
+
*/
|
|
31
|
+
function cloneAndFreezeRecord(value) {
|
|
32
|
+
const cloned = {};
|
|
33
|
+
for (const [key, val] of Object.entries(value)) if (val === null || val === void 0) cloned[key] = val;
|
|
34
|
+
else if (Array.isArray(val)) cloned[key] = Object.freeze([...val]);
|
|
35
|
+
else if (typeof val === "object") cloned[key] = cloneAndFreezeRecord(val);
|
|
36
|
+
else cloned[key] = val;
|
|
37
|
+
return Object.freeze(cloned);
|
|
38
|
+
}
|
|
39
|
+
function createPostgresMigrationRunner(family) {
|
|
40
|
+
return new PostgresMigrationRunner(family);
|
|
41
|
+
}
|
|
42
|
+
var PostgresMigrationRunner = class {
|
|
43
|
+
family;
|
|
44
|
+
constructor(family) {
|
|
45
|
+
this.family = family;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Body of the migration runner without transaction management. The
|
|
49
|
+
* caller ({@link PostgresMigrationRunner.execute}) owns the
|
|
50
|
+
* `BEGIN`/`COMMIT`/`ROLLBACK` lifecycle.
|
|
51
|
+
*/
|
|
52
|
+
async executeOnConnection(options) {
|
|
53
|
+
const schema = options.schemaName ?? Object.keys(options.destinationContract.storage.namespaces).find((id) => id !== UNBOUND_NAMESPACE_ID) ?? UNBOUND_NAMESPACE_ID;
|
|
54
|
+
const driver = options.driver;
|
|
55
|
+
if (options.space !== void 0 && options.space !== options.plan.spaceId) throw postgresError("MIGRATION.CONTRACT_SPACE_VIOLATION", `SqlMigrationRunner: options.space (${options.space}) does not match plan.spaceId (${options.plan.spaceId})`, { meta: {
|
|
56
|
+
space: options.space,
|
|
57
|
+
planSpaceId: options.plan.spaceId
|
|
58
|
+
} });
|
|
59
|
+
const space = options.plan.spaceId;
|
|
60
|
+
const lockKey = `${LOCK_DOMAIN}:${schema}:${space}`;
|
|
61
|
+
const planOps = blindCast(await Promise.all(options.plan.operations));
|
|
62
|
+
const destinationCheck = this.ensurePlanMatchesDestinationContract(options.plan.destination, options.destinationContract);
|
|
63
|
+
if (!destinationCheck.ok) return destinationCheck;
|
|
64
|
+
const policyCheck = this.enforcePolicyCompatibility(options.policy, planOps);
|
|
65
|
+
if (!policyCheck.ok) return policyCheck;
|
|
66
|
+
await this.acquireLock(driver, lockKey);
|
|
67
|
+
const ensureResult = await this.ensureControlTables(driver, options.destinationContract);
|
|
68
|
+
if (!ensureResult.ok) return ensureResult;
|
|
69
|
+
const existingMarker = await this.family.readMarker({
|
|
70
|
+
driver,
|
|
71
|
+
space
|
|
72
|
+
});
|
|
73
|
+
const markerCheck = this.ensureMarkerCompatibility(existingMarker, options.plan);
|
|
74
|
+
if (!markerCheck.ok) return markerCheck;
|
|
75
|
+
const markerAtDestination = this.markerMatchesDestination(existingMarker, options.plan);
|
|
76
|
+
const isSelfEdge = options.plan.origin?.storageHash === options.plan.destination.storageHash;
|
|
77
|
+
const skipOperations = markerAtDestination && options.plan.origin != null && !isSelfEdge;
|
|
78
|
+
let applyValue;
|
|
79
|
+
if (skipOperations) applyValue = {
|
|
80
|
+
operationsExecuted: 0,
|
|
81
|
+
executedOperations: []
|
|
82
|
+
};
|
|
83
|
+
else {
|
|
84
|
+
const applyResult = await this.applyPlan(driver, options, planOps);
|
|
85
|
+
if (!applyResult.ok) return applyResult;
|
|
86
|
+
applyValue = applyResult.value;
|
|
87
|
+
}
|
|
88
|
+
if (space === APP_SPACE_ID) {
|
|
89
|
+
const schemaNode = await this.family.introspect({
|
|
90
|
+
driver,
|
|
91
|
+
contract: options.destinationContract
|
|
92
|
+
});
|
|
93
|
+
const schemaVerifyResult = this.family.verifySchema({
|
|
94
|
+
contract: options.destinationContract,
|
|
95
|
+
schema: schemaNode,
|
|
96
|
+
strict: options.strictVerification ?? true,
|
|
97
|
+
frameworkComponents: options.frameworkComponents
|
|
98
|
+
});
|
|
99
|
+
if (!schemaVerifyResult.ok) return runnerFailure("MIGRATION.SCHEMA_VERIFY_FAILED", schemaVerifyResult.summary, {
|
|
100
|
+
why: "The resulting database schema does not satisfy the destination contract.",
|
|
101
|
+
meta: { issues: schemaVerifyResult.schema.issues }
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const incomingInvariants = options.plan.providedInvariants ?? [];
|
|
105
|
+
const existingInvariants = new Set(existingMarker?.invariants ?? []);
|
|
106
|
+
const incomingIsSubsetOfExisting = incomingInvariants.every((id) => existingInvariants.has(id));
|
|
107
|
+
if (!(isSelfEdge && applyValue.operationsExecuted === 0 && incomingIsSubsetOfExisting)) {
|
|
108
|
+
const markerResult = await this.upsertMarker(driver, options, existingMarker, space);
|
|
109
|
+
if (!markerResult.ok) return markerResult;
|
|
110
|
+
await this.recordLedgerEntries(driver, options, applyValue.executedOperations, planOps.length);
|
|
111
|
+
}
|
|
112
|
+
return runnerSuccess({
|
|
113
|
+
operationsPlanned: planOps.length,
|
|
114
|
+
operationsExecuted: applyValue.operationsExecuted
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async execute(options) {
|
|
118
|
+
const driver = options.driver;
|
|
119
|
+
const perSpaceOptions = options.perSpaceOptions;
|
|
120
|
+
if (perSpaceOptions.length === 0) return ok({ perSpaceResults: [] });
|
|
121
|
+
await this.beginTransaction(driver);
|
|
122
|
+
let committed = false;
|
|
123
|
+
try {
|
|
124
|
+
const perSpaceResults = [];
|
|
125
|
+
for (const spaceOptions of perSpaceOptions) {
|
|
126
|
+
const space = spaceOptions.space ?? spaceOptions.plan.spaceId;
|
|
127
|
+
const result = await this.executeOnConnection({
|
|
128
|
+
...spaceOptions,
|
|
129
|
+
driver,
|
|
130
|
+
space
|
|
131
|
+
});
|
|
132
|
+
if (!result.ok) return notOk({
|
|
133
|
+
...result.failure,
|
|
134
|
+
failingSpace: space
|
|
135
|
+
});
|
|
136
|
+
perSpaceResults.push({
|
|
137
|
+
space,
|
|
138
|
+
value: result.value
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
await this.commitTransaction(driver);
|
|
142
|
+
committed = true;
|
|
143
|
+
return ok({ perSpaceResults });
|
|
144
|
+
} finally {
|
|
145
|
+
if (!committed) await this.rollbackTransaction(driver);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async applyPlan(driver, options, ops) {
|
|
149
|
+
const checks = options.executionChecks;
|
|
150
|
+
const runPrechecks = checks?.prechecks !== false;
|
|
151
|
+
const runPostchecks = checks?.postchecks !== false;
|
|
152
|
+
const runIdempotency = checks?.idempotencyChecks !== false;
|
|
153
|
+
let operationsExecuted = 0;
|
|
154
|
+
const executedOperations = [];
|
|
155
|
+
for (const operation of ops) {
|
|
156
|
+
options.callbacks?.onOperationStart?.(operation);
|
|
157
|
+
try {
|
|
158
|
+
if (runPostchecks && runIdempotency) {
|
|
159
|
+
if (await this.expectationsAreSatisfied(driver, operation.postcheck)) {
|
|
160
|
+
executedOperations.push(this.createPostcheckPreSatisfiedSkipRecord(operation));
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (runPrechecks) {
|
|
165
|
+
const precheckResult = await this.runExpectationSteps(driver, operation.precheck, operation, "precheck");
|
|
166
|
+
if (!precheckResult.ok) return precheckResult;
|
|
167
|
+
}
|
|
168
|
+
const executeResult = await this.runExecuteSteps(driver, operation.execute, operation);
|
|
169
|
+
if (!executeResult.ok) return executeResult;
|
|
170
|
+
if (runPostchecks) {
|
|
171
|
+
const postcheckResult = await this.runExpectationSteps(driver, operation.postcheck, operation, "postcheck");
|
|
172
|
+
if (!postcheckResult.ok) return postcheckResult;
|
|
173
|
+
}
|
|
174
|
+
executedOperations.push(operation);
|
|
175
|
+
operationsExecuted += 1;
|
|
176
|
+
} finally {
|
|
177
|
+
options.callbacks?.onOperationComplete?.(operation);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return ok({
|
|
181
|
+
operationsExecuted,
|
|
182
|
+
executedOperations
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async ensureControlTables(driver, contract) {
|
|
186
|
+
const lowererContext = { contract };
|
|
187
|
+
const [schemaQuery, ...tableQueries] = this.family.bootstrapControlTableQueries();
|
|
188
|
+
if (schemaQuery === void 0) throw new InternalError("Postgres control-table bootstrap must include CREATE SCHEMA");
|
|
189
|
+
await this.executeStatement(driver, await this.family.lowerAst(schemaQuery, lowererContext));
|
|
190
|
+
const legacyDetection = await this.detectLegacyMarkerShape(driver);
|
|
191
|
+
if (!legacyDetection.ok) return legacyDetection;
|
|
192
|
+
for (const query of tableQueries) await this.executeStatement(driver, await this.family.lowerAst(query, lowererContext));
|
|
193
|
+
return okVoid();
|
|
194
|
+
}
|
|
195
|
+
async detectLegacyMarkerShape(driver) {
|
|
196
|
+
const result = await driver.query(`select column_name
|
|
197
|
+
from information_schema.columns
|
|
198
|
+
where table_schema = 'prisma_contract'
|
|
199
|
+
and table_name = 'marker'`);
|
|
200
|
+
if (result.rows.length === 0) return okVoid();
|
|
201
|
+
const columns = new Set(result.rows.map((row) => row.column_name));
|
|
202
|
+
if (columns.has("space")) return okVoid();
|
|
203
|
+
return runnerFailure("MIGRATION.LEGACY_MARKER_SHAPE", "Legacy marker-table shape detected on prisma_contract.marker (no `space` column). Prisma Next is in pre-1.0; the previous transitional auto-migration to the per-space-row schema has been removed. Drop `prisma_contract.marker` and re-run `dbInit` to reinitialise from a clean baseline.", { meta: {
|
|
204
|
+
table: "prisma_contract.marker",
|
|
205
|
+
columns: [...columns].sort()
|
|
206
|
+
} });
|
|
207
|
+
}
|
|
208
|
+
async runExpectationSteps(driver, steps, operation, phase) {
|
|
209
|
+
for (const step of steps) {
|
|
210
|
+
const result = await driver.query(step.sql, step.params ?? []);
|
|
211
|
+
if (!this.stepResultIsTrue(result.rows)) return runnerFailure(phase === "precheck" ? "MIGRATION.PRECHECK_FAILED" : "MIGRATION.POSTCHECK_FAILED", `Operation ${operation.id} failed during ${phase}: ${step.description}`, { meta: {
|
|
212
|
+
operationId: operation.id,
|
|
213
|
+
phase,
|
|
214
|
+
stepDescription: step.description
|
|
215
|
+
} });
|
|
216
|
+
}
|
|
217
|
+
return okVoid();
|
|
218
|
+
}
|
|
219
|
+
async runExecuteSteps(driver, steps, operation) {
|
|
220
|
+
for (const step of steps) try {
|
|
221
|
+
await driver.query(step.sql, step.params ?? []);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (SqlQueryError.is(error)) return runnerFailure("MIGRATION.EXECUTION_FAILED", `Operation ${operation.id} failed during execution: ${step.description}`, {
|
|
224
|
+
why: error.message,
|
|
225
|
+
meta: {
|
|
226
|
+
operationId: operation.id,
|
|
227
|
+
stepDescription: step.description,
|
|
228
|
+
sql: step.sql,
|
|
229
|
+
sqlState: error.sqlState,
|
|
230
|
+
constraint: error.constraint,
|
|
231
|
+
table: error.table,
|
|
232
|
+
column: error.column,
|
|
233
|
+
detail: error.detail
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
return okVoid();
|
|
239
|
+
}
|
|
240
|
+
stepResultIsTrue(rows) {
|
|
241
|
+
if (!rows || rows.length === 0) return false;
|
|
242
|
+
const firstRow = rows[0];
|
|
243
|
+
const firstValue = firstRow ? Object.values(firstRow)[0] : void 0;
|
|
244
|
+
if (typeof firstValue === "boolean") return firstValue;
|
|
245
|
+
if (typeof firstValue === "number") return firstValue !== 0;
|
|
246
|
+
if (typeof firstValue === "string") {
|
|
247
|
+
const lower = firstValue.toLowerCase();
|
|
248
|
+
if (lower === "t" || lower === "true" || lower === "1") return true;
|
|
249
|
+
if (lower === "f" || lower === "false" || lower === "0") return false;
|
|
250
|
+
return firstValue.length > 0;
|
|
251
|
+
}
|
|
252
|
+
return Boolean(firstValue);
|
|
253
|
+
}
|
|
254
|
+
async expectationsAreSatisfied(driver, steps) {
|
|
255
|
+
if (steps.length === 0) return false;
|
|
256
|
+
for (const step of steps) {
|
|
257
|
+
const result = await driver.query(step.sql, step.params ?? []);
|
|
258
|
+
if (!this.stepResultIsTrue(result.rows)) return false;
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
createPostcheckPreSatisfiedSkipRecord(operation) {
|
|
263
|
+
const clonedMeta = operation.meta ? cloneAndFreezeRecord(operation.meta) : void 0;
|
|
264
|
+
const runnerMeta = Object.freeze({
|
|
265
|
+
skipped: true,
|
|
266
|
+
reason: "postcheck_pre_satisfied"
|
|
267
|
+
});
|
|
268
|
+
const mergedMeta = Object.freeze({
|
|
269
|
+
...clonedMeta ?? {},
|
|
270
|
+
runner: runnerMeta
|
|
271
|
+
});
|
|
272
|
+
const frozenPostcheck = Object.freeze([...operation.postcheck]);
|
|
273
|
+
return Object.freeze({
|
|
274
|
+
id: operation.id,
|
|
275
|
+
label: operation.label,
|
|
276
|
+
...ifDefined("summary", operation.summary),
|
|
277
|
+
operationClass: operation.operationClass,
|
|
278
|
+
target: operation.target,
|
|
279
|
+
precheck: Object.freeze([]),
|
|
280
|
+
execute: Object.freeze([]),
|
|
281
|
+
postcheck: frozenPostcheck,
|
|
282
|
+
...ifDefined("meta", operation.meta || mergedMeta ? mergedMeta : void 0)
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
markerMatchesDestination(marker, plan) {
|
|
286
|
+
if (!marker) return false;
|
|
287
|
+
if (marker.storageHash !== plan.destination.storageHash) return false;
|
|
288
|
+
if (plan.destination.profileHash && marker.profileHash !== plan.destination.profileHash) return false;
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
enforcePolicyCompatibility(policy, operations) {
|
|
292
|
+
const allowedClasses = new Set(policy.allowedOperationClasses);
|
|
293
|
+
for (const operation of operations) if (!allowedClasses.has(operation.operationClass)) return runnerFailure("MIGRATION.POLICY_VIOLATION", `Operation ${operation.id} has class "${operation.operationClass}" which is not allowed by policy.`, {
|
|
294
|
+
why: `Policy only allows: ${policy.allowedOperationClasses.join(", ")}.`,
|
|
295
|
+
meta: {
|
|
296
|
+
operationId: operation.id,
|
|
297
|
+
operationClass: operation.operationClass,
|
|
298
|
+
allowedClasses: policy.allowedOperationClasses
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
return okVoid();
|
|
302
|
+
}
|
|
303
|
+
ensureMarkerCompatibility(marker, plan) {
|
|
304
|
+
const origin = plan.origin ?? null;
|
|
305
|
+
if (!origin) return okVoid();
|
|
306
|
+
if (!marker) return runnerFailure("MIGRATION.MARKER_ORIGIN_MISMATCH", `Missing contract marker: expected origin storage hash ${origin.storageHash}.`, { meta: { expectedOriginStorageHash: origin.storageHash } });
|
|
307
|
+
if (marker.storageHash !== origin.storageHash) return runnerFailure("MIGRATION.MARKER_ORIGIN_MISMATCH", `Existing contract marker (${marker.storageHash}) does not match plan origin (${origin.storageHash}).`, { meta: {
|
|
308
|
+
markerStorageHash: marker.storageHash,
|
|
309
|
+
expectedOriginStorageHash: origin.storageHash
|
|
310
|
+
} });
|
|
311
|
+
if (origin.profileHash && marker.profileHash !== origin.profileHash) return runnerFailure("MIGRATION.MARKER_ORIGIN_MISMATCH", `Existing contract marker profile hash (${marker.profileHash}) does not match plan origin profile hash (${origin.profileHash}).`, { meta: {
|
|
312
|
+
markerProfileHash: marker.profileHash,
|
|
313
|
+
expectedOriginProfileHash: origin.profileHash
|
|
314
|
+
} });
|
|
315
|
+
return okVoid();
|
|
316
|
+
}
|
|
317
|
+
ensurePlanMatchesDestinationContract(destination, contract) {
|
|
318
|
+
if (destination.storageHash !== contract.storage.storageHash) return runnerFailure("MIGRATION.DESTINATION_CONTRACT_MISMATCH", `Plan destination storage hash (${destination.storageHash}) does not match provided contract storage hash (${contract.storage.storageHash}).`, { meta: {
|
|
319
|
+
planStorageHash: destination.storageHash,
|
|
320
|
+
contractStorageHash: contract.storage.storageHash
|
|
321
|
+
} });
|
|
322
|
+
if (destination.profileHash && contract.profileHash && destination.profileHash !== contract.profileHash) return runnerFailure("MIGRATION.DESTINATION_CONTRACT_MISMATCH", `Plan destination profile hash (${destination.profileHash}) does not match provided contract profile hash (${contract.profileHash}).`, { meta: {
|
|
323
|
+
planProfileHash: destination.profileHash,
|
|
324
|
+
contractProfileHash: contract.profileHash
|
|
325
|
+
} });
|
|
326
|
+
return okVoid();
|
|
327
|
+
}
|
|
328
|
+
async upsertMarker(driver, options, existingMarker, space) {
|
|
329
|
+
const destination = {
|
|
330
|
+
storageHash: options.plan.destination.storageHash,
|
|
331
|
+
profileHash: options.plan.destination.profileHash ?? options.destinationContract.profileHash ?? options.plan.destination.storageHash,
|
|
332
|
+
invariants: options.plan.providedInvariants ?? []
|
|
333
|
+
};
|
|
334
|
+
if (!existingMarker) {
|
|
335
|
+
await this.family.initMarker({
|
|
336
|
+
driver,
|
|
337
|
+
space,
|
|
338
|
+
destination
|
|
339
|
+
});
|
|
340
|
+
return okVoid();
|
|
341
|
+
}
|
|
342
|
+
if (!await this.family.updateMarker({
|
|
343
|
+
driver,
|
|
344
|
+
space,
|
|
345
|
+
expectedFrom: existingMarker.storageHash,
|
|
346
|
+
destination
|
|
347
|
+
})) return runnerFailure("MIGRATION.MARKER_CAS_FAILURE", "Marker was modified by another process during migration execution.", { meta: {
|
|
348
|
+
space,
|
|
349
|
+
expectedStorageHash: existingMarker.storageHash,
|
|
350
|
+
destinationStorageHash: options.plan.destination.storageHash
|
|
351
|
+
} });
|
|
352
|
+
return okVoid();
|
|
353
|
+
}
|
|
354
|
+
async recordLedgerEntries(driver, options, executedOperations, planOpsLength) {
|
|
355
|
+
const space = options.plan.spaceId;
|
|
356
|
+
const edges = options.migrationEdges;
|
|
357
|
+
const totalEdgeOps = edges.reduce((sum, edge) => sum + edge.operationCount, 0);
|
|
358
|
+
if (totalEdgeOps !== planOpsLength) throw new InternalError(`Ledger write: plan.operations length (${planOpsLength}) does not match sum of migrationEdges operationCount (${totalEdgeOps})`);
|
|
359
|
+
let offset = 0;
|
|
360
|
+
for (const edge of edges) {
|
|
361
|
+
const edgeOps = executedOperations.slice(offset, offset + edge.operationCount);
|
|
362
|
+
offset += edge.operationCount;
|
|
363
|
+
await this.family.writeLedgerEntry({
|
|
364
|
+
driver,
|
|
365
|
+
space,
|
|
366
|
+
entry: {
|
|
367
|
+
edgeId: `${edge.from}->${edge.to}`,
|
|
368
|
+
from: edge.from,
|
|
369
|
+
to: edge.to,
|
|
370
|
+
migrationName: edge.dirName,
|
|
371
|
+
migrationHash: edge.migrationHash,
|
|
372
|
+
operations: edgeOps,
|
|
373
|
+
...ifDefined("destinationContractJson", edge.destinationContractJson)
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async acquireLock(driver, key) {
|
|
379
|
+
await driver.query("select pg_advisory_xact_lock(hashtext($1))", [key]);
|
|
380
|
+
}
|
|
381
|
+
async beginTransaction(driver) {
|
|
382
|
+
await driver.query("BEGIN");
|
|
383
|
+
}
|
|
384
|
+
async commitTransaction(driver) {
|
|
385
|
+
await driver.query("COMMIT");
|
|
386
|
+
}
|
|
387
|
+
async rollbackTransaction(driver) {
|
|
388
|
+
await driver.query("ROLLBACK");
|
|
389
|
+
}
|
|
390
|
+
async executeStatement(driver, statement) {
|
|
391
|
+
await driver.query(statement.sql, statement.params);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* Postgres target `SchemaVerifier` concretion. Plugs into the
|
|
396
|
+
* SQL-shared verification surface; production verification today still
|
|
397
|
+
* routes through the family verify verdict (`verifySqlSchemaByDiff` over
|
|
398
|
+
* `diffPostgresSchema`), which carries options (codec hooks,
|
|
399
|
+
* normalizers, framework components) that the framework-level
|
|
400
|
+
* `SchemaVerifyOptions` shape does not yet surface.
|
|
401
|
+
*
|
|
402
|
+
* The hooks return the empty list pending the call-site migration that
|
|
403
|
+
* routes the existing verifier behaviour through the SPI — at that
|
|
404
|
+
* point `verifyCommonSqlSchema` will likely lift onto the family base
|
|
405
|
+
* (mirroring `verifyCommonMongoSchema`) and `verifyTargetExtensions`
|
|
406
|
+
* will house Postgres-only kinds (functions, RLS policies in a future
|
|
407
|
+
* project).
|
|
408
|
+
*/
|
|
409
|
+
var PostgresSchemaVerifier = class extends SqlSchemaVerifierBase {
|
|
410
|
+
verifyCommonSqlSchema(_options) {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
verifyTargetExtensions(_options) {
|
|
414
|
+
return [];
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
function buildFieldNamesByTable(tables) {
|
|
418
|
+
const fieldNamesByTable = /* @__PURE__ */ new Map();
|
|
419
|
+
for (const table of Object.values(tables)) {
|
|
420
|
+
const assignmentOrder = [...Object.values(table.columns).map((column, index) => {
|
|
421
|
+
const { name, map } = toFieldName(column.name);
|
|
422
|
+
return {
|
|
423
|
+
columnName: column.name,
|
|
424
|
+
desiredFieldName: name,
|
|
425
|
+
fieldMap: map,
|
|
426
|
+
index
|
|
427
|
+
};
|
|
428
|
+
})].sort((left, right) => {
|
|
429
|
+
const mapComparison = Number(left.fieldMap !== void 0) - Number(right.fieldMap !== void 0);
|
|
430
|
+
if (mapComparison !== 0) return mapComparison;
|
|
431
|
+
return left.index - right.index;
|
|
432
|
+
});
|
|
433
|
+
const usedFieldNames = /* @__PURE__ */ new Set();
|
|
434
|
+
const tableFieldNames = /* @__PURE__ */ new Map();
|
|
435
|
+
for (const column of assignmentOrder) {
|
|
436
|
+
const fieldName = createUniqueFieldName(column.desiredFieldName, usedFieldNames);
|
|
437
|
+
usedFieldNames.add(fieldName);
|
|
438
|
+
tableFieldNames.set(column.columnName, {
|
|
439
|
+
fieldName,
|
|
440
|
+
fieldMap: column.fieldMap
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
fieldNamesByTable.set(table.name, tableFieldNames);
|
|
444
|
+
}
|
|
445
|
+
return fieldNamesByTable;
|
|
446
|
+
}
|
|
447
|
+
function resolveColumnFieldName(fieldNamesByTable, tableName, columnName) {
|
|
448
|
+
return fieldNamesByTable.get(tableName)?.get(columnName)?.fieldName ?? toFieldName(columnName).name;
|
|
449
|
+
}
|
|
450
|
+
function createUniqueFieldName(desiredName, usedFieldNames) {
|
|
451
|
+
if (!usedFieldNames.has(desiredName)) return desiredName;
|
|
452
|
+
let counter = 2;
|
|
453
|
+
while (usedFieldNames.has(`${desiredName}${counter}`)) counter++;
|
|
454
|
+
return `${desiredName}${counter}`;
|
|
455
|
+
}
|
|
456
|
+
function buildTopLevelNameMap(sources, normalize, kind, sourceKind) {
|
|
457
|
+
const results = /* @__PURE__ */ new Map();
|
|
458
|
+
const normalizedToSources = /* @__PURE__ */ new Map();
|
|
459
|
+
for (const source of sources) {
|
|
460
|
+
const normalized = normalize(source);
|
|
461
|
+
results.set(source, normalized);
|
|
462
|
+
normalizedToSources.set(normalized.name, [...normalizedToSources.get(normalized.name) ?? [], source]);
|
|
463
|
+
}
|
|
464
|
+
const duplicates = [...normalizedToSources.entries()].filter(([, conflictingSources]) => conflictingSources.length > 1);
|
|
465
|
+
if (duplicates.length > 0) throw postgresError("CONTRACT.NAME_DUPLICATE", `PSL ${kind} name collisions detected:\n${duplicates.map(([normalizedName, conflictingSources]) => `- ${kind} "${normalizedName}" from ${sourceKind}s ${conflictingSources.map((source) => `"${source}"`).join(", ")}`).join("\n")}`, { meta: {
|
|
466
|
+
kind,
|
|
467
|
+
names: duplicates.map(([normalizedName]) => normalizedName)
|
|
468
|
+
} });
|
|
469
|
+
return results;
|
|
470
|
+
}
|
|
471
|
+
function topologicalSort(models, tables, modelNameMap) {
|
|
472
|
+
const modelByName = /* @__PURE__ */ new Map();
|
|
473
|
+
for (const model of models) modelByName.set(model.name, model);
|
|
474
|
+
const deps = /* @__PURE__ */ new Map();
|
|
475
|
+
const tableToModel = /* @__PURE__ */ new Map();
|
|
476
|
+
for (const tableName of Object.keys(tables)) {
|
|
477
|
+
const modelName = modelNameMap.get(tableName);
|
|
478
|
+
assertDefined(modelName, `topologicalSort: no model name mapped for table "${tableName}"`);
|
|
479
|
+
tableToModel.set(tableName, modelName);
|
|
480
|
+
deps.set(modelName, /* @__PURE__ */ new Set());
|
|
481
|
+
}
|
|
482
|
+
for (const [tableName, table] of Object.entries(tables)) {
|
|
483
|
+
const modelName = tableToModel.get(tableName);
|
|
484
|
+
assertDefined(modelName, `topologicalSort: no model name recorded for table "${tableName}"`);
|
|
485
|
+
const modelDeps = deps.get(modelName);
|
|
486
|
+
assertDefined(modelDeps, `topologicalSort: no dependency set recorded for model "${modelName}"`);
|
|
487
|
+
for (const fk of table.foreignKeys) {
|
|
488
|
+
const refModelName = tableToModel.get(fk.referencedTable);
|
|
489
|
+
if (refModelName && refModelName !== modelName) modelDeps.add(refModelName);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const result = [];
|
|
493
|
+
const visited = /* @__PURE__ */ new Set();
|
|
494
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
495
|
+
const sortedNames = [...deps.keys()].sort();
|
|
496
|
+
function visit(name) {
|
|
497
|
+
if (visited.has(name)) return;
|
|
498
|
+
if (visiting.has(name)) return;
|
|
499
|
+
visiting.add(name);
|
|
500
|
+
const nameDeps = deps.get(name);
|
|
501
|
+
assertDefined(nameDeps, `topologicalSort: no dependency set recorded for model "${name}"`);
|
|
502
|
+
const sortedDeps = [...nameDeps].sort();
|
|
503
|
+
for (const dep of sortedDeps) visit(dep);
|
|
504
|
+
visiting.delete(name);
|
|
505
|
+
visited.add(name);
|
|
506
|
+
const model = modelByName.get(name);
|
|
507
|
+
assertDefined(model, `topologicalSort: no model block recorded for "${name}"`);
|
|
508
|
+
result.push(model);
|
|
509
|
+
}
|
|
510
|
+
for (const name of sortedNames) visit(name);
|
|
511
|
+
return result;
|
|
512
|
+
}
|
|
513
|
+
const SYNTHETIC_SPAN = {
|
|
514
|
+
start: {
|
|
515
|
+
offset: 0,
|
|
516
|
+
line: 1,
|
|
517
|
+
column: 1
|
|
518
|
+
},
|
|
519
|
+
end: {
|
|
520
|
+
offset: 0,
|
|
521
|
+
line: 1,
|
|
522
|
+
column: 1
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
function buildSimpleConstraintFieldAttribute(name, constraintName) {
|
|
526
|
+
if (constraintName === void 0) return buildAttribute("field", name, []);
|
|
527
|
+
return buildAttribute("field", name, [namedArg("map", `"${escapePslString(constraintName)}"`)]);
|
|
528
|
+
}
|
|
529
|
+
function parseDefaultAttributeString(attributeText) {
|
|
530
|
+
return buildAttribute("field", "default", [positionalArg(attributeText.replace(/^@default\(/, "").replace(/\)$/, ""))]);
|
|
531
|
+
}
|
|
532
|
+
function buildMapAttribute(target, mapName) {
|
|
533
|
+
return buildAttribute(target, "map", [positionalArg(`"${escapePslString(mapName)}"`)]);
|
|
534
|
+
}
|
|
535
|
+
function buildAttribute(target, name, args) {
|
|
536
|
+
return {
|
|
537
|
+
kind: "attribute",
|
|
538
|
+
target,
|
|
539
|
+
name,
|
|
540
|
+
args,
|
|
541
|
+
span: SYNTHETIC_SPAN
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function positionalArg(value) {
|
|
545
|
+
return {
|
|
546
|
+
kind: "positional",
|
|
547
|
+
value,
|
|
548
|
+
span: SYNTHETIC_SPAN
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function namedArg(name, value) {
|
|
552
|
+
return {
|
|
553
|
+
kind: "named",
|
|
554
|
+
name,
|
|
555
|
+
value,
|
|
556
|
+
span: SYNTHETIC_SPAN
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function escapePslString(value) {
|
|
560
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Formats a resolved literal-default array as PSL literal-list syntax
|
|
564
|
+
* (`[1, 2, 3]`, `["a", "b"]`, `[]`). PSL's list-literal grammar only accepts
|
|
565
|
+
* string/number/boolean elements, so any other element (e.g. `null`, a
|
|
566
|
+
* nested array/object) makes the value unrepresentable and this returns
|
|
567
|
+
* `undefined`.
|
|
568
|
+
*/
|
|
569
|
+
function formatPslListLiteralValue(elements) {
|
|
570
|
+
const parts = [];
|
|
571
|
+
for (const element of elements) if (typeof element === "string") parts.push(`"${escapePslString(element)}"`);
|
|
572
|
+
else if (typeof element === "number" || typeof element === "boolean") parts.push(String(element));
|
|
573
|
+
else return;
|
|
574
|
+
return `[${parts.join(", ")}]`;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Resolves a `SqlColumnIR.default` value into a normalized {@link ColumnDefault}.
|
|
578
|
+
*
|
|
579
|
+
* `SqlSchemaIR` types the column default as `string` (a raw database default
|
|
580
|
+
* expression). Some legacy fixtures and tests still pass already-normalized
|
|
581
|
+
* `ColumnDefault` objects in the same slot, so we accept either shape
|
|
582
|
+
* defensively at runtime.
|
|
583
|
+
*/
|
|
584
|
+
function parseColumnDefault(value, nativeType, rawDefaultParser) {
|
|
585
|
+
if (typeof value === "string") return rawDefaultParser ? rawDefaultParser(value, nativeType) : void 0;
|
|
586
|
+
return isColumnDefault(value) ? value : void 0;
|
|
587
|
+
}
|
|
588
|
+
const PSL_SCALAR_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
589
|
+
"String",
|
|
590
|
+
"Boolean",
|
|
591
|
+
"Int",
|
|
592
|
+
"BigInt",
|
|
593
|
+
"Float",
|
|
594
|
+
"Decimal",
|
|
595
|
+
"DateTime",
|
|
596
|
+
"Json",
|
|
597
|
+
"Bytes"
|
|
598
|
+
]);
|
|
599
|
+
/**
|
|
600
|
+
* Builds one `native_enum` extension-block AST node per introspected enum
|
|
601
|
+
* definition. Block names go through the shared top-level transform
|
|
602
|
+
* (`toEnumName`, intra-enum collisions throw like model collisions) and are
|
|
603
|
+
* then reserved against the model names — an enum whose PSL name a model
|
|
604
|
+
* already claims gets a numeric suffix, with `@@map` carrying the real type
|
|
605
|
+
* name. Members print as explicit `member = "value"` pairs: the member name
|
|
606
|
+
* is the sanitized value (deduplicated within the block), the JSON-encoded
|
|
607
|
+
* value carries the truth verbatim.
|
|
608
|
+
*/
|
|
609
|
+
function buildNativeEnumBlocks(definitions, modelNames) {
|
|
610
|
+
const enumNames = buildTopLevelNameMap([...definitions.keys()].sort(), toEnumName, "enum", "enum type");
|
|
611
|
+
const usedTopLevelNames = new Set(PSL_SCALAR_TYPE_NAMES);
|
|
612
|
+
for (const result of modelNames.values()) usedTopLevelNames.add(result.name);
|
|
613
|
+
const enumNameMap = /* @__PURE__ */ new Map();
|
|
614
|
+
const enumBlocks = [];
|
|
615
|
+
for (const [typeName, result] of enumNames) {
|
|
616
|
+
const name = createUniqueFieldName(result.name, usedTopLevelNames);
|
|
617
|
+
usedTopLevelNames.add(name);
|
|
618
|
+
enumNameMap.set(typeName, name);
|
|
619
|
+
enumBlocks.push(buildNativeEnumBlock(name, typeName, definitions.get(typeName) ?? []));
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
enumNameMap,
|
|
623
|
+
enumBlocks
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function buildNativeEnumBlock(name, typeName, values) {
|
|
627
|
+
const usedMemberNames = /* @__PURE__ */ new Set();
|
|
628
|
+
const parameters = {};
|
|
629
|
+
for (const value of values) {
|
|
630
|
+
const memberName = createUniqueFieldName(toEnumMemberName(value), usedMemberNames);
|
|
631
|
+
usedMemberNames.add(memberName);
|
|
632
|
+
parameters[memberName] = {
|
|
633
|
+
kind: "value",
|
|
634
|
+
raw: JSON.stringify(value),
|
|
635
|
+
span: SYNTHETIC_SPAN
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
return {
|
|
639
|
+
kind: "native_enum",
|
|
640
|
+
keyword: "native_enum",
|
|
641
|
+
name,
|
|
642
|
+
parameters,
|
|
643
|
+
blockAttributes: name === typeName ? [] : [{
|
|
644
|
+
name: "map",
|
|
645
|
+
args: [{
|
|
646
|
+
kind: "positional",
|
|
647
|
+
value: `"${escapePslString(typeName)}"`,
|
|
648
|
+
span: SYNTHETIC_SPAN
|
|
649
|
+
}],
|
|
650
|
+
span: SYNTHETIC_SPAN
|
|
651
|
+
}],
|
|
652
|
+
span: SYNTHETIC_SPAN
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Coordinates every element a set of already-assembled contracts declare,
|
|
657
|
+
* mapped to the {@link SqlDescribedContractSpace} that owns it and keyed by
|
|
658
|
+
* the shared {@link coordinateKey} helper. `contract infer` uses this to omit
|
|
659
|
+
* database elements a stack extension pack's contract space already
|
|
660
|
+
* describes — reusing the same coordinate walk the contract-space aggregate
|
|
661
|
+
* and cross-space collision check use (`elementCoordinates`), rather than a
|
|
662
|
+
* bespoke per-entity-kind membership test — and, for a foreign key whose
|
|
663
|
+
* referenced table an entry owns, to resolve the qualified cross-space
|
|
664
|
+
* relation it describes.
|
|
665
|
+
*/
|
|
666
|
+
function describedContractOwners(describedContracts) {
|
|
667
|
+
const owners = /* @__PURE__ */ new Map();
|
|
668
|
+
for (const entry of describedContracts) for (const coordinate of elementCoordinates(entry.contract.storage)) owners.set(coordinateKey(coordinate), entry);
|
|
669
|
+
return owners;
|
|
670
|
+
}
|
|
671
|
+
function resolveCrossSpaceTarget(owner, namespaceId, tableName) {
|
|
672
|
+
const domainNamespace = owner.contract.domain.namespaces[namespaceId];
|
|
673
|
+
if (domainNamespace === void 0) return;
|
|
674
|
+
for (const [modelName, model] of Object.entries(domainNamespace.models)) {
|
|
675
|
+
const storage = blindCast(model.storage);
|
|
676
|
+
if (storage.namespaceId !== namespaceId || storage.table !== tableName) continue;
|
|
677
|
+
const fieldNamesByColumn = /* @__PURE__ */ new Map();
|
|
678
|
+
for (const [fieldName, fieldStorage] of Object.entries(storage.fields)) fieldNamesByColumn.set(fieldStorage.column, { fieldName });
|
|
679
|
+
return {
|
|
680
|
+
spaceId: owner.spaceId,
|
|
681
|
+
namespaceId,
|
|
682
|
+
modelName,
|
|
683
|
+
fieldNamesByColumn
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Classifies every foreign key on a surviving table into one of three cases.
|
|
689
|
+
* A foreign key that carries a `referencedSchema` is checked against the
|
|
690
|
+
* pack-owned coordinates first, so a pack-owned target wins even when a local
|
|
691
|
+
* table happens to share its bare name; only foreign keys with no owned
|
|
692
|
+
* coordinate fall through to the local/dangling distinction.
|
|
693
|
+
*
|
|
694
|
+
* - **Cross-space**: `referencedSchema` is set and a described contract owns
|
|
695
|
+
* the coordinate `(referencedSchema, 'table', referencedTable)`. The
|
|
696
|
+
* referenced table is absent from the tree (omitted because the pack
|
|
697
|
+
* describes it, or never introspected — `contract infer` walks a single
|
|
698
|
+
* namespace). Removed from `foreignKeys` (so `inferRelations` never falls
|
|
699
|
+
* back to a bare, unqualified table name for it) and replaced with a
|
|
700
|
+
* `RelationField` qualified with the owning pack's space id and namespace
|
|
701
|
+
* id. `owners` holds only pack-declared coordinates, so an app's own table
|
|
702
|
+
* (e.g. `public.users`) is never owned and cannot be captured here.
|
|
703
|
+
* - **Local**: not a pack-owned coordinate, and the referenced table survived
|
|
704
|
+
* introspection — left untouched, `inferRelations` handles it as before.
|
|
705
|
+
* - **Dangling**: not a pack-owned coordinate, and the referenced table is
|
|
706
|
+
* neither in the tree nor owned by any described contract. Removed from
|
|
707
|
+
* `foreignKeys`, keeping the scalar column, rather than emitting a relation
|
|
708
|
+
* to a model that was never defined.
|
|
709
|
+
*
|
|
710
|
+
* A pack that owns the referenced coordinate but declares no domain model
|
|
711
|
+
* mapped to it is malformed; that case throws rather than degrading to a
|
|
712
|
+
* silent drop, which would contradict the dangling definition above.
|
|
713
|
+
*/
|
|
714
|
+
function resolveForeignKeys(tables, owners) {
|
|
715
|
+
const resultTables = {};
|
|
716
|
+
const extraRelationsByTable = /* @__PURE__ */ new Map();
|
|
717
|
+
const crossSpaceFieldNamesByTable = /* @__PURE__ */ new Map();
|
|
718
|
+
const danglingForeignKeysByTable = /* @__PURE__ */ new Map();
|
|
719
|
+
for (const [tableName, table] of Object.entries(tables)) {
|
|
720
|
+
const keptForeignKeys = [];
|
|
721
|
+
for (const fk of table.foreignKeys) {
|
|
722
|
+
if (fk.referencedSchema !== void 0) {
|
|
723
|
+
const owner = owners.get(coordinateKey({
|
|
724
|
+
namespaceId: fk.referencedSchema,
|
|
725
|
+
entityKind: "table",
|
|
726
|
+
entityName: fk.referencedTable
|
|
727
|
+
}));
|
|
728
|
+
if (owner !== void 0) {
|
|
729
|
+
const target = resolveCrossSpaceTarget(owner, fk.referencedSchema, fk.referencedTable);
|
|
730
|
+
if (target === void 0) throw postgresError("CONTRACT.PACK_CONTRIBUTION_INVALID", `contract infer: described contract space "${owner.spaceId}" owns storage coordinate "${fk.referencedSchema}.${fk.referencedTable}" but declares no domain model mapped to it. A pack that describes a table must also declare the domain model it maps to; this pack is malformed.`, { meta: {
|
|
731
|
+
spaceId: owner.spaceId,
|
|
732
|
+
tableName: fk.referencedTable
|
|
733
|
+
} });
|
|
734
|
+
if (!crossSpaceFieldNamesByTable.has(fk.referencedTable)) crossSpaceFieldNamesByTable.set(fk.referencedTable, target.fieldNamesByColumn);
|
|
735
|
+
const fieldName = deriveRelationFieldName(fk.columns, fk.referencedTable);
|
|
736
|
+
const optional = fk.columns.some((columnName) => table.columns[columnName]?.nullable ?? false);
|
|
737
|
+
const relationField = {
|
|
738
|
+
...buildChildRelationField(fieldName, target.modelName, fk, optional, void 0, table),
|
|
739
|
+
typeNamespaceId: target.namespaceId,
|
|
740
|
+
typeContractSpaceId: target.spaceId
|
|
741
|
+
};
|
|
742
|
+
const existingRelations = extraRelationsByTable.get(tableName);
|
|
743
|
+
if (existingRelations) existingRelations.push(relationField);
|
|
744
|
+
else extraRelationsByTable.set(tableName, [relationField]);
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (tables[fk.referencedTable] !== void 0) keptForeignKeys.push(fk);
|
|
749
|
+
else {
|
|
750
|
+
const dangling = {
|
|
751
|
+
columns: fk.columns,
|
|
752
|
+
referencedSchema: fk.referencedSchema,
|
|
753
|
+
referencedTable: fk.referencedTable
|
|
754
|
+
};
|
|
755
|
+
const existingDangling = danglingForeignKeysByTable.get(tableName);
|
|
756
|
+
if (existingDangling) existingDangling.push(dangling);
|
|
757
|
+
else danglingForeignKeysByTable.set(tableName, [dangling]);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
resultTables[tableName] = keptForeignKeys.length === table.foreignKeys.length ? table : new SqlTableIR({
|
|
761
|
+
...table,
|
|
762
|
+
foreignKeys: keptForeignKeys
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
tables: resultTables,
|
|
767
|
+
extraRelationsByTable,
|
|
768
|
+
crossSpaceFieldNamesByTable,
|
|
769
|
+
danglingForeignKeysByTable
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Explains a foreign key `resolveForeignKeys` dropped as dangling: the
|
|
774
|
+
* database enforces it, but its target lives outside the introspected
|
|
775
|
+
* schema, so infer has no model to point a relation at. The suggested fix
|
|
776
|
+
* targets the common cause — an unconfigured extension pack's schema
|
|
777
|
+
* (e.g. Supabase's `auth`).
|
|
778
|
+
*/
|
|
779
|
+
function buildDanglingForeignKeyWarning(danglingForeignKeys, fieldNamesByTable, tableName) {
|
|
780
|
+
return `Foreign key ${danglingForeignKeys.map((fk) => {
|
|
781
|
+
const fieldNames = fk.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, tableName, columnName));
|
|
782
|
+
const target = fk.referencedSchema !== void 0 ? `${fk.referencedSchema}.${fk.referencedTable}` : fk.referencedTable;
|
|
783
|
+
return `"${fieldNames.join(", ")}" -> "${target}"`;
|
|
784
|
+
}).join(", ")} exists in the database, but its target schema is outside the introspected scope, so no relation field was generated. If the target schema is described by an extension pack, add it to extensions and re-run infer.`;
|
|
785
|
+
}
|
|
786
|
+
function buildModelConstraintAttribute(name, fields, constraintName) {
|
|
787
|
+
const args = [positionalArg(`[${fields.join(", ")}]`)];
|
|
788
|
+
if (constraintName !== void 0) args.push(namedArg("map", `"${escapePslString(constraintName)}"`));
|
|
789
|
+
return buildAttribute("model", name, args);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Emits one `@@index` attribute at full fidelity. The index's identity is
|
|
793
|
+
* re-detected rather than trusted: `name:` is emitted only when the live name
|
|
794
|
+
* parses as a wire name AND that hash recomputes from the introspected
|
|
795
|
+
* content; otherwise the live name is adopted verbatim with `map:`.
|
|
796
|
+
*/
|
|
797
|
+
function buildIndexAttribute(index, fieldNames) {
|
|
798
|
+
const args = [];
|
|
799
|
+
if (fieldNames !== void 0) args.push(positionalArg(`[${fieldNames.join(", ")}]`));
|
|
800
|
+
else {
|
|
801
|
+
assertDefined(index.expression, `buildIndexAttribute: index "${index.name}" carries neither columns nor expression; SqlIndexIR enforces exactly one`);
|
|
802
|
+
args.push(namedArg("expression", `"${escapePslString(index.expression)}"`));
|
|
803
|
+
}
|
|
804
|
+
const parsed = parseWireName(index.name);
|
|
805
|
+
const recomputed = computeIndexContentHash({
|
|
806
|
+
...index.columns !== void 0 ? { columns: index.columns } : {},
|
|
807
|
+
...index.expression !== void 0 ? { expression: index.expression } : {},
|
|
808
|
+
...index.where !== void 0 ? { where: index.where } : {},
|
|
809
|
+
unique: index.unique,
|
|
810
|
+
...index.type !== void 0 ? { type: index.type } : {},
|
|
811
|
+
...index.options !== void 0 ? { options: index.options } : {}
|
|
812
|
+
});
|
|
813
|
+
if (parsed !== void 0 && parsed.hash === recomputed) args.push(namedArg("name", `"${escapePslString(parsed.prefix)}"`));
|
|
814
|
+
else args.push(namedArg("map", `"${escapePslString(index.name)}"`));
|
|
815
|
+
if (index.where !== void 0) args.push(namedArg("where", `"${escapePslString(index.where)}"`));
|
|
816
|
+
if (index.unique) args.push(namedArg("unique", "true"));
|
|
817
|
+
const hasOptions = index.options !== void 0 && Object.keys(index.options).length > 0;
|
|
818
|
+
if (index.type !== void 0 || hasOptions) args.push(namedArg("type", `"${escapePslString(index.type ?? "btree")}"`));
|
|
819
|
+
if (hasOptions) {
|
|
820
|
+
const entries = Object.entries(index.options ?? {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, value]) => `${key}: "${escapePslString(String(value))}"`);
|
|
821
|
+
args.push(namedArg("options", `{ ${entries.join(", ")} }`));
|
|
822
|
+
}
|
|
823
|
+
return buildAttribute("model", "index", args);
|
|
824
|
+
}
|
|
825
|
+
function buildModel(table, typeMap, enumNameMap, fieldNamesByTable, defaultMapping, rawDefaultParser, relationFields, danglingForeignKeys, rlsEnabled = false, policySkipNotes = []) {
|
|
826
|
+
const { name: modelName, map: mapName } = toModelName(table.name);
|
|
827
|
+
const fieldNameMap = fieldNamesByTable.get(table.name);
|
|
828
|
+
const pkColumns = new Set(table.primaryKey?.columns ?? []);
|
|
829
|
+
const isSinglePk = pkColumns.size === 1;
|
|
830
|
+
const singlePkConstraintName = isSinglePk ? table.primaryKey?.name : void 0;
|
|
831
|
+
const uniqueColumns = /* @__PURE__ */ new Map();
|
|
832
|
+
for (const unique of table.uniques) if (unique.columns.length === 1) {
|
|
833
|
+
const [columnName = ""] = unique.columns;
|
|
834
|
+
const existingConstraintName = uniqueColumns.get(columnName);
|
|
835
|
+
if (!uniqueColumns.has(columnName) || existingConstraintName === void 0 && unique.name) uniqueColumns.set(columnName, unique.name);
|
|
836
|
+
}
|
|
837
|
+
const fields = [];
|
|
838
|
+
for (const column of Object.values(table.columns)) fields.push(buildScalarField(column, table, typeMap, enumNameMap, fieldNameMap, defaultMapping, rawDefaultParser, pkColumns, isSinglePk, singlePkConstraintName, uniqueColumns));
|
|
839
|
+
const usedFieldNames = new Set(fields.map((field) => field.name));
|
|
840
|
+
for (const rel of relationFields) fields.push(buildRelationField(rel, table.name, fieldNamesByTable, usedFieldNames));
|
|
841
|
+
const modelAttributes = [];
|
|
842
|
+
if (table.primaryKey && table.primaryKey.columns.length > 1) {
|
|
843
|
+
const pkFieldNames = table.primaryKey.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
|
|
844
|
+
modelAttributes.push(buildModelConstraintAttribute("id", pkFieldNames, table.primaryKey.name));
|
|
845
|
+
}
|
|
846
|
+
for (const unique of table.uniques) if (unique.columns.length > 1) {
|
|
847
|
+
const uniqueFieldNames = unique.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
|
|
848
|
+
modelAttributes.push(buildModelConstraintAttribute("unique", uniqueFieldNames, unique.name));
|
|
849
|
+
}
|
|
850
|
+
for (const index of table.indexes) {
|
|
851
|
+
const indexFieldNames = index.columns?.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
|
|
852
|
+
modelAttributes.push(buildIndexAttribute(index, indexFieldNames));
|
|
853
|
+
}
|
|
854
|
+
if (mapName) modelAttributes.push(buildMapAttribute("model", mapName));
|
|
855
|
+
if (rlsEnabled) modelAttributes.push(buildAttribute("model", "rls", []));
|
|
856
|
+
const warnings = [];
|
|
857
|
+
if (!table.primaryKey) warnings.push("This table has no primary key in the database");
|
|
858
|
+
if (danglingForeignKeys.length > 0) warnings.push(buildDanglingForeignKeyWarning(danglingForeignKeys, fieldNamesByTable, table.name));
|
|
859
|
+
const commentLines = [...warnings.length > 0 ? [`// WARNING: ${warnings.join(" ")}`] : [], ...policySkipNotes];
|
|
860
|
+
const comment = commentLines.length > 0 ? commentLines.join("\n") : void 0;
|
|
861
|
+
return {
|
|
862
|
+
kind: "model",
|
|
863
|
+
name: modelName,
|
|
864
|
+
fields,
|
|
865
|
+
attributes: modelAttributes,
|
|
866
|
+
span: SYNTHETIC_SPAN,
|
|
867
|
+
...comment !== void 0 ? { comment } : {}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function buildScalarField(column, table, typeMap, enumNameMap, fieldNameMap, defaultMapping, rawDefaultParser, pkColumns, isSinglePk, singlePkConstraintName, uniqueColumns) {
|
|
871
|
+
const resolvedField = fieldNameMap?.get(column.name);
|
|
872
|
+
const fieldName = resolvedField?.fieldName ?? toFieldName(column.name).name;
|
|
873
|
+
const fieldMap = resolvedField?.fieldMap;
|
|
874
|
+
const resolution = typeMap.resolve(column.nativeType, table.annotations);
|
|
875
|
+
if ("unsupported" in resolution) {
|
|
876
|
+
const attrs = [];
|
|
877
|
+
if (fieldMap !== void 0) attrs.push(buildMapAttribute("field", fieldMap));
|
|
878
|
+
return {
|
|
879
|
+
kind: "field",
|
|
880
|
+
name: fieldName,
|
|
881
|
+
typeName: `Unsupported("${escapePslString(resolution.nativeType)}")`,
|
|
882
|
+
optional: column.nullable,
|
|
883
|
+
list: column.many === true,
|
|
884
|
+
attributes: attrs,
|
|
885
|
+
span: SYNTHETIC_SPAN
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
let typeName = resolution.pslType.name;
|
|
889
|
+
let typeConstructor = resolution.pslType.args ? {
|
|
890
|
+
kind: "typeConstructor",
|
|
891
|
+
path: [resolution.pslType.name],
|
|
892
|
+
args: resolution.pslType.args.map(positionalArg),
|
|
893
|
+
span: SYNTHETIC_SPAN
|
|
894
|
+
} : void 0;
|
|
895
|
+
const enumPslName = enumNameMap.get(column.nativeType);
|
|
896
|
+
if (enumPslName) {
|
|
897
|
+
typeName = enumPslName;
|
|
898
|
+
typeConstructor = {
|
|
899
|
+
kind: "typeConstructor",
|
|
900
|
+
path: ["pg", "enum"],
|
|
901
|
+
args: [positionalArg(enumPslName)],
|
|
902
|
+
span: SYNTHETIC_SPAN
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
const attributes = [];
|
|
906
|
+
const isId = isSinglePk && pkColumns.has(column.name);
|
|
907
|
+
if (isId) attributes.push(buildSimpleConstraintFieldAttribute("id", singlePkConstraintName));
|
|
908
|
+
if (column.default === void 0 && column.resolvedDefault?.kind === "function" && column.resolvedDefault.expression === "autoincrement()") attributes.push(parseDefaultAttributeString("@default(autoincrement())"));
|
|
909
|
+
else if (column.many === true && column.resolvedDefault?.kind === "literal") {
|
|
910
|
+
const formatted = Array.isArray(column.resolvedDefault.value) ? formatPslListLiteralValue(column.resolvedDefault.value) : void 0;
|
|
911
|
+
if (formatted !== void 0) attributes.push(parseDefaultAttributeString(`@default(${formatted})`));
|
|
912
|
+
} else if (column.default !== void 0) {
|
|
913
|
+
const parsed = parseColumnDefault(column.default, column.nativeType, rawDefaultParser);
|
|
914
|
+
if (parsed) {
|
|
915
|
+
const result = mapDefault(parsed, defaultMapping);
|
|
916
|
+
if ("attribute" in result) attributes.push(parseDefaultAttributeString(result.attribute));
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (uniqueColumns.has(column.name) && !isId) {
|
|
920
|
+
const uniqueConstraintName = uniqueColumns.get(column.name);
|
|
921
|
+
attributes.push(buildSimpleConstraintFieldAttribute("unique", uniqueConstraintName));
|
|
922
|
+
}
|
|
923
|
+
if (fieldMap !== void 0) attributes.push(buildMapAttribute("field", fieldMap));
|
|
924
|
+
return {
|
|
925
|
+
kind: "field",
|
|
926
|
+
name: fieldName,
|
|
927
|
+
typeName,
|
|
928
|
+
...ifDefined("typeConstructor", typeConstructor),
|
|
929
|
+
optional: column.nullable,
|
|
930
|
+
list: column.many === true,
|
|
931
|
+
attributes,
|
|
932
|
+
span: SYNTHETIC_SPAN
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function buildRelationField(rel, hostTableName, fieldNamesByTable, usedFieldNames) {
|
|
936
|
+
const fieldName = createUniqueFieldName(rel.fieldName, usedFieldNames);
|
|
937
|
+
usedFieldNames.add(fieldName);
|
|
938
|
+
const args = [];
|
|
939
|
+
if (rel.fields && rel.references) {
|
|
940
|
+
if (rel.relationName) args.push(namedArg("name", `"${escapePslString(rel.relationName)}"`));
|
|
941
|
+
args.push(namedArg("fields", `[${rel.fields.map((columnName) => resolveColumnFieldName(fieldNamesByTable, hostTableName, columnName)).join(", ")}]`));
|
|
942
|
+
args.push(namedArg("references", `[${rel.references.map((columnName) => resolveColumnFieldName(fieldNamesByTable, rel.referencedTableName ?? "", columnName)).join(", ")}]`));
|
|
943
|
+
if (rel.onDelete) args.push(namedArg("onDelete", rel.onDelete));
|
|
944
|
+
if (rel.onUpdate) args.push(namedArg("onUpdate", rel.onUpdate));
|
|
945
|
+
if (rel.fkName) args.push(namedArg("map", `"${escapePslString(rel.fkName)}"`));
|
|
946
|
+
if (rel.index === false) args.push(namedArg("index", "false"));
|
|
947
|
+
} else if (rel.relationName) args.push(namedArg("name", `"${escapePslString(rel.relationName)}"`));
|
|
948
|
+
const attrs = args.length > 0 ? [buildAttribute("field", "relation", args)] : [];
|
|
949
|
+
return {
|
|
950
|
+
kind: "field",
|
|
951
|
+
name: fieldName,
|
|
952
|
+
typeName: rel.typeName,
|
|
953
|
+
...ifDefined("typeNamespaceId", rel.typeNamespaceId),
|
|
954
|
+
...ifDefined("typeContractSpaceId", rel.typeContractSpaceId),
|
|
955
|
+
optional: rel.optional,
|
|
956
|
+
list: rel.list,
|
|
957
|
+
attributes: attrs,
|
|
958
|
+
span: SYNTHETIC_SPAN
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
const POLICY_OPERATION_KEYWORD = {
|
|
962
|
+
select: "policy_select",
|
|
963
|
+
insert: "policy_insert",
|
|
964
|
+
update: "policy_update",
|
|
965
|
+
delete: "policy_delete",
|
|
966
|
+
all: "policy_all"
|
|
967
|
+
};
|
|
968
|
+
/** The PSL tokenizer's identifier grammar: leading letter/underscore, then letters/digits/`_`/`-`. */
|
|
969
|
+
const PSL_IDENTIFIER = /^[\p{L}_][\p{L}\p{N}_-]*$/u;
|
|
970
|
+
/** Replaces invalid character runs with `_`; prepends `_` when the first character is invalid. */
|
|
971
|
+
function sanitizePolicyHead(raw) {
|
|
972
|
+
let head = raw.replace(/[^\p{L}\p{N}_-]+/gu, "_");
|
|
973
|
+
if (!/^[\p{L}_]/u.test(head)) head = `_${head}`;
|
|
974
|
+
return head;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Builds one `policy_<operation>` block per introspected policy. Every
|
|
978
|
+
* adopted policy is exact-named: a reprinted body never reliably re-hashes to
|
|
979
|
+
* the live suffix, so `@@map` always carries the physical name and the head
|
|
980
|
+
* is only an identifier. A policy granting to a role whose name is not a
|
|
981
|
+
* legal identifier cannot be authored at all — role references have no
|
|
982
|
+
* `@@map` escape — so it is skipped with a note.
|
|
983
|
+
*/
|
|
984
|
+
function buildPolicyBlocks(policiesByTable, modelNameMap, reservedHeads = /* @__PURE__ */ new Set()) {
|
|
985
|
+
const all = [];
|
|
986
|
+
for (const [tableName, policies] of policiesByTable) for (const policy of policies) all.push({
|
|
987
|
+
policy,
|
|
988
|
+
tableName
|
|
989
|
+
});
|
|
990
|
+
all.sort((a, b) => {
|
|
991
|
+
if (a.policy.name !== b.policy.name) return a.policy.name < b.policy.name ? -1 : 1;
|
|
992
|
+
if (a.tableName !== b.tableName) return a.tableName < b.tableName ? -1 : 1;
|
|
993
|
+
return a.policy.operation < b.policy.operation ? -1 : a.policy.operation > b.policy.operation ? 1 : 0;
|
|
994
|
+
});
|
|
995
|
+
const usedHeads = new Set(reservedHeads);
|
|
996
|
+
const blocks = [];
|
|
997
|
+
const skipNotesByTable = /* @__PURE__ */ new Map();
|
|
998
|
+
for (const { policy, tableName } of all) {
|
|
999
|
+
const modelName = modelNameMap.get(tableName);
|
|
1000
|
+
assertDefined(modelName, `buildPolicyBlocks: policy "${policy.name}" targets table "${tableName}" with no emitted model; tables and policies come from the same introspection walk`);
|
|
1001
|
+
const badRole = policy.roles.find((role) => !PSL_IDENTIFIER.test(role));
|
|
1002
|
+
if (badRole !== void 0) {
|
|
1003
|
+
const notes = skipNotesByTable.get(tableName) ?? [];
|
|
1004
|
+
notes.push(`// prisma-next: skipped policy "${policy.name}": role "${badRole}" is not a valid PSL identifier and role references cannot be escaped`);
|
|
1005
|
+
skipNotesByTable.set(tableName, notes);
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
let head = sanitizePolicyHead(parseWireName(policy.name)?.prefix ?? policy.name);
|
|
1009
|
+
if (usedHeads.has(head)) {
|
|
1010
|
+
let n = 2;
|
|
1011
|
+
while (usedHeads.has(`${head}_${n}`)) n += 1;
|
|
1012
|
+
head = `${head}_${n}`;
|
|
1013
|
+
}
|
|
1014
|
+
usedHeads.add(head);
|
|
1015
|
+
blocks.push({
|
|
1016
|
+
kind: "policy",
|
|
1017
|
+
keyword: POLICY_OPERATION_KEYWORD[policy.operation],
|
|
1018
|
+
name: head,
|
|
1019
|
+
parameters: {
|
|
1020
|
+
target: {
|
|
1021
|
+
kind: "ref",
|
|
1022
|
+
identifier: modelName,
|
|
1023
|
+
span: SYNTHETIC_SPAN
|
|
1024
|
+
},
|
|
1025
|
+
roles: {
|
|
1026
|
+
kind: "list",
|
|
1027
|
+
items: policy.roles.map((role) => ({
|
|
1028
|
+
kind: "ref",
|
|
1029
|
+
identifier: role,
|
|
1030
|
+
span: SYNTHETIC_SPAN
|
|
1031
|
+
})),
|
|
1032
|
+
span: SYNTHETIC_SPAN
|
|
1033
|
+
},
|
|
1034
|
+
...policy.using !== void 0 ? { using: {
|
|
1035
|
+
kind: "value",
|
|
1036
|
+
raw: JSON.stringify(policy.using),
|
|
1037
|
+
span: SYNTHETIC_SPAN
|
|
1038
|
+
} } : {},
|
|
1039
|
+
...policy.withCheck !== void 0 ? { withCheck: {
|
|
1040
|
+
kind: "value",
|
|
1041
|
+
raw: JSON.stringify(policy.withCheck),
|
|
1042
|
+
span: SYNTHETIC_SPAN
|
|
1043
|
+
} } : {},
|
|
1044
|
+
...policy.permissive ? {} : { permissive: {
|
|
1045
|
+
kind: "value",
|
|
1046
|
+
raw: "false",
|
|
1047
|
+
span: SYNTHETIC_SPAN
|
|
1048
|
+
} }
|
|
1049
|
+
},
|
|
1050
|
+
blockAttributes: [{
|
|
1051
|
+
name: "map",
|
|
1052
|
+
args: [{
|
|
1053
|
+
kind: "positional",
|
|
1054
|
+
value: `"${escapePslString(policy.name)}"`,
|
|
1055
|
+
span: SYNTHETIC_SPAN
|
|
1056
|
+
}],
|
|
1057
|
+
span: SYNTHETIC_SPAN
|
|
1058
|
+
}],
|
|
1059
|
+
span: SYNTHETIC_SPAN
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
blocks,
|
|
1064
|
+
skipNotesByTable
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
const POSTGRES_FUNCTION_ATTRIBUTES = { "gen_random_uuid()": "@default(dbgenerated(\"gen_random_uuid()\"))" };
|
|
1068
|
+
function formatDbGeneratedAttribute(expression) {
|
|
1069
|
+
return `@default(dbgenerated(${JSON.stringify(expression)}))`;
|
|
1070
|
+
}
|
|
1071
|
+
function createPostgresDefaultMapping() {
|
|
1072
|
+
return {
|
|
1073
|
+
functionAttributes: POSTGRES_FUNCTION_ATTRIBUTES,
|
|
1074
|
+
fallbackFunctionAttribute: formatDbGeneratedAttribute
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
const POSTGRES_TO_PSL = {
|
|
1078
|
+
text: "String",
|
|
1079
|
+
bool: "Boolean",
|
|
1080
|
+
boolean: "Boolean",
|
|
1081
|
+
int4: "Int",
|
|
1082
|
+
integer: "Int",
|
|
1083
|
+
int8: "BigInt",
|
|
1084
|
+
bigint: "BigInt",
|
|
1085
|
+
float8: "Float",
|
|
1086
|
+
"double precision": "Float",
|
|
1087
|
+
jsonb: "Jsonb",
|
|
1088
|
+
bytea: "Bytes"
|
|
1089
|
+
};
|
|
1090
|
+
const PRESERVED_NATIVE_TYPES = {
|
|
1091
|
+
"character varying": "VarChar",
|
|
1092
|
+
character: "Char",
|
|
1093
|
+
char: "Char",
|
|
1094
|
+
varchar: "VarChar",
|
|
1095
|
+
uuid: "Uuid",
|
|
1096
|
+
inet: "Inet",
|
|
1097
|
+
int2: "SmallInt",
|
|
1098
|
+
smallint: "SmallInt",
|
|
1099
|
+
float4: "Real",
|
|
1100
|
+
real: "Real",
|
|
1101
|
+
numeric: "Numeric",
|
|
1102
|
+
decimal: "Numeric",
|
|
1103
|
+
timestamp: "Timestamp",
|
|
1104
|
+
"timestamp without time zone": "Timestamp",
|
|
1105
|
+
timestamptz: "Timestamptz",
|
|
1106
|
+
"timestamp with time zone": "Timestamptz",
|
|
1107
|
+
date: "Date",
|
|
1108
|
+
time: "Time",
|
|
1109
|
+
"time without time zone": "Time",
|
|
1110
|
+
timetz: "Timetz",
|
|
1111
|
+
"time with time zone": "Timetz",
|
|
1112
|
+
json: "Json"
|
|
1113
|
+
};
|
|
1114
|
+
const PARAMETERIZED_NATIVE_TYPES = {
|
|
1115
|
+
"character varying": "VarChar",
|
|
1116
|
+
character: "Char",
|
|
1117
|
+
char: "Char",
|
|
1118
|
+
varchar: "VarChar",
|
|
1119
|
+
numeric: "Numeric",
|
|
1120
|
+
decimal: "Numeric",
|
|
1121
|
+
timestamp: "Timestamp",
|
|
1122
|
+
timestamptz: "Timestamptz",
|
|
1123
|
+
time: "Time",
|
|
1124
|
+
timetz: "Timetz"
|
|
1125
|
+
};
|
|
1126
|
+
const PARAMETERIZED_TYPE_PATTERN = /^(.+?)\((.+)\)$/;
|
|
1127
|
+
function getOwnMappingValue(map, key) {
|
|
1128
|
+
return Object.hasOwn(map, key) ? map[key] : void 0;
|
|
1129
|
+
}
|
|
1130
|
+
function splitTypeParameterList(params) {
|
|
1131
|
+
return params.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
1132
|
+
}
|
|
1133
|
+
function createPostgresTypeMap(enumTypeNames) {
|
|
1134
|
+
return { resolve(nativeType) {
|
|
1135
|
+
if (enumTypeNames?.has(nativeType)) return {
|
|
1136
|
+
pslType: { name: nativeType },
|
|
1137
|
+
nativeType
|
|
1138
|
+
};
|
|
1139
|
+
const paramMatch = nativeType.match(PARAMETERIZED_TYPE_PATTERN);
|
|
1140
|
+
if (paramMatch) {
|
|
1141
|
+
const [, baseType = nativeType, params = ""] = paramMatch;
|
|
1142
|
+
const typeName = getOwnMappingValue(PARAMETERIZED_NATIVE_TYPES, baseType);
|
|
1143
|
+
if (typeName) return {
|
|
1144
|
+
pslType: {
|
|
1145
|
+
name: typeName,
|
|
1146
|
+
args: splitTypeParameterList(params)
|
|
1147
|
+
},
|
|
1148
|
+
nativeType,
|
|
1149
|
+
typeParams: {
|
|
1150
|
+
baseType,
|
|
1151
|
+
params
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
const preservedType = getOwnMappingValue(PRESERVED_NATIVE_TYPES, nativeType);
|
|
1156
|
+
if (preservedType) return {
|
|
1157
|
+
pslType: { name: preservedType },
|
|
1158
|
+
nativeType
|
|
1159
|
+
};
|
|
1160
|
+
const pslType = getOwnMappingValue(POSTGRES_TO_PSL, nativeType);
|
|
1161
|
+
if (pslType) return {
|
|
1162
|
+
pslType: { name: pslType },
|
|
1163
|
+
nativeType
|
|
1164
|
+
};
|
|
1165
|
+
return {
|
|
1166
|
+
unsupported: true,
|
|
1167
|
+
nativeType
|
|
1168
|
+
};
|
|
1169
|
+
} };
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Infers a PSL AST (for `printPsl`) from an introspected Postgres schema tree.
|
|
1173
|
+
*
|
|
1174
|
+
* Target-owned inference: it walks the `PostgresDatabaseSchemaNode` tree and
|
|
1175
|
+
* owns the Postgres dialect knowledge — the native type map and default map.
|
|
1176
|
+
* Relation inference, name transforms, generic default mapping, and raw-default
|
|
1177
|
+
* parsing are shape-neutral utilities imported from the SQL family.
|
|
1178
|
+
*
|
|
1179
|
+
* This slice emits relational-only PSL, byte-identical to the prior flat
|
|
1180
|
+
* inference: the tree's tables (across its namespaces — `contract infer`
|
|
1181
|
+
* introspects a single live namespace) are gathered into the model set and
|
|
1182
|
+
* emitted as one `UNSPECIFIED_PSL_NAMESPACE_ID` bucket. Top-level entities
|
|
1183
|
+
* (policies/roles → PSL extension blocks) are a later slice.
|
|
1184
|
+
*
|
|
1185
|
+
* `describedContracts` — the stack's extension packs' already-assembled
|
|
1186
|
+
* contracts, each paired with its space id — is consulted while gathering
|
|
1187
|
+
* tables: a table whose coordinate `(schemaName, 'table', tableName)` one of
|
|
1188
|
+
* those contracts already declares is omitted, before the duplicate-name
|
|
1189
|
+
* check below and before relation inference, so it cannot spuriously
|
|
1190
|
+
* collide with an app table and never contributes a bare relation field. A
|
|
1191
|
+
* surviving table's foreign key into an omitted, pack-owned table is not
|
|
1192
|
+
* dropped: {@link resolveForeignKeys} rewrites it into a relation qualified
|
|
1193
|
+
* with the pack's space id (`<spaceId>:<namespaceId>.<Model>`) instead.
|
|
1194
|
+
*/
|
|
1195
|
+
function inferPostgresPslContract(tree, describedContracts) {
|
|
1196
|
+
const namespaces = Object.values(tree.namespaces);
|
|
1197
|
+
const owners = describedContractOwners(describedContracts ?? []);
|
|
1198
|
+
const enumDefinitions = /* @__PURE__ */ new Map();
|
|
1199
|
+
const packOwnedEnumTypesByNamespace = /* @__PURE__ */ new Map();
|
|
1200
|
+
const enumNamespaceNames = /* @__PURE__ */ new Set();
|
|
1201
|
+
for (const namespace of namespaces) for (const { typeName, members } of namespace.nativeEnums) {
|
|
1202
|
+
const owner = owners.get(coordinateKey({
|
|
1203
|
+
namespaceId: namespace.schemaName,
|
|
1204
|
+
entityKind: "native_enum",
|
|
1205
|
+
entityName: typeName
|
|
1206
|
+
}));
|
|
1207
|
+
if (owner !== void 0) {
|
|
1208
|
+
const owned = packOwnedEnumTypesByNamespace.get(namespace.schemaName) ?? /* @__PURE__ */ new Map();
|
|
1209
|
+
owned.set(typeName, owner.spaceId);
|
|
1210
|
+
owned.set(`${namespace.schemaName}.${typeName}`, owner.spaceId);
|
|
1211
|
+
packOwnedEnumTypesByNamespace.set(namespace.schemaName, owned);
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
enumDefinitions.set(typeName, members);
|
|
1215
|
+
enumNamespaceNames.add(namespace.schemaName);
|
|
1216
|
+
}
|
|
1217
|
+
const tables = {};
|
|
1218
|
+
const tableNamespaceNames = /* @__PURE__ */ new Set();
|
|
1219
|
+
const rlsEnabledTables = /* @__PURE__ */ new Set();
|
|
1220
|
+
const policiesByTable = /* @__PURE__ */ new Map();
|
|
1221
|
+
for (const namespace of namespaces) {
|
|
1222
|
+
const ownedEnumTypes = packOwnedEnumTypesByNamespace.get(namespace.schemaName);
|
|
1223
|
+
for (const [tableName, table] of Object.entries(namespace.tables)) {
|
|
1224
|
+
if (owners.has(coordinateKey({
|
|
1225
|
+
namespaceId: namespace.schemaName,
|
|
1226
|
+
entityKind: "table",
|
|
1227
|
+
entityName: tableName
|
|
1228
|
+
}))) continue;
|
|
1229
|
+
if (tables[tableName] !== void 0) throw postgresError("CONTRACT.INFER_UNSUPPORTED", `contract infer: duplicate table name "${tableName}" across schemas is not yet supported (single-namespace PSL inference emits one flat bucket; multi-namespace \`namespace { … }\` output is a later slice).`, { meta: { tableName } });
|
|
1230
|
+
if (ownedEnumTypes !== void 0) for (const column of Object.values(table.columns)) {
|
|
1231
|
+
const owningSpaceId = ownedEnumTypes.get(column.nativeType);
|
|
1232
|
+
if (owningSpaceId !== void 0) throw postgresError("CONTRACT.INFER_UNSUPPORTED", `contract infer: column "${tableName}"."${column.name}" is typed by native enum type "${column.nativeType}", which extension pack space "${owningSpaceId}" already describes. A cross-space enum-typed column has no authorable PSL form yet; describe the table in that pack's contract or retype the column before re-running contract infer.`, { meta: {
|
|
1233
|
+
tableName,
|
|
1234
|
+
columnName: column.name
|
|
1235
|
+
} });
|
|
1236
|
+
}
|
|
1237
|
+
tables[tableName] = new SqlTableIR(table);
|
|
1238
|
+
tableNamespaceNames.add(namespace.schemaName);
|
|
1239
|
+
if (table.rlsEnabled) rlsEnabledTables.add(tableName);
|
|
1240
|
+
if (table.policies.length > 0) policiesByTable.set(tableName, table.policies);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
let wrapNamespaceName;
|
|
1244
|
+
if (enumDefinitions.size > 0 || policiesByTable.size > 0) {
|
|
1245
|
+
const contentNamespaces = /* @__PURE__ */ new Set([...enumNamespaceNames, ...tableNamespaceNames]);
|
|
1246
|
+
if (contentNamespaces.size > 1) throw postgresError("CONTRACT.INFER_UNSUPPORTED", `contract infer: adopting native enums or RLS policies with content across multiple schemas is not yet supported (single-namespace PSL inference emits one \`namespace { … }\` block; multi-namespace output is a later slice). Schemas: ${[...contentNamespaces].sort().join(", ")}.`, { meta: { schemas: [...contentNamespaces].sort() } });
|
|
1247
|
+
wrapNamespaceName = [...contentNamespaces][0];
|
|
1248
|
+
}
|
|
1249
|
+
const { tables: resolvedTables, extraRelationsByTable, crossSpaceFieldNamesByTable, danglingForeignKeysByTable } = resolveForeignKeys(tables, owners);
|
|
1250
|
+
const schemaIR = new SqlSchemaIR({ tables: resolvedTables });
|
|
1251
|
+
const enumTypeNames = new Set(enumDefinitions.keys());
|
|
1252
|
+
if (wrapNamespaceName !== void 0) for (const typeName of enumDefinitions.keys()) enumTypeNames.add(`${wrapNamespaceName}.${typeName}`);
|
|
1253
|
+
const enumInfo = {
|
|
1254
|
+
typeNames: enumTypeNames,
|
|
1255
|
+
definitions: enumDefinitions
|
|
1256
|
+
};
|
|
1257
|
+
return buildPslDocumentAst(schemaIR, {
|
|
1258
|
+
typeMap: createPostgresTypeMap(enumInfo.typeNames),
|
|
1259
|
+
defaultMapping: createPostgresDefaultMapping(),
|
|
1260
|
+
parseRawDefault,
|
|
1261
|
+
...enumDefinitions.size > 0 ? { enumInfo } : {}
|
|
1262
|
+
}, {
|
|
1263
|
+
extraRelationsByTable,
|
|
1264
|
+
crossSpaceFieldNamesByTable,
|
|
1265
|
+
danglingForeignKeysByTable
|
|
1266
|
+
}, wrapNamespaceName, {
|
|
1267
|
+
rlsEnabledTables,
|
|
1268
|
+
policiesByTable
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
function buildPslDocumentAst(schemaIR, options, foreignKeyExtras, namespaceName, rlsExtras) {
|
|
1272
|
+
const { typeMap, defaultMapping, parseRawDefault: rawDefaultParser } = options;
|
|
1273
|
+
const { extraRelationsByTable, crossSpaceFieldNamesByTable, danglingForeignKeysByTable } = foreignKeyExtras;
|
|
1274
|
+
const modelNames = buildTopLevelNameMap(Object.keys(schemaIR.tables), toModelName, "model", "table");
|
|
1275
|
+
const modelNameMap = new Map([...modelNames].map(([tableName, result]) => [tableName, result.name]));
|
|
1276
|
+
const { enumNameMap: bareEnumNameMap, enumBlocks } = buildNativeEnumBlocks(options.enumInfo?.definitions ?? /* @__PURE__ */ new Map(), modelNames);
|
|
1277
|
+
const enumNameMap = new Map(bareEnumNameMap);
|
|
1278
|
+
if (namespaceName !== void 0) for (const [typeName, pslName] of bareEnumNameMap) enumNameMap.set(`${namespaceName}.${typeName}`, pslName);
|
|
1279
|
+
const fieldNamesByTable = new Map([...crossSpaceFieldNamesByTable, ...buildFieldNamesByTable(schemaIR.tables)]);
|
|
1280
|
+
const { relationsByTable } = inferRelations(schemaIR.tables, modelNameMap);
|
|
1281
|
+
const policyEmission = buildPolicyBlocks(rlsExtras?.policiesByTable ?? /* @__PURE__ */ new Map(), modelNameMap, /* @__PURE__ */ new Set([...modelNameMap.values(), ...bareEnumNameMap.values()]));
|
|
1282
|
+
const models = [];
|
|
1283
|
+
for (const table of Object.values(schemaIR.tables)) models.push(buildModel(table, typeMap, enumNameMap, fieldNamesByTable, defaultMapping, rawDefaultParser, [...relationsByTable.get(table.name) ?? [], ...extraRelationsByTable.get(table.name) ?? []], danglingForeignKeysByTable.get(table.name) ?? [], rlsExtras?.rlsEnabledTables.has(table.name) ?? false, policyEmission.skipNotesByTable.get(table.name) ?? []));
|
|
1284
|
+
const sortedModels = topologicalSort(models, schemaIR.tables, modelNameMap);
|
|
1285
|
+
return {
|
|
1286
|
+
kind: "document",
|
|
1287
|
+
sourceId: "<sql-schema-ir>",
|
|
1288
|
+
namespaces: [makePslNamespace({
|
|
1289
|
+
kind: "namespace",
|
|
1290
|
+
name: namespaceName ?? UNSPECIFIED_PSL_NAMESPACE_ID,
|
|
1291
|
+
entries: makePslNamespaceEntries(sortedModels, [], [...enumBlocks, ...policyEmission.blocks]),
|
|
1292
|
+
span: SYNTHETIC_SPAN
|
|
1293
|
+
})],
|
|
1294
|
+
span: SYNTHETIC_SPAN
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
function postgresRenderDefault(def, column) {
|
|
1298
|
+
if (def.kind === "function") return def.expression;
|
|
1299
|
+
return renderDefaultLiteral(def.value, column);
|
|
1300
|
+
}
|
|
1301
|
+
const postgresTargetDescriptor = {
|
|
1302
|
+
...postgresTargetDescriptorMeta,
|
|
1303
|
+
contractSerializer: new PostgresContractSerializer(),
|
|
1304
|
+
schemaVerifier: new PostgresSchemaVerifier(),
|
|
1305
|
+
inferPslContract(schema, describedContracts) {
|
|
1306
|
+
PostgresDatabaseSchemaNode.assert(schema);
|
|
1307
|
+
return inferPostgresPslContract(schema, describedContracts);
|
|
1308
|
+
},
|
|
1309
|
+
diffSchema(input) {
|
|
1310
|
+
return diffPostgresSchema(input);
|
|
1311
|
+
},
|
|
1312
|
+
classifySubjectGranularity: postgresDiffSubjectGranularity,
|
|
1313
|
+
classifyEntityKind: postgresDiffSubjectEntityKind,
|
|
1314
|
+
migrations: {
|
|
1315
|
+
createPlanner(adapter) {
|
|
1316
|
+
return createPostgresMigrationPlanner(adapter);
|
|
1317
|
+
},
|
|
1318
|
+
createRunner(family) {
|
|
1319
|
+
return createPostgresMigrationRunner(family);
|
|
1320
|
+
},
|
|
1321
|
+
contractToSchema(contract, frameworkComponents) {
|
|
1322
|
+
const expander = buildNativeTypeExpander(frameworkComponents);
|
|
1323
|
+
return contractToPostgresDatabaseSchemaNode(blindCast(contract), {
|
|
1324
|
+
annotationNamespace: "pg",
|
|
1325
|
+
...ifDefined("expandNativeType", expander),
|
|
1326
|
+
renderDefault: postgresRenderDefault,
|
|
1327
|
+
resolveDefault: postgresResolveDefault
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
create() {
|
|
1332
|
+
return {
|
|
1333
|
+
familyId: "sql",
|
|
1334
|
+
targetId: "postgres"
|
|
1335
|
+
};
|
|
1336
|
+
},
|
|
1337
|
+
/**
|
|
1338
|
+
* Direct method for SQL-specific usage.
|
|
1339
|
+
* @deprecated Use migrations.createPlanner() for CLI compatibility.
|
|
1340
|
+
*/
|
|
1341
|
+
createPlanner(adapter) {
|
|
1342
|
+
return createPostgresMigrationPlanner(adapter);
|
|
1343
|
+
},
|
|
1344
|
+
/**
|
|
1345
|
+
* Direct method for SQL-specific usage.
|
|
1346
|
+
* @deprecated Use migrations.createRunner() for CLI compatibility.
|
|
1347
|
+
*/
|
|
1348
|
+
createRunner(family) {
|
|
1349
|
+
return createPostgresMigrationRunner(family);
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
//#endregion
|
|
1353
|
+
export { postgresTargetDescriptor as n, postgresRenderDefault as t };
|
|
1354
|
+
|
|
1355
|
+
//# sourceMappingURL=control-DRMWptiJ.mjs.map
|