@prisma/orm-family-sql 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 +28 -0
- package/dist/ast-BstHjAoG.mjs +325 -0
- package/dist/ast-BstHjAoG.mjs.map +1 -0
- package/dist/ast-DSuzmsWn.d.mts +287 -0
- package/dist/ast-DSuzmsWn.d.mts.map +1 -0
- package/dist/authoring-type-constructors-Blqoa2Ua-DyCmD6mp.mjs +316 -0
- package/dist/authoring-type-constructors-Blqoa2Ua-DyCmD6mp.mjs.map +1 -0
- package/dist/build-contract-DBCz_YSs-DO0wMHai.mjs +778 -0
- package/dist/build-contract-DBCz_YSs-DO0wMHai.mjs.map +1 -0
- package/dist/builder.d.mts +3 -0
- package/dist/builder.mjs +1 -0
- package/dist/builder__runtime.d.mts +42 -0
- package/dist/builder__runtime.d.mts.map +1 -0
- package/dist/builder__runtime.mjs +955 -0
- package/dist/builder__runtime.mjs.map +1 -0
- package/dist/builder__types.d.mts +3 -0
- package/dist/builder__types.mjs +1 -0
- package/dist/canonicalization-hooks-CYnW_sGn.d.mts +10 -0
- package/dist/canonicalization-hooks-CYnW_sGn.d.mts.map +1 -0
- package/dist/canonicalization-hooks-DfIc_lOO.mjs +87 -0
- package/dist/canonicalization-hooks-DfIc_lOO.mjs.map +1 -0
- package/dist/codec-descriptor-registry-BJVJRftS.mjs +96 -0
- package/dist/codec-descriptor-registry-BJVJRftS.mjs.map +1 -0
- package/dist/codec-types-MOaceoPi-C5eL2sao.d.mts +116 -0
- package/dist/codec-types-MOaceoPi-C5eL2sao.d.mts.map +1 -0
- package/dist/config-types-47qB33rW.d.mts +21 -0
- package/dist/config-types-47qB33rW.d.mts.map +1 -0
- package/dist/config-types-N7PJc6VN.mjs +65 -0
- package/dist/config-types-N7PJc6VN.mjs.map +1 -0
- package/dist/contract-builder-MvJJrQ-5.d.mts +1306 -0
- package/dist/contract-builder-MvJJrQ-5.d.mts.map +1 -0
- package/dist/contract-builder-YjMQcvZ1.mjs +1615 -0
- package/dist/contract-builder-YjMQcvZ1.mjs.map +1 -0
- package/dist/contract-emitter.d.mts +25 -0
- package/dist/contract-emitter.d.mts.map +1 -0
- package/dist/contract-emitter.mjs +2 -0
- package/dist/contract-errors-BCN6RVI5-5IHBF_lg.mjs +9 -0
- package/dist/contract-errors-BCN6RVI5-5IHBF_lg.mjs.map +1 -0
- package/dist/contract-psl.d.mts +65 -0
- package/dist/contract-psl.d.mts.map +1 -0
- package/dist/contract-psl.mjs +2 -0
- package/dist/contract-psl__provider.d.mts +23 -0
- package/dist/contract-psl__provider.d.mts.map +1 -0
- package/dist/contract-psl__provider.mjs +105 -0
- package/dist/contract-psl__provider.mjs.map +1 -0
- package/dist/contract-ts.d.mts +3 -0
- package/dist/contract-ts.mjs +4 -0
- package/dist/contract-ts__config-types.d.mts +2 -0
- package/dist/contract-ts__config-types.mjs +2 -0
- package/dist/contract-ts__contract-builder.d.mts +2 -0
- package/dist/contract-ts__contract-builder.mjs +3 -0
- package/dist/contract-view-BMR5IWk4.mjs +39 -0
- package/dist/contract-view-BMR5IWk4.mjs.map +1 -0
- package/dist/contract-view-BbWQp6r4.d.mts +61 -0
- package/dist/contract-view-BbWQp6r4.d.mts.map +1 -0
- package/dist/contract.d.mts +26 -0
- package/dist/contract.mjs +17 -0
- package/dist/contract__canonicalization-hooks.d.mts +2 -0
- package/dist/contract__canonicalization-hooks.mjs +2 -0
- package/dist/contract__contract-view.d.mts +2 -0
- package/dist/contract__contract-view.mjs +2 -0
- package/dist/contract__entity-handle-lowering-hook.d.mts +2 -0
- package/dist/contract__entity-handle-lowering-hook.mjs +2 -0
- package/dist/contract__entity-kinds.d.mts +2 -0
- package/dist/contract__entity-kinds.mjs +2 -0
- package/dist/contract__factories.d.mts +2 -0
- package/dist/contract__factories.mjs +2 -0
- package/dist/contract__foreign-key-materialization.d.mts +2 -0
- package/dist/contract__foreign-key-materialization.mjs +2 -0
- package/dist/contract__index-naming.d.mts +2 -0
- package/dist/contract__index-naming.mjs +2 -0
- package/dist/contract__index-type-validation.d.mts +2 -0
- package/dist/contract__index-type-validation.mjs +2 -0
- package/dist/contract__index-types.d.mts +3 -0
- package/dist/contract__index-types.mjs +2 -0
- package/dist/contract__pack-types.d.mts +2 -0
- package/dist/contract__pack-types.mjs +1 -0
- package/dist/contract__referential-action-sql.d.mts +2 -0
- package/dist/contract__referential-action-sql.mjs +2 -0
- package/dist/contract__resolve-storage-table.d.mts +2 -0
- package/dist/contract__resolve-storage-table.mjs +2 -0
- package/dist/contract__types.d.mts +11 -0
- package/dist/contract__types.mjs +4 -0
- package/dist/contract__validators.d.mts +2 -0
- package/dist/contract__validators.mjs +3 -0
- package/dist/contract__value-set-derivation-hook.d.mts +2 -0
- package/dist/contract__value-set-derivation-hook.mjs +2 -0
- package/dist/control-B7iS4jv4.mjs +1326 -0
- package/dist/control-B7iS4jv4.mjs.map +1 -0
- package/dist/control-BHmsZudH.d.mts +948 -0
- package/dist/control-BHmsZudH.d.mts.map +1 -0
- package/dist/control-adapter-4y1O8Ps3.d.mts +1 -0
- package/dist/control-adapter-C99J7aEF-C-17lFY8.d.mts +191 -0
- package/dist/control-adapter-C99J7aEF-C-17lFY8.d.mts.map +1 -0
- package/dist/db-DhMsmBS--DKENDi3l.d.mts +365 -0
- package/dist/db-DhMsmBS--DKENDi3l.d.mts.map +1 -0
- package/dist/ddl-types-BMp2bWVS-D1waZJ4n.mjs +153 -0
- package/dist/ddl-types-BMp2bWVS-D1waZJ4n.mjs.map +1 -0
- package/dist/ddl-types-rrMsf77U-DGctYf-T.d.mts +149 -0
- package/dist/ddl-types-rrMsf77U-DGctYf-T.d.mts.map +1 -0
- package/dist/diff-B16bL_IR.mjs +13 -0
- package/dist/diff-B16bL_IR.mjs.map +1 -0
- package/dist/diff-DhlzHOMD.d.mts +104 -0
- package/dist/diff-DhlzHOMD.d.mts.map +1 -0
- package/dist/dist-C_q1_QR9.mjs +9 -0
- package/dist/dist-C_q1_QR9.mjs.map +1 -0
- package/dist/dist-DrScXbhq.mjs +414 -0
- package/dist/dist-DrScXbhq.mjs.map +1 -0
- package/dist/dist-x6jvtkKM.mjs +68 -0
- package/dist/dist-x6jvtkKM.mjs.map +1 -0
- package/dist/entity-handle-lowering-hook-CIvRKBfa.d.mts +92 -0
- package/dist/entity-handle-lowering-hook-CIvRKBfa.d.mts.map +1 -0
- package/dist/entity-handle-lowering-hook-DwLIKNiS.mjs +11 -0
- package/dist/entity-handle-lowering-hook-DwLIKNiS.mjs.map +1 -0
- package/dist/entity-kinds-DKxwKolf-CcsY-iYV.mjs +136 -0
- package/dist/entity-kinds-DKxwKolf-CcsY-iYV.mjs.map +1 -0
- package/dist/entity-kinds-Dh6VH4uK.d.mts +29 -0
- package/dist/entity-kinds-Dh6VH4uK.d.mts.map +1 -0
- package/dist/errors-B5g0xWro-mWH2C07F.mjs +9 -0
- package/dist/errors-B5g0xWro-mWH2C07F.mjs.map +1 -0
- package/dist/errors.d.mts +63 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +2 -0
- package/dist/expression-DIf8F4Mc.d.mts +167 -0
- package/dist/expression-DIf8F4Mc.d.mts.map +1 -0
- package/dist/expression-RUO52bJB.mjs +109 -0
- package/dist/expression-RUO52bJB.mjs.map +1 -0
- package/dist/factories-B7tA4y9D.d.mts +39 -0
- package/dist/factories-B7tA4y9D.d.mts.map +1 -0
- package/dist/factories-CDG9zBxC.mjs +77 -0
- package/dist/factories-CDG9zBxC.mjs.map +1 -0
- package/dist/family.d.mts +13 -0
- package/dist/family.mjs +12 -0
- package/dist/family__control-adapter.d.mts +3 -0
- package/dist/family__control-adapter.mjs +1 -0
- package/dist/family__control.d.mts +4 -0
- package/dist/family__control.mjs +4 -0
- package/dist/family__diff.d.mts +3 -0
- package/dist/family__diff.mjs +3 -0
- package/dist/family__ir.d.mts +2 -0
- package/dist/family__ir.mjs +3 -0
- package/dist/family__migration.d.mts +2 -0
- package/dist/family__migration.mjs +2 -0
- package/dist/family__pack.d.mts +249 -0
- package/dist/family__pack.d.mts.map +1 -0
- package/dist/family__pack.mjs +18 -0
- package/dist/family__pack.mjs.map +1 -0
- package/dist/family__psl-infer.d.mts +2 -0
- package/dist/family__psl-infer.mjs +2 -0
- package/dist/family__runtime.d.mts +3 -0
- package/dist/family__runtime.mjs +3 -0
- package/dist/family__verify.d.mts +2 -0
- package/dist/family__verify.mjs +2 -0
- package/dist/foreign-key-BSy9u4O4-BnsOe5Nf.d.mts +85 -0
- package/dist/foreign-key-BSy9u4O4-BnsOe5Nf.d.mts.map +1 -0
- package/dist/foreign-key-materialization-Ca-wRbmj.d.mts +65 -0
- package/dist/foreign-key-materialization-Ca-wRbmj.d.mts.map +1 -0
- package/dist/foreign-key-materialization-HI7BIQrG.mjs +72 -0
- package/dist/foreign-key-materialization-HI7BIQrG.mjs.map +1 -0
- package/dist/index-Bb9U36N4.d.mts +511 -0
- package/dist/index-Bb9U36N4.d.mts.map +1 -0
- package/dist/index-BgaTJRxj.d.mts +25 -0
- package/dist/index-BgaTJRxj.d.mts.map +1 -0
- package/dist/index-naming-BZWoh45T.d.mts +62 -0
- package/dist/index-naming-BZWoh45T.d.mts.map +1 -0
- package/dist/index-naming-BoGBcMmI-DiJAr66H.mjs +108 -0
- package/dist/index-naming-BoGBcMmI-DiJAr66H.mjs.map +1 -0
- package/dist/index-type-validation-COH4d78M.mjs +17 -0
- package/dist/index-type-validation-COH4d78M.mjs.map +1 -0
- package/dist/index-type-validation-DN0L2mqm.d.mts +10 -0
- package/dist/index-type-validation-DN0L2mqm.d.mts.map +1 -0
- package/dist/index-types-CNhHgKHi.d.mts +1 -0
- package/dist/index-types-DJQ_xwaE.mjs +40 -0
- package/dist/index-types-DJQ_xwaE.mjs.map +1 -0
- package/dist/index-types-DPVhsW0v-D-BikM4h.d.mts +31 -0
- package/dist/index-types-DPVhsW0v-D-BikM4h.d.mts.map +1 -0
- package/dist/interpreter-5WgpL-ax-_nqHLUTr.mjs +3526 -0
- package/dist/interpreter-5WgpL-ax-_nqHLUTr.mjs.map +1 -0
- package/dist/ir-B3bKMnGQ.d.mts +123 -0
- package/dist/ir-B3bKMnGQ.d.mts.map +1 -0
- package/dist/ir-I-xciGUH.mjs +36 -0
- package/dist/ir-I-xciGUH.mjs.map +1 -0
- package/dist/lane-query-builder.d.mts +236 -0
- package/dist/lane-query-builder.d.mts.map +1 -0
- package/dist/lane-query-builder.mjs +67 -0
- package/dist/lane-query-builder.mjs.map +1 -0
- package/dist/middleware-B4XbF4d4-tfwBl6mX.d.mts +119 -0
- package/dist/middleware-B4XbF4d4-tfwBl6mX.d.mts.map +1 -0
- package/dist/middleware-ByRsXxxq.d.mts +1 -0
- package/dist/middleware-CMr4CHNz-DHIUdxlm.mjs +80 -0
- package/dist/middleware-CMr4CHNz-DHIUdxlm.mjs.map +1 -0
- package/dist/migration-CK4oePIo.mjs +44 -0
- package/dist/migration-CK4oePIo.mjs.map +1 -0
- package/dist/migration-CW0ZJLxP.d.mts +44 -0
- package/dist/migration-CW0ZJLxP.d.mts.map +1 -0
- package/dist/naming-BwqqVcZy-BgTObIQw.mjs +177 -0
- package/dist/naming-BwqqVcZy-BgTObIQw.mjs.map +1 -0
- package/dist/naming-jLgdauLi-C84G_UHi.d.mts +133 -0
- package/dist/naming-jLgdauLi-C84G_UHi.d.mts.map +1 -0
- package/dist/operations.d.mts +2 -0
- package/dist/operations.mjs +2 -0
- package/dist/orm-client.d.mts +1316 -0
- package/dist/orm-client.d.mts.map +1 -0
- package/dist/orm-client.mjs +4544 -0
- package/dist/orm-client.mjs.map +1 -0
- package/dist/pack-types-BcLTpwbv.d.mts +14 -0
- package/dist/pack-types-BcLTpwbv.d.mts.map +1 -0
- package/dist/plan-BTD8xFfa-Dd_K-A1R.d.mts +45 -0
- package/dist/plan-BTD8xFfa-Dd_K-A1R.d.mts.map +1 -0
- package/dist/plan-CkpcgUaM.mjs +34 -0
- package/dist/plan-CkpcgUaM.mjs.map +1 -0
- package/dist/plan-Cm4jaoNM.d.mts +2 -0
- package/dist/psl-infer-6je61Z6J.d.mts +119 -0
- package/dist/psl-infer-6je61Z6J.d.mts.map +1 -0
- package/dist/psl-infer-DGftl-HW.mjs +362 -0
- package/dist/psl-infer-DGftl-HW.mjs.map +1 -0
- package/dist/query-lane-context-Bz8XJZ45.d.mts +1 -0
- package/dist/query-lane-context-DtdVMqoz-C2Tj5QHA.d.mts +94 -0
- package/dist/query-lane-context-DtdVMqoz-C2Tj5QHA.d.mts.map +1 -0
- package/dist/referential-action-sql-Csd6dA8G.d.mts +12 -0
- package/dist/referential-action-sql-Csd6dA8G.d.mts.map +1 -0
- package/dist/referential-action-sql-DSqkrySN.mjs +17 -0
- package/dist/referential-action-sql-DSqkrySN.mjs.map +1 -0
- package/dist/relational-core.d.mts +13 -0
- package/dist/relational-core.mjs +8 -0
- package/dist/relational-core__ast.d.mts +5 -0
- package/dist/relational-core__ast.mjs +5 -0
- package/dist/relational-core__codec-descriptor-registry.d.mts +39 -0
- package/dist/relational-core__codec-descriptor-registry.d.mts.map +1 -0
- package/dist/relational-core__codec-descriptor-registry.mjs +2 -0
- package/dist/relational-core__contract-free.d.mts +220 -0
- package/dist/relational-core__contract-free.d.mts.map +1 -0
- package/dist/relational-core__contract-free.mjs +393 -0
- package/dist/relational-core__contract-free.mjs.map +1 -0
- package/dist/relational-core__expression.d.mts +2 -0
- package/dist/relational-core__expression.mjs +2 -0
- package/dist/relational-core__middleware.d.mts +3 -0
- package/dist/relational-core__middleware.mjs +2 -0
- package/dist/relational-core__plan.d.mts +4 -0
- package/dist/relational-core__plan.mjs +2 -0
- package/dist/relational-core__query-lane-context.d.mts +3 -0
- package/dist/relational-core__query-lane-context.mjs +1 -0
- package/dist/relational-core__types.d.mts +3 -0
- package/dist/relational-core__types.mjs +1 -0
- package/dist/resolve-storage-table-DUwyRJmP.d.mts +21 -0
- package/dist/resolve-storage-table-DUwyRJmP.d.mts.map +1 -0
- package/dist/resolve-storage-table-DZsfnzeW.mjs +46 -0
- package/dist/resolve-storage-table-DZsfnzeW.mjs.map +1 -0
- package/dist/runtime-DNewRuec.d.mts +46 -0
- package/dist/runtime-DNewRuec.d.mts.map +1 -0
- package/dist/runtime-tIHpGCcG.mjs +55 -0
- package/dist/runtime-tIHpGCcG.mjs.map +1 -0
- package/dist/runtime.d.mts +4 -0
- package/dist/runtime.mjs +1606 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/schema-differ-DpG_gnur-g5_iks8G.d.mts +47 -0
- package/dist/schema-differ-DpG_gnur-g5_iks8G.d.mts.map +1 -0
- package/dist/schema-ir.d.mts +2 -0
- package/dist/schema-ir.mjs +2 -0
- package/dist/schema-ir__naming.d.mts +2 -0
- package/dist/schema-ir__naming.mjs +2 -0
- package/dist/schema-ir__types.d.mts +3 -0
- package/dist/schema-ir__types.mjs +2 -0
- package/dist/schema-verify-vtFZbmGq-BNfTr6fU.mjs +222 -0
- package/dist/schema-verify-vtFZbmGq-BNfTr6fU.mjs.map +1 -0
- package/dist/serialized-index-B96AwqtU-DcqcB4q_.d.mts +26 -0
- package/dist/serialized-index-B96AwqtU-DcqcB4q_.d.mts.map +1 -0
- package/dist/serialized-index-Dp5_EjXD-DMsw8jf3.mjs +64 -0
- package/dist/serialized-index-Dp5_EjXD-DMsw8jf3.mjs.map +1 -0
- package/dist/sql-contract-serializer-2oBWuxTe-B9hzBBv1.mjs +171 -0
- package/dist/sql-contract-serializer-2oBWuxTe-B9hzBBv1.mjs.map +1 -0
- package/dist/sql-execution-plan-CJmsCP7n-DoEpNn7r.d.mts +32 -0
- package/dist/sql-execution-plan-CJmsCP7n-DoEpNn7r.d.mts.map +1 -0
- package/dist/sql-index-DsI8HAeK-BJsn-jvD.d.mts +72 -0
- package/dist/sql-index-DsI8HAeK-BJsn-jvD.d.mts.map +1 -0
- package/dist/sql-node-kEz6YFZo-v2F7delx.d.mts +42 -0
- package/dist/sql-node-kEz6YFZo-v2F7delx.d.mts.map +1 -0
- package/dist/sql-storage-B0QwiTg6-B8DVctJm.d.mts +171 -0
- package/dist/sql-storage-B0QwiTg6-B8DVctJm.d.mts.map +1 -0
- package/dist/storage-table-BBaAmanc-BwR37LMA.d.mts +130 -0
- package/dist/storage-table-BBaAmanc-BwR37LMA.d.mts.map +1 -0
- package/dist/storage-table-_LWDmPfv-DFhU2dML.mjs +277 -0
- package/dist/storage-table-_LWDmPfv-DFhU2dML.mjs.map +1 -0
- package/dist/storage-value-set-BGlj5Uus-CvZD--GG.d.mts +41 -0
- package/dist/storage-value-set-BGlj5Uus-CvZD--GG.d.mts.map +1 -0
- package/dist/timestamp-now-generator-DRXygu32-DN9Qb1uI.mjs +173 -0
- package/dist/timestamp-now-generator-DRXygu32-DN9Qb1uI.mjs.map +1 -0
- package/dist/types-BchwQpmO.d.mts +16 -0
- package/dist/types-BchwQpmO.d.mts.map +1 -0
- package/dist/types-BgkZru_1-H7yfikSQ.mjs +1444 -0
- package/dist/types-BgkZru_1-H7yfikSQ.mjs.map +1 -0
- package/dist/types-CMMQS6aG.mjs +111 -0
- package/dist/types-CMMQS6aG.mjs.map +1 -0
- package/dist/types-CuDrWc5v-ISVXBmBs.d.mts +134 -0
- package/dist/types-CuDrWc5v-ISVXBmBs.d.mts.map +1 -0
- package/dist/types-CvbBw1Jo-3KQdErJY.d.mts +688 -0
- package/dist/types-CvbBw1Jo-3KQdErJY.d.mts.map +1 -0
- package/dist/types-DIBA80mc-wf5wLmON.d.mts +723 -0
- package/dist/types-DIBA80mc-wf5wLmON.d.mts.map +1 -0
- package/dist/types-DMxc5KZa.d.mts +1 -0
- package/dist/types-Dl20lxky-DC_jdA0T.d.mts +247 -0
- package/dist/types-Dl20lxky-DC_jdA0T.d.mts.map +1 -0
- package/dist/types-DmhUVq4u-BpqqjPJx.mjs +719 -0
- package/dist/types-DmhUVq4u-BpqqjPJx.mjs.map +1 -0
- package/dist/types-IqZMLrFC-DtdJNICr.d.mts +426 -0
- package/dist/types-IqZMLrFC-DtdJNICr.d.mts.map +1 -0
- package/dist/types-TvBZ2Ne9.d.mts +1 -0
- package/dist/unique-constraint-jQWfH7dg-zLWtFp99.d.mts +32 -0
- package/dist/unique-constraint-jQWfH7dg-zLWtFp99.d.mts.map +1 -0
- package/dist/util-DQQgv2j1-D6INZaRG.mjs +34 -0
- package/dist/util-DQQgv2j1-D6INZaRG.mjs.map +1 -0
- package/dist/validators-C4PItWnf.mjs +0 -0
- package/dist/validators-C4PItWnf.mjs.map +1 -0
- package/dist/validators-CxeHhHrf.d.mts +206 -0
- package/dist/validators-CxeHhHrf.d.mts.map +1 -0
- package/dist/value-set-derivation-hook-BhgGVtw0.d.mts +35 -0
- package/dist/value-set-derivation-hook-BhgGVtw0.d.mts.map +1 -0
- package/dist/value-set-derivation-hook-Di9JZ0X7.mjs +22 -0
- package/dist/value-set-derivation-hook-Di9JZ0X7.mjs.map +1 -0
- package/dist/verify-E6Dd2vYp-gYwnlhLm.mjs +86 -0
- package/dist/verify-E6Dd2vYp-gYwnlhLm.mjs.map +1 -0
- package/dist/verify-zDRnw0Rn.d.mts +26 -0
- package/dist/verify-zDRnw0Rn.d.mts.map +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,4544 @@
|
|
|
1
|
+
import { t as resolveStorageTable } from "./resolve-storage-table-DZsfnzeW.mjs";
|
|
2
|
+
import { i as isUniqueConstraintViolation } from "./dist-x6jvtkKM.mjs";
|
|
3
|
+
import { C as LiteralExpr, D as OrExpr, F as SubqueryExpr, I as TableSource, L as UpdateAst, O as OrderByItem, P as SelectAst, R as WindowFuncExpr, S as ListExpression, T as NullCheckExpr, V as isWhereExpr, _ as InsertAst, b as JsonArrayAggExpr, c as DeleteAst, f as EqColJoinOn, j as ProjectionItem, k as ParamRef, l as DerivedTableSource, n as AndExpr, o as ColumnRef, p as ExistsExpr, r as BinaryExpr, s as DefaultValueExpr, t as AggregateExpr, v as InsertOnConflict, w as NotExpr, x as JsonObjectExpr, y as JoinAst } from "./types-BgkZru_1-H7yfikSQ.mjs";
|
|
4
|
+
import { t as collectOrderedParamRefs } from "./util-DQQgv2j1-D6INZaRG.mjs";
|
|
5
|
+
import { n as JsonDocumentProjection, r as NativeJsonValueProjection, t as CodecJsonValueProjection } from "./ast-BstHjAoG.mjs";
|
|
6
|
+
import { n as codecRefForStorageColumn } from "./codec-descriptor-registry-BJVJRftS.mjs";
|
|
7
|
+
import { structuredError } from "@prisma/orm-framework/utils/structured-error";
|
|
8
|
+
import { InternalError, assertNever } from "@prisma/orm-framework/utils/internal-error";
|
|
9
|
+
import { domainModelsAtDefaultNamespace } from "@prisma/orm-framework/contract/types";
|
|
10
|
+
import { blindCast, castAs } from "@prisma/orm-framework/utils/casts";
|
|
11
|
+
import { ifDefined } from "@prisma/orm-framework/utils/defined";
|
|
12
|
+
import { assertDefined, invariant } from "@prisma/orm-framework/utils/assertions";
|
|
13
|
+
import { AsyncIterableResult, createMetaBuilder, isRuntimeError, runtimeError } from "@prisma/orm-framework/components/runtime";
|
|
14
|
+
//#region ../../../3-extensions/sql-orm-client/dist/index.mjs
|
|
15
|
+
function ormError(code, message, options) {
|
|
16
|
+
return structuredError(code, message, options);
|
|
17
|
+
}
|
|
18
|
+
function resolveTableForContract(contract, namespaceId, tableName) {
|
|
19
|
+
return resolveStorageTable(contract.storage, tableName, namespaceId);
|
|
20
|
+
}
|
|
21
|
+
function requireStorageTableForContract(contract, namespaceId, tableName) {
|
|
22
|
+
const resolved = resolveTableForContract(contract, namespaceId, tableName);
|
|
23
|
+
if (resolved === void 0) throw ormError("ORM.TABLE_UNKNOWN", `Unknown table "${tableName}"`, { meta: {
|
|
24
|
+
namespaceId,
|
|
25
|
+
tableName
|
|
26
|
+
} });
|
|
27
|
+
return resolved;
|
|
28
|
+
}
|
|
29
|
+
function storageTableForContract(contract, namespaceId, tableName) {
|
|
30
|
+
return requireStorageTableForContract(contract, namespaceId, tableName).table;
|
|
31
|
+
}
|
|
32
|
+
function domainModelNamesInNamespace(contract, namespaceId) {
|
|
33
|
+
const namespace = contract.domain.namespaces[namespaceId];
|
|
34
|
+
return namespace ? Object.keys(namespace.models) : [];
|
|
35
|
+
}
|
|
36
|
+
function domainModelTableInNamespace(contract, namespaceId, modelName) {
|
|
37
|
+
const table = (contract.domain.namespaces[namespaceId]?.models[modelName])?.storage["table"];
|
|
38
|
+
return typeof table === "string" ? table : void 0;
|
|
39
|
+
}
|
|
40
|
+
function tableSourceForContract(contract, namespaceId, tableName, alias) {
|
|
41
|
+
const resolved = requireStorageTableForContract(contract, namespaceId, tableName);
|
|
42
|
+
const effectiveAlias = alias !== void 0 && alias !== tableName ? alias : void 0;
|
|
43
|
+
return TableSource.named(tableName, effectiveAlias, resolved.namespaceId);
|
|
44
|
+
}
|
|
45
|
+
const POLYMORPHIC_DISCRIMINATOR_ALIAS = "__prisma_polymorphic_discriminator";
|
|
46
|
+
function modelsOf(contract, namespaceId) {
|
|
47
|
+
const namespace = contract.domain.namespaces[namespaceId];
|
|
48
|
+
if (namespace === void 0) throw new InternalError(`domain namespace "${namespaceId}" is not present on the contract`);
|
|
49
|
+
return blindCast(namespace.models);
|
|
50
|
+
}
|
|
51
|
+
function metadataCacheKey(namespaceId, modelName) {
|
|
52
|
+
return `${namespaceId}\u0000${modelName}`;
|
|
53
|
+
}
|
|
54
|
+
function modelOf(contract, namespaceId, name) {
|
|
55
|
+
const model = contract.domain.namespaces[namespaceId]?.models[name];
|
|
56
|
+
return model === void 0 ? void 0 : blindCast(model);
|
|
57
|
+
}
|
|
58
|
+
const fieldToColumnCache = /* @__PURE__ */ new WeakMap();
|
|
59
|
+
const columnToFieldCache = /* @__PURE__ */ new WeakMap();
|
|
60
|
+
const polymorphismCache = /* @__PURE__ */ new WeakMap();
|
|
61
|
+
function resolvePolymorphismInfo(contract, namespaceId, modelName) {
|
|
62
|
+
let perContract = polymorphismCache.get(contract);
|
|
63
|
+
if (!perContract) {
|
|
64
|
+
perContract = /* @__PURE__ */ new Map();
|
|
65
|
+
polymorphismCache.set(contract, perContract);
|
|
66
|
+
}
|
|
67
|
+
const cacheKey = metadataCacheKey(namespaceId, modelName);
|
|
68
|
+
if (perContract.has(cacheKey)) return perContract.get(cacheKey);
|
|
69
|
+
const models = modelsOf(contract, namespaceId);
|
|
70
|
+
const model = models[modelName];
|
|
71
|
+
if (!model?.discriminator || !model.variants) {
|
|
72
|
+
perContract.set(cacheKey, void 0);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const baseTable = model.storage?.table;
|
|
76
|
+
if (!baseTable) {
|
|
77
|
+
perContract.set(cacheKey, void 0);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const discriminatorField = model.discriminator.field;
|
|
81
|
+
const discriminatorColumn = resolveFieldToColumn(contract, namespaceId, modelName, discriminatorField);
|
|
82
|
+
const variants = /* @__PURE__ */ new Map();
|
|
83
|
+
const variantsByValue = /* @__PURE__ */ new Map();
|
|
84
|
+
const mtiVariants = [];
|
|
85
|
+
for (const [variantModelName, variantEntry] of Object.entries(model.variants)) {
|
|
86
|
+
const variantModel = models[variantModelName];
|
|
87
|
+
if (!variantModel) throw new InternalError(`Model "${modelName}" declares variant "${variantModelName}", but that model is missing from the contract`);
|
|
88
|
+
const variantTable = variantModel.storage?.table ?? baseTable;
|
|
89
|
+
const strategy = variantTable === baseTable ? "sti" : "mti";
|
|
90
|
+
const info = {
|
|
91
|
+
modelName: variantModelName,
|
|
92
|
+
value: variantEntry.value,
|
|
93
|
+
table: variantTable,
|
|
94
|
+
strategy
|
|
95
|
+
};
|
|
96
|
+
variants.set(variantModelName, info);
|
|
97
|
+
variantsByValue.set(variantEntry.value, info);
|
|
98
|
+
if (strategy === "mti") mtiVariants.push(info);
|
|
99
|
+
}
|
|
100
|
+
const result = {
|
|
101
|
+
discriminatorField,
|
|
102
|
+
discriminatorColumn,
|
|
103
|
+
baseTable,
|
|
104
|
+
variants,
|
|
105
|
+
variantsByValue,
|
|
106
|
+
mtiVariants
|
|
107
|
+
};
|
|
108
|
+
perContract.set(cacheKey, result);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
function resolveFieldToColumn(contract, namespaceId, modelName, fieldName) {
|
|
112
|
+
return getFieldToColumnMap(contract, namespaceId, modelName)[fieldName] ?? fieldName;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Map the fields that an MTI variant contributes to `{ table, column }` refs
|
|
116
|
+
* qualified against the variant's own table — the table the read path joins
|
|
117
|
+
* into the correlated child SELECT. STI variants contribute nothing here:
|
|
118
|
+
* their columns live on the base table and resolve through the ordinary
|
|
119
|
+
* base-table field map. Base fields are intentionally absent so callers can
|
|
120
|
+
* gate variant qualification strictly to variant-owned fields.
|
|
121
|
+
*
|
|
122
|
+
* `baseModelName` is a default-namespace model name, consistent with the rest
|
|
123
|
+
* of this module; namespace context is bound downstream at table resolution.
|
|
124
|
+
*
|
|
125
|
+
* Uncached on purpose: `resolvePolymorphismInfo` already memoizes the variant
|
|
126
|
+
* lookup, and the remaining work is one pass over the variant's field→column
|
|
127
|
+
* map, so a second cache layer would buy nothing.
|
|
128
|
+
*/
|
|
129
|
+
function resolveVariantFieldColumns(contract, namespaceId, baseModelName, variantName) {
|
|
130
|
+
const variant = resolvePolymorphismInfo(contract, namespaceId, baseModelName)?.variants.get(variantName);
|
|
131
|
+
const result = {};
|
|
132
|
+
if (variant && variant.strategy === "mti") {
|
|
133
|
+
const variantFieldToColumn = getFieldToColumnMap(contract, namespaceId, variant.modelName);
|
|
134
|
+
for (const [field, column] of Object.entries(variantFieldToColumn)) result[field] = {
|
|
135
|
+
table: variant.table,
|
|
136
|
+
column
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
function getFieldToColumnMap(contract, namespaceId, modelName) {
|
|
142
|
+
let perContract = fieldToColumnCache.get(contract);
|
|
143
|
+
if (!perContract) {
|
|
144
|
+
perContract = /* @__PURE__ */ new Map();
|
|
145
|
+
fieldToColumnCache.set(contract, perContract);
|
|
146
|
+
}
|
|
147
|
+
const cacheKey = metadataCacheKey(namespaceId, modelName);
|
|
148
|
+
let cached = perContract.get(cacheKey);
|
|
149
|
+
if (cached) return cached;
|
|
150
|
+
const storageFields = modelsOf(contract, namespaceId)[modelName]?.storage?.fields ?? {};
|
|
151
|
+
cached = {};
|
|
152
|
+
for (const [f, s] of Object.entries(storageFields)) if (s?.column) cached[f] = s.column;
|
|
153
|
+
perContract.set(cacheKey, cached);
|
|
154
|
+
return cached;
|
|
155
|
+
}
|
|
156
|
+
function getColumnToFieldMap(contract, namespaceId, modelName) {
|
|
157
|
+
let perContract = columnToFieldCache.get(contract);
|
|
158
|
+
if (!perContract) {
|
|
159
|
+
perContract = /* @__PURE__ */ new Map();
|
|
160
|
+
columnToFieldCache.set(contract, perContract);
|
|
161
|
+
}
|
|
162
|
+
const cacheKey = metadataCacheKey(namespaceId, modelName);
|
|
163
|
+
let cached = perContract.get(cacheKey);
|
|
164
|
+
if (cached) return cached;
|
|
165
|
+
const storageFields = modelsOf(contract, namespaceId)[modelName]?.storage?.fields ?? {};
|
|
166
|
+
cached = {};
|
|
167
|
+
for (const [f, s] of Object.entries(storageFields)) if (s?.column) cached[s.column] = f;
|
|
168
|
+
perContract.set(cacheKey, cached);
|
|
169
|
+
return cached;
|
|
170
|
+
}
|
|
171
|
+
const completeColumnToFieldCache = /* @__PURE__ */ new WeakMap();
|
|
172
|
+
/**
|
|
173
|
+
* Like getColumnToFieldMap but includes identity-mapped fields (where field name equals column
|
|
174
|
+
* name). getColumnToFieldMap only returns explicit remaps; this returns ALL column→field entries.
|
|
175
|
+
*/
|
|
176
|
+
function getCompleteColumnToFieldMap(contract, namespaceId, modelName) {
|
|
177
|
+
let perContract = completeColumnToFieldCache.get(contract);
|
|
178
|
+
if (!perContract) {
|
|
179
|
+
perContract = /* @__PURE__ */ new Map();
|
|
180
|
+
completeColumnToFieldCache.set(contract, perContract);
|
|
181
|
+
}
|
|
182
|
+
const cacheKey = metadataCacheKey(namespaceId, modelName);
|
|
183
|
+
let cached = perContract.get(cacheKey);
|
|
184
|
+
if (cached) return cached;
|
|
185
|
+
const storageFields = modelsOf(contract, namespaceId)[modelName]?.storage?.fields ?? {};
|
|
186
|
+
cached = {};
|
|
187
|
+
for (const [f, s] of Object.entries(storageFields)) cached[s?.column ?? f] = f;
|
|
188
|
+
perContract.set(cacheKey, cached);
|
|
189
|
+
return cached;
|
|
190
|
+
}
|
|
191
|
+
function resolveIncludeRelation(contract, namespaceId, baseModelName, relationName, variantName) {
|
|
192
|
+
const polymorphism = resolvePolymorphismInfo(contract, namespaceId, baseModelName);
|
|
193
|
+
const variant = variantName === void 0 ? void 0 : polymorphism?.variants.get(variantName);
|
|
194
|
+
let declaringModelName = baseModelName;
|
|
195
|
+
let localTableName = resolveModelTableName(contract, namespaceId, baseModelName);
|
|
196
|
+
let relation;
|
|
197
|
+
if (variant !== void 0) {
|
|
198
|
+
const candidate = resolveModelRelations(contract, namespaceId, variant.modelName)[relationName];
|
|
199
|
+
if (candidate !== void 0) {
|
|
200
|
+
relation = candidate;
|
|
201
|
+
declaringModelName = variant.modelName;
|
|
202
|
+
localTableName = variant.table;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
relation ??= resolveModelRelations(contract, namespaceId, baseModelName)[relationName];
|
|
206
|
+
if (!relation) throw ormError("ORM.RELATION_UNKNOWN", `Relation '${relationName}' not found on model '${baseModelName}'`, { meta: {
|
|
207
|
+
model: baseModelName,
|
|
208
|
+
relation: relationName
|
|
209
|
+
} });
|
|
210
|
+
const localField = relation.on.localFields[0];
|
|
211
|
+
const targetField = relation.on.targetFields[0];
|
|
212
|
+
if (!localField || !targetField) throw new InternalError(`Relation '${relationName}' on model '${declaringModelName}' has incomplete join metadata (missing localFields or targetFields)`);
|
|
213
|
+
const relatedTableName = resolveModelTableName(contract, relation.toNamespace, relation.to);
|
|
214
|
+
const localColumn = resolveFieldToColumn(contract, namespaceId, declaringModelName, localField);
|
|
215
|
+
const targetColumn = resolveFieldToColumn(contract, relation.toNamespace, relation.to, targetField);
|
|
216
|
+
let through;
|
|
217
|
+
if (relation.through !== void 0) {
|
|
218
|
+
const parentLocalColumns = relation.on.localFields.map((field) => resolveFieldToColumn(contract, namespaceId, declaringModelName, field));
|
|
219
|
+
through = {
|
|
220
|
+
table: relation.through.table,
|
|
221
|
+
namespaceId: relation.through.namespaceId,
|
|
222
|
+
parentColumns: relation.through.parentColumns,
|
|
223
|
+
childColumns: relation.through.childColumns,
|
|
224
|
+
targetColumns: relation.through.targetColumns,
|
|
225
|
+
parentLocalColumns
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
relatedModelName: relation.to,
|
|
230
|
+
relatedNamespaceId: relation.toNamespace,
|
|
231
|
+
relatedTableName,
|
|
232
|
+
localTableName,
|
|
233
|
+
targetColumn,
|
|
234
|
+
localColumn,
|
|
235
|
+
cardinality: relation.cardinality,
|
|
236
|
+
...ifDefined("through", through)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function resolveThrough(contract, through) {
|
|
240
|
+
if (!through) return void 0;
|
|
241
|
+
const { table, namespaceId, parentColumns, childColumns, targetColumns } = through;
|
|
242
|
+
const junctionTable = contract.storage.namespaces[namespaceId]?.entries.table?.[table];
|
|
243
|
+
if (!junctionTable) return void 0;
|
|
244
|
+
const fkColumnSet = /* @__PURE__ */ new Set([...parentColumns, ...childColumns]);
|
|
245
|
+
const requiredPayloadColumns = [];
|
|
246
|
+
for (const [colName, col] of Object.entries(junctionTable.columns)) if (!fkColumnSet.has(colName) && !col.nullable && col.default === void 0 && !hasExecutionCreateDefault(contract, namespaceId, table, colName)) requiredPayloadColumns.push(colName);
|
|
247
|
+
return {
|
|
248
|
+
table,
|
|
249
|
+
namespaceId,
|
|
250
|
+
parentColumns,
|
|
251
|
+
childColumns,
|
|
252
|
+
targetColumns,
|
|
253
|
+
requiredPayloadColumns
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function hasExecutionCreateDefault(contract, namespace, table, column) {
|
|
257
|
+
return contract.execution?.mutations.defaults.some((mutationDefault) => mutationDefault.ref.namespace === namespace && mutationDefault.ref.table === table && mutationDefault.ref.column === column && mutationDefault.onCreate !== void 0) ?? false;
|
|
258
|
+
}
|
|
259
|
+
const modelRelationsCache = /* @__PURE__ */ new WeakMap();
|
|
260
|
+
function resolveModelRelations(contract, namespaceId, modelName) {
|
|
261
|
+
let perContract = modelRelationsCache.get(contract);
|
|
262
|
+
if (!perContract) {
|
|
263
|
+
perContract = /* @__PURE__ */ new Map();
|
|
264
|
+
modelRelationsCache.set(contract, perContract);
|
|
265
|
+
}
|
|
266
|
+
const cacheKey = metadataCacheKey(namespaceId, modelName);
|
|
267
|
+
const cached = perContract.get(cacheKey);
|
|
268
|
+
if (cached) return cached;
|
|
269
|
+
const relationMap = modelsOf(contract, namespaceId)[modelName]?.relations ?? {};
|
|
270
|
+
const resolved = {};
|
|
271
|
+
for (const [name, value] of Object.entries(relationMap)) {
|
|
272
|
+
if (!value || typeof value !== "object") continue;
|
|
273
|
+
const rel = blindCast(value);
|
|
274
|
+
const localFields = rel.on?.localFields;
|
|
275
|
+
const targetFields = rel.on?.targetFields;
|
|
276
|
+
if (!rel.to || typeof rel.to !== "object" || typeof rel.to.model !== "string" || !Array.isArray(localFields) || !Array.isArray(targetFields)) continue;
|
|
277
|
+
const through = resolveThrough(contract, rel.through);
|
|
278
|
+
resolved[name] = {
|
|
279
|
+
to: rel.to.model,
|
|
280
|
+
toNamespace: rel.to.namespace,
|
|
281
|
+
cardinality: parseRelationCardinality(rel.cardinality),
|
|
282
|
+
on: {
|
|
283
|
+
localFields: blindCast(localFields),
|
|
284
|
+
targetFields: blindCast(targetFields)
|
|
285
|
+
},
|
|
286
|
+
...through !== void 0 ? { through } : {}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
perContract.set(cacheKey, resolved);
|
|
290
|
+
return resolved;
|
|
291
|
+
}
|
|
292
|
+
function parseRelationCardinality(value) {
|
|
293
|
+
if (value === "1:1" || value === "N:1" || value === "1:N" || value === "N:M") return value;
|
|
294
|
+
}
|
|
295
|
+
function resolveUpsertConflictColumns(contract, namespaceId, modelName, conflictOn) {
|
|
296
|
+
if (conflictOn && typeof conflictOn === "object") {
|
|
297
|
+
const columns = Object.keys(conflictOn).map((fieldName) => resolveFieldToColumn(contract, namespaceId, modelName, fieldName));
|
|
298
|
+
if (columns.length > 0) return columns;
|
|
299
|
+
}
|
|
300
|
+
return [...storageTableForContract(contract, namespaceId, resolveModelTableName(contract, namespaceId, modelName)).primaryKey?.columns ?? []];
|
|
301
|
+
}
|
|
302
|
+
function resolveModelTableName(contract, namespaceId, modelName) {
|
|
303
|
+
const table = domainModelTableInNamespace(contract, namespaceId, modelName);
|
|
304
|
+
if (table === void 0) throw new InternalError(`Model "${modelName}" has invalid or missing storage.table in namespace "${namespaceId}"`);
|
|
305
|
+
return table;
|
|
306
|
+
}
|
|
307
|
+
function resolvePrimaryKeyColumn(contract, namespaceId, tableName) {
|
|
308
|
+
return resolveTableForContract(contract, namespaceId, tableName)?.table.primaryKey?.columns[0] ?? "id";
|
|
309
|
+
}
|
|
310
|
+
function resolveRowIdentityColumns(contract, namespaceId, tableName) {
|
|
311
|
+
let table;
|
|
312
|
+
try {
|
|
313
|
+
table = storageTableForContract(contract, namespaceId, tableName);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (error instanceof Error && error.message.includes("ambiguous")) throw error;
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
if (table.primaryKey && table.primaryKey.columns.length > 0) return table.primaryKey.columns;
|
|
319
|
+
for (const unique of table.uniques) if (unique.columns.length > 0) return unique.columns;
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
function assertReturningCapability(contract, action) {
|
|
323
|
+
if (hasContractCapability(contract, "returning")) return;
|
|
324
|
+
throw ormError("ORM.CAPABILITY_MISSING", `${action} requires contract capability "returning"`, { meta: {
|
|
325
|
+
capability: "returning",
|
|
326
|
+
action
|
|
327
|
+
} });
|
|
328
|
+
}
|
|
329
|
+
function hasContractCapability(contract, capability) {
|
|
330
|
+
const capabilities = contract.capabilities;
|
|
331
|
+
const value = capabilities[capability];
|
|
332
|
+
if (capabilityEnabled(value)) return true;
|
|
333
|
+
return Object.values(capabilities).some((targetCapabilities) => {
|
|
334
|
+
if (typeof targetCapabilities !== "object" || targetCapabilities === null) return false;
|
|
335
|
+
return capabilityEnabled(targetCapabilities[capability]);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function capabilityEnabled(value) {
|
|
339
|
+
if (value === true) return true;
|
|
340
|
+
if (typeof value !== "object" || value === null) return false;
|
|
341
|
+
return Object.values(blindCast(value)).some((flag) => flag === true);
|
|
342
|
+
}
|
|
343
|
+
function isToOneCardinality(cardinality) {
|
|
344
|
+
return cardinality === "1:1" || cardinality === "N:1";
|
|
345
|
+
}
|
|
346
|
+
function createAggregateBuilder(contract, namespaceId, modelName) {
|
|
347
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
348
|
+
return {
|
|
349
|
+
count() {
|
|
350
|
+
return {
|
|
351
|
+
kind: "aggregate",
|
|
352
|
+
fn: "count"
|
|
353
|
+
};
|
|
354
|
+
},
|
|
355
|
+
sum(field) {
|
|
356
|
+
return createFieldAggregateSelector(fieldToColumn, field, "sum");
|
|
357
|
+
},
|
|
358
|
+
avg(field) {
|
|
359
|
+
return createFieldAggregateSelector(fieldToColumn, field, "avg");
|
|
360
|
+
},
|
|
361
|
+
min(field) {
|
|
362
|
+
return createFieldAggregateSelector(fieldToColumn, field, "min");
|
|
363
|
+
},
|
|
364
|
+
max(field) {
|
|
365
|
+
return createFieldAggregateSelector(fieldToColumn, field, "max");
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function isAggregateSelector(value) {
|
|
370
|
+
if (!value || typeof value !== "object") return false;
|
|
371
|
+
const candidate = value;
|
|
372
|
+
if (candidate.kind !== "aggregate") return false;
|
|
373
|
+
return candidate.fn === "count" || candidate.fn === "sum" || candidate.fn === "avg" || candidate.fn === "min" || candidate.fn === "max";
|
|
374
|
+
}
|
|
375
|
+
function createFieldAggregateSelector(fieldToColumn, field, fn) {
|
|
376
|
+
const fieldName = field;
|
|
377
|
+
return {
|
|
378
|
+
kind: "aggregate",
|
|
379
|
+
fn,
|
|
380
|
+
column: fieldToColumn[fieldName] ?? fieldName
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function normalizeAggregateResult(aggregateSpec, row) {
|
|
384
|
+
const result = {};
|
|
385
|
+
for (const [alias, selector] of Object.entries(aggregateSpec)) {
|
|
386
|
+
const value = row[alias];
|
|
387
|
+
if (value === null) {
|
|
388
|
+
result[alias] = null;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (value === void 0) {
|
|
392
|
+
result[alias] = selector.fn === "count" ? 0 : null;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (typeof value === "number") {
|
|
396
|
+
result[alias] = value;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (typeof value === "bigint") {
|
|
400
|
+
result[alias] = Number(value);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (typeof value === "string") {
|
|
404
|
+
const numeric = Number(value);
|
|
405
|
+
result[alias] = Number.isNaN(numeric) ? value : numeric;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
result[alias] = value;
|
|
409
|
+
}
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
function mapFieldsToColumns(contract, namespaceId, modelName, fieldNames) {
|
|
413
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
414
|
+
return fieldNames.map((fieldName) => fieldToColumn[fieldName] ?? fieldName);
|
|
415
|
+
}
|
|
416
|
+
function mapCursorValuesToColumns(contract, namespaceId, modelName, cursorValues) {
|
|
417
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
418
|
+
const mappedCursor = {};
|
|
419
|
+
for (const [fieldName, value] of Object.entries(cursorValues)) {
|
|
420
|
+
if (value === void 0) continue;
|
|
421
|
+
const columnName = fieldToColumn[fieldName] ?? fieldName;
|
|
422
|
+
mappedCursor[columnName] = value;
|
|
423
|
+
}
|
|
424
|
+
return mappedCursor;
|
|
425
|
+
}
|
|
426
|
+
function stripHiddenMappedFields(contract, namespaceId, modelName, mapped, hiddenColumns) {
|
|
427
|
+
if (hiddenColumns.length === 0) return;
|
|
428
|
+
const columnToField = getColumnToFieldMap(contract, namespaceId, modelName);
|
|
429
|
+
for (const hiddenColumn of hiddenColumns) {
|
|
430
|
+
const fieldName = columnToField[hiddenColumn] ?? hiddenColumn;
|
|
431
|
+
delete mapped[fieldName];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function mapStorageRowToModelFields(contract, namespaceId, modelName, row) {
|
|
435
|
+
const columnToField = getColumnToFieldMap(contract, namespaceId, modelName);
|
|
436
|
+
if (Object.keys(columnToField).length === 0) return { ...row };
|
|
437
|
+
const mapped = {};
|
|
438
|
+
for (const [columnName, value] of Object.entries(row)) mapped[columnToField[columnName] ?? columnName] = value;
|
|
439
|
+
return mapped;
|
|
440
|
+
}
|
|
441
|
+
const mergedColumnToFieldCache = /* @__PURE__ */ new WeakMap();
|
|
442
|
+
function getMergedColumnToFieldMap(contract, namespaceId, baseModelName, variantModelName, variantTable) {
|
|
443
|
+
const cacheKey = `${namespaceId}:${baseModelName}:${variantModelName}:${variantTable ?? ""}`;
|
|
444
|
+
let perContract = mergedColumnToFieldCache.get(contract);
|
|
445
|
+
if (!perContract) {
|
|
446
|
+
perContract = /* @__PURE__ */ new Map();
|
|
447
|
+
mergedColumnToFieldCache.set(contract, perContract);
|
|
448
|
+
}
|
|
449
|
+
const cached = perContract.get(cacheKey);
|
|
450
|
+
if (cached) return cached;
|
|
451
|
+
const baseMap = getCompleteColumnToFieldMap(contract, namespaceId, baseModelName);
|
|
452
|
+
const variantMap = getCompleteColumnToFieldMap(contract, namespaceId, variantModelName);
|
|
453
|
+
const merged = { ...baseMap };
|
|
454
|
+
for (const [col, field] of Object.entries(variantMap)) if (variantTable) merged[`${variantTable}__${col}`] = field;
|
|
455
|
+
else merged[col] = field;
|
|
456
|
+
perContract.set(cacheKey, merged);
|
|
457
|
+
return merged;
|
|
458
|
+
}
|
|
459
|
+
function mapPolymorphicRow(contract, namespaceId, baseModelName, polyInfo, row, variantName) {
|
|
460
|
+
const discriminatorValue = row[polyInfo.discriminatorColumn] ?? row["__prisma_polymorphic_discriminator"];
|
|
461
|
+
const variant = variantName ? polyInfo.variants.get(variantName) : typeof discriminatorValue === "string" ? polyInfo.variantsByValue.get(discriminatorValue) : void 0;
|
|
462
|
+
if (!variant) {
|
|
463
|
+
const baseMap = getCompleteColumnToFieldMap(contract, namespaceId, baseModelName);
|
|
464
|
+
const mapped = {};
|
|
465
|
+
for (const [col, val] of Object.entries(row)) {
|
|
466
|
+
const field = baseMap[col];
|
|
467
|
+
if (field !== void 0) mapped[field] = val;
|
|
468
|
+
}
|
|
469
|
+
return mapped;
|
|
470
|
+
}
|
|
471
|
+
const mtiTable = variant.strategy === "mti" ? variant.table : void 0;
|
|
472
|
+
const mergedMap = getMergedColumnToFieldMap(contract, namespaceId, baseModelName, variant.modelName, mtiTable);
|
|
473
|
+
const mapped = {};
|
|
474
|
+
for (const [col, val] of Object.entries(row)) {
|
|
475
|
+
const field = mergedMap[col];
|
|
476
|
+
if (field !== void 0) mapped[field] = val;
|
|
477
|
+
}
|
|
478
|
+
return mapped;
|
|
479
|
+
}
|
|
480
|
+
function mapModelDataToStorageRow(contract, namespaceId, modelName, row) {
|
|
481
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
482
|
+
const mapped = {};
|
|
483
|
+
for (const [fieldName, value] of Object.entries(row)) {
|
|
484
|
+
if (value === void 0) continue;
|
|
485
|
+
const columnName = fieldToColumn[fieldName] ?? fieldName;
|
|
486
|
+
mapped[columnName] = value;
|
|
487
|
+
}
|
|
488
|
+
return mapped;
|
|
489
|
+
}
|
|
490
|
+
function mapResultRows(result, mapper) {
|
|
491
|
+
const generator = async function* () {
|
|
492
|
+
for await (const value of result) yield mapper(value);
|
|
493
|
+
};
|
|
494
|
+
return new AsyncIterableResult(generator());
|
|
495
|
+
}
|
|
496
|
+
async function acquireRuntimeScope(runtime) {
|
|
497
|
+
if (typeof runtime.connection !== "function") return { scope: runtime };
|
|
498
|
+
const connection = await runtime.connection();
|
|
499
|
+
const release = connection.release;
|
|
500
|
+
if (typeof release === "function") return {
|
|
501
|
+
scope: connection,
|
|
502
|
+
release: () => release.call(connection) ?? Promise.resolve()
|
|
503
|
+
};
|
|
504
|
+
return { scope: connection };
|
|
505
|
+
}
|
|
506
|
+
function executeQueryPlan(scope, plan) {
|
|
507
|
+
return scope.execute(plan);
|
|
508
|
+
}
|
|
509
|
+
function deriveParamsFromAst(ast) {
|
|
510
|
+
return { params: collectOrderedParamRefs(ast).map((p) => p.kind === "param-ref" ? p.value : void 0) };
|
|
511
|
+
}
|
|
512
|
+
function resolveTableColumns(contract, namespaceId, tableName) {
|
|
513
|
+
try {
|
|
514
|
+
return Object.keys(storageTableForContract(contract, namespaceId, tableName).columns);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
if (error instanceof Error && error.message.includes("ambiguous")) throw error;
|
|
517
|
+
throw ormError("ORM.TABLE_UNKNOWN", `Unknown table "${tableName}" in SQL ORM query planner`, { meta: {
|
|
518
|
+
namespaceId,
|
|
519
|
+
tableName
|
|
520
|
+
} });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function buildOrmPlanMeta(contract, annotations) {
|
|
524
|
+
const annotationRecord = annotations !== void 0 && annotations.size > 0 ? Object.freeze(Object.fromEntries(annotations)) : void 0;
|
|
525
|
+
return {
|
|
526
|
+
target: contract.target,
|
|
527
|
+
targetFamily: contract.targetFamily,
|
|
528
|
+
storageHash: contract.storage.storageHash,
|
|
529
|
+
...ifDefined("profileHash", contract.profileHash),
|
|
530
|
+
...ifDefined("annotations", annotationRecord),
|
|
531
|
+
lane: "orm-client"
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function buildOrmQueryPlan(contract, ast, params, annotations) {
|
|
535
|
+
return Object.freeze({
|
|
536
|
+
ast,
|
|
537
|
+
params: [...params],
|
|
538
|
+
meta: buildOrmPlanMeta(contract, annotations)
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Merges annotations into an existing `SqlQueryPlan`'s
|
|
543
|
+
* `meta.annotations` and returns a new frozen plan.
|
|
544
|
+
*
|
|
545
|
+
* Used by the ORM dispatch path to attach terminal-call annotations to
|
|
546
|
+
* plans produced by mutation compile functions (which don't take
|
|
547
|
+
* annotations as parameters). Reads compile through `compileSelect`-
|
|
548
|
+
* family functions that pass `state.annotations` directly to
|
|
549
|
+
* `buildOrmQueryPlan`; this helper is the alternate path for write
|
|
550
|
+
* terminals where annotations arrive at the call site, not via state.
|
|
551
|
+
*
|
|
552
|
+
* Returns the input plan unchanged when `annotations` is undefined
|
|
553
|
+
* or empty. Reserved framework namespaces (`codecs`, `limit`) on the
|
|
554
|
+
* input plan win over caller-supplied entries under the same key —
|
|
555
|
+
* see the reserved-namespace policy on `defineAnnotation`.
|
|
556
|
+
*/
|
|
557
|
+
function mergeAnnotations(plan, annotations) {
|
|
558
|
+
if (annotations === void 0 || annotations.size === 0) return plan;
|
|
559
|
+
const callerEntries = {};
|
|
560
|
+
for (const [namespace, value] of annotations) callerEntries[namespace] = value;
|
|
561
|
+
const mergedAnnotations = Object.freeze({
|
|
562
|
+
...callerEntries,
|
|
563
|
+
...plan.meta.annotations ?? {}
|
|
564
|
+
});
|
|
565
|
+
return Object.freeze({
|
|
566
|
+
...plan,
|
|
567
|
+
meta: Object.freeze({
|
|
568
|
+
...plan.meta,
|
|
569
|
+
annotations: mergedAnnotations
|
|
570
|
+
})
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
function combineWhereExprs(filters) {
|
|
574
|
+
if (filters.length === 0) return;
|
|
575
|
+
if (filters.length === 1) return filters[0];
|
|
576
|
+
return AndExpr.of(filters);
|
|
577
|
+
}
|
|
578
|
+
function toAggregateProjection(contract, namespaceId, tableName, selector) {
|
|
579
|
+
if (selector.fn === "count") return {
|
|
580
|
+
expr: AggregateExpr.count(),
|
|
581
|
+
codec: void 0
|
|
582
|
+
};
|
|
583
|
+
if (!selector.column) throw ormError("ORM.AGGREGATE_SELECTOR_INVALID", `Aggregate selector "${selector.fn}" requires a field`, { meta: { fn: selector.fn } });
|
|
584
|
+
const expr = new AggregateExpr(selector.fn, ColumnRef.of(tableName, selector.column));
|
|
585
|
+
if (selector.fn === "min" || selector.fn === "max") return {
|
|
586
|
+
expr,
|
|
587
|
+
codec: codecRefForStorageColumn(contract.storage, namespaceId, tableName, selector.column)
|
|
588
|
+
};
|
|
589
|
+
return {
|
|
590
|
+
expr,
|
|
591
|
+
codec: void 0
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function validateGroupedComparable(value) {
|
|
595
|
+
switch (value.kind) {
|
|
596
|
+
case "param-ref": throw ormError("ORM.HAVING_EXPRESSION_UNSUPPORTED", "ParamRef is not supported in grouped having expressions", { meta: { kind: value.kind } });
|
|
597
|
+
case "literal":
|
|
598
|
+
case "column-ref":
|
|
599
|
+
case "identifier-ref":
|
|
600
|
+
case "aggregate":
|
|
601
|
+
case "operation": return value;
|
|
602
|
+
case "function-call":
|
|
603
|
+
case "cast":
|
|
604
|
+
case "case": throw ormError("ORM.HAVING_EXPRESSION_UNSUPPORTED", `Unsupported comparable kind in grouped having: "${value.kind}"`, { meta: { kind: value.kind } });
|
|
605
|
+
case "list":
|
|
606
|
+
if (value.values.some((entry) => entry.kind === "param-ref")) throw ormError("ORM.HAVING_EXPRESSION_UNSUPPORTED", "ParamRef is not supported in grouped having expressions", { meta: { kind: "list" } });
|
|
607
|
+
return value;
|
|
608
|
+
default: throw ormError("ORM.HAVING_EXPRESSION_UNSUPPORTED", `Unsupported comparable kind in grouped having: "${value.kind}"`, { meta: { kind: value.kind } });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function validateGroupedMetricExpr(expr) {
|
|
612
|
+
if (expr.kind !== "aggregate") throw ormError("ORM.HAVING_EXPRESSION_UNSUPPORTED", "groupBy().having() only supports aggregate metric expressions", { meta: { kind: expr.kind } });
|
|
613
|
+
return expr;
|
|
614
|
+
}
|
|
615
|
+
function rejectHavingExpr(expr) {
|
|
616
|
+
throw ormError("ORM.HAVING_EXPRESSION_UNSUPPORTED", `Unsupported grouped having expression kind "${expr.kind}"`, { meta: { kind: expr.kind } });
|
|
617
|
+
}
|
|
618
|
+
function validateGroupedHavingExpr(expr) {
|
|
619
|
+
return expr.accept({
|
|
620
|
+
columnRef: rejectHavingExpr,
|
|
621
|
+
identifierRef: rejectHavingExpr,
|
|
622
|
+
subquery: rejectHavingExpr,
|
|
623
|
+
operation: rejectHavingExpr,
|
|
624
|
+
aggregate: rejectHavingExpr,
|
|
625
|
+
windowFunc: rejectHavingExpr,
|
|
626
|
+
functionCall: rejectHavingExpr,
|
|
627
|
+
cast: rejectHavingExpr,
|
|
628
|
+
case: rejectHavingExpr,
|
|
629
|
+
jsonObject: rejectHavingExpr,
|
|
630
|
+
jsonArrayAgg: rejectHavingExpr,
|
|
631
|
+
literal: rejectHavingExpr,
|
|
632
|
+
param() {
|
|
633
|
+
throw ormError("ORM.HAVING_EXPRESSION_UNSUPPORTED", "ParamRef is not supported in grouped having expressions", { meta: { kind: "param-ref" } });
|
|
634
|
+
},
|
|
635
|
+
preparedParam() {
|
|
636
|
+
throw ormError("ORM.HAVING_EXPRESSION_UNSUPPORTED", "PreparedParamRef is not supported in grouped having expressions", { meta: { kind: "prepared-param-ref" } });
|
|
637
|
+
},
|
|
638
|
+
list: rejectHavingExpr,
|
|
639
|
+
and(expr) {
|
|
640
|
+
return AndExpr.of(expr.exprs.map((child) => validateGroupedHavingExpr(child)));
|
|
641
|
+
},
|
|
642
|
+
or(expr) {
|
|
643
|
+
return OrExpr.of(expr.exprs.map((child) => validateGroupedHavingExpr(child)));
|
|
644
|
+
},
|
|
645
|
+
exists: rejectHavingExpr,
|
|
646
|
+
nullCheck(expr) {
|
|
647
|
+
return new NullCheckExpr(validateGroupedMetricExpr(expr.expr), expr.isNull);
|
|
648
|
+
},
|
|
649
|
+
not(expr) {
|
|
650
|
+
return new NotExpr(validateGroupedHavingExpr(expr.expr));
|
|
651
|
+
},
|
|
652
|
+
binary(expr) {
|
|
653
|
+
return new BinaryExpr(expr.op, validateGroupedMetricExpr(expr.left), validateGroupedComparable(expr.right));
|
|
654
|
+
},
|
|
655
|
+
rawExpr: rejectHavingExpr
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
function compileAggregate(contract, namespaceId, tableName, filters, aggregateSpec) {
|
|
659
|
+
const entries = Object.entries(aggregateSpec);
|
|
660
|
+
if (entries.length === 0) throw ormError("ORM.AGGREGATE_SELECTOR_MISSING", "aggregate() requires at least one aggregation selector", { meta: {
|
|
661
|
+
method: "aggregate",
|
|
662
|
+
namespaceId,
|
|
663
|
+
tableName
|
|
664
|
+
} });
|
|
665
|
+
const projection = entries.map(([alias, selector]) => {
|
|
666
|
+
const { expr, codec } = toAggregateProjection(contract, namespaceId, tableName, selector);
|
|
667
|
+
return ProjectionItem.of(alias, expr, codec);
|
|
668
|
+
});
|
|
669
|
+
let ast = SelectAst.from(tableSourceForContract(contract, namespaceId, tableName)).withProjection(projection);
|
|
670
|
+
const where = combineWhereExprs(filters);
|
|
671
|
+
if (where) ast = ast.withWhere(where);
|
|
672
|
+
const { params } = deriveParamsFromAst(ast);
|
|
673
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
674
|
+
}
|
|
675
|
+
function compileGroupedAggregate(contract, namespaceId, tableName, filters, groupByColumns, aggregateSpec, havingExpr) {
|
|
676
|
+
if (groupByColumns.length === 0) throw ormError("ORM.GROUP_BY_FIELD_MISSING", "groupBy() requires at least one field", { meta: {
|
|
677
|
+
namespaceId,
|
|
678
|
+
tableName
|
|
679
|
+
} });
|
|
680
|
+
const entries = Object.entries(aggregateSpec);
|
|
681
|
+
if (entries.length === 0) throw ormError("ORM.AGGREGATE_SELECTOR_MISSING", "groupBy().aggregate() requires at least one aggregation selector", { meta: {
|
|
682
|
+
method: "groupBy.aggregate",
|
|
683
|
+
namespaceId,
|
|
684
|
+
tableName
|
|
685
|
+
} });
|
|
686
|
+
const projection = [...groupByColumns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), codecRefForStorageColumn(contract.storage, namespaceId, tableName, column))), ...entries.map(([alias, selector]) => {
|
|
687
|
+
const { expr, codec } = toAggregateProjection(contract, namespaceId, tableName, selector);
|
|
688
|
+
return ProjectionItem.of(alias, expr, codec);
|
|
689
|
+
})];
|
|
690
|
+
let ast = SelectAst.from(tableSourceForContract(contract, namespaceId, tableName)).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
|
|
691
|
+
const where = combineWhereExprs(filters);
|
|
692
|
+
if (where) ast = ast.withWhere(where);
|
|
693
|
+
if (havingExpr) ast = ast.withHaving(validateGroupedHavingExpr(havingExpr));
|
|
694
|
+
const { params } = deriveParamsFromAst(ast);
|
|
695
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
696
|
+
}
|
|
697
|
+
function buildReturningColumns(contract, namespaceId, tableName, returningColumns) {
|
|
698
|
+
return (returningColumns && returningColumns.length > 0 ? [...returningColumns] : resolveTableColumns(contract, namespaceId, tableName)).map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), codecRefForStorageColumn(contract.storage, namespaceId, tableName, column)));
|
|
699
|
+
}
|
|
700
|
+
function toParamAssignments(contract, namespaceId, tableName, values) {
|
|
701
|
+
const assignments = {};
|
|
702
|
+
const table = storageTableForContract(contract, namespaceId, tableName);
|
|
703
|
+
for (const [column, value] of Object.entries(values)) {
|
|
704
|
+
if (!table.columns[column]) throw ormError("ORM.COLUMN_UNKNOWN", `Unknown column "${column}" in table "${tableName}"`, { meta: {
|
|
705
|
+
namespaceId,
|
|
706
|
+
tableName,
|
|
707
|
+
column
|
|
708
|
+
} });
|
|
709
|
+
const codec = codecRefForStorageColumn(contract.storage, namespaceId, tableName, column);
|
|
710
|
+
assignments[column] = ParamRef.of(value, {
|
|
711
|
+
name: column,
|
|
712
|
+
...ifDefined("codec", codec)
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
return { assignments };
|
|
716
|
+
}
|
|
717
|
+
function normalizeInsertRows(contract, namespaceId, tableName, rows) {
|
|
718
|
+
if (rows.length === 0) throw new InternalError("normalizeInsertRows requires at least one row");
|
|
719
|
+
const orderedColumns = [];
|
|
720
|
+
const seenColumns = /* @__PURE__ */ new Set();
|
|
721
|
+
for (const row of rows) for (const column of Object.keys(row)) {
|
|
722
|
+
if (seenColumns.has(column)) continue;
|
|
723
|
+
seenColumns.add(column);
|
|
724
|
+
orderedColumns.push(column);
|
|
725
|
+
}
|
|
726
|
+
const table = storageTableForContract(contract, namespaceId, tableName);
|
|
727
|
+
return { rows: rows.map((row) => {
|
|
728
|
+
if (orderedColumns.length === 0) return {};
|
|
729
|
+
const normalizedRow = {};
|
|
730
|
+
for (const column of orderedColumns) {
|
|
731
|
+
if (Object.hasOwn(row, column)) {
|
|
732
|
+
if (!table.columns[column]) throw ormError("ORM.COLUMN_UNKNOWN", `Unknown column "${column}" in table "${tableName}"`, { meta: {
|
|
733
|
+
namespaceId,
|
|
734
|
+
tableName,
|
|
735
|
+
column
|
|
736
|
+
} });
|
|
737
|
+
const codec = codecRefForStorageColumn(contract.storage, namespaceId, tableName, column);
|
|
738
|
+
normalizedRow[column] = ParamRef.of(row[column], {
|
|
739
|
+
name: column,
|
|
740
|
+
...ifDefined("codec", codec)
|
|
741
|
+
});
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
normalizedRow[column] = new DefaultValueExpr();
|
|
745
|
+
}
|
|
746
|
+
return normalizedRow;
|
|
747
|
+
}) };
|
|
748
|
+
}
|
|
749
|
+
function compileInsertReturning(contract, namespaceId, tableName, rows, returningColumns) {
|
|
750
|
+
const { rows: normalizedRows } = normalizeInsertRows(contract, namespaceId, tableName, rows);
|
|
751
|
+
const ast = InsertAst.into(tableSourceForContract(contract, namespaceId, tableName)).withRows(normalizedRows).withReturning(buildReturningColumns(contract, namespaceId, tableName, returningColumns));
|
|
752
|
+
const { params } = deriveParamsFromAst(ast);
|
|
753
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
754
|
+
}
|
|
755
|
+
function compileInsertCount(contract, namespaceId, tableName, rows) {
|
|
756
|
+
const { rows: normalizedRows } = normalizeInsertRows(contract, namespaceId, tableName, rows);
|
|
757
|
+
const ast = InsertAst.into(tableSourceForContract(contract, namespaceId, tableName)).withRows(normalizedRows);
|
|
758
|
+
const { params } = deriveParamsFromAst(ast);
|
|
759
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
760
|
+
}
|
|
761
|
+
function stripUndefinedValues(row) {
|
|
762
|
+
const result = {};
|
|
763
|
+
for (const [key, value] of Object.entries(row)) if (value !== void 0) result[key] = value;
|
|
764
|
+
return result;
|
|
765
|
+
}
|
|
766
|
+
function createTableRefRemapper$1(fromTable, toTable) {
|
|
767
|
+
return {
|
|
768
|
+
columnRef: (col) => col.table === fromTable ? ColumnRef.of(toTable, col.column) : col,
|
|
769
|
+
tableSource: (source) => {
|
|
770
|
+
if (source.alias === fromTable) return TableSource.named(source.name, toTable, source.namespaceId);
|
|
771
|
+
if (!source.alias && source.name === fromTable) return TableSource.named(source.name, toTable, source.namespaceId);
|
|
772
|
+
return source;
|
|
773
|
+
},
|
|
774
|
+
eqColJoinOn: (on) => EqColJoinOn.of(on.left.table === fromTable ? ColumnRef.of(toTable, on.left.column) : on.left, on.right.table === fromTable ? ColumnRef.of(toTable, on.right.column) : on.right)
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
function buildCountMutationWhere(contract, namespaceId, tableName, filters, variantName, modelName) {
|
|
778
|
+
if (!variantName || !modelName) return combineWhereExprs(filters);
|
|
779
|
+
const polyInfo = resolvePolymorphismInfo(contract, namespaceId, modelName);
|
|
780
|
+
const variant = polyInfo?.variants.get(variantName);
|
|
781
|
+
if (!polyInfo || !variant || variant.strategy !== "mti") return combineWhereExprs(filters);
|
|
782
|
+
const pkColumn = resolvePrimaryKeyColumn(contract, namespaceId, tableName);
|
|
783
|
+
const baseTableRef = `${tableName}__write_filter`;
|
|
784
|
+
const remapper = createTableRefRemapper$1(tableName, baseTableRef);
|
|
785
|
+
const innerFilters = filters.map((filter) => filter.rewrite(remapper));
|
|
786
|
+
const where = combineWhereExprs([BinaryExpr.eq(ColumnRef.of(baseTableRef, pkColumn), ColumnRef.of(tableName, pkColumn)), ...innerFilters]);
|
|
787
|
+
const joinOn = EqColJoinOn.of(ColumnRef.of(baseTableRef, pkColumn), ColumnRef.of(variant.table, pkColumn));
|
|
788
|
+
let subquery = SelectAst.from(TableSource.named(tableName, baseTableRef, namespaceId)).withProjection([ProjectionItem.of("_write_filter", ColumnRef.of(baseTableRef, pkColumn))]).withJoins([JoinAst.inner(tableSourceForContract(contract, namespaceId, variant.table), joinOn)]);
|
|
789
|
+
if (where) subquery = subquery.withWhere(where);
|
|
790
|
+
return ExistsExpr.exists(subquery);
|
|
791
|
+
}
|
|
792
|
+
function groupRowsByColumnSignature(rows) {
|
|
793
|
+
const groups = [];
|
|
794
|
+
let currentKey = "";
|
|
795
|
+
let currentGroup = [];
|
|
796
|
+
for (const rawRow of rows) {
|
|
797
|
+
const row = stripUndefinedValues(rawRow);
|
|
798
|
+
const key = Object.keys(row).sort().join(",");
|
|
799
|
+
if (key !== currentKey || currentGroup.length === 0) {
|
|
800
|
+
if (currentGroup.length > 0) groups.push(currentGroup);
|
|
801
|
+
currentKey = key;
|
|
802
|
+
currentGroup = [row];
|
|
803
|
+
} else currentGroup.push(row);
|
|
804
|
+
}
|
|
805
|
+
if (currentGroup.length > 0) groups.push(currentGroup);
|
|
806
|
+
return groups;
|
|
807
|
+
}
|
|
808
|
+
function compileInsertReturningSplit(contract, namespaceId, tableName, rows, returningColumns) {
|
|
809
|
+
if (rows.length === 0) throw ormError("ORM.MUTATION_DATA_MISSING", "create() requires at least one row", { meta: {
|
|
810
|
+
method: "create",
|
|
811
|
+
namespaceId,
|
|
812
|
+
tableName
|
|
813
|
+
} });
|
|
814
|
+
return groupRowsByColumnSignature(rows).map((group) => compileInsertReturning(contract, namespaceId, tableName, group, returningColumns));
|
|
815
|
+
}
|
|
816
|
+
function compileInsertCountSplit(contract, namespaceId, tableName, rows) {
|
|
817
|
+
if (rows.length === 0) throw ormError("ORM.MUTATION_DATA_MISSING", "createAndCount() requires at least one row", { meta: {
|
|
818
|
+
method: "createAndCount",
|
|
819
|
+
namespaceId,
|
|
820
|
+
tableName
|
|
821
|
+
} });
|
|
822
|
+
return groupRowsByColumnSignature(rows).map((group) => compileInsertCount(contract, namespaceId, tableName, group));
|
|
823
|
+
}
|
|
824
|
+
function compileUpsertReturning(contract, namespaceId, tableName, createValues, updateValues, conflictColumns, returningColumns) {
|
|
825
|
+
const createAssignments = toParamAssignments(contract, namespaceId, tableName, createValues);
|
|
826
|
+
const updateAssignments = Object.keys(updateValues).length > 0 ? toParamAssignments(contract, namespaceId, tableName, updateValues) : void 0;
|
|
827
|
+
const onConflict = updateAssignments ? InsertOnConflict.on(conflictColumns.map((column) => ColumnRef.of(tableName, column))).doUpdateSet(updateAssignments.assignments) : InsertOnConflict.on(conflictColumns.map((column) => ColumnRef.of(tableName, column))).doNothing();
|
|
828
|
+
const ast = InsertAst.into(tableSourceForContract(contract, namespaceId, tableName)).withRows([createAssignments.assignments]).withOnConflict(onConflict).withReturning(buildReturningColumns(contract, namespaceId, tableName, returningColumns));
|
|
829
|
+
const { params } = deriveParamsFromAst(ast);
|
|
830
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
831
|
+
}
|
|
832
|
+
function compileUpdateReturning(contract, namespaceId, tableName, setValues, filters, returningColumns) {
|
|
833
|
+
const where = combineWhereExprs(filters);
|
|
834
|
+
const { assignments } = toParamAssignments(contract, namespaceId, tableName, setValues);
|
|
835
|
+
let ast = UpdateAst.table(tableSourceForContract(contract, namespaceId, tableName)).withSet(assignments).withReturning(buildReturningColumns(contract, namespaceId, tableName, returningColumns));
|
|
836
|
+
if (where) ast = ast.withWhere(where);
|
|
837
|
+
const { params } = deriveParamsFromAst(ast);
|
|
838
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
839
|
+
}
|
|
840
|
+
function compileUpdateCount(contract, namespaceId, tableName, setValues, filters, variantName, modelName) {
|
|
841
|
+
const where = buildCountMutationWhere(contract, namespaceId, tableName, filters, variantName, modelName);
|
|
842
|
+
const { assignments } = toParamAssignments(contract, namespaceId, tableName, setValues);
|
|
843
|
+
let ast = UpdateAst.table(tableSourceForContract(contract, namespaceId, tableName)).withSet(assignments);
|
|
844
|
+
if (where) ast = ast.withWhere(where);
|
|
845
|
+
const { params } = deriveParamsFromAst(ast);
|
|
846
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
847
|
+
}
|
|
848
|
+
function compileDeleteReturning(contract, namespaceId, tableName, filters, returningColumns) {
|
|
849
|
+
const where = combineWhereExprs(filters);
|
|
850
|
+
let ast = DeleteAst.from(tableSourceForContract(contract, namespaceId, tableName)).withReturning(buildReturningColumns(contract, namespaceId, tableName, returningColumns));
|
|
851
|
+
if (where) ast = ast.withWhere(where);
|
|
852
|
+
const { params } = deriveParamsFromAst(ast);
|
|
853
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
854
|
+
}
|
|
855
|
+
function compileDeleteCount(contract, namespaceId, tableName, filters, variantName, modelName) {
|
|
856
|
+
const where = buildCountMutationWhere(contract, namespaceId, tableName, filters, variantName, modelName);
|
|
857
|
+
let ast = DeleteAst.from(tableSourceForContract(contract, namespaceId, tableName));
|
|
858
|
+
if (where) ast = ast.withWhere(where);
|
|
859
|
+
const { params } = deriveParamsFromAst(ast);
|
|
860
|
+
return buildOrmQueryPlan(contract, ast, params);
|
|
861
|
+
}
|
|
862
|
+
function augmentSelectionForJoinColumns(selectedFields, requiredColumns) {
|
|
863
|
+
if (!selectedFields) return {
|
|
864
|
+
selectedForQuery: selectedFields,
|
|
865
|
+
hiddenColumns: []
|
|
866
|
+
};
|
|
867
|
+
const hiddenColumns = requiredColumns.filter((column) => !selectedFields.includes(column));
|
|
868
|
+
if (hiddenColumns.length === 0) return {
|
|
869
|
+
selectedForQuery: selectedFields,
|
|
870
|
+
hiddenColumns: []
|
|
871
|
+
};
|
|
872
|
+
return {
|
|
873
|
+
selectedForQuery: [...selectedFields, ...hiddenColumns],
|
|
874
|
+
hiddenColumns
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function namespaceCoordinateForSource(source) {
|
|
878
|
+
return source.kind === "table-source" ? source.namespaceId : void 0;
|
|
879
|
+
}
|
|
880
|
+
function bindWhereExpr(contract, expr, namespaceId) {
|
|
881
|
+
return bindWhereExprNode(contract, expr, namespaceId);
|
|
882
|
+
}
|
|
883
|
+
function bindWhereExprNode(contract, expr, namespaceId) {
|
|
884
|
+
return expr.accept({
|
|
885
|
+
columnRef(expr) {
|
|
886
|
+
return bindExpression(contract, expr);
|
|
887
|
+
},
|
|
888
|
+
identifierRef(expr) {
|
|
889
|
+
return expr;
|
|
890
|
+
},
|
|
891
|
+
subquery(expr) {
|
|
892
|
+
return bindExpression(contract, expr);
|
|
893
|
+
},
|
|
894
|
+
operation(expr) {
|
|
895
|
+
return bindExpression(contract, expr);
|
|
896
|
+
},
|
|
897
|
+
aggregate(expr) {
|
|
898
|
+
return bindExpression(contract, expr);
|
|
899
|
+
},
|
|
900
|
+
windowFunc(expr) {
|
|
901
|
+
return bindExpression(contract, expr);
|
|
902
|
+
},
|
|
903
|
+
functionCall(expr) {
|
|
904
|
+
return bindExpression(contract, expr);
|
|
905
|
+
},
|
|
906
|
+
cast(expr) {
|
|
907
|
+
return bindExpression(contract, expr);
|
|
908
|
+
},
|
|
909
|
+
case(expr) {
|
|
910
|
+
return bindExpression(contract, expr);
|
|
911
|
+
},
|
|
912
|
+
jsonObject(expr) {
|
|
913
|
+
return bindExpression(contract, expr);
|
|
914
|
+
},
|
|
915
|
+
jsonArrayAgg(expr) {
|
|
916
|
+
return bindExpression(contract, expr);
|
|
917
|
+
},
|
|
918
|
+
literal(expr) {
|
|
919
|
+
return expr;
|
|
920
|
+
},
|
|
921
|
+
param(expr) {
|
|
922
|
+
return expr;
|
|
923
|
+
},
|
|
924
|
+
preparedParam(expr) {
|
|
925
|
+
return expr;
|
|
926
|
+
},
|
|
927
|
+
list(expr) {
|
|
928
|
+
return bindExpression(contract, expr);
|
|
929
|
+
},
|
|
930
|
+
binary(expr) {
|
|
931
|
+
const left = bindExpression(contract, expr.left);
|
|
932
|
+
const bindingColumn = left.kind === "column-ref" ? left : void 0;
|
|
933
|
+
return new BinaryExpr(expr.op, left, bindComparable(contract, expr.right, bindingColumn, namespaceId));
|
|
934
|
+
},
|
|
935
|
+
and(expr) {
|
|
936
|
+
return AndExpr.of(expr.exprs.map((part) => bindWhereExprNode(contract, part, namespaceId)));
|
|
937
|
+
},
|
|
938
|
+
or(expr) {
|
|
939
|
+
return OrExpr.of(expr.exprs.map((part) => bindWhereExprNode(contract, part, namespaceId)));
|
|
940
|
+
},
|
|
941
|
+
exists(expr) {
|
|
942
|
+
return expr.notExists ? ExistsExpr.notExists(bindSelectAst(contract, expr.subquery)) : ExistsExpr.exists(bindSelectAst(contract, expr.subquery));
|
|
943
|
+
},
|
|
944
|
+
nullCheck(expr) {
|
|
945
|
+
return expr.isNull ? NullCheckExpr.isNull(bindExpression(contract, expr.expr)) : NullCheckExpr.isNotNull(bindExpression(contract, expr.expr));
|
|
946
|
+
},
|
|
947
|
+
not(expr) {
|
|
948
|
+
return new NotExpr(bindWhereExprNode(contract, expr.expr, namespaceId));
|
|
949
|
+
},
|
|
950
|
+
rawExpr(expr) {
|
|
951
|
+
return expr;
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
function bindComparable(contract, comparable, bindingColumn, namespaceId) {
|
|
956
|
+
if (comparable.kind === "param-ref" || bindingColumn === void 0) return comparable.kind === "param-ref" ? comparable : comparable.kind === "literal" || comparable.kind === "list" ? comparable : bindExpression(contract, comparable);
|
|
957
|
+
if (comparable.kind === "literal") return createParamRef(contract, bindingColumn, comparable.value, namespaceId);
|
|
958
|
+
if (comparable.kind === "list") return ListExpression.of(comparable.values.map((value) => value.kind === "literal" ? createParamRef(contract, bindingColumn, value.value, namespaceId) : value));
|
|
959
|
+
return bindExpression(contract, comparable);
|
|
960
|
+
}
|
|
961
|
+
function createParamRef(contract, columnRef, value, namespaceId) {
|
|
962
|
+
const resolved = resolveStorageTable(contract.storage, columnRef.table, namespaceId);
|
|
963
|
+
if (resolved === void 0 || !resolved.table.columns[columnRef.column]) throw ormError("ORM.COLUMN_UNKNOWN", `Unknown column "${columnRef.column}" in table "${columnRef.table}"`, { meta: {
|
|
964
|
+
tableName: columnRef.table,
|
|
965
|
+
column: columnRef.column
|
|
966
|
+
} });
|
|
967
|
+
const codec = codecRefForStorageColumn(contract.storage, resolved.namespaceId, columnRef.table, columnRef.column);
|
|
968
|
+
return ParamRef.of(value, codec ? { codec } : void 0);
|
|
969
|
+
}
|
|
970
|
+
function createExpressionBinder(contract) {
|
|
971
|
+
return { select: (ast) => bindSelectAst(contract, ast) };
|
|
972
|
+
}
|
|
973
|
+
function bindExpression(contract, expr) {
|
|
974
|
+
return expr.rewrite(createExpressionBinder(contract));
|
|
975
|
+
}
|
|
976
|
+
function bindProjectionExpr(contract, expr) {
|
|
977
|
+
return expr.kind === "literal" ? expr : bindExpression(contract, expr);
|
|
978
|
+
}
|
|
979
|
+
function bindOrderByItem(contract, orderItem) {
|
|
980
|
+
return new OrderByItem(bindExpression(contract, orderItem.expr), orderItem.dir);
|
|
981
|
+
}
|
|
982
|
+
function bindJoin(contract, join) {
|
|
983
|
+
const namespaceId = namespaceCoordinateForSource(join.source);
|
|
984
|
+
return new JoinAst(join.joinType, bindFromSource(contract, join.source), join.on.kind === "eq-col-join-on" ? join.on : bindWhereExprNode(contract, join.on, namespaceId), join.lateral);
|
|
985
|
+
}
|
|
986
|
+
function bindFromSource(contract, source) {
|
|
987
|
+
if (source.kind === "table-source") return source;
|
|
988
|
+
if (source.kind === "derived-table-source") return DerivedTableSource.as(source.alias, bindSelectAst(contract, source.query));
|
|
989
|
+
return source;
|
|
990
|
+
}
|
|
991
|
+
function bindSelectAst(contract, ast) {
|
|
992
|
+
const namespaceId = ast.from !== void 0 ? namespaceCoordinateForSource(ast.from) : void 0;
|
|
993
|
+
return new SelectAst({
|
|
994
|
+
...ast.from !== void 0 ? { from: bindFromSource(contract, ast.from) } : {},
|
|
995
|
+
joins: ast.joins?.map((join) => bindJoin(contract, join)),
|
|
996
|
+
projection: ast.projection.map((projection) => new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr), projection.codec)),
|
|
997
|
+
where: ast.where ? bindWhereExprNode(contract, ast.where, namespaceId) : void 0,
|
|
998
|
+
orderBy: ast.orderBy?.map((orderItem) => bindOrderByItem(contract, orderItem)),
|
|
999
|
+
distinct: ast.distinct,
|
|
1000
|
+
distinctOn: ast.distinctOn?.map((expr) => bindExpression(contract, expr)),
|
|
1001
|
+
groupBy: ast.groupBy?.map((expr) => bindExpression(contract, expr)),
|
|
1002
|
+
having: ast.having ? bindWhereExprNode(contract, ast.having, namespaceId) : void 0,
|
|
1003
|
+
limit: ast.limit,
|
|
1004
|
+
offset: ast.offset,
|
|
1005
|
+
selectAllIntent: ast.selectAllIntent
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* The rule for which JSON projection variant an include entry carries. Every
|
|
1010
|
+
* site that puts a value into a `json_build_object` or a `json_agg` goes
|
|
1011
|
+
* through here, and the renderers read the variant to decide how the value
|
|
1012
|
+
* reaches JSON.
|
|
1013
|
+
*
|
|
1014
|
+
* - `codec`: the value is a column whose storage codec is known, so the
|
|
1015
|
+
* renderer can ask that codec's descriptor for the projection producing its
|
|
1016
|
+
* canonical JSON. The `CodecRef` is resolved at planning time by
|
|
1017
|
+
* `codecRefForStorageColumn` and carried on the `ProjectionItem` through
|
|
1018
|
+
* every wrap between the column and here.
|
|
1019
|
+
* - `document`: the value is already a JSON document — a nested include's
|
|
1020
|
+
* correlated subquery, a combine branch, or the object a child row set is
|
|
1021
|
+
* aggregated from. Its parts were made canonical at the level that produced
|
|
1022
|
+
* them; this level only nests it.
|
|
1023
|
+
* - `native`: the value has no codec identity. An aggregate is the case that
|
|
1024
|
+
* arises today: `COUNT(*)` and `SUM(col)` are computed, and the aggregate's
|
|
1025
|
+
* own result type is not the column's codec. Native is what a value with no
|
|
1026
|
+
* codec identity means, which is a different thing from defaulting a codec
|
|
1027
|
+
* to identity — that, the project forbids.
|
|
1028
|
+
*
|
|
1029
|
+
* A document never carries a codec and a codec-bearing column is never a
|
|
1030
|
+
* document, so the first two cases cannot both apply.
|
|
1031
|
+
*/
|
|
1032
|
+
function jsonEntryProjection(value, identity) {
|
|
1033
|
+
if (identity.codec !== void 0) return new CodecJsonValueProjection(value, identity.codec);
|
|
1034
|
+
if (identity.document === true) return new JsonDocumentProjection(value);
|
|
1035
|
+
return new NativeJsonValueProjection(value);
|
|
1036
|
+
}
|
|
1037
|
+
function buildProjection(contract, namespaceId, tableName, selectedFields, tableRef = tableName) {
|
|
1038
|
+
return (selectedFields !== void 0 ? [...selectedFields] : resolveTableColumns(contract, namespaceId, tableName)).map((column) => ProjectionItem.of(column, ColumnRef.of(tableRef, column), codecRefForStorageColumn(contract.storage, namespaceId, tableName, column)));
|
|
1039
|
+
}
|
|
1040
|
+
function appendUnique(values, value) {
|
|
1041
|
+
if (!values.includes(value)) values.push(value);
|
|
1042
|
+
}
|
|
1043
|
+
function resolvePolymorphicProjectionSelection(contract, namespaceId, modelName, polyInfo, state) {
|
|
1044
|
+
if (state.selectedFields === void 0) return {
|
|
1045
|
+
baseSelectedFields: void 0,
|
|
1046
|
+
selectedMtiColumnsByTable: void 0,
|
|
1047
|
+
needsHiddenDiscriminator: false
|
|
1048
|
+
};
|
|
1049
|
+
const baseTableColumns = new Set(resolveTableColumns(contract, namespaceId, polyInfo.baseTable));
|
|
1050
|
+
const baseFieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
1051
|
+
const variantFieldMaps = Array.from(polyInfo.variants.values(), (variant) => ({
|
|
1052
|
+
variant,
|
|
1053
|
+
columnToField: getCompleteColumnToFieldMap(contract, namespaceId, variant.modelName)
|
|
1054
|
+
}));
|
|
1055
|
+
const baseSelectedFields = [];
|
|
1056
|
+
const selectedMtiColumnsByTable = /* @__PURE__ */ new Map();
|
|
1057
|
+
let hasVariantOwnedSelection = false;
|
|
1058
|
+
for (const selectedField of state.selectedFields) {
|
|
1059
|
+
const baseColumn = baseFieldToColumn[selectedField] ?? (baseTableColumns.has(selectedField) ? selectedField : void 0);
|
|
1060
|
+
if (baseColumn !== void 0) appendUnique(baseSelectedFields, baseColumn);
|
|
1061
|
+
let matchedVariantField = false;
|
|
1062
|
+
for (const { variant, columnToField } of variantFieldMaps) for (const [column, field] of Object.entries(columnToField)) {
|
|
1063
|
+
if (selectedField !== field && selectedField !== column) continue;
|
|
1064
|
+
matchedVariantField = true;
|
|
1065
|
+
hasVariantOwnedSelection = true;
|
|
1066
|
+
if (variant.strategy === "sti") {
|
|
1067
|
+
appendUnique(baseSelectedFields, column);
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
let selectedColumns = selectedMtiColumnsByTable.get(variant.table);
|
|
1071
|
+
if (selectedColumns === void 0) {
|
|
1072
|
+
selectedColumns = /* @__PURE__ */ new Set();
|
|
1073
|
+
selectedMtiColumnsByTable.set(variant.table, selectedColumns);
|
|
1074
|
+
}
|
|
1075
|
+
selectedColumns.add(column);
|
|
1076
|
+
}
|
|
1077
|
+
if (baseColumn === void 0 && !matchedVariantField) appendUnique(baseSelectedFields, selectedField);
|
|
1078
|
+
}
|
|
1079
|
+
return {
|
|
1080
|
+
baseSelectedFields,
|
|
1081
|
+
selectedMtiColumnsByTable,
|
|
1082
|
+
needsHiddenDiscriminator: state.variantName === void 0 && hasVariantOwnedSelection && !baseSelectedFields.includes(polyInfo.discriminatorColumn)
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
function buildHiddenDiscriminatorProjection(contract, namespaceId, polyInfo, tableRef, needed) {
|
|
1086
|
+
if (!needed) return [];
|
|
1087
|
+
return [ProjectionItem.of(POLYMORPHIC_DISCRIMINATOR_ALIAS, ColumnRef.of(tableRef, polyInfo.discriminatorColumn), codecRefForStorageColumn(contract.storage, namespaceId, polyInfo.baseTable, polyInfo.discriminatorColumn))];
|
|
1088
|
+
}
|
|
1089
|
+
function createBoundaryExpr(tableName, entry) {
|
|
1090
|
+
return new BinaryExpr(entry.direction === "asc" ? "gt" : "lt", ColumnRef.of(tableName, entry.column), LiteralExpr.of(entry.value));
|
|
1091
|
+
}
|
|
1092
|
+
function buildLexicographicCursorWhere(tableName, entries) {
|
|
1093
|
+
const branches = entries.map((entry, index) => {
|
|
1094
|
+
const branchExprs = [];
|
|
1095
|
+
for (const prefixEntry of entries.slice(0, index)) branchExprs.push(BinaryExpr.eq(ColumnRef.of(tableName, prefixEntry.column), LiteralExpr.of(prefixEntry.value)));
|
|
1096
|
+
branchExprs.push(createBoundaryExpr(tableName, entry));
|
|
1097
|
+
if (branchExprs.length === 1) {
|
|
1098
|
+
const branch = branchExprs[0];
|
|
1099
|
+
assertDefined(branch, "cursor branch contains its boundary expression");
|
|
1100
|
+
return branch;
|
|
1101
|
+
}
|
|
1102
|
+
return AndExpr.of(branchExprs);
|
|
1103
|
+
});
|
|
1104
|
+
if (branches.length === 1) {
|
|
1105
|
+
const branch = branches[0];
|
|
1106
|
+
assertDefined(branch, "cursor expression contains its single branch");
|
|
1107
|
+
return branch;
|
|
1108
|
+
}
|
|
1109
|
+
return OrExpr.of(branches);
|
|
1110
|
+
}
|
|
1111
|
+
function buildCursorWhere(tableName, orderBy, cursor) {
|
|
1112
|
+
if (!cursor || !orderBy || orderBy.length === 0) return;
|
|
1113
|
+
const entries = [];
|
|
1114
|
+
for (const order of orderBy) {
|
|
1115
|
+
if (order.expr.kind !== "column-ref") continue;
|
|
1116
|
+
const column = order.expr.column;
|
|
1117
|
+
const value = cursor[column];
|
|
1118
|
+
if (value === void 0) throw ormError("ORM.CURSOR_VALUE_MISSING", `Missing cursor value for orderBy column "${column}"`, { meta: { column } });
|
|
1119
|
+
entries.push({
|
|
1120
|
+
column,
|
|
1121
|
+
direction: order.dir,
|
|
1122
|
+
value
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
const firstEntry = entries[0];
|
|
1126
|
+
if (entries.length === 1 && firstEntry !== void 0) return createBoundaryExpr(tableName, firstEntry);
|
|
1127
|
+
return buildLexicographicCursorWhere(tableName, entries);
|
|
1128
|
+
}
|
|
1129
|
+
function createTableRefRemapper(fromTable, toTable) {
|
|
1130
|
+
return {
|
|
1131
|
+
columnRef: (col) => col.table === fromTable ? ColumnRef.of(toTable, col.column) : col,
|
|
1132
|
+
tableSource: (source) => {
|
|
1133
|
+
if (source.alias === fromTable) return TableSource.named(source.name, toTable, source.namespaceId);
|
|
1134
|
+
if (!source.alias && source.name === fromTable) return TableSource.named(source.name, toTable, source.namespaceId);
|
|
1135
|
+
return source;
|
|
1136
|
+
},
|
|
1137
|
+
eqColJoinOn: (on) => EqColJoinOn.of(on.left.table === fromTable ? ColumnRef.of(toTable, on.left.column) : on.left, on.right.table === fromTable ? ColumnRef.of(toTable, on.right.column) : on.right)
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
function buildStateWhere(contract, tableName, state, options) {
|
|
1141
|
+
const filterTableName = options?.filterTableName;
|
|
1142
|
+
const cursorWhere = buildCursorWhere(filterTableName ?? tableName, state.orderBy, state.cursor);
|
|
1143
|
+
const remappedFilters = filterTableName && filterTableName !== tableName ? state.filters.map((filter) => filter.rewrite(createTableRefRemapper(filterTableName, tableName))) : state.filters;
|
|
1144
|
+
const boundCursorWhere = cursorWhere ? bindWhereExpr(contract, cursorWhere, options?.namespaceId) : void 0;
|
|
1145
|
+
const remappedCursorWhere = boundCursorWhere && filterTableName && filterTableName !== tableName ? boundCursorWhere.rewrite(createTableRefRemapper(filterTableName, tableName)) : boundCursorWhere;
|
|
1146
|
+
return combineWhereExprs(remappedCursorWhere ? [...remappedFilters, remappedCursorWhere] : remappedFilters);
|
|
1147
|
+
}
|
|
1148
|
+
function buildIncludeOrderArtifacts(relationName, rowAlias, childOrderBy) {
|
|
1149
|
+
if (!childOrderBy || childOrderBy.length === 0) return {
|
|
1150
|
+
childOrderBy: void 0,
|
|
1151
|
+
hiddenOrderProjection: [],
|
|
1152
|
+
aggregateOrderBy: void 0
|
|
1153
|
+
};
|
|
1154
|
+
const hiddenOrderProjection = childOrderBy.map((orderItem, index) => ProjectionItem.of(`${relationName}__order_${index}`, orderItem.expr));
|
|
1155
|
+
return {
|
|
1156
|
+
childOrderBy,
|
|
1157
|
+
hiddenOrderProjection,
|
|
1158
|
+
aggregateOrderBy: hiddenOrderProjection.map((projection, index) => {
|
|
1159
|
+
const orderItem = childOrderBy[index];
|
|
1160
|
+
if (!orderItem) throw new InternalError(`Missing include order metadata at index ${index}`);
|
|
1161
|
+
return new OrderByItem(ColumnRef.of(rowAlias, projection.alias), orderItem.dir);
|
|
1162
|
+
})
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Wrap a base SELECT in a `ROW_NUMBER() OVER (PARTITION BY … ORDER BY …) = 1`
|
|
1167
|
+
* filter, implementing Prisma-style `.distinct(cols)` semantics: one
|
|
1168
|
+
* representative row per `(distinctColumnRefs)` group is kept; the rest
|
|
1169
|
+
* are dropped.
|
|
1170
|
+
*
|
|
1171
|
+
* Picking which row survives in each partition is governed by
|
|
1172
|
+
* `rankingOrderBy`. When the caller's `orderBy` doesn't fully order rows
|
|
1173
|
+
* within a partition (e.g. user wrote `.distinct('title')` with no
|
|
1174
|
+
* `orderBy`, or ties in their ordering), the choice is
|
|
1175
|
+
* implementation-defined — matching Prisma's documented nested-distinct
|
|
1176
|
+
* behaviour. Callers that want determinism should pass an `orderBy` that
|
|
1177
|
+
* is total within each partition.
|
|
1178
|
+
*
|
|
1179
|
+
* The wrapper forwards every column of `base.projection` through the
|
|
1180
|
+
* derived alias, so the wrapper's projection is byte-identical in alias
|
|
1181
|
+
* names — making this transparent to any outer query (`json_agg`,
|
|
1182
|
+
* correlated subquery, top-level SELECT) that consumes the SELECT.
|
|
1183
|
+
*/
|
|
1184
|
+
function wrapWithRowNumberDedup(options) {
|
|
1185
|
+
const { base, distinctColumnRefs, rankingOrderBy, rankedAlias } = options;
|
|
1186
|
+
const rnAlias = "__prisma_distinct_rn";
|
|
1187
|
+
const effectiveOrderBy = rankingOrderBy.length > 0 ? rankingOrderBy : distinctColumnRefs.map((expr) => OrderByItem.asc(expr));
|
|
1188
|
+
const inner = base.withProjection([...base.projection, ProjectionItem.of(rnAlias, WindowFuncExpr.rowNumber({
|
|
1189
|
+
partitionBy: distinctColumnRefs,
|
|
1190
|
+
orderBy: effectiveOrderBy
|
|
1191
|
+
}))]);
|
|
1192
|
+
return SelectAst.from(DerivedTableSource.as(rankedAlias, inner)).withProjection(base.projection.map((item) => ProjectionItem.of(item.alias, ColumnRef.of(rankedAlias, item.alias), item.codec))).withWhere(BinaryExpr.eq(ColumnRef.of(rankedAlias, rnAlias), LiteralExpr.of(1)));
|
|
1193
|
+
}
|
|
1194
|
+
function localColumnsForRowInclude(include) {
|
|
1195
|
+
return include.through?.parentLocalColumns ?? [include.localColumn];
|
|
1196
|
+
}
|
|
1197
|
+
function resolveParentLocalRefs(parentSource, include, localColumns) {
|
|
1198
|
+
return localColumns.map((column) => {
|
|
1199
|
+
if (include.localTableName === parentSource.baseTableName) return ColumnRef.of(parentSource.tableRef, column);
|
|
1200
|
+
if (parentSource.variantColumnsProjected) return ColumnRef.of(parentSource.tableRef, `${include.localTableName}__${column}`);
|
|
1201
|
+
return ColumnRef.of(include.localTableName, column);
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
function resolveChildTableSource(include, parentLocalRefs) {
|
|
1205
|
+
const alias = parentLocalRefs.some((ref) => ref.table === include.relatedTableName) ? `${include.relationName}__child` : void 0;
|
|
1206
|
+
return {
|
|
1207
|
+
alias,
|
|
1208
|
+
tableRef: alias ?? include.relatedTableName
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Recursively build the correlated-subquery projections for the nested
|
|
1213
|
+
* includes attached to a child SELECT. Used by `buildIncludeChildRowsSelect`
|
|
1214
|
+
* to wire depth-2+ aggregates into the inner SELECT at each level.
|
|
1215
|
+
*
|
|
1216
|
+
* Each nested include contributes a single projection item whose
|
|
1217
|
+
* expression is a correlated subquery.
|
|
1218
|
+
*/
|
|
1219
|
+
function buildNestedIncludeProjections(contract, parentSource, includes) {
|
|
1220
|
+
return includes.map((nested) => buildCorrelatedIncludeProjection(contract, parentSource, nested).projection);
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* The aliases a level's nested includes contribute. Each such subquery
|
|
1224
|
+
* projects a JSON document — an object for a scalar include, an array of
|
|
1225
|
+
* objects for a row include — which is what tells the enclosing level to nest
|
|
1226
|
+
* the value rather than convert it.
|
|
1227
|
+
*/
|
|
1228
|
+
function documentAliasesOf(nestedProjections) {
|
|
1229
|
+
return new Set(nestedProjections.map((item) => item.alias));
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Resolve the MTI variant joins + `variant_table__column` projection for an
|
|
1233
|
+
* include whose target model is polymorphic, mirroring the parent path in
|
|
1234
|
+
* `compileSelectWithIncludes`. The discriminator column and any STI
|
|
1235
|
+
* variant-specific columns live on the base table and reach the row through
|
|
1236
|
+
* the ordinary base-column projection (`buildProjection`); only the MTI
|
|
1237
|
+
* variant tables need a join.
|
|
1238
|
+
*
|
|
1239
|
+
* When the child base table is aliased (self-relations), `buildMtiJoins`
|
|
1240
|
+
* emits a join `ON` against the unaliased base table name, which would fall
|
|
1241
|
+
* out of scope. Remap it to the child alias — the same remap the row builder
|
|
1242
|
+
* already applies to `orderBy`/`where`.
|
|
1243
|
+
*/
|
|
1244
|
+
function buildChildPolymorphismJoinsAndProjection(contract, include, childTableAlias, childTableRef) {
|
|
1245
|
+
const polyInfo = resolvePolymorphismInfo(contract, include.relatedNamespaceId, include.relatedModelName);
|
|
1246
|
+
if (!polyInfo) return {
|
|
1247
|
+
joins: [],
|
|
1248
|
+
projection: [],
|
|
1249
|
+
hiddenProjection: [],
|
|
1250
|
+
baseSelectedFields: include.nested.selectedFields
|
|
1251
|
+
};
|
|
1252
|
+
const selection = resolvePolymorphicProjectionSelection(contract, include.relatedNamespaceId, include.relatedModelName, polyInfo, include.nested);
|
|
1253
|
+
const { joins, projection } = buildMtiJoins(contract, include.relatedNamespaceId, polyInfo, include.nested.variantName, selection.selectedMtiColumnsByTable);
|
|
1254
|
+
const hiddenProjection = buildHiddenDiscriminatorProjection(contract, include.relatedNamespaceId, polyInfo, childTableRef, selection.needsHiddenDiscriminator);
|
|
1255
|
+
if (!childTableAlias) return {
|
|
1256
|
+
joins,
|
|
1257
|
+
projection,
|
|
1258
|
+
hiddenProjection,
|
|
1259
|
+
baseSelectedFields: selection.baseSelectedFields
|
|
1260
|
+
};
|
|
1261
|
+
const remapper = createTableRefRemapper(polyInfo.baseTable, childTableRef);
|
|
1262
|
+
return {
|
|
1263
|
+
joins: joins.map((join) => join.rewrite(remapper)),
|
|
1264
|
+
projection,
|
|
1265
|
+
hiddenProjection,
|
|
1266
|
+
baseSelectedFields: selection.baseSelectedFields
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
function buildRequiredMtiJoinKeyProjection(contract, include) {
|
|
1270
|
+
const polyInfo = resolvePolymorphismInfo(contract, include.relatedNamespaceId, include.relatedModelName);
|
|
1271
|
+
if (!polyInfo) return [];
|
|
1272
|
+
const mtiTables = new Set(polyInfo.mtiVariants.map((variant) => variant.table));
|
|
1273
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
1274
|
+
const projection = [];
|
|
1275
|
+
for (const nested of include.nested.includes) {
|
|
1276
|
+
if (!mtiTables.has(nested.localTableName)) continue;
|
|
1277
|
+
for (const column of localColumnsForRowInclude(nested)) {
|
|
1278
|
+
const alias = `${nested.localTableName}__${column}`;
|
|
1279
|
+
if (aliases.has(alias)) continue;
|
|
1280
|
+
aliases.add(alias);
|
|
1281
|
+
projection.push(ProjectionItem.of(alias, ColumnRef.of(nested.localTableName, column), codecRefForStorageColumn(contract.storage, include.relatedNamespaceId, nested.localTableName, column)));
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return projection;
|
|
1285
|
+
}
|
|
1286
|
+
function mergeProjectionByAlias(projection, additional) {
|
|
1287
|
+
const aliases = new Set(projection.map((item) => item.alias));
|
|
1288
|
+
const merged = [...projection];
|
|
1289
|
+
for (const item of additional) if (!aliases.has(item.alias)) {
|
|
1290
|
+
aliases.add(item.alias);
|
|
1291
|
+
merged.push(item);
|
|
1292
|
+
}
|
|
1293
|
+
return merged;
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Build the correlated WHERE and junction JOIN artifacts for a many-to-many
|
|
1297
|
+
* include. The resulting WHERE correlates the junction to the parent rows
|
|
1298
|
+
* (AND-ed across all column pairs for composite keys). The junction JOIN
|
|
1299
|
+
* connects child rows to the junction via the child columns.
|
|
1300
|
+
*/
|
|
1301
|
+
function buildManyToManyJunctionArtifacts(parentLocalRefs, childTableRef, through) {
|
|
1302
|
+
const { table: junctionTable, parentColumns, childColumns, targetColumns, namespaceId } = through;
|
|
1303
|
+
invariant(childColumns.length === targetColumns.length, `M:N junction '${junctionTable}': childColumns (${childColumns.length}) and targetColumns (${targetColumns.length}) must have equal length`);
|
|
1304
|
+
invariant(parentColumns.length === parentLocalRefs.length, `M:N junction '${junctionTable}': parentColumns (${parentColumns.length}) and parentLocalColumns (${parentLocalRefs.length}) must have equal length`);
|
|
1305
|
+
const joinOnPairs = childColumns.map((junctionCol, i) => {
|
|
1306
|
+
const targetCol = targetColumns[i];
|
|
1307
|
+
assertDefined(targetCol, `M:N junction '${junctionTable}': missing target column at index ${i}`);
|
|
1308
|
+
return BinaryExpr.eq(ColumnRef.of(junctionTable, junctionCol), ColumnRef.of(childTableRef, targetCol));
|
|
1309
|
+
});
|
|
1310
|
+
const firstJoinPair = joinOnPairs[0];
|
|
1311
|
+
const joinOn = joinOnPairs.length === 1 && firstJoinPair ? firstJoinPair : AndExpr.of(joinOnPairs);
|
|
1312
|
+
const correlationPairs = parentColumns.map((junctionCol, i) => {
|
|
1313
|
+
const parentLocalRef = parentLocalRefs[i];
|
|
1314
|
+
assertDefined(parentLocalRef, `M:N junction '${junctionTable}': missing parent-local column ref at index ${i}`);
|
|
1315
|
+
return BinaryExpr.eq(ColumnRef.of(junctionTable, junctionCol), parentLocalRef);
|
|
1316
|
+
});
|
|
1317
|
+
const firstCorrelationPair = correlationPairs[0];
|
|
1318
|
+
return {
|
|
1319
|
+
whereExpr: correlationPairs.length === 1 && firstCorrelationPair ? firstCorrelationPair : AndExpr.of(correlationPairs),
|
|
1320
|
+
junctionJoin: JoinAst.inner(TableSource.named(junctionTable, void 0, namespaceId), joinOn, false)
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function buildIncludeChildRowsSelect(contract, parentSource, include) {
|
|
1324
|
+
const childState = include.nested;
|
|
1325
|
+
const parentLocalRefs = resolveParentLocalRefs(parentSource, include, localColumnsForRowInclude(include));
|
|
1326
|
+
const childSource = resolveChildTableSource(include, parentLocalRefs);
|
|
1327
|
+
const childTableAlias = childSource.alias;
|
|
1328
|
+
const childTableRef = childSource.tableRef;
|
|
1329
|
+
const rowsAlias = `${include.relationName}__rows`;
|
|
1330
|
+
const remappedChildOrderBy = childTableAlias && childState.orderBy ? childState.orderBy.map((item) => item.rewrite(createTableRefRemapper(include.relatedTableName, childTableRef))) : childState.orderBy;
|
|
1331
|
+
const { childOrderBy, hiddenOrderProjection, aggregateOrderBy } = buildIncludeOrderArtifacts(include.relationName, rowsAlias, remappedChildOrderBy);
|
|
1332
|
+
const childWhere = buildStateWhere(contract, childTableRef, childState, {
|
|
1333
|
+
filterTableName: include.relatedTableName,
|
|
1334
|
+
namespaceId: include.relatedNamespaceId
|
|
1335
|
+
});
|
|
1336
|
+
let whereExpr;
|
|
1337
|
+
let junctionJoins = [];
|
|
1338
|
+
if (include.through !== void 0) {
|
|
1339
|
+
const artifacts = buildManyToManyJunctionArtifacts(parentLocalRefs, childTableRef, include.through);
|
|
1340
|
+
whereExpr = childWhere ? AndExpr.of([artifacts.whereExpr, childWhere]) : artifacts.whereExpr;
|
|
1341
|
+
junctionJoins = [artifacts.junctionJoin];
|
|
1342
|
+
} else {
|
|
1343
|
+
const parentLocalRef = parentLocalRefs[0];
|
|
1344
|
+
assertDefined(parentLocalRef, `Include '${include.relationName}' has no parent-local column ref`);
|
|
1345
|
+
const joinExpr = BinaryExpr.eq(ColumnRef.of(childTableRef, include.targetColumn), parentLocalRef);
|
|
1346
|
+
whereExpr = childWhere ? AndExpr.of([joinExpr, childWhere]) : joinExpr;
|
|
1347
|
+
}
|
|
1348
|
+
if (childState.distinct !== void 0 && childState.distinct.length > 0 && childState.includes.length > 0) return buildDistinctNonLeafChildRowsSelect({
|
|
1349
|
+
contract,
|
|
1350
|
+
include,
|
|
1351
|
+
childTableAlias,
|
|
1352
|
+
childTableRef,
|
|
1353
|
+
rowsAlias,
|
|
1354
|
+
childOrderBy,
|
|
1355
|
+
hiddenOrderProjection,
|
|
1356
|
+
aggregateOrderBy,
|
|
1357
|
+
whereExpr,
|
|
1358
|
+
junctionJoins
|
|
1359
|
+
});
|
|
1360
|
+
const polyJoinsAndProjection = buildChildPolymorphismJoinsAndProjection(contract, include, childTableAlias, childTableRef);
|
|
1361
|
+
const scalarProjection = buildProjection(contract, include.relatedNamespaceId, include.relatedTableName, polyJoinsAndProjection.baseSelectedFields, childTableRef);
|
|
1362
|
+
const nestedProjections = buildNestedIncludeProjections(contract, {
|
|
1363
|
+
baseTableName: include.relatedTableName,
|
|
1364
|
+
tableRef: childTableRef,
|
|
1365
|
+
variantColumnsProjected: false
|
|
1366
|
+
}, childState.includes);
|
|
1367
|
+
const childProjection = [
|
|
1368
|
+
...scalarProjection,
|
|
1369
|
+
...polyJoinsAndProjection.projection,
|
|
1370
|
+
...polyJoinsAndProjection.hiddenProjection,
|
|
1371
|
+
...nestedProjections
|
|
1372
|
+
];
|
|
1373
|
+
let childRows = SelectAst.from(tableSourceForContract(contract, include.relatedNamespaceId, include.relatedTableName, childTableAlias)).withProjection([...childProjection, ...hiddenOrderProjection]).withWhere(whereExpr);
|
|
1374
|
+
if (polyJoinsAndProjection.joins.length > 0) childRows = childRows.withJoins([...polyJoinsAndProjection.joins]);
|
|
1375
|
+
if (junctionJoins.length > 0) childRows = childRows.withJoins(junctionJoins);
|
|
1376
|
+
if (childState.distinctOn && childState.distinctOn.length > 0) {
|
|
1377
|
+
childRows = childRows.withDistinctOn(childState.distinctOn.map((column) => ColumnRef.of(childTableRef, column)));
|
|
1378
|
+
if (childOrderBy) childRows = childRows.withOrderBy(childOrderBy);
|
|
1379
|
+
} else if (childState.distinct && childState.distinct.length > 0) {
|
|
1380
|
+
const rankedAlias = `${include.relationName}__distinct`;
|
|
1381
|
+
childRows = wrapWithRowNumberDedup({
|
|
1382
|
+
base: childRows,
|
|
1383
|
+
distinctColumnRefs: childState.distinct.map((column) => ColumnRef.of(childTableRef, column)),
|
|
1384
|
+
rankingOrderBy: childOrderBy ?? [],
|
|
1385
|
+
rankedAlias
|
|
1386
|
+
});
|
|
1387
|
+
if (childOrderBy) childRows = childRows.withOrderBy(childOrderBy.map((item, index) => new OrderByItem(ColumnRef.of(rankedAlias, `${include.relationName}__order_${index}`), item.dir)));
|
|
1388
|
+
} else if (childOrderBy) childRows = childRows.withOrderBy(childOrderBy);
|
|
1389
|
+
if (childState.limit !== void 0) childRows = childRows.withLimit(childState.limit);
|
|
1390
|
+
if (childState.offset !== void 0) childRows = childRows.withOffset(childState.offset);
|
|
1391
|
+
return {
|
|
1392
|
+
childRows,
|
|
1393
|
+
childProjection,
|
|
1394
|
+
documentAliases: documentAliasesOf(nestedProjections),
|
|
1395
|
+
rowsAlias,
|
|
1396
|
+
aggregateOrderBy
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
function buildDistinctNonLeafChildRowsSelect(options) {
|
|
1400
|
+
const { contract, include, childTableAlias, childTableRef, rowsAlias, childOrderBy, hiddenOrderProjection, aggregateOrderBy, whereExpr, junctionJoins } = options;
|
|
1401
|
+
const childState = include.nested;
|
|
1402
|
+
const grandchildJoinColumns = Array.from(new Set(childState.includes.flatMap((nested) => nested.localTableName === include.relatedTableName ? localColumnsForRowInclude(nested) : [])));
|
|
1403
|
+
const { selectedForQuery } = augmentSelectionForJoinColumns(childState.selectedFields, grandchildJoinColumns);
|
|
1404
|
+
const visiblePolyProjection = buildChildPolymorphismJoinsAndProjection(contract, include, childTableAlias, childTableRef);
|
|
1405
|
+
const queryPolyProjection = buildChildPolymorphismJoinsAndProjection(contract, {
|
|
1406
|
+
...include,
|
|
1407
|
+
nested: {
|
|
1408
|
+
...childState,
|
|
1409
|
+
selectedFields: selectedForQuery
|
|
1410
|
+
}
|
|
1411
|
+
}, childTableAlias, childTableRef);
|
|
1412
|
+
const innerScalarProjection = buildProjection(contract, include.relatedNamespaceId, include.relatedTableName, queryPolyProjection.baseSelectedFields, childTableRef);
|
|
1413
|
+
const innerMtiProjection = mergeProjectionByAlias(queryPolyProjection.projection, buildRequiredMtiJoinKeyProjection(contract, include));
|
|
1414
|
+
let baseInner = SelectAst.from(tableSourceForContract(contract, include.relatedNamespaceId, include.relatedTableName, childTableAlias)).withProjection([
|
|
1415
|
+
...innerScalarProjection,
|
|
1416
|
+
...innerMtiProjection,
|
|
1417
|
+
...queryPolyProjection.hiddenProjection,
|
|
1418
|
+
...hiddenOrderProjection
|
|
1419
|
+
]).withWhere(whereExpr);
|
|
1420
|
+
const distinctExtraJoins = [...queryPolyProjection.joins, ...junctionJoins];
|
|
1421
|
+
if (distinctExtraJoins.length > 0) baseInner = baseInner.withJoins(distinctExtraJoins);
|
|
1422
|
+
const distinctColumns = childState.distinct;
|
|
1423
|
+
if (distinctColumns === void 0 || distinctColumns.length === 0) throw new InternalError("buildDistinctNonLeafChildRowsSelect requires a non-empty `distinct` selection");
|
|
1424
|
+
const rankedAlias = `${include.relationName}__ranked`;
|
|
1425
|
+
let innerSelect = wrapWithRowNumberDedup({
|
|
1426
|
+
base: baseInner,
|
|
1427
|
+
distinctColumnRefs: distinctColumns.map((column) => ColumnRef.of(childTableRef, column)),
|
|
1428
|
+
rankingOrderBy: childOrderBy ?? [],
|
|
1429
|
+
rankedAlias
|
|
1430
|
+
});
|
|
1431
|
+
if (childOrderBy) innerSelect = innerSelect.withOrderBy(childOrderBy.map((item, index) => new OrderByItem(ColumnRef.of(rankedAlias, `${include.relationName}__order_${index}`), item.dir)));
|
|
1432
|
+
if (childState.limit !== void 0) innerSelect = innerSelect.withLimit(childState.limit);
|
|
1433
|
+
if (childState.offset !== void 0) innerSelect = innerSelect.withOffset(childState.offset);
|
|
1434
|
+
const distinctAlias = `${include.relationName}__distinct`;
|
|
1435
|
+
const outerScalarProjection = buildProjection(contract, include.relatedNamespaceId, include.relatedTableName, visiblePolyProjection.baseSelectedFields, distinctAlias);
|
|
1436
|
+
const outerNestedProjections = buildNestedIncludeProjections(contract, {
|
|
1437
|
+
baseTableName: include.relatedTableName,
|
|
1438
|
+
tableRef: distinctAlias,
|
|
1439
|
+
variantColumnsProjected: true
|
|
1440
|
+
}, childState.includes);
|
|
1441
|
+
const outerPolyProjection = visiblePolyProjection.projection.map((proj) => ProjectionItem.of(proj.alias, ColumnRef.of(distinctAlias, proj.alias), proj.codec));
|
|
1442
|
+
const outerHiddenProjection = visiblePolyProjection.hiddenProjection.map((proj) => ProjectionItem.of(proj.alias, ColumnRef.of(distinctAlias, proj.alias), proj.codec));
|
|
1443
|
+
const outerHiddenOrderProjection = hiddenOrderProjection.map((proj) => ProjectionItem.of(proj.alias, ColumnRef.of(distinctAlias, proj.alias), proj.codec));
|
|
1444
|
+
const childProjection = [
|
|
1445
|
+
...outerScalarProjection,
|
|
1446
|
+
...outerPolyProjection,
|
|
1447
|
+
...outerHiddenProjection,
|
|
1448
|
+
...outerNestedProjections
|
|
1449
|
+
];
|
|
1450
|
+
return {
|
|
1451
|
+
childRows: SelectAst.from(DerivedTableSource.as(distinctAlias, innerSelect)).withProjection([...childProjection, ...outerHiddenOrderProjection]),
|
|
1452
|
+
childProjection,
|
|
1453
|
+
documentAliases: documentAliasesOf(outerNestedProjections),
|
|
1454
|
+
rowsAlias,
|
|
1455
|
+
aggregateOrderBy
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Build the inner SELECT for a scalar include reducer (`count` /
|
|
1460
|
+
* `sum` / `avg` / `min` / `max`).
|
|
1461
|
+
*
|
|
1462
|
+
* Emits one row containing `json_build_object('value', AGG(...))`
|
|
1463
|
+
* over the child relation correlated to the parent via the FK. The
|
|
1464
|
+
* JSON wrap lets the value flow through the existing include-payload
|
|
1465
|
+
* decoder unchanged (it JSON.parses the column and the scalar branch
|
|
1466
|
+
* pulls `.value` out).
|
|
1467
|
+
*
|
|
1468
|
+
* The refine state's pipeline composes through to the aggregate's
|
|
1469
|
+
* input set: `where` / `orderBy` / `take` / `skip` / `distinct` shape
|
|
1470
|
+
* the rows the aggregate sees, matching the natural compositional
|
|
1471
|
+
* semantic of
|
|
1472
|
+
*
|
|
1473
|
+
* `db.User.include('posts', p => p.where(W).take(N).count()) // ≤ N`
|
|
1474
|
+
*
|
|
1475
|
+
* When `take` / `skip` / `distinct` is set, the aggregate's input
|
|
1476
|
+
* cannot just be the bare correlated table — a top-level `LIMIT` on
|
|
1477
|
+
* the aggregating SELECT only trims the (already one-row) output, not
|
|
1478
|
+
* the rows being aggregated. We therefore wrap the source in a
|
|
1479
|
+
* derived SELECT that materialises the shaped row set, then
|
|
1480
|
+
* aggregate over that. `orderBy` alone (no `take` / `skip` /
|
|
1481
|
+
* `distinct`) is dropped at the SQL level since reordering does not
|
|
1482
|
+
* change which rows are aggregated.
|
|
1483
|
+
*/
|
|
1484
|
+
function buildIncludeChildScalarSelect(contract, parentSource, include, scalar) {
|
|
1485
|
+
const parentLocalRefs = resolveParentLocalRefs(parentSource, include, [include.localColumn]);
|
|
1486
|
+
const parentLocalRef = parentLocalRefs[0];
|
|
1487
|
+
assertDefined(parentLocalRef, `Include '${include.relationName}' has no parent-local column ref`);
|
|
1488
|
+
const childSource = resolveChildTableSource(include, parentLocalRefs);
|
|
1489
|
+
const childTableAlias = childSource.alias;
|
|
1490
|
+
const childTableRef = childSource.tableRef;
|
|
1491
|
+
const state = scalar.state;
|
|
1492
|
+
const joinExpr = BinaryExpr.eq(ColumnRef.of(childTableRef, include.targetColumn), parentLocalRef);
|
|
1493
|
+
const childWhere = buildStateWhere(contract, childTableRef, state, {
|
|
1494
|
+
filterTableName: include.relatedTableName,
|
|
1495
|
+
namespaceId: include.relatedNamespaceId
|
|
1496
|
+
});
|
|
1497
|
+
const whereExpr = childWhere ? AndExpr.of([joinExpr, childWhere]) : joinExpr;
|
|
1498
|
+
const remappedOrderBy = childTableAlias && state.orderBy ? state.orderBy.map((item) => item.rewrite(createTableRefRemapper(include.relatedTableName, childTableRef))) : state.orderBy;
|
|
1499
|
+
const hasPagination = state.limit !== void 0 || state.offset !== void 0;
|
|
1500
|
+
const hasDistinct = state.distinct !== void 0 && state.distinct.length > 0 || state.distinctOn !== void 0 && state.distinctOn.length > 0;
|
|
1501
|
+
if (!(hasPagination || hasDistinct)) {
|
|
1502
|
+
const aggregateExpr = buildIncludeAggregateExpr(scalar, childTableRef);
|
|
1503
|
+
const jsonObjectExpr = JsonObjectExpr.fromEntries([JsonObjectExpr.entry("value", jsonEntryProjection(aggregateExpr, {}))]);
|
|
1504
|
+
return SelectAst.from(tableSourceForContract(contract, include.relatedNamespaceId, include.relatedTableName, childTableAlias)).withProjection([ProjectionItem.of(include.relationName, jsonObjectExpr)]).withWhere(whereExpr);
|
|
1505
|
+
}
|
|
1506
|
+
const innerAlias = `${include.relationName}__scalar`;
|
|
1507
|
+
const hiddenOrderProjection = state.distinct !== void 0 && state.distinct.length > 0 && remappedOrderBy !== void 0 && remappedOrderBy.length > 0 ? remappedOrderBy.map((item, index) => ProjectionItem.of(`${include.relationName}__order_${index}`, item.expr)) : [];
|
|
1508
|
+
const innerProjection = [...scalar.column !== void 0 ? [ProjectionItem.of(scalar.column, ColumnRef.of(childTableRef, scalar.column))] : [ProjectionItem.of("__row", LiteralExpr.of(1))], ...hiddenOrderProjection];
|
|
1509
|
+
let inner = SelectAst.from(tableSourceForContract(contract, include.relatedNamespaceId, include.relatedTableName, childTableAlias)).withProjection(innerProjection).withWhere(whereExpr);
|
|
1510
|
+
if (state.distinctOn !== void 0 && state.distinctOn.length > 0) {
|
|
1511
|
+
inner = inner.withDistinctOn(state.distinctOn.map((column) => ColumnRef.of(childTableRef, column)));
|
|
1512
|
+
if (remappedOrderBy !== void 0 && remappedOrderBy.length > 0) inner = inner.withOrderBy(remappedOrderBy);
|
|
1513
|
+
} else if (state.distinct !== void 0 && state.distinct.length > 0) {
|
|
1514
|
+
const rankedAlias = `${include.relationName}__scalar_distinct`;
|
|
1515
|
+
inner = wrapWithRowNumberDedup({
|
|
1516
|
+
base: inner,
|
|
1517
|
+
distinctColumnRefs: state.distinct.map((column) => ColumnRef.of(childTableRef, column)),
|
|
1518
|
+
rankingOrderBy: remappedOrderBy ?? [],
|
|
1519
|
+
rankedAlias
|
|
1520
|
+
});
|
|
1521
|
+
if (remappedOrderBy !== void 0 && remappedOrderBy.length > 0) inner = inner.withOrderBy(remappedOrderBy.map((item, index) => new OrderByItem(ColumnRef.of(rankedAlias, `${include.relationName}__order_${index}`), item.dir)));
|
|
1522
|
+
} else if (remappedOrderBy !== void 0 && remappedOrderBy.length > 0) inner = inner.withOrderBy(remappedOrderBy);
|
|
1523
|
+
if (state.limit !== void 0) inner = inner.withLimit(state.limit);
|
|
1524
|
+
if (state.offset !== void 0) inner = inner.withOffset(state.offset);
|
|
1525
|
+
const outerAggregateExpr = buildIncludeAggregateExpr(scalar, innerAlias);
|
|
1526
|
+
const outerJsonObjectExpr = JsonObjectExpr.fromEntries([JsonObjectExpr.entry("value", jsonEntryProjection(outerAggregateExpr, {}))]);
|
|
1527
|
+
return SelectAst.from(DerivedTableSource.as(innerAlias, inner)).withProjection([ProjectionItem.of(include.relationName, outerJsonObjectExpr)]);
|
|
1528
|
+
}
|
|
1529
|
+
function buildIncludeAggregateExpr(scalar, childTableRef) {
|
|
1530
|
+
if (scalar.fn === "count") return AggregateExpr.count();
|
|
1531
|
+
if (scalar.column === void 0) throw ormError("ORM.AGGREGATE_SELECTOR_INVALID", `Aggregate selector "${scalar.fn}" requires a column`, { meta: { fn: scalar.fn } });
|
|
1532
|
+
const columnRef = ColumnRef.of(childTableRef, scalar.column);
|
|
1533
|
+
switch (scalar.fn) {
|
|
1534
|
+
case "sum": return AggregateExpr.sum(columnRef);
|
|
1535
|
+
case "avg": return AggregateExpr.avg(columnRef);
|
|
1536
|
+
case "min": return AggregateExpr.min(columnRef);
|
|
1537
|
+
case "max": return AggregateExpr.max(columnRef);
|
|
1538
|
+
default: return assertNever(scalar.fn);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Build the inner SELECT for a `combine({ a, b, ... })` include.
|
|
1543
|
+
*
|
|
1544
|
+
* Each branch produces a self-contained SELECT projecting one row
|
|
1545
|
+
* with one column aliased to the relation name. The branches are
|
|
1546
|
+
* stitched together as cross-joined derived tables (FROM <first>
|
|
1547
|
+
* INNER JOIN <second> ON TRUE ...), and the outer projection packs
|
|
1548
|
+
* them into a single `json_build_object` keyed by branch name. The
|
|
1549
|
+
* resulting subquery emits exactly one row per parent row containing
|
|
1550
|
+
* the combined JSON — embedded as a correlated subquery in the outer
|
|
1551
|
+
* projection.
|
|
1552
|
+
*
|
|
1553
|
+
* Row branches reuse the standalone row-include builder; scalar
|
|
1554
|
+
* branches reuse `buildIncludeChildScalarSelect` — the `{value: ...}`
|
|
1555
|
+
* envelope survives into the combined JSON and the decoder unwraps
|
|
1556
|
+
* it per scalar branch. Distinct/take/skip semantics inside a row
|
|
1557
|
+
* branch fan out naturally because the row builder is invoked with
|
|
1558
|
+
* a synthetic IncludeExpr whose `nested` is the branch's state.
|
|
1559
|
+
*/
|
|
1560
|
+
function buildIncludeChildCombineSelect(contract, parentSource, include, branches) {
|
|
1561
|
+
const branchEntries = Object.entries(branches);
|
|
1562
|
+
if (branchEntries.length === 0) throw ormError("ORM.INCLUDE_INVALID", `combine() include "${include.relationName}" has no branches`, { meta: { relation: include.relationName } });
|
|
1563
|
+
const compiledBranches = branchEntries.map(([name, branch]) => ({
|
|
1564
|
+
name,
|
|
1565
|
+
alias: `${include.relationName}__combine__${name}`,
|
|
1566
|
+
select: buildIncludeChildCombineBranchSelect(contract, parentSource, include, branch)
|
|
1567
|
+
}));
|
|
1568
|
+
const jsonObjectExpr = JsonObjectExpr.fromEntries(compiledBranches.map((branch) => JsonObjectExpr.entry(branch.name, jsonEntryProjection(ColumnRef.of(branch.alias, include.relationName), { document: true }))));
|
|
1569
|
+
const [firstBranch, ...restBranches] = compiledBranches;
|
|
1570
|
+
if (!firstBranch) throw new InternalError(`combine() include "${include.relationName}" has no branches`);
|
|
1571
|
+
const joins = restBranches.map((branch) => JoinAst.inner(DerivedTableSource.as(branch.alias, branch.select), AndExpr.true(), false));
|
|
1572
|
+
return SelectAst.from(DerivedTableSource.as(firstBranch.alias, firstBranch.select)).withProjection([ProjectionItem.of(include.relationName, jsonObjectExpr)]).withJoins(joins);
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Compile one branch of a `combine({ ... })` into a SelectAst that
|
|
1576
|
+
* projects exactly one row with one column aliased to the parent
|
|
1577
|
+
* relation name. Dispatches to the standalone scalar / row builders
|
|
1578
|
+
* with the branch's state spliced into a synthetic IncludeExpr.
|
|
1579
|
+
*/
|
|
1580
|
+
function buildIncludeChildCombineBranchSelect(contract, parentSource, include, branch) {
|
|
1581
|
+
if (branch.kind === "scalar") return buildIncludeChildScalarSelect(contract, parentSource, include, branch.selector);
|
|
1582
|
+
return buildIncludeChildRowsAggregateSelect(contract, parentSource, {
|
|
1583
|
+
...include,
|
|
1584
|
+
nested: branch.state,
|
|
1585
|
+
scalar: void 0,
|
|
1586
|
+
combine: void 0
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Internal helper: build the inner aggregate SELECT that `json_agg`s
|
|
1591
|
+
* child rows into a single JSON-array column aliased to the relation
|
|
1592
|
+
* name. Used by both the standalone row correlated-subquery path and
|
|
1593
|
+
* by combine's row branches.
|
|
1594
|
+
*/
|
|
1595
|
+
function buildIncludeChildRowsAggregateSelect(contract, parentSource, include) {
|
|
1596
|
+
const { childRows, childProjection, documentAliases, rowsAlias, aggregateOrderBy } = buildIncludeChildRowsSelect(contract, parentSource, include);
|
|
1597
|
+
const jsonObjectExpr = JsonObjectExpr.fromEntries(childProjection.map((item) => JsonObjectExpr.entry(item.alias, jsonEntryProjection(ColumnRef.of(rowsAlias, item.alias), {
|
|
1598
|
+
codec: item.codec,
|
|
1599
|
+
document: documentAliases.has(item.alias)
|
|
1600
|
+
}))));
|
|
1601
|
+
return SelectAst.from(DerivedTableSource.as(rowsAlias, childRows)).withProjection([ProjectionItem.of(include.relationName, JsonArrayAggExpr.of(jsonEntryProjection(jsonObjectExpr, { document: true }), "emptyArray", aggregateOrderBy))]);
|
|
1602
|
+
}
|
|
1603
|
+
function buildCorrelatedIncludeProjection(contract, parentSource, include) {
|
|
1604
|
+
if (include.scalar) {
|
|
1605
|
+
const scalarSelect = buildIncludeChildScalarSelect(contract, parentSource, include, include.scalar);
|
|
1606
|
+
return { projection: ProjectionItem.of(include.relationName, SubqueryExpr.of(scalarSelect)) };
|
|
1607
|
+
}
|
|
1608
|
+
if (include.combine) {
|
|
1609
|
+
const combineSelect = buildIncludeChildCombineSelect(contract, parentSource, include, include.combine);
|
|
1610
|
+
return { projection: ProjectionItem.of(include.relationName, SubqueryExpr.of(combineSelect)) };
|
|
1611
|
+
}
|
|
1612
|
+
const aggregateQuery = buildIncludeChildRowsAggregateSelect(contract, parentSource, include);
|
|
1613
|
+
return { projection: ProjectionItem.of(include.relationName, SubqueryExpr.of(aggregateQuery)) };
|
|
1614
|
+
}
|
|
1615
|
+
function buildSelectAst(contract, tableName, state, options) {
|
|
1616
|
+
const namespaceId = options.namespaceId;
|
|
1617
|
+
const projection = [...buildProjection(contract, namespaceId, tableName, state.selectedFields, tableName), ...options.includeProjection ?? []];
|
|
1618
|
+
const where = options.where ?? buildStateWhere(contract, tableName, state, { namespaceId });
|
|
1619
|
+
const usesRowNumberDistinct = state.distinct !== void 0 && state.distinct.length > 0;
|
|
1620
|
+
const fromSource = usesRowNumberDistinct ? DerivedTableSource.as(tableName, buildTopLevelDistinctRankedInner(contract, namespaceId, tableName, state, where)) : tableSourceForContract(contract, namespaceId, tableName);
|
|
1621
|
+
let ast = SelectAst.from(fromSource).withProjection(projection);
|
|
1622
|
+
if (usesRowNumberDistinct) ast = ast.withWhere(BinaryExpr.eq(ColumnRef.of(tableName, "__prisma_distinct_rn"), LiteralExpr.of(1)));
|
|
1623
|
+
else if (where) ast = ast.withWhere(where);
|
|
1624
|
+
if (state.orderBy) ast = ast.withOrderBy(state.orderBy);
|
|
1625
|
+
if (state.selectedFields === void 0) ast = ast.withSelectAllIntent({ table: tableName });
|
|
1626
|
+
if (state.distinctOn && state.distinctOn.length > 0) ast = ast.withDistinctOn(state.distinctOn.map((column) => ColumnRef.of(tableName, column)));
|
|
1627
|
+
if (state.limit !== void 0) ast = ast.withLimit(state.limit);
|
|
1628
|
+
if (state.offset !== void 0) ast = ast.withOffset(state.offset);
|
|
1629
|
+
if (options.joins && options.joins.length > 0) ast = ast.withJoins(options.joins);
|
|
1630
|
+
return ast;
|
|
1631
|
+
}
|
|
1632
|
+
function buildTopLevelDistinctRankedInner(contract, namespaceId, tableName, state, where) {
|
|
1633
|
+
const distinctColumns = state.distinct;
|
|
1634
|
+
if (distinctColumns === void 0 || distinctColumns.length === 0) throw new InternalError("buildTopLevelDistinctRankedInner called without `state.distinct`");
|
|
1635
|
+
const allColsProjection = resolveTableColumns(contract, namespaceId, tableName).map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column)));
|
|
1636
|
+
const distinctColumnRefs = distinctColumns.map((column) => ColumnRef.of(tableName, column));
|
|
1637
|
+
const rankingOrderBy = state.orderBy && state.orderBy.length > 0 ? state.orderBy : distinctColumnRefs.map((expr) => OrderByItem.asc(expr));
|
|
1638
|
+
let inner = SelectAst.from(tableSourceForContract(contract, namespaceId, tableName)).withProjection([...allColsProjection, ProjectionItem.of("__prisma_distinct_rn", WindowFuncExpr.rowNumber({
|
|
1639
|
+
partitionBy: distinctColumnRefs,
|
|
1640
|
+
orderBy: rankingOrderBy
|
|
1641
|
+
}))]);
|
|
1642
|
+
if (where) inner = inner.withWhere(where);
|
|
1643
|
+
return inner;
|
|
1644
|
+
}
|
|
1645
|
+
function buildMtiJoins(contract, namespaceId, polyInfo, variantName, selectedColumnsByTable) {
|
|
1646
|
+
const joins = [];
|
|
1647
|
+
const projection = [];
|
|
1648
|
+
const pkColumn = resolvePrimaryKeyColumn(contract, namespaceId, polyInfo.baseTable);
|
|
1649
|
+
const variantsToJoin = variantName ? polyInfo.mtiVariants.filter((v) => v.modelName === variantName) : polyInfo.mtiVariants;
|
|
1650
|
+
for (const variant of variantsToJoin) {
|
|
1651
|
+
const joinType = variantName ? "inner" : "left";
|
|
1652
|
+
const joinOn = EqColJoinOn.of(ColumnRef.of(polyInfo.baseTable, pkColumn), ColumnRef.of(variant.table, pkColumn));
|
|
1653
|
+
const join = joinType === "inner" ? JoinAst.inner(tableSourceForContract(contract, namespaceId, variant.table), joinOn) : JoinAst.left(tableSourceForContract(contract, namespaceId, variant.table), joinOn);
|
|
1654
|
+
joins.push(join);
|
|
1655
|
+
const variantColumns = resolveTableColumns(contract, namespaceId, variant.table);
|
|
1656
|
+
const selectedVariantColumns = selectedColumnsByTable?.get(variant.table);
|
|
1657
|
+
for (const col of variantColumns) {
|
|
1658
|
+
if (col === pkColumn) continue;
|
|
1659
|
+
if (selectedColumnsByTable !== void 0 && selectedVariantColumns?.has(col) !== true) continue;
|
|
1660
|
+
const alias = `${variant.table}__${col}`;
|
|
1661
|
+
projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col), codecRefForStorageColumn(contract.storage, namespaceId, variant.table, col)));
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return {
|
|
1665
|
+
joins,
|
|
1666
|
+
projection
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
function compileSelect(contract, namespaceId, tableName, state, modelName) {
|
|
1670
|
+
const polyInfo = modelName ? resolvePolymorphismInfo(contract, namespaceId, modelName) : void 0;
|
|
1671
|
+
const selection = polyInfo && modelName ? resolvePolymorphicProjectionSelection(contract, namespaceId, modelName, polyInfo, state) : void 0;
|
|
1672
|
+
const projectionState = selection ? {
|
|
1673
|
+
...state,
|
|
1674
|
+
selectedFields: selection.baseSelectedFields
|
|
1675
|
+
} : state;
|
|
1676
|
+
const mtiArtifacts = polyInfo && polyInfo.mtiVariants.length > 0 ? buildMtiJoins(contract, namespaceId, polyInfo, state.variantName, selection?.selectedMtiColumnsByTable) : void 0;
|
|
1677
|
+
const hiddenProjection = polyInfo && selection ? buildHiddenDiscriminatorProjection(contract, namespaceId, polyInfo, tableName, selection.needsHiddenDiscriminator) : [];
|
|
1678
|
+
const ast = buildSelectAst(contract, tableName, {
|
|
1679
|
+
...projectionState,
|
|
1680
|
+
includes: []
|
|
1681
|
+
}, {
|
|
1682
|
+
joins: mtiArtifacts?.joins ?? [],
|
|
1683
|
+
includeProjection: [...mtiArtifacts?.projection ?? [], ...hiddenProjection],
|
|
1684
|
+
namespaceId
|
|
1685
|
+
});
|
|
1686
|
+
const { params } = deriveParamsFromAst(ast);
|
|
1687
|
+
return buildOrmQueryPlan(contract, ast, params, state.annotations);
|
|
1688
|
+
}
|
|
1689
|
+
function compileSelectWithIncludes(contract, namespaceId, tableName, state, modelName) {
|
|
1690
|
+
const includeJoins = [];
|
|
1691
|
+
const includeProjection = [];
|
|
1692
|
+
const topLevelWhere = buildStateWhere(contract, tableName, state, { namespaceId });
|
|
1693
|
+
const polyInfo = modelName ? resolvePolymorphismInfo(contract, namespaceId, modelName) : void 0;
|
|
1694
|
+
const selection = polyInfo && modelName ? resolvePolymorphicProjectionSelection(contract, namespaceId, modelName, polyInfo, state) : void 0;
|
|
1695
|
+
const projectionState = selection ? {
|
|
1696
|
+
...state,
|
|
1697
|
+
selectedFields: selection.baseSelectedFields
|
|
1698
|
+
} : state;
|
|
1699
|
+
if (polyInfo && selection) {
|
|
1700
|
+
if (polyInfo.mtiVariants.length > 0) {
|
|
1701
|
+
const mtiArtifacts = buildMtiJoins(contract, namespaceId, polyInfo, state.variantName, selection.selectedMtiColumnsByTable);
|
|
1702
|
+
includeJoins.push(...mtiArtifacts.joins);
|
|
1703
|
+
includeProjection.push(...mtiArtifacts.projection);
|
|
1704
|
+
}
|
|
1705
|
+
includeProjection.push(...buildHiddenDiscriminatorProjection(contract, namespaceId, polyInfo, tableName, selection.needsHiddenDiscriminator));
|
|
1706
|
+
}
|
|
1707
|
+
const parentSource = {
|
|
1708
|
+
baseTableName: tableName,
|
|
1709
|
+
tableRef: tableName,
|
|
1710
|
+
variantColumnsProjected: false
|
|
1711
|
+
};
|
|
1712
|
+
for (const include of state.includes) {
|
|
1713
|
+
const artifact = buildCorrelatedIncludeProjection(contract, parentSource, include);
|
|
1714
|
+
includeProjection.push(artifact.projection);
|
|
1715
|
+
}
|
|
1716
|
+
const ast = buildSelectAst(contract, tableName, {
|
|
1717
|
+
...projectionState,
|
|
1718
|
+
includes: []
|
|
1719
|
+
}, {
|
|
1720
|
+
joins: includeJoins,
|
|
1721
|
+
includeProjection,
|
|
1722
|
+
namespaceId,
|
|
1723
|
+
...ifDefined("where", topLevelWhere)
|
|
1724
|
+
});
|
|
1725
|
+
const { params } = deriveParamsFromAst(ast);
|
|
1726
|
+
return buildOrmQueryPlan(contract, ast, params, state.annotations);
|
|
1727
|
+
}
|
|
1728
|
+
function emptyState() {
|
|
1729
|
+
return {
|
|
1730
|
+
filters: [],
|
|
1731
|
+
includes: [],
|
|
1732
|
+
orderBy: void 0,
|
|
1733
|
+
cursor: void 0,
|
|
1734
|
+
distinct: void 0,
|
|
1735
|
+
distinctOn: void 0,
|
|
1736
|
+
selectedFields: void 0,
|
|
1737
|
+
limit: void 0,
|
|
1738
|
+
offset: void 0,
|
|
1739
|
+
variantName: void 0,
|
|
1740
|
+
annotations: /* @__PURE__ */ new Map()
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
function param(codec, value) {
|
|
1744
|
+
if (codec === void 0) return ParamRef.of(value);
|
|
1745
|
+
return ParamRef.of(value, { codec });
|
|
1746
|
+
}
|
|
1747
|
+
function paramList(codec, values) {
|
|
1748
|
+
return ListExpression.of(values.map((value) => param(codec, value)));
|
|
1749
|
+
}
|
|
1750
|
+
function scalarComparisonMethod(op) {
|
|
1751
|
+
return ((left, codec) => (value) => {
|
|
1752
|
+
if (value === null && (op === "eq" || op === "neq")) return op === "eq" ? NullCheckExpr.isNull(left) : NullCheckExpr.isNotNull(left);
|
|
1753
|
+
return new BinaryExpr(op, left, param(codec, value));
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
function listComparisonMethod(op) {
|
|
1757
|
+
return ((left, codec) => (values) => new BinaryExpr(op, left, paramList(codec, values)));
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Declares trait requirements and runtime factory for each comparison method.
|
|
1761
|
+
*
|
|
1762
|
+
* - `traits: []` means "no trait required" — always available
|
|
1763
|
+
* - Multi-trait: `traits: ['equality', 'order']` means BOTH traits are required
|
|
1764
|
+
*/
|
|
1765
|
+
const COMPARISON_METHODS_META = {
|
|
1766
|
+
eq: {
|
|
1767
|
+
traits: ["equality"],
|
|
1768
|
+
create: scalarComparisonMethod("eq")
|
|
1769
|
+
},
|
|
1770
|
+
neq: {
|
|
1771
|
+
traits: ["equality"],
|
|
1772
|
+
create: scalarComparisonMethod("neq")
|
|
1773
|
+
},
|
|
1774
|
+
in: {
|
|
1775
|
+
traits: ["equality"],
|
|
1776
|
+
create: listComparisonMethod("in")
|
|
1777
|
+
},
|
|
1778
|
+
notIn: {
|
|
1779
|
+
traits: ["equality"],
|
|
1780
|
+
create: listComparisonMethod("notIn")
|
|
1781
|
+
},
|
|
1782
|
+
gt: {
|
|
1783
|
+
traits: ["order"],
|
|
1784
|
+
create: scalarComparisonMethod("gt")
|
|
1785
|
+
},
|
|
1786
|
+
lt: {
|
|
1787
|
+
traits: ["order"],
|
|
1788
|
+
create: scalarComparisonMethod("lt")
|
|
1789
|
+
},
|
|
1790
|
+
gte: {
|
|
1791
|
+
traits: ["order"],
|
|
1792
|
+
create: scalarComparisonMethod("gte")
|
|
1793
|
+
},
|
|
1794
|
+
lte: {
|
|
1795
|
+
traits: ["order"],
|
|
1796
|
+
create: scalarComparisonMethod("lte")
|
|
1797
|
+
},
|
|
1798
|
+
like: {
|
|
1799
|
+
traits: ["textual"],
|
|
1800
|
+
create: scalarComparisonMethod("like")
|
|
1801
|
+
},
|
|
1802
|
+
asc: {
|
|
1803
|
+
traits: ["order"],
|
|
1804
|
+
create: (left) => () => OrderByItem.asc(left)
|
|
1805
|
+
},
|
|
1806
|
+
desc: {
|
|
1807
|
+
traits: ["order"],
|
|
1808
|
+
create: (left) => () => OrderByItem.desc(left)
|
|
1809
|
+
},
|
|
1810
|
+
isNull: {
|
|
1811
|
+
traits: [],
|
|
1812
|
+
create: (left) => () => NullCheckExpr.isNull(left)
|
|
1813
|
+
},
|
|
1814
|
+
isNotNull: {
|
|
1815
|
+
traits: [],
|
|
1816
|
+
create: (left) => () => NullCheckExpr.isNotNull(left)
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
function dispatchCollectionRows(options) {
|
|
1820
|
+
const { context, runtime, state, tableName, modelName, namespaceId } = options;
|
|
1821
|
+
const { contract } = context;
|
|
1822
|
+
const polyInfo = resolvePolymorphismInfo(contract, namespaceId, modelName);
|
|
1823
|
+
if (state.includes.length === 0) return mapResultRows(executeQueryPlan(runtime, compileSelect(contract, namespaceId, tableName, state, modelName)), polyInfo ? (rawRow) => blindCast(mapPolymorphicRow(contract, namespaceId, modelName, polyInfo, rawRow, state.variantName)) : (rawRow) => blindCast(mapStorageRowToModelFields(contract, namespaceId, modelName, rawRow)));
|
|
1824
|
+
return dispatchWithIncludes(options);
|
|
1825
|
+
}
|
|
1826
|
+
function dispatchWithIncludes(options) {
|
|
1827
|
+
const { context, runtime, state, tableName, modelName, namespaceId } = options;
|
|
1828
|
+
const { contract } = context;
|
|
1829
|
+
const generator = async function* () {
|
|
1830
|
+
const { scope, release } = await acquireRuntimeScope(runtime);
|
|
1831
|
+
try {
|
|
1832
|
+
const parentRowsRaw = await executeQueryPlan(scope, compileSelectWithIncludes(contract, namespaceId, tableName, state, modelName)).toArray();
|
|
1833
|
+
if (parentRowsRaw.length === 0) return;
|
|
1834
|
+
const polyInfo = resolvePolymorphismInfo(contract, namespaceId, modelName);
|
|
1835
|
+
const parentRows = parentRowsRaw.map((row) => {
|
|
1836
|
+
return {
|
|
1837
|
+
raw: row,
|
|
1838
|
+
mapped: polyInfo ? mapPolymorphicRow(contract, namespaceId, modelName, polyInfo, row, state.variantName) : mapStorageRowToModelFields(contract, namespaceId, modelName, row)
|
|
1839
|
+
};
|
|
1840
|
+
});
|
|
1841
|
+
for (const parent of parentRows) for (const include of state.includes) parent.mapped[include.relationName] = await decodeIncludePayload(contract, context, include, parent.raw[include.relationName]);
|
|
1842
|
+
for (const row of parentRows) yield blindCast(row.mapped);
|
|
1843
|
+
} finally {
|
|
1844
|
+
if (release) await release();
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
return new AsyncIterableResult(generator());
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Reload the rows a mutation just wrote (create / createAll / update /
|
|
1851
|
+
* updateAll / upsert) through the read-path dispatch, so `.include()`
|
|
1852
|
+
* relations resolve via the exact same correlated-subquery builder,
|
|
1853
|
+
* decoding, and polymorphism mapping a read query uses — there is no
|
|
1854
|
+
* parallel mutation read-back implementation.
|
|
1855
|
+
*
|
|
1856
|
+
* The mutation returns only its identity columns (PK / unique); this
|
|
1857
|
+
* re-selects those rows with the caller's projection + includes, keyed
|
|
1858
|
+
* by `identity IN (...)`. One round-trip regardless of row count or
|
|
1859
|
+
* include depth. The read-back observes the just-written rows because
|
|
1860
|
+
* it runs on the same runtime — and therefore the same transaction —
|
|
1861
|
+
* the mutation ran on.
|
|
1862
|
+
*
|
|
1863
|
+
* Delete read-back does NOT come through here: a parent-anchored
|
|
1864
|
+
* include query can't observe an already-deleted row, so delete reads
|
|
1865
|
+
* its snapshot before issuing the DELETE (see `collection.ts`).
|
|
1866
|
+
*/
|
|
1867
|
+
function reloadMutationRowsByIdentities(options) {
|
|
1868
|
+
const { context, runtime, tableName, modelName, namespaceId, identityRows, selectedFields, includes } = options;
|
|
1869
|
+
const { contract } = context;
|
|
1870
|
+
if (identityRows.length === 0) return emptyResult();
|
|
1871
|
+
const identityColumns = resolveRowIdentityColumns(contract, namespaceId, tableName);
|
|
1872
|
+
if (identityColumns.length === 0) throw ormError("ORM.ROW_IDENTITY_MISSING", `Cannot load includes for the mutation result on model "${modelName}": table "${tableName}" has no primary key or unique constraint to key the include read-back on.`, { meta: {
|
|
1873
|
+
model: modelName,
|
|
1874
|
+
table: tableName
|
|
1875
|
+
} });
|
|
1876
|
+
const identityFilter = buildIdentityInFilter(contract, namespaceId, tableName, identityColumns, identityRows);
|
|
1877
|
+
if (!identityFilter) return emptyResult();
|
|
1878
|
+
return dispatchCollectionRows({
|
|
1879
|
+
context,
|
|
1880
|
+
runtime,
|
|
1881
|
+
state: {
|
|
1882
|
+
...emptyState(),
|
|
1883
|
+
filters: [identityFilter],
|
|
1884
|
+
selectedFields,
|
|
1885
|
+
includes
|
|
1886
|
+
},
|
|
1887
|
+
tableName,
|
|
1888
|
+
modelName,
|
|
1889
|
+
namespaceId
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
function emptyResult() {
|
|
1893
|
+
return new AsyncIterableResult((async function* () {})());
|
|
1894
|
+
}
|
|
1895
|
+
function buildIdentityInFilter(contract, namespaceId, tableName, identityColumns, identityRows) {
|
|
1896
|
+
const [singleColumn, ...rest] = identityColumns;
|
|
1897
|
+
if (singleColumn !== void 0 && rest.length === 0) {
|
|
1898
|
+
const values = identityRows.map((row) => row[singleColumn]).filter((value) => value !== void 0);
|
|
1899
|
+
if (values.length === 0) return;
|
|
1900
|
+
return bindWhereExpr(contract, BinaryExpr.in(ColumnRef.of(tableName, singleColumn), ListExpression.fromValues(values)), namespaceId);
|
|
1901
|
+
}
|
|
1902
|
+
if (identityRows.length === 0) return;
|
|
1903
|
+
const tuples = identityRows.map((row) => AndExpr.of(identityColumns.map((column) => BinaryExpr.eq(ColumnRef.of(tableName, column), LiteralExpr.of(row[column])))));
|
|
1904
|
+
return bindWhereExpr(contract, OrExpr.of(tuples), namespaceId);
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Decode a single-query include payload from a parent row's raw cell
|
|
1908
|
+
* into the model-shaped value that downstream consumers see. Recurses
|
|
1909
|
+
* through `include.nested.includes` so depth-2+ trees — emitted by the
|
|
1910
|
+
* recursive correlated-subquery builder — are decoded symmetrically.
|
|
1911
|
+
*
|
|
1912
|
+
* The shape produced by the SQL side is one JSON column per top-level
|
|
1913
|
+
* include; values nested inside that JSON are already-parsed JS values
|
|
1914
|
+
* after the outer `JSON.parse`, so `parseIncludedRows` recognises both
|
|
1915
|
+
* the string (top-level) and array (nested) forms.
|
|
1916
|
+
*
|
|
1917
|
+
* Scalar leaves arrive wrapped in a `{ value: <primitive> }` JSON
|
|
1918
|
+
* envelope (see `buildIncludeChildScalarSelect`); the branch below
|
|
1919
|
+
* unwraps that envelope and passes the value straight through. The
|
|
1920
|
+
* empty-relation default is driven by SQL semantics, not the decoder:
|
|
1921
|
+
* `COUNT(*)` over an empty input set is `0`; `SUM` / `AVG` / `MIN` /
|
|
1922
|
+
* `MAX` over an empty input set are SQL `NULL`, which surfaces as
|
|
1923
|
+
* `null` in TS — the documented contract for those reducers.
|
|
1924
|
+
*
|
|
1925
|
+
* Combine descriptors arrive as a JSON object keyed by branch name;
|
|
1926
|
+
* each branch is dispatched to the row or scalar decoder per its
|
|
1927
|
+
* declared shape (see `decodeCombineIncludePayload`).
|
|
1928
|
+
*/
|
|
1929
|
+
async function decodeIncludePayload(contract, context, include, raw) {
|
|
1930
|
+
if (include.scalar) return Promise.resolve(decodeScalarIncludePayload(include, include.scalar, raw));
|
|
1931
|
+
if (include.combine) return decodeCombineIncludePayload(contract, context, include, include.combine, raw);
|
|
1932
|
+
const rawChildren = parseIncludedRows(include, raw);
|
|
1933
|
+
const polyInfo = resolvePolymorphismInfo(contract, include.relatedNamespaceId, include.relatedModelName);
|
|
1934
|
+
const mapChildRow = polyInfo ? (childRow) => mapPolymorphicRow(contract, include.relatedNamespaceId, include.relatedModelName, polyInfo, childRow, include.nested.variantName) : (childRow) => mapStorageRowToModelFields(contract, include.relatedNamespaceId, include.relatedModelName, childRow);
|
|
1935
|
+
const mappedChildren = [];
|
|
1936
|
+
for (const childRow of rawChildren) {
|
|
1937
|
+
const decodedChildRow = await decodeIncludedStorageRow(contract, context, include, childRow);
|
|
1938
|
+
const mapped = mapChildRow(decodedChildRow);
|
|
1939
|
+
for (const nestedInclude of include.nested.includes) mapped[nestedInclude.relationName] = await decodeIncludePayload(contract, context, nestedInclude, decodedChildRow[nestedInclude.relationName]);
|
|
1940
|
+
mappedChildren.push(mapped);
|
|
1941
|
+
}
|
|
1942
|
+
return coerceSingleQueryIncludeResult(mappedChildren, include.cardinality);
|
|
1943
|
+
}
|
|
1944
|
+
async function decodeIncludedStorageRow(contract, context, include, row) {
|
|
1945
|
+
const decoded = {};
|
|
1946
|
+
for (const [key, value] of Object.entries(row)) {
|
|
1947
|
+
if (value === null || value === void 0) {
|
|
1948
|
+
decoded[key] = value;
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
const ref = resolveIncludedColumnRef(contract, include, key);
|
|
1952
|
+
if (!ref) {
|
|
1953
|
+
decoded[key] = value;
|
|
1954
|
+
continue;
|
|
1955
|
+
}
|
|
1956
|
+
const codec = context.contractCodecs.forColumn(include.relatedNamespaceId, ref.table, ref.column);
|
|
1957
|
+
if (!codec) {
|
|
1958
|
+
decoded[key] = value;
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
decoded[key] = await decodeIncludedColumnValue(ref, context.codecDescriptors.codecRefForColumn(include.relatedNamespaceId, ref.table, ref.column)?.codecId ?? ref.storageColumn.codecId, codec, value);
|
|
1962
|
+
}
|
|
1963
|
+
return decoded;
|
|
1964
|
+
}
|
|
1965
|
+
function resolveIncludedColumnRef(contract, include, key) {
|
|
1966
|
+
const baseColumn = resolveStorageColumn(contract, include.relatedNamespaceId, include.relatedTableName, key);
|
|
1967
|
+
if (baseColumn) return {
|
|
1968
|
+
table: include.relatedTableName,
|
|
1969
|
+
column: key,
|
|
1970
|
+
storageColumn: baseColumn
|
|
1971
|
+
};
|
|
1972
|
+
const polyInfo = resolvePolymorphismInfo(contract, include.relatedNamespaceId, include.relatedModelName);
|
|
1973
|
+
if (!polyInfo) return;
|
|
1974
|
+
for (const variant of polyInfo.mtiVariants) {
|
|
1975
|
+
const prefix = `${variant.table}__`;
|
|
1976
|
+
if (!key.startsWith(prefix)) continue;
|
|
1977
|
+
const column = key.slice(prefix.length);
|
|
1978
|
+
const variantColumn = resolveStorageColumn(contract, include.relatedNamespaceId, variant.table, column);
|
|
1979
|
+
if (variantColumn) return {
|
|
1980
|
+
table: variant.table,
|
|
1981
|
+
column,
|
|
1982
|
+
storageColumn: variantColumn
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
function resolveStorageColumn(contract, namespaceId, tableName, columnName) {
|
|
1987
|
+
return contract.storage.namespaces[namespaceId]?.entries.table?.[tableName]?.columns[columnName];
|
|
1988
|
+
}
|
|
1989
|
+
async function decodeIncludedColumnValue(ref, codecId, codec, value) {
|
|
1990
|
+
if (ref.storageColumn.many === true) {
|
|
1991
|
+
if (!Array.isArray(value)) wrapIncludedDecodeFailure(/* @__PURE__ */ new TypeError(`expected an array from the driver for many-typed column, got ${typeof value}`), ref, codecId);
|
|
1992
|
+
const decoded = [];
|
|
1993
|
+
for (const element of value) {
|
|
1994
|
+
if (element === null || element === void 0) {
|
|
1995
|
+
decoded.push(null);
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
decoded.push(decodeIncludedJsonValue(ref, codecId, codec, element));
|
|
1999
|
+
}
|
|
2000
|
+
return decoded;
|
|
2001
|
+
}
|
|
2002
|
+
return decodeIncludedJsonValue(ref, codecId, codec, value);
|
|
2003
|
+
}
|
|
2004
|
+
function decodeIncludedJsonValue(ref, codecId, codec, value) {
|
|
2005
|
+
try {
|
|
2006
|
+
return codec.decodeJson(blindCast(value));
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
if (isRuntimeError(error)) throw error;
|
|
2009
|
+
wrapIncludedDecodeFailure(error, ref, codecId);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
function wrapIncludedDecodeFailure(error, ref, codecId) {
|
|
2013
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2014
|
+
const wrapped = runtimeError("RUNTIME.DECODE_FAILED", `Failed to decode column ${ref.table}.${ref.column} with codec '${codecId}': ${message}`, {
|
|
2015
|
+
table: ref.table,
|
|
2016
|
+
column: ref.column,
|
|
2017
|
+
codec: codecId
|
|
2018
|
+
});
|
|
2019
|
+
wrapped.cause = error;
|
|
2020
|
+
throw wrapped;
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Decode the combine payload produced by `buildIncludeChildCombineSelect`.
|
|
2024
|
+
*
|
|
2025
|
+
* The raw value is a JSON object (already parsed by the SQL layer when
|
|
2026
|
+
* top-level, or already a JS object when nested) whose keys are branch
|
|
2027
|
+
* names. Each branch value is decoded per its declared kind:
|
|
2028
|
+
* - row branch -> recurse via `decodeIncludePayload` with a synthetic
|
|
2029
|
+
* IncludeExpr carrying the branch's state in `nested`. This walks
|
|
2030
|
+
* nested row-level includes the same way a plain row include would.
|
|
2031
|
+
* - scalar branch -> unwrap the `{value: ...}` envelope via the
|
|
2032
|
+
* standalone scalar decoder.
|
|
2033
|
+
*
|
|
2034
|
+
* On a parent with zero matching child rows the correlated subquery
|
|
2035
|
+
* still produces one row (aggregates collapse the empty input to a
|
|
2036
|
+
* single row), so the combine envelope here is always present in the
|
|
2037
|
+
* read path. The
|
|
2038
|
+
* mutation read-back's `assignEmptyMutationIncludes` writes the empty
|
|
2039
|
+
* per-branch shape directly to `parent.mapped[relationName]` for any
|
|
2040
|
+
* parent absent from the read-back result and never enters the decoder,
|
|
2041
|
+
* so a missing or non-object envelope here is always a planner/decoder
|
|
2042
|
+
* bug — `parseCombineEnvelope` throws loudly rather than papering over
|
|
2043
|
+
* it with an empty shape.
|
|
2044
|
+
*/
|
|
2045
|
+
async function decodeCombineIncludePayload(contract, context, include, branches, raw) {
|
|
2046
|
+
const parsed = parseCombineEnvelope(include, raw);
|
|
2047
|
+
const result = {};
|
|
2048
|
+
for (const [branchName, branch] of Object.entries(branches)) {
|
|
2049
|
+
const branchRaw = parsed[branchName];
|
|
2050
|
+
if (branch.kind === "rows") result[branchName] = await decodeIncludePayload(contract, context, {
|
|
2051
|
+
...include,
|
|
2052
|
+
nested: branch.state,
|
|
2053
|
+
scalar: void 0,
|
|
2054
|
+
combine: void 0
|
|
2055
|
+
}, branchRaw);
|
|
2056
|
+
else result[branchName] = decodeScalarIncludePayload(include, branch.selector, branchRaw);
|
|
2057
|
+
}
|
|
2058
|
+
return result;
|
|
2059
|
+
}
|
|
2060
|
+
function parseCombineEnvelope(include, raw) {
|
|
2061
|
+
if (raw === null || raw === void 0) throw new InternalError(`combine() envelope for include "${include.relationName}" is missing (got ${raw === null ? "null" : "undefined"}); the correlated subquery should always produce a JSON object — this indicates a planner or decoder bug.`);
|
|
2062
|
+
const parsed = parseIncludePayload(raw);
|
|
2063
|
+
if (!isPlainObjectEnvelope(parsed)) throw new InternalError(`combine() envelope for include "${include.relationName}" has unexpected shape (expected object, got ${describeEnvelopeShape(parsed)}); this indicates a planner or decoder bug.`);
|
|
2064
|
+
return parsed;
|
|
2065
|
+
}
|
|
2066
|
+
function isPlainObjectEnvelope(value) {
|
|
2067
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2068
|
+
}
|
|
2069
|
+
function describeEnvelopeShape(value) {
|
|
2070
|
+
if (value === null) return "null";
|
|
2071
|
+
if (Array.isArray(value)) return "array";
|
|
2072
|
+
return typeof value;
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Pull the primitive scalar value out of the JSON envelope emitted by
|
|
2076
|
+
* the correlated scalar builder.
|
|
2077
|
+
*
|
|
2078
|
+
* Contract: the envelope is always either
|
|
2079
|
+
* - a `{ value: <primitive> }` JSON object (the SQL path), or
|
|
2080
|
+
* - `null` / `undefined` (the mutation read-back's
|
|
2081
|
+
* `assignEmptyMutationIncludes` short-circuit before this decoder
|
|
2082
|
+
* runs, for a parent absent from the read-back result).
|
|
2083
|
+
*
|
|
2084
|
+
* Any other shape — array, primitive, string that JSON-parses to
|
|
2085
|
+
* non-object — indicates a planner / decoder bug, so we throw
|
|
2086
|
+
* loudly naming the include relation rather than soft-handling.
|
|
2087
|
+
* Mirrors `parseCombineEnvelope`'s strict shape gate.
|
|
2088
|
+
*
|
|
2089
|
+
* Values are passed through unchanged — no JS-side `Number()` coercion
|
|
2090
|
+
* and no JS-side empty-relation defaulting. SQL semantics drive the
|
|
2091
|
+
* empty-relation case: `COUNT(*)` over an empty input set is `0`;
|
|
2092
|
+
* `SUM` / `AVG` / `MIN` / `MAX` over an empty input set return SQL
|
|
2093
|
+
* `NULL`, which surfaces as `null` here. The outer `raw === null`
|
|
2094
|
+
* fallback is defensive cover for an empty parent set; in single-query
|
|
2095
|
+
* dispatch the correlated subquery always produces a row, so the inner
|
|
2096
|
+
* envelope's `value` is always set by SQL.
|
|
2097
|
+
*/
|
|
2098
|
+
function decodeScalarIncludePayload(include, scalar, raw) {
|
|
2099
|
+
if (raw === null || raw === void 0) return emptyScalarResult(scalar.fn);
|
|
2100
|
+
const parsed = parseIncludePayload(raw);
|
|
2101
|
+
if (!isPlainObjectEnvelope(parsed)) throw new InternalError(`scalar() envelope for include "${include.relationName}" has unexpected shape (expected object, got ${describeEnvelopeShape(parsed)}); this indicates a planner or decoder bug.`);
|
|
2102
|
+
return parsed["value"];
|
|
2103
|
+
}
|
|
2104
|
+
function parseIncludedRows(include, value) {
|
|
2105
|
+
if (value === null || value === void 0) return [];
|
|
2106
|
+
const parsed = parseIncludePayload(value);
|
|
2107
|
+
if (!Array.isArray(parsed)) return [];
|
|
2108
|
+
const rows = [];
|
|
2109
|
+
for (const item of parsed) {
|
|
2110
|
+
if (!isPlainObjectEnvelope(item)) throw new InternalError(`Include row envelope for relation "${include.relationName}" has unexpected shape (expected object, got ${describeEnvelopeShape(item)}); this indicates a planner or decoder bug.`);
|
|
2111
|
+
rows.push({ ...item });
|
|
2112
|
+
}
|
|
2113
|
+
return rows;
|
|
2114
|
+
}
|
|
2115
|
+
function parseIncludePayload(value) {
|
|
2116
|
+
if (typeof value !== "string") return value;
|
|
2117
|
+
try {
|
|
2118
|
+
return JSON.parse(value);
|
|
2119
|
+
} catch {
|
|
2120
|
+
return [];
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
function coerceSingleQueryIncludeResult(rows, cardinality) {
|
|
2124
|
+
return isToOneCardinality(cardinality) ? rows[0] ?? null : rows;
|
|
2125
|
+
}
|
|
2126
|
+
function emptyScalarResult(fn) {
|
|
2127
|
+
return fn === "count" ? 0 : null;
|
|
2128
|
+
}
|
|
2129
|
+
function createMutationRowMapper(contract, namespaceId, modelName, variantName) {
|
|
2130
|
+
const polyInfo = resolvePolymorphismInfo(contract, namespaceId, modelName);
|
|
2131
|
+
return polyInfo ? (row) => mapPolymorphicRow(contract, namespaceId, modelName, polyInfo, row, variantName) : (row) => mapStorageRowToModelFields(contract, namespaceId, modelName, row);
|
|
2132
|
+
}
|
|
2133
|
+
function dispatchMutationRows(options) {
|
|
2134
|
+
const { context, runtime, compiled, tableName, modelName, namespaceId, variantName, includes, selectedFields, hiddenColumns, mapRow } = options;
|
|
2135
|
+
const { contract } = context;
|
|
2136
|
+
const mapStorageRow = createMutationRowMapper(contract, namespaceId, modelName, variantName);
|
|
2137
|
+
if (includes.length === 0) return mapResultRows(executeQueryPlan(runtime, compiled), (rawRow) => {
|
|
2138
|
+
const mapped = mapStorageRow(rawRow);
|
|
2139
|
+
if (hiddenColumns.length > 0) stripHiddenMappedFields(contract, namespaceId, modelName, mapped, hiddenColumns);
|
|
2140
|
+
return mapRow(mapped);
|
|
2141
|
+
});
|
|
2142
|
+
const generator = async function* () {
|
|
2143
|
+
const identityRows = await executeQueryPlan(runtime, compiled).toArray();
|
|
2144
|
+
yield* reloadMutationRowsByIdentities({
|
|
2145
|
+
context,
|
|
2146
|
+
runtime,
|
|
2147
|
+
tableName,
|
|
2148
|
+
modelName,
|
|
2149
|
+
namespaceId,
|
|
2150
|
+
identityRows,
|
|
2151
|
+
selectedFields,
|
|
2152
|
+
includes
|
|
2153
|
+
});
|
|
2154
|
+
};
|
|
2155
|
+
return new AsyncIterableResult(generator());
|
|
2156
|
+
}
|
|
2157
|
+
function dispatchSplitMutationRows(options) {
|
|
2158
|
+
const { context, runtime, plans, tableName, modelName, namespaceId, variantName, includes, selectedFields, hiddenColumns, mapRow } = options;
|
|
2159
|
+
const { contract } = context;
|
|
2160
|
+
const mapStorageRow = createMutationRowMapper(contract, namespaceId, modelName, variantName);
|
|
2161
|
+
const generator = async function* () {
|
|
2162
|
+
if (includes.length > 0) {
|
|
2163
|
+
const identityRows = [];
|
|
2164
|
+
for (const plan of plans) identityRows.push(...await executeQueryPlan(runtime, plan).toArray());
|
|
2165
|
+
yield* reloadMutationRowsByIdentities({
|
|
2166
|
+
context,
|
|
2167
|
+
runtime,
|
|
2168
|
+
tableName,
|
|
2169
|
+
modelName,
|
|
2170
|
+
namespaceId,
|
|
2171
|
+
identityRows,
|
|
2172
|
+
selectedFields,
|
|
2173
|
+
includes
|
|
2174
|
+
});
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
for (const plan of plans) for await (const rawRow of executeQueryPlan(runtime, plan)) {
|
|
2178
|
+
const mapped = mapStorageRow(rawRow);
|
|
2179
|
+
if (hiddenColumns.length > 0) stripHiddenMappedFields(contract, namespaceId, modelName, mapped, hiddenColumns);
|
|
2180
|
+
yield mapRow(mapped);
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
return new AsyncIterableResult(generator());
|
|
2184
|
+
}
|
|
2185
|
+
async function executeMutationReturningSingleRow(options) {
|
|
2186
|
+
const { context, runtime, compiled, tableName, modelName, namespaceId, variantName, includes, selectedFields, hiddenColumns, mapRow, operation, onMissingRowMessage } = options;
|
|
2187
|
+
const { contract } = context;
|
|
2188
|
+
const mapStorageRow = createMutationRowMapper(contract, namespaceId, modelName, variantName);
|
|
2189
|
+
if (includes.length === 0) {
|
|
2190
|
+
const first = (await executeQueryPlan(runtime, compiled).toArray())[0];
|
|
2191
|
+
if (!first) return null;
|
|
2192
|
+
const mapped = mapStorageRow(first);
|
|
2193
|
+
if (hiddenColumns.length > 0) stripHiddenMappedFields(contract, namespaceId, modelName, mapped, hiddenColumns);
|
|
2194
|
+
return mapRow(mapped);
|
|
2195
|
+
}
|
|
2196
|
+
const identityRows = await executeQueryPlan(runtime, compiled).toArray();
|
|
2197
|
+
if (identityRows.length === 0) return null;
|
|
2198
|
+
for await (const row of reloadMutationRowsByIdentities({
|
|
2199
|
+
context,
|
|
2200
|
+
runtime,
|
|
2201
|
+
tableName,
|
|
2202
|
+
modelName,
|
|
2203
|
+
namespaceId,
|
|
2204
|
+
identityRows,
|
|
2205
|
+
selectedFields,
|
|
2206
|
+
includes
|
|
2207
|
+
})) return row;
|
|
2208
|
+
throw ormError("ORM.MUTATION_ROW_MISSING", onMissingRowMessage, { meta: {
|
|
2209
|
+
operation,
|
|
2210
|
+
model: modelName,
|
|
2211
|
+
tableName
|
|
2212
|
+
} });
|
|
2213
|
+
}
|
|
2214
|
+
function and(...exprs) {
|
|
2215
|
+
return AndExpr.of(exprs);
|
|
2216
|
+
}
|
|
2217
|
+
function or(...exprs) {
|
|
2218
|
+
return OrExpr.of(exprs);
|
|
2219
|
+
}
|
|
2220
|
+
function not(expr) {
|
|
2221
|
+
return expr.not();
|
|
2222
|
+
}
|
|
2223
|
+
function all() {
|
|
2224
|
+
return AndExpr.true();
|
|
2225
|
+
}
|
|
2226
|
+
function shorthandToWhereExpr(context, namespaceId, modelName, filters) {
|
|
2227
|
+
const contract = context.contract;
|
|
2228
|
+
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
2229
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
2230
|
+
const exprs = [];
|
|
2231
|
+
for (const [fieldName, value] of Object.entries(filters)) {
|
|
2232
|
+
if (value === void 0) continue;
|
|
2233
|
+
const columnName = fieldToColumn[fieldName] ?? fieldName;
|
|
2234
|
+
const left = ColumnRef.of(tableName, columnName);
|
|
2235
|
+
if (value === null) {
|
|
2236
|
+
exprs.push(NullCheckExpr.isNull(left));
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
2239
|
+
assertFieldHasEqualityTrait(context, namespaceId, modelName, fieldName);
|
|
2240
|
+
exprs.push(BinaryExpr.eq(left, LiteralExpr.of(value)));
|
|
2241
|
+
}
|
|
2242
|
+
if (exprs.length === 0) return;
|
|
2243
|
+
return exprs.length === 1 ? exprs[0] : and(...exprs);
|
|
2244
|
+
}
|
|
2245
|
+
function assertFieldHasEqualityTrait(context, namespaceId, modelName, fieldName) {
|
|
2246
|
+
const fieldType = modelOf(context.contract, namespaceId, modelName)?.fields?.[fieldName]?.type;
|
|
2247
|
+
const codecId = fieldType?.kind === "scalar" ? fieldType.codecId : void 0;
|
|
2248
|
+
if (!(codecId ? context.codecDescriptors.descriptorFor(codecId)?.traits ?? [] : []).includes("equality")) throw ormError("ORM.FILTER_UNSUPPORTED", `Shorthand filter on "${modelName}.${fieldName}": field does not support equality comparisons`, { meta: {
|
|
2249
|
+
model: modelName,
|
|
2250
|
+
field: fieldName,
|
|
2251
|
+
trait: "equality"
|
|
2252
|
+
} });
|
|
2253
|
+
}
|
|
2254
|
+
var GroupedCollection = class GroupedCollection {
|
|
2255
|
+
ctx;
|
|
2256
|
+
contract;
|
|
2257
|
+
modelName;
|
|
2258
|
+
tableName;
|
|
2259
|
+
namespaceId;
|
|
2260
|
+
baseFilters;
|
|
2261
|
+
groupByFields;
|
|
2262
|
+
groupByColumns;
|
|
2263
|
+
havingFilters;
|
|
2264
|
+
constructor(ctx, modelName, options) {
|
|
2265
|
+
this.ctx = ctx;
|
|
2266
|
+
this.contract = ctx.context.contract;
|
|
2267
|
+
this.modelName = modelName;
|
|
2268
|
+
this.tableName = options.tableName;
|
|
2269
|
+
this.namespaceId = options.namespaceId;
|
|
2270
|
+
this.baseFilters = options.baseFilters;
|
|
2271
|
+
this.groupByFields = options.groupByFields;
|
|
2272
|
+
this.groupByColumns = options.groupByColumns;
|
|
2273
|
+
this.havingFilters = options.havingFilters;
|
|
2274
|
+
}
|
|
2275
|
+
having(predicate) {
|
|
2276
|
+
const havingExpr = predicate(createHavingBuilder(this.contract, this.namespaceId, this.modelName, this.tableName));
|
|
2277
|
+
return new GroupedCollection(this.ctx, this.modelName, {
|
|
2278
|
+
tableName: this.tableName,
|
|
2279
|
+
namespaceId: this.namespaceId,
|
|
2280
|
+
baseFilters: this.baseFilters,
|
|
2281
|
+
groupByFields: this.groupByFields,
|
|
2282
|
+
groupByColumns: this.groupByColumns,
|
|
2283
|
+
havingFilters: [...this.havingFilters, havingExpr]
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Read terminal: run a grouped aggregate query.
|
|
2288
|
+
*
|
|
2289
|
+
* Accepts an optional `configure` callback that receives a
|
|
2290
|
+
* `MetaBuilder<'read'>` for attaching typed annotations.
|
|
2291
|
+
* Annotations are merged into the compiled plan's `meta.annotations`.
|
|
2292
|
+
*/
|
|
2293
|
+
async aggregate(fn, configure) {
|
|
2294
|
+
const aggregateSpec = fn(createAggregateBuilder(this.contract, this.namespaceId, this.modelName));
|
|
2295
|
+
const aggregateEntries = Object.entries(aggregateSpec);
|
|
2296
|
+
if (aggregateEntries.length === 0) throw ormError("ORM.AGGREGATE_SELECTOR_MISSING", "groupBy().aggregate() requires at least one aggregation selector", { meta: {
|
|
2297
|
+
method: "groupBy.aggregate",
|
|
2298
|
+
model: this.modelName
|
|
2299
|
+
} });
|
|
2300
|
+
for (const [alias, selector] of aggregateEntries) if (!isAggregateSelector(selector)) throw ormError("ORM.AGGREGATE_SELECTOR_INVALID", `groupBy().aggregate() selector "${alias}" is invalid`, { meta: {
|
|
2301
|
+
method: "groupBy.aggregate",
|
|
2302
|
+
model: this.modelName,
|
|
2303
|
+
alias
|
|
2304
|
+
} });
|
|
2305
|
+
let annotationsMap;
|
|
2306
|
+
if (configure !== void 0) {
|
|
2307
|
+
const meta = createMetaBuilder("read", "groupBy.aggregate");
|
|
2308
|
+
configure(meta);
|
|
2309
|
+
if (meta.annotations.size > 0) annotationsMap = meta.annotations;
|
|
2310
|
+
}
|
|
2311
|
+
const compiled = mergeAnnotations(compileGroupedAggregate(this.contract, this.namespaceId, this.tableName, this.baseFilters, this.groupByColumns, aggregateSpec, combineWhereExprs(this.havingFilters)), annotationsMap);
|
|
2312
|
+
return (await executeQueryPlan(this.ctx.runtime, compiled).toArray()).map((row) => {
|
|
2313
|
+
const mapped = mapStorageRowToModelFields(this.contract, this.namespaceId, this.modelName, row);
|
|
2314
|
+
for (const [alias, selector] of aggregateEntries) mapped[alias] = coerceAggregateValue(selector.fn, row[alias]);
|
|
2315
|
+
return mapped;
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
};
|
|
2319
|
+
function createHavingBuilder(contract, namespaceId, modelName, tableName) {
|
|
2320
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
2321
|
+
const createMetricExpr = (fn, fieldName) => new AggregateExpr(fn, ColumnRef.of(tableName, fieldToColumn[fieldName] ?? fieldName));
|
|
2322
|
+
return {
|
|
2323
|
+
count() {
|
|
2324
|
+
return createHavingComparisonMethods(AggregateExpr.count());
|
|
2325
|
+
},
|
|
2326
|
+
sum(field) {
|
|
2327
|
+
return createHavingComparisonMethods(createMetricExpr("sum", field));
|
|
2328
|
+
},
|
|
2329
|
+
avg(field) {
|
|
2330
|
+
return createHavingComparisonMethods(createMetricExpr("avg", field));
|
|
2331
|
+
},
|
|
2332
|
+
min(field) {
|
|
2333
|
+
return createHavingComparisonMethods(createMetricExpr("min", field));
|
|
2334
|
+
},
|
|
2335
|
+
max(field) {
|
|
2336
|
+
return createHavingComparisonMethods(createMetricExpr("max", field));
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
function createHavingComparisonMethods(metric) {
|
|
2341
|
+
const buildBinaryExpr = (op, value) => new BinaryExpr(op, metric, LiteralExpr.of(value));
|
|
2342
|
+
return {
|
|
2343
|
+
eq(value) {
|
|
2344
|
+
return buildBinaryExpr("eq", value);
|
|
2345
|
+
},
|
|
2346
|
+
neq(value) {
|
|
2347
|
+
return buildBinaryExpr("neq", value);
|
|
2348
|
+
},
|
|
2349
|
+
gt(value) {
|
|
2350
|
+
return buildBinaryExpr("gt", value);
|
|
2351
|
+
},
|
|
2352
|
+
lt(value) {
|
|
2353
|
+
return buildBinaryExpr("lt", value);
|
|
2354
|
+
},
|
|
2355
|
+
gte(value) {
|
|
2356
|
+
return buildBinaryExpr("gte", value);
|
|
2357
|
+
},
|
|
2358
|
+
lte(value) {
|
|
2359
|
+
return buildBinaryExpr("lte", value);
|
|
2360
|
+
}
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
function coerceAggregateValue(fn, value) {
|
|
2364
|
+
if (value === null) return null;
|
|
2365
|
+
if (value === void 0) return fn === "count" ? 0 : null;
|
|
2366
|
+
if (typeof value === "number") return value;
|
|
2367
|
+
if (typeof value === "bigint") return Number(value);
|
|
2368
|
+
if (typeof value === "string") {
|
|
2369
|
+
const numeric = Number(value);
|
|
2370
|
+
return Number.isNaN(numeric) ? value : numeric;
|
|
2371
|
+
}
|
|
2372
|
+
return value;
|
|
2373
|
+
}
|
|
2374
|
+
const aggregateFns = /* @__PURE__ */ new Set([
|
|
2375
|
+
"count",
|
|
2376
|
+
"sum",
|
|
2377
|
+
"avg",
|
|
2378
|
+
"min",
|
|
2379
|
+
"max"
|
|
2380
|
+
]);
|
|
2381
|
+
function createIncludeScalar(fn, state, column) {
|
|
2382
|
+
return {
|
|
2383
|
+
kind: "includeScalar",
|
|
2384
|
+
fn,
|
|
2385
|
+
state,
|
|
2386
|
+
...column !== void 0 ? { column } : {}
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
function createIncludeCombine(branches) {
|
|
2390
|
+
return {
|
|
2391
|
+
kind: "includeCombine",
|
|
2392
|
+
branches
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
function isIncludeScalar(value) {
|
|
2396
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2397
|
+
const candidate = value;
|
|
2398
|
+
return candidate.kind === "includeScalar" && typeof candidate.fn === "string" && aggregateFns.has(candidate.fn) && isCollectionState(candidate.state);
|
|
2399
|
+
}
|
|
2400
|
+
function isIncludeCombine(value) {
|
|
2401
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2402
|
+
const candidate = value;
|
|
2403
|
+
if (candidate.kind !== "includeCombine") return false;
|
|
2404
|
+
if (typeof candidate.branches !== "object" || candidate.branches === null) return false;
|
|
2405
|
+
return true;
|
|
2406
|
+
}
|
|
2407
|
+
function isCollectionStateCarrier(value) {
|
|
2408
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2409
|
+
return isCollectionState(value.state);
|
|
2410
|
+
}
|
|
2411
|
+
function isCollectionState(value) {
|
|
2412
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2413
|
+
const candidate = value;
|
|
2414
|
+
return Array.isArray(candidate.filters) && Array.isArray(candidate.includes);
|
|
2415
|
+
}
|
|
2416
|
+
function hasThrough$1(relation) {
|
|
2417
|
+
return relation.through !== void 0;
|
|
2418
|
+
}
|
|
2419
|
+
function createModelAccessor(context, namespaceId, modelName, variantName) {
|
|
2420
|
+
const contract = context.contract;
|
|
2421
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
2422
|
+
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
2423
|
+
const modelRelations = resolveModelRelations(contract, namespaceId, modelName);
|
|
2424
|
+
const variantFieldColumns = variantName ? resolveVariantFieldColumns(contract, namespaceId, modelName, variantName) : {};
|
|
2425
|
+
const variantCoordinates = variantName ? {
|
|
2426
|
+
name: variantName,
|
|
2427
|
+
relations: resolveModelRelations(contract, namespaceId, variantName),
|
|
2428
|
+
tableName: resolvePolymorphismInfo(contract, namespaceId, modelName)?.variants.get(variantName)?.table ?? tableName
|
|
2429
|
+
} : void 0;
|
|
2430
|
+
const opsByCodecId = /* @__PURE__ */ new Map();
|
|
2431
|
+
function registerOp(codecId, op) {
|
|
2432
|
+
let existing = opsByCodecId.get(codecId);
|
|
2433
|
+
if (!existing) {
|
|
2434
|
+
existing = [];
|
|
2435
|
+
opsByCodecId.set(codecId, existing);
|
|
2436
|
+
}
|
|
2437
|
+
existing.push(op);
|
|
2438
|
+
}
|
|
2439
|
+
for (const [name, entry] of Object.entries(context.queryOperations.entries())) {
|
|
2440
|
+
const op = [name, entry];
|
|
2441
|
+
const self = entry.self;
|
|
2442
|
+
if (!self) continue;
|
|
2443
|
+
if (self.codecId !== void 0) registerOp(self.codecId, op);
|
|
2444
|
+
else if (self.traits !== void 0) for (const descriptor of context.codecDescriptors.values()) {
|
|
2445
|
+
const descriptorTraits = descriptor.traits;
|
|
2446
|
+
if (self.traits.every((t) => descriptorTraits.includes(t))) registerOp(descriptor.codecId, op);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
return blindCast(new Proxy({}, { get(_target, prop) {
|
|
2450
|
+
if (typeof prop !== "string") return;
|
|
2451
|
+
if (variantCoordinates) {
|
|
2452
|
+
const variantRelation = variantCoordinates.relations[prop];
|
|
2453
|
+
if (variantRelation) return createRelationFilterAccessor(context, namespaceId, variantCoordinates.name, variantCoordinates.tableName, prop, variantRelation);
|
|
2454
|
+
}
|
|
2455
|
+
const relation = modelRelations[prop];
|
|
2456
|
+
if (relation) return createRelationFilterAccessor(context, namespaceId, modelName, tableName, prop, relation);
|
|
2457
|
+
const variantField = variantFieldColumns[prop];
|
|
2458
|
+
const resolvedTable = variantField?.table ?? tableName;
|
|
2459
|
+
const columnName = variantField?.column ?? fieldToColumn[prop] ?? prop;
|
|
2460
|
+
const column = resolveColumn(contract, namespaceId, resolvedTable, columnName);
|
|
2461
|
+
if (!column) return;
|
|
2462
|
+
const traits = context.codecDescriptors.descriptorFor(column.codecId)?.traits ?? [];
|
|
2463
|
+
const operations = opsByCodecId.get(column.codecId) ?? [];
|
|
2464
|
+
const codec = codecRefForStorageColumn(contract.storage, namespaceId, resolvedTable, columnName);
|
|
2465
|
+
return createScalarFieldAccessor(resolvedTable, columnName, column.codecId, column.nullable, codec, traits, operations, context);
|
|
2466
|
+
} }));
|
|
2467
|
+
}
|
|
2468
|
+
function resolveColumn(contract, namespaceId, tableName, columnName) {
|
|
2469
|
+
let table;
|
|
2470
|
+
try {
|
|
2471
|
+
table = storageTableForContract(contract, namespaceId, tableName);
|
|
2472
|
+
} catch {
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
const column = table.columns[columnName];
|
|
2476
|
+
if (!column) return void 0;
|
|
2477
|
+
return {
|
|
2478
|
+
codecId: column.codecId,
|
|
2479
|
+
nullable: column.nullable
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
function createScalarFieldAccessor(tableName, columnName, codecId, nullable, codec, traits, operations, context) {
|
|
2483
|
+
const column = ColumnRef.of(tableName, columnName);
|
|
2484
|
+
const comparisonEntries = [];
|
|
2485
|
+
for (const [name, meta] of Object.entries(COMPARISON_METHODS_META)) {
|
|
2486
|
+
if (meta.traits.some((t) => !traits.includes(t))) continue;
|
|
2487
|
+
comparisonEntries.push([name, meta.create(column, codec)]);
|
|
2488
|
+
}
|
|
2489
|
+
const accessor = blindCast({
|
|
2490
|
+
returnType: {
|
|
2491
|
+
codecId,
|
|
2492
|
+
nullable,
|
|
2493
|
+
codec
|
|
2494
|
+
},
|
|
2495
|
+
codec,
|
|
2496
|
+
buildAst: () => column,
|
|
2497
|
+
...Object.fromEntries(comparisonEntries)
|
|
2498
|
+
});
|
|
2499
|
+
for (const [name, entry] of operations) accessor[name] = createExtensionMethodFactory(accessor, entry, context);
|
|
2500
|
+
return blindCast(accessor);
|
|
2501
|
+
}
|
|
2502
|
+
function createExtensionMethodFactory(selfExpr, entry, context) {
|
|
2503
|
+
return (...args) => {
|
|
2504
|
+
const result = blindCast(entry.impl)(selfExpr, ...args);
|
|
2505
|
+
const returnCodecId = result.returnType.codecId;
|
|
2506
|
+
const returnTraits = context.codecDescriptors.descriptorFor(returnCodecId)?.traits ?? [];
|
|
2507
|
+
if (returnTraits.includes("boolean")) return result.buildAst();
|
|
2508
|
+
const resultAst = result.buildAst();
|
|
2509
|
+
const returnCodec = { codecId: returnCodecId };
|
|
2510
|
+
const methods = {};
|
|
2511
|
+
for (const [resultMethodName, meta] of Object.entries(COMPARISON_METHODS_META)) {
|
|
2512
|
+
if (meta.traits.some((t) => !returnTraits.includes(t))) continue;
|
|
2513
|
+
methods[resultMethodName] = meta.create(resultAst, returnCodec);
|
|
2514
|
+
}
|
|
2515
|
+
return methods;
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
function createRelationFilterAccessor(context, parentNamespaceId, parentModelName, parentTableName, relationName, relation) {
|
|
2519
|
+
const relatedTableName = resolveModelTableName(context.contract, relation.toNamespace, relation.to);
|
|
2520
|
+
return {
|
|
2521
|
+
some: (predicate) => buildExistsExpr(context, parentNamespaceId, parentModelName, parentTableName, relatedTableName, relationName, relation, {
|
|
2522
|
+
mode: "some",
|
|
2523
|
+
predicate
|
|
2524
|
+
}),
|
|
2525
|
+
every: (predicate) => buildExistsExpr(context, parentNamespaceId, parentModelName, parentTableName, relatedTableName, relationName, relation, {
|
|
2526
|
+
mode: "every",
|
|
2527
|
+
predicate
|
|
2528
|
+
}),
|
|
2529
|
+
none: (predicate) => buildExistsExpr(context, parentNamespaceId, parentModelName, parentTableName, relatedTableName, relationName, relation, {
|
|
2530
|
+
mode: "none",
|
|
2531
|
+
predicate
|
|
2532
|
+
})
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
function buildExistsExpr(context, parentNamespaceId, parentModelName, parentTableName, relatedTableName, relationName, relation, options) {
|
|
2536
|
+
if (hasThrough$1(relation)) return buildManyToManyExistsExpr(context, parentNamespaceId, parentModelName, parentTableName, relatedTableName, relationName, relation, options);
|
|
2537
|
+
const filterPlan = planRelationFilterMode(buildJoinWhere(context.contract, parentNamespaceId, parentModelName, parentTableName, relatedTableName, relation), toRelationWhereExpr(context, relation.toNamespace, relation.to, options.predicate), options.mode);
|
|
2538
|
+
if (filterPlan.kind === "constantTrue") return AndExpr.true();
|
|
2539
|
+
const selectProjectionColumn = firstTargetColumn(context.contract, relation) ?? "id";
|
|
2540
|
+
const subquery = SelectAst.from(tableSourceForContract(context.contract, relation.toNamespace, relatedTableName)).withProjection([ProjectionItem.of("_exists", ColumnRef.of(relatedTableName, selectProjectionColumn))]).withWhere(filterPlan.where);
|
|
2541
|
+
return filterPlan.notExists ? ExistsExpr.notExists(subquery) : ExistsExpr.exists(subquery);
|
|
2542
|
+
}
|
|
2543
|
+
function buildManyToManyExistsExpr(context, parentNamespaceId, parentModelName, parentTableName, relatedTableName, relationName, relation, options) {
|
|
2544
|
+
const { through } = relation;
|
|
2545
|
+
const junctionTable = through.table;
|
|
2546
|
+
const relatedTableAlias = parentNamespaceId === relation.toNamespace && parentTableName === relatedTableName ? `${relationName}__child` : void 0;
|
|
2547
|
+
const relatedTableRef = relatedTableAlias ?? relatedTableName;
|
|
2548
|
+
const junctionJoinOn = buildPairedColumnExprs(junctionTable, through.childColumns, relatedTableRef, through.targetColumns);
|
|
2549
|
+
const parentLocalColumns = relation.on.localFields.map((field) => resolveFieldToColumn(context.contract, parentNamespaceId, parentModelName, field));
|
|
2550
|
+
const filterPlan = planRelationFilterMode(buildPairedColumnExprs(junctionTable, through.parentColumns, parentTableName, parentLocalColumns), remapColumnRefs(relatedTableName, relatedTableRef, toRelationWhereExpr(context, relation.toNamespace, relation.to, options.predicate)), options.mode);
|
|
2551
|
+
if (filterPlan.kind === "constantTrue") return AndExpr.true();
|
|
2552
|
+
const firstTargetCol = firstJoinColumn(through.targetColumns, "targetColumns");
|
|
2553
|
+
const subquery = SelectAst.from(tableSourceForContract(context.contract, relation.toNamespace, relatedTableName, relatedTableAlias)).withJoins([JoinAst.inner(TableSource.named(junctionTable, void 0, through.namespaceId), junctionJoinOn)]).withProjection([ProjectionItem.of("_exists", ColumnRef.of(relatedTableRef, firstTargetCol))]).withWhere(filterPlan.where);
|
|
2554
|
+
return filterPlan.notExists ? ExistsExpr.notExists(subquery) : ExistsExpr.exists(subquery);
|
|
2555
|
+
}
|
|
2556
|
+
function planRelationFilterMode(joinWhere, childWhere, mode) {
|
|
2557
|
+
if (mode === "every") {
|
|
2558
|
+
if (!childWhere) return { kind: "constantTrue" };
|
|
2559
|
+
return {
|
|
2560
|
+
kind: "exists",
|
|
2561
|
+
notExists: true,
|
|
2562
|
+
where: and(joinWhere, not(childWhere))
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
if (mode === "none") return {
|
|
2566
|
+
kind: "exists",
|
|
2567
|
+
notExists: true,
|
|
2568
|
+
where: childWhere ? and(joinWhere, childWhere) : joinWhere
|
|
2569
|
+
};
|
|
2570
|
+
return {
|
|
2571
|
+
kind: "exists",
|
|
2572
|
+
notExists: false,
|
|
2573
|
+
where: childWhere ? and(joinWhere, childWhere) : joinWhere
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
function remapColumnRefs(tableName, tableRef, expr) {
|
|
2577
|
+
if (!expr || tableName === tableRef) return expr;
|
|
2578
|
+
return expr.rewrite({ columnRef: (column) => column.table === tableName ? ColumnRef.of(tableRef, column.column) : column });
|
|
2579
|
+
}
|
|
2580
|
+
function firstJoinColumn(columns, label) {
|
|
2581
|
+
const first = columns[0];
|
|
2582
|
+
if (!first) throw new InternalError(`Relation metadata is missing ${label}`);
|
|
2583
|
+
return first;
|
|
2584
|
+
}
|
|
2585
|
+
function buildPairedColumnExprs(leftTable, leftColumns, rightTable, rightColumns) {
|
|
2586
|
+
if (leftColumns.length !== rightColumns.length) throw new InternalError(`Relation metadata has mismatched join column counts: ${leftColumns.length} left column(s), ${rightColumns.length} right column(s)`);
|
|
2587
|
+
if (leftColumns.length === 0) throw new InternalError("Relation metadata is missing join columns");
|
|
2588
|
+
const exprs = [];
|
|
2589
|
+
for (let i = 0; i < leftColumns.length; i++) {
|
|
2590
|
+
const left = leftColumns[i];
|
|
2591
|
+
const right = rightColumns[i];
|
|
2592
|
+
if (!left || !right) throw new InternalError(`Relation metadata is missing a join column pair at index ${i}`);
|
|
2593
|
+
exprs.push(BinaryExpr.eq(ColumnRef.of(leftTable, left), ColumnRef.of(rightTable, right)));
|
|
2594
|
+
}
|
|
2595
|
+
if (exprs.length === 1 && exprs[0]) return exprs[0];
|
|
2596
|
+
return and(...exprs);
|
|
2597
|
+
}
|
|
2598
|
+
function toRelationWhereExpr(context, relatedNamespaceId, relatedModelName, predicate) {
|
|
2599
|
+
if (!predicate) return;
|
|
2600
|
+
const accessor = createModelAccessor(context, relatedNamespaceId, relatedModelName);
|
|
2601
|
+
if (typeof predicate === "function") return predicate(accessor);
|
|
2602
|
+
const exprs = [];
|
|
2603
|
+
for (const [fieldName, value] of Object.entries(predicate)) {
|
|
2604
|
+
if (value === void 0) continue;
|
|
2605
|
+
const fieldAccessor = blindCast(accessor)[fieldName];
|
|
2606
|
+
if (!fieldAccessor) throw ormError("ORM.FIELD_UNKNOWN", `Shorthand filter on "${relatedModelName}.${fieldName}": field is not defined on the model`, { meta: {
|
|
2607
|
+
model: relatedModelName,
|
|
2608
|
+
field: fieldName
|
|
2609
|
+
} });
|
|
2610
|
+
if (value === null) {
|
|
2611
|
+
if (!fieldAccessor.isNull) throw new InternalError(`Shorthand filter on "${relatedModelName}.${fieldName}": isNull is unexpectedly missing — this is a bug in trait gating`);
|
|
2612
|
+
exprs.push(fieldAccessor.isNull());
|
|
2613
|
+
continue;
|
|
2614
|
+
}
|
|
2615
|
+
if (!fieldAccessor.eq) throw ormError("ORM.FILTER_UNSUPPORTED", `Shorthand filter on "${relatedModelName}.${fieldName}": field does not support equality comparisons`, { meta: {
|
|
2616
|
+
model: relatedModelName,
|
|
2617
|
+
field: fieldName,
|
|
2618
|
+
trait: "equality"
|
|
2619
|
+
} });
|
|
2620
|
+
exprs.push(fieldAccessor.eq(value));
|
|
2621
|
+
}
|
|
2622
|
+
if (exprs.length === 0) return;
|
|
2623
|
+
return exprs.length === 1 ? exprs[0] : and(...exprs);
|
|
2624
|
+
}
|
|
2625
|
+
function buildJoinWhere(contract, parentNamespaceId, parentModelName, parentTableName, relatedTableName, relation) {
|
|
2626
|
+
const localFields = relation.on?.localFields ?? [];
|
|
2627
|
+
const targetFields = relation.on?.targetFields ?? [];
|
|
2628
|
+
const joinExprs = [];
|
|
2629
|
+
const count = Math.min(localFields.length, targetFields.length);
|
|
2630
|
+
for (let i = 0; i < count; i++) {
|
|
2631
|
+
const localField = localFields[i];
|
|
2632
|
+
const targetField = targetFields[i];
|
|
2633
|
+
if (!localField || !targetField) continue;
|
|
2634
|
+
const localColumn = resolveFieldToColumn(contract, parentNamespaceId, parentModelName, localField);
|
|
2635
|
+
const targetColumn = resolveFieldToColumn(contract, relation.toNamespace, relation.to, targetField);
|
|
2636
|
+
joinExprs.push(BinaryExpr.eq(ColumnRef.of(relatedTableName, targetColumn), ColumnRef.of(parentTableName, localColumn)));
|
|
2637
|
+
}
|
|
2638
|
+
if (joinExprs.length === 0) throw new InternalError("Relation metadata is missing join columns");
|
|
2639
|
+
const firstExpr = joinExprs[0];
|
|
2640
|
+
if (joinExprs.length === 1 && firstExpr !== void 0) return firstExpr;
|
|
2641
|
+
return and(...joinExprs);
|
|
2642
|
+
}
|
|
2643
|
+
function firstTargetColumn(contract, relation) {
|
|
2644
|
+
const firstField = (relation.on?.targetFields)?.[0];
|
|
2645
|
+
if (!firstField) return;
|
|
2646
|
+
return resolveFieldToColumn(contract, relation.toNamespace, relation.to, firstField);
|
|
2647
|
+
}
|
|
2648
|
+
function createRelationMutator() {
|
|
2649
|
+
return {
|
|
2650
|
+
create(data) {
|
|
2651
|
+
return {
|
|
2652
|
+
kind: "create",
|
|
2653
|
+
data: Array.isArray(data) ? [...data] : [data]
|
|
2654
|
+
};
|
|
2655
|
+
},
|
|
2656
|
+
connect(criteria) {
|
|
2657
|
+
return {
|
|
2658
|
+
kind: "connect",
|
|
2659
|
+
criteria: Array.isArray(criteria) ? [...criteria] : [criteria]
|
|
2660
|
+
};
|
|
2661
|
+
},
|
|
2662
|
+
disconnect(criteria) {
|
|
2663
|
+
if (!criteria) return { kind: "disconnect" };
|
|
2664
|
+
return {
|
|
2665
|
+
kind: "disconnect",
|
|
2666
|
+
criteria: [...criteria]
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
function isRelationMutationDescriptor(value) {
|
|
2672
|
+
if (!value || typeof value !== "object") return false;
|
|
2673
|
+
const candidate = value;
|
|
2674
|
+
if (candidate.kind !== "create" && candidate.kind !== "connect" && candidate.kind !== "disconnect") return false;
|
|
2675
|
+
return true;
|
|
2676
|
+
}
|
|
2677
|
+
function isRelationMutationCallback(value) {
|
|
2678
|
+
return typeof value === "function";
|
|
2679
|
+
}
|
|
2680
|
+
function hasThrough(relation) {
|
|
2681
|
+
return relation.through !== void 0;
|
|
2682
|
+
}
|
|
2683
|
+
function hasNestedMutationCallbacks(contract, namespaceId, modelName, data) {
|
|
2684
|
+
const relationNames = new Set(Object.keys(resolveModelRelations(contract, namespaceId, modelName)));
|
|
2685
|
+
for (const [fieldName, value] of Object.entries(data)) {
|
|
2686
|
+
if (!relationNames.has(fieldName)) continue;
|
|
2687
|
+
if (isRelationMutationCallback(value)) return true;
|
|
2688
|
+
}
|
|
2689
|
+
return false;
|
|
2690
|
+
}
|
|
2691
|
+
async function executeNestedCreateMutation(options) {
|
|
2692
|
+
return withMutationScope(options.runtime, async (scope) => createGraph(scope, options.context, options.namespaceId, options.modelName, options.data));
|
|
2693
|
+
}
|
|
2694
|
+
async function executeNestedUpdateMutation(options) {
|
|
2695
|
+
return withMutationScope(options.runtime, async (scope) => updateFirstGraph(scope, options.context, options.namespaceId, options.modelName, options.filters, options.data));
|
|
2696
|
+
}
|
|
2697
|
+
function buildPrimaryKeyFilterFromRow(contract, namespaceId, modelName, row) {
|
|
2698
|
+
const fieldName = toFieldName(contract, namespaceId, modelName, resolvePrimaryKeyColumn(contract, namespaceId, resolveModelTableName(contract, namespaceId, modelName)));
|
|
2699
|
+
const value = row[fieldName];
|
|
2700
|
+
if (value === void 0) throw new InternalError(`Missing primary key field "${fieldName}" while reloading model "${modelName}"`);
|
|
2701
|
+
return { [fieldName]: value };
|
|
2702
|
+
}
|
|
2703
|
+
async function withMutationScope(runtime, run) {
|
|
2704
|
+
if (typeof runtime.transaction === "function") return runInTransaction(await runtime.transaction(), run);
|
|
2705
|
+
if (typeof runtime.connection === "function") {
|
|
2706
|
+
const connection = await runtime.connection();
|
|
2707
|
+
try {
|
|
2708
|
+
if (typeof connection.transaction === "function") return await runInTransaction(await connection.transaction(), run);
|
|
2709
|
+
return await run(connection);
|
|
2710
|
+
} finally {
|
|
2711
|
+
await connection.release?.();
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
return run(runtime);
|
|
2715
|
+
}
|
|
2716
|
+
async function runInTransaction(transaction, run) {
|
|
2717
|
+
try {
|
|
2718
|
+
const result = await run(transaction);
|
|
2719
|
+
if (typeof transaction.commit === "function") await transaction.commit();
|
|
2720
|
+
return result;
|
|
2721
|
+
} catch (error) {
|
|
2722
|
+
if (typeof transaction.rollback === "function") await transaction.rollback();
|
|
2723
|
+
throw error;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
async function createGraph(scope, context, namespaceId, modelName, input) {
|
|
2727
|
+
const contract = context.contract;
|
|
2728
|
+
const parsed = parseMutationInput(contract, namespaceId, modelName, input);
|
|
2729
|
+
const { parentOwned, childOwned, junctionOwned } = partitionByOwnership(parsed.relationMutations);
|
|
2730
|
+
const scalarData = { ...parsed.scalarData };
|
|
2731
|
+
for (const relationMutation of parentOwned) {
|
|
2732
|
+
if (relationMutation.mutation.kind === "disconnect") throw ormError("ORM.RELATION_MUTATION_UNSUPPORTED", "disconnect() is only supported in update() nested mutations", { meta: {
|
|
2733
|
+
kind: "disconnect",
|
|
2734
|
+
relation: relationMutation.relation.relationName
|
|
2735
|
+
} });
|
|
2736
|
+
await applyParentOwnedMutation(scope, context, namespaceId, modelName, scalarData, relationMutation.relation, relationMutation.mutation);
|
|
2737
|
+
}
|
|
2738
|
+
for (const relationMutation of junctionOwned) {
|
|
2739
|
+
if (relationMutation.mutation.kind === "disconnect") throw ormError("ORM.RELATION_MUTATION_UNSUPPORTED", "disconnect() is only supported in update() nested mutations", { meta: {
|
|
2740
|
+
kind: "disconnect",
|
|
2741
|
+
relation: relationMutation.relation.relationName
|
|
2742
|
+
} });
|
|
2743
|
+
await preflightJunctionOwnedCreateMutation(scope, context, relationMutation);
|
|
2744
|
+
}
|
|
2745
|
+
const parentRow = await insertSingleRow(scope, context, namespaceId, modelName, scalarData);
|
|
2746
|
+
for (const relationMutation of childOwned) {
|
|
2747
|
+
if (relationMutation.mutation.kind === "disconnect") throw ormError("ORM.RELATION_MUTATION_UNSUPPORTED", "disconnect() is only supported in update() nested mutations", { meta: {
|
|
2748
|
+
kind: "disconnect",
|
|
2749
|
+
relation: relationMutation.relation.relationName
|
|
2750
|
+
} });
|
|
2751
|
+
await applyChildOwnedMutation(scope, context, namespaceId, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
2752
|
+
}
|
|
2753
|
+
for (const relationMutation of junctionOwned) await applyJunctionOwnedMutation(scope, context, namespaceId, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
2754
|
+
return parentRow;
|
|
2755
|
+
}
|
|
2756
|
+
async function updateFirstGraph(scope, context, namespaceId, modelName, filters, input) {
|
|
2757
|
+
const contract = context.contract;
|
|
2758
|
+
const existingRow = await findFirstByFilters(scope, contract, namespaceId, modelName, filters);
|
|
2759
|
+
if (!existingRow) return null;
|
|
2760
|
+
const parsed = parseMutationInput(contract, namespaceId, modelName, input);
|
|
2761
|
+
const { parentOwned, childOwned, junctionOwned } = partitionByOwnership(parsed.relationMutations);
|
|
2762
|
+
const scalarData = { ...parsed.scalarData };
|
|
2763
|
+
for (const relationMutation of parentOwned) await applyParentOwnedMutation(scope, context, namespaceId, modelName, scalarData, relationMutation.relation, relationMutation.mutation);
|
|
2764
|
+
for (const relationMutation of junctionOwned) await preflightJunctionOwnedCreateMutation(scope, context, relationMutation);
|
|
2765
|
+
let parentRow = existingRow;
|
|
2766
|
+
const mappedUpdateData = mapModelDataToStorageRow(contract, namespaceId, modelName, scalarData);
|
|
2767
|
+
if (Object.keys(mappedUpdateData).length > 0) {
|
|
2768
|
+
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
2769
|
+
const appliedUpdateDefaults = context.applyMutationDefaults({
|
|
2770
|
+
op: "update",
|
|
2771
|
+
table: tableName,
|
|
2772
|
+
namespace: namespaceId,
|
|
2773
|
+
values: mappedUpdateData
|
|
2774
|
+
});
|
|
2775
|
+
for (const def of appliedUpdateDefaults) mappedUpdateData[def.column] = def.value;
|
|
2776
|
+
const pkWhere = shorthandToWhereExpr(context, namespaceId, modelName, castAs(buildPrimaryKeyFilterFromRow(contract, namespaceId, modelName, existingRow)));
|
|
2777
|
+
if (!pkWhere) throw new InternalError(`Failed to build primary key filter for model "${modelName}"`);
|
|
2778
|
+
const updatedRaw = (await executeQueryPlan(scope, compileUpdateReturning(contract, namespaceId, tableName, mappedUpdateData, [pkWhere], void 0)).toArray())[0];
|
|
2779
|
+
if (updatedRaw) parentRow = mapStorageRowToModelFields(contract, namespaceId, modelName, updatedRaw);
|
|
2780
|
+
}
|
|
2781
|
+
for (const relationMutation of childOwned) await applyChildOwnedMutation(scope, context, namespaceId, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
2782
|
+
for (const relationMutation of junctionOwned) await applyJunctionOwnedMutation(scope, context, namespaceId, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
2783
|
+
return parentRow;
|
|
2784
|
+
}
|
|
2785
|
+
function parseMutationInput(contract, namespaceId, modelName, input) {
|
|
2786
|
+
const scalarData = {};
|
|
2787
|
+
const relationDefinitions = new Map(getRelationDefinitions(contract, namespaceId, modelName).map((relation) => [relation.relationName, relation]));
|
|
2788
|
+
const relationMutations = [];
|
|
2789
|
+
for (const [fieldName, value] of Object.entries(input)) {
|
|
2790
|
+
const relation = relationDefinitions.get(fieldName);
|
|
2791
|
+
if (!relation) {
|
|
2792
|
+
scalarData[fieldName] = value;
|
|
2793
|
+
continue;
|
|
2794
|
+
}
|
|
2795
|
+
if (!isRelationMutationCallback(value)) throw ormError("ORM.RELATION_MUTATION_INVALID", `Relation field "${fieldName}" on model "${modelName}" expects a mutator callback`, { meta: {
|
|
2796
|
+
relation: fieldName,
|
|
2797
|
+
model: modelName,
|
|
2798
|
+
problem: "missing-callback"
|
|
2799
|
+
} });
|
|
2800
|
+
const mutation = value(createRelationMutator());
|
|
2801
|
+
if (!isRelationMutationDescriptor(mutation)) throw ormError("ORM.RELATION_MUTATION_INVALID", `Relation field "${fieldName}" on model "${modelName}" returned an invalid mutation descriptor`, { meta: {
|
|
2802
|
+
relation: fieldName,
|
|
2803
|
+
model: modelName,
|
|
2804
|
+
problem: "invalid-descriptor"
|
|
2805
|
+
} });
|
|
2806
|
+
relationMutations.push({
|
|
2807
|
+
relation,
|
|
2808
|
+
mutation
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2811
|
+
return {
|
|
2812
|
+
scalarData,
|
|
2813
|
+
relationMutations
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
function partitionByOwnership(relationMutations) {
|
|
2817
|
+
const parentOwned = [];
|
|
2818
|
+
const childOwned = [];
|
|
2819
|
+
const junctionOwned = [];
|
|
2820
|
+
for (const relationMutation of relationMutations) {
|
|
2821
|
+
if (hasThrough(relationMutation.relation)) {
|
|
2822
|
+
junctionOwned.push({
|
|
2823
|
+
relation: relationMutation.relation,
|
|
2824
|
+
mutation: relationMutation.mutation
|
|
2825
|
+
});
|
|
2826
|
+
continue;
|
|
2827
|
+
}
|
|
2828
|
+
if (relationMutation.relation.cardinality === "N:1") {
|
|
2829
|
+
parentOwned.push(relationMutation);
|
|
2830
|
+
continue;
|
|
2831
|
+
}
|
|
2832
|
+
childOwned.push(relationMutation);
|
|
2833
|
+
}
|
|
2834
|
+
return {
|
|
2835
|
+
parentOwned,
|
|
2836
|
+
childOwned,
|
|
2837
|
+
junctionOwned
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
async function applyParentOwnedMutation(scope, context, parentNamespaceId, parentModelName, scalarData, relation, mutation) {
|
|
2841
|
+
const contract = context.contract;
|
|
2842
|
+
if (mutation.kind === "disconnect") {
|
|
2843
|
+
for (const localColumn of relation.localColumns) {
|
|
2844
|
+
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, localColumn);
|
|
2845
|
+
scalarData[parentFieldName] = null;
|
|
2846
|
+
}
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
if (mutation.kind === "create") {
|
|
2850
|
+
const row = mutation.data[0];
|
|
2851
|
+
if (!row) throw ormError("ORM.RELATION_MUTATION_INVALID", `create() nested mutation for relation "${relation.relationName}" requires data`, { meta: {
|
|
2852
|
+
kind: "create",
|
|
2853
|
+
relation: relation.relationName,
|
|
2854
|
+
problem: "missing-data"
|
|
2855
|
+
} });
|
|
2856
|
+
copyRelatedValuesToParent(contract, parentNamespaceId, parentModelName, relation, scalarData, await createGraph(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(row)));
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
const criterion = mutation.criteria[0];
|
|
2860
|
+
if (!criterion) throw ormError("ORM.RELATION_MUTATION_INVALID", `connect() nested mutation for relation "${relation.relationName}" requires criterion`, { meta: {
|
|
2861
|
+
kind: "connect",
|
|
2862
|
+
relation: relation.relationName,
|
|
2863
|
+
problem: "missing-criterion"
|
|
2864
|
+
} });
|
|
2865
|
+
const relatedRow = await findRowByCriterion(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(criterion));
|
|
2866
|
+
if (!relatedRow) throw ormError("ORM.RELATION_ROW_MISSING", `connect() nested mutation for relation "${relation.relationName}" did not find a matching row`, { meta: {
|
|
2867
|
+
kind: "connect",
|
|
2868
|
+
relation: relation.relationName
|
|
2869
|
+
} });
|
|
2870
|
+
copyRelatedValuesToParent(contract, parentNamespaceId, parentModelName, relation, scalarData, relatedRow);
|
|
2871
|
+
}
|
|
2872
|
+
function copyRelatedValuesToParent(contract, parentNamespaceId, parentModelName, relation, scalarData, relatedRow) {
|
|
2873
|
+
for (let i = 0; i < relation.localColumns.length; i++) {
|
|
2874
|
+
const localColumn = relation.localColumns[i];
|
|
2875
|
+
const targetColumn = relation.targetColumns[i];
|
|
2876
|
+
if (!localColumn || !targetColumn) continue;
|
|
2877
|
+
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, localColumn);
|
|
2878
|
+
scalarData[parentFieldName] = relatedRow[toFieldName(contract, relation.relatedNamespaceId, relation.relatedModelName, targetColumn)];
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
async function applyChildOwnedMutation(scope, context, parentNamespaceId, parentModelName, parentRow, relation, mutation) {
|
|
2882
|
+
const contract = context.contract;
|
|
2883
|
+
const parentValues = readParentColumnValues(contract, parentNamespaceId, parentModelName, relation, parentRow);
|
|
2884
|
+
if (mutation.kind === "create") {
|
|
2885
|
+
for (const childInput of mutation.data) {
|
|
2886
|
+
const payload = { ...castAs(childInput) };
|
|
2887
|
+
for (const [childColumn, parentValue] of parentValues.entries()) {
|
|
2888
|
+
const childFieldName = toFieldName(contract, relation.relatedNamespaceId, relation.relatedModelName, childColumn);
|
|
2889
|
+
payload[childFieldName] = parentValue;
|
|
2890
|
+
}
|
|
2891
|
+
await createGraph(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(payload));
|
|
2892
|
+
}
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
if (mutation.kind === "connect") {
|
|
2896
|
+
for (const criterion of mutation.criteria) {
|
|
2897
|
+
const criterionWhere = shorthandToWhereExpr(context, relation.relatedNamespaceId, relation.relatedModelName, castAs(criterion));
|
|
2898
|
+
if (!criterionWhere) throw ormError("ORM.RELATION_MUTATION_INVALID", `connect() nested mutation for relation "${relation.relationName}" requires non-empty criterion`, { meta: {
|
|
2899
|
+
kind: "connect",
|
|
2900
|
+
relation: relation.relationName,
|
|
2901
|
+
problem: "empty-criterion"
|
|
2902
|
+
} });
|
|
2903
|
+
const setValues = {};
|
|
2904
|
+
for (const [childColumn, parentValue] of parentValues.entries()) setValues[childColumn] = parentValue;
|
|
2905
|
+
await executeUpdateCount(scope, contract, relation.relatedNamespaceId, relation.relatedTableName, setValues, [criterionWhere]);
|
|
2906
|
+
}
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
const setValues = {};
|
|
2910
|
+
for (const childColumn of parentValues.keys()) setValues[childColumn] = null;
|
|
2911
|
+
if (!mutation.criteria || mutation.criteria.length === 0) {
|
|
2912
|
+
const parentJoinWhere = buildChildJoinWhere(relation, parentValues);
|
|
2913
|
+
await executeUpdateCount(scope, contract, relation.relatedNamespaceId, relation.relatedTableName, setValues, [parentJoinWhere]);
|
|
2914
|
+
return;
|
|
2915
|
+
}
|
|
2916
|
+
for (const criterion of mutation.criteria) {
|
|
2917
|
+
const criterionWhere = shorthandToWhereExpr(context, relation.relatedNamespaceId, relation.relatedModelName, castAs(criterion));
|
|
2918
|
+
if (!criterionWhere) throw ormError("ORM.RELATION_MUTATION_INVALID", `disconnect() nested mutation for relation "${relation.relationName}" requires non-empty criterion`, { meta: {
|
|
2919
|
+
kind: "disconnect",
|
|
2920
|
+
relation: relation.relationName,
|
|
2921
|
+
problem: "empty-criterion"
|
|
2922
|
+
} });
|
|
2923
|
+
const parentJoinWhere = buildChildJoinWhere(relation, parentValues);
|
|
2924
|
+
await executeUpdateCount(scope, contract, relation.relatedNamespaceId, relation.relatedTableName, setValues, [and(parentJoinWhere, criterionWhere)]);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
async function applyJunctionOwnedMutation(scope, context, parentNamespaceId, parentModelName, parentRow, relation, mutation) {
|
|
2928
|
+
const contract = context.contract;
|
|
2929
|
+
const parentPkValues = readJunctionParentValues(contract, parentNamespaceId, parentModelName, relation, parentRow);
|
|
2930
|
+
assertJunctionPayloadWritable(relation, mutation.kind);
|
|
2931
|
+
if (mutation.kind === "create") {
|
|
2932
|
+
for (const childInput of mutation.data) await insertJunctionLink(scope, context, relation, parentPkValues, readJunctionTargetValues(contract, relation, await createGraph(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(childInput))), "create");
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
if (mutation.kind === "connect") {
|
|
2936
|
+
for (const criterion of mutation.criteria) await insertJunctionLink(scope, context, relation, parentPkValues, await resolveJunctionTargetValues(scope, context, relation, "connect", criterion), "connect");
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
if (!mutation.criteria || mutation.criteria.length === 0) throw ormError("ORM.RELATION_MUTATION_INVALID", `disconnect() nested mutation for relation "${relation.relationName}" requires criterion`, { meta: {
|
|
2940
|
+
kind: "disconnect",
|
|
2941
|
+
relation: relation.relationName,
|
|
2942
|
+
problem: "missing-criterion"
|
|
2943
|
+
} });
|
|
2944
|
+
for (const criterion of mutation.criteria) await deleteJunctionLink(scope, context, relation, parentPkValues, await resolveJunctionTargetValues(scope, context, relation, "disconnect", criterion));
|
|
2945
|
+
}
|
|
2946
|
+
async function preflightJunctionOwnedCreateMutation(scope, context, relationMutation) {
|
|
2947
|
+
const { relation, mutation } = relationMutation;
|
|
2948
|
+
assertJunctionMetadataShape(relation);
|
|
2949
|
+
assertJunctionPayloadWritable(relation, mutation.kind);
|
|
2950
|
+
if (mutation.kind !== "connect") return;
|
|
2951
|
+
const seenTargetKeys = /* @__PURE__ */ new Set();
|
|
2952
|
+
for (const criterion of mutation.criteria) {
|
|
2953
|
+
const targetValues = await resolveJunctionTargetValues(scope, context, relation, "connect", criterion);
|
|
2954
|
+
const targetKey = JSON.stringify([...targetValues.entries()]);
|
|
2955
|
+
if (seenTargetKeys.has(targetKey)) throw ormError("ORM.RELATION_MUTATION_INVALID", `connect() nested mutation for relation "${relation.relationName}" resolved duplicate junction link targets; remove the duplicate criteria`, { meta: {
|
|
2956
|
+
kind: "connect",
|
|
2957
|
+
relation: relation.relationName,
|
|
2958
|
+
problem: "duplicate-criteria"
|
|
2959
|
+
} });
|
|
2960
|
+
seenTargetKeys.add(targetKey);
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
function assertJunctionPayloadWritable(relation, mutationKind) {
|
|
2964
|
+
const through = relation.through;
|
|
2965
|
+
if (mutationKind !== "create" && mutationKind !== "connect" || through.requiredPayloadColumns.length === 0) return;
|
|
2966
|
+
const cols = through.requiredPayloadColumns.map((c) => `\`${c}\``).join(", ");
|
|
2967
|
+
throw ormError("ORM.RELATION_MUTATION_UNSUPPORTED", `Cannot \`${mutationKind}\` on relation \`${relation.relationName}\`: its junction \`${through.table}\` has required column(s) ${cols} the relation API can't populate. Write the \`${through.table}\` junction directly or use the SQL builder.`, { meta: {
|
|
2968
|
+
kind: mutationKind,
|
|
2969
|
+
relation: relation.relationName,
|
|
2970
|
+
reason: "junction-required-columns",
|
|
2971
|
+
junction: through.table
|
|
2972
|
+
} });
|
|
2973
|
+
}
|
|
2974
|
+
async function resolveJunctionTargetValues(scope, context, relation, kind, criterion) {
|
|
2975
|
+
const relatedRow = await findRowByCriterion(scope, context, relation.relatedNamespaceId, relation.relatedModelName, criterion);
|
|
2976
|
+
if (!relatedRow) throw ormError("ORM.RELATION_ROW_MISSING", `${kind}() nested mutation for relation "${relation.relationName}" did not find a matching row`, { meta: {
|
|
2977
|
+
kind,
|
|
2978
|
+
relation: relation.relationName
|
|
2979
|
+
} });
|
|
2980
|
+
return readJunctionTargetValues(context.contract, relation, relatedRow);
|
|
2981
|
+
}
|
|
2982
|
+
function readJunctionParentValues(contract, parentNamespaceId, parentModelName, relation, parentRow) {
|
|
2983
|
+
const values = /* @__PURE__ */ new Map();
|
|
2984
|
+
assertJunctionParentMetadataLength(relation);
|
|
2985
|
+
for (let i = 0; i < relation.through.parentColumns.length; i++) {
|
|
2986
|
+
const junctionColumn = relation.through.parentColumns[i];
|
|
2987
|
+
const parentColumn = relation.localColumns[i];
|
|
2988
|
+
if (junctionColumn === void 0 || parentColumn === void 0) throw new InternalError(`Relation "${relation.relationName}" has incomplete junction metadata for parent columns`);
|
|
2989
|
+
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, parentColumn);
|
|
2990
|
+
const parentValue = parentRow[parentFieldName];
|
|
2991
|
+
if (parentValue === void 0) throw new InternalError(`Nested mutation requires parent field "${parentFieldName}" to be present in returned row`);
|
|
2992
|
+
values.set(junctionColumn, parentValue);
|
|
2993
|
+
}
|
|
2994
|
+
return values;
|
|
2995
|
+
}
|
|
2996
|
+
function readJunctionTargetValues(contract, relation, relatedRow) {
|
|
2997
|
+
const values = /* @__PURE__ */ new Map();
|
|
2998
|
+
assertJunctionTargetMetadataLength(relation);
|
|
2999
|
+
for (let i = 0; i < relation.through.childColumns.length; i++) {
|
|
3000
|
+
const junctionColumn = relation.through.childColumns[i];
|
|
3001
|
+
const targetColumn = relation.through.targetColumns[i];
|
|
3002
|
+
if (junctionColumn === void 0 || targetColumn === void 0) throw new InternalError(`Relation "${relation.relationName}" has incomplete junction metadata for target columns`);
|
|
3003
|
+
const targetFieldName = toFieldName(contract, relation.relatedNamespaceId, relation.relatedModelName, targetColumn);
|
|
3004
|
+
const targetValue = relatedRow[targetFieldName];
|
|
3005
|
+
if (targetValue === void 0) throw new InternalError(`Nested mutation requires target field "${targetFieldName}" to be present in returned row`);
|
|
3006
|
+
values.set(junctionColumn, targetValue);
|
|
3007
|
+
}
|
|
3008
|
+
return values;
|
|
3009
|
+
}
|
|
3010
|
+
function assertJunctionMetadataLength(relation, throughColumnName, throughColumns, pairedColumnName, pairedColumns) {
|
|
3011
|
+
if (throughColumns.length === pairedColumns.length) return;
|
|
3012
|
+
throw new InternalError(`Relation "${relation.relationName}" has invalid junction metadata: ${throughColumnName} has ${throughColumns.length} column(s), but ${pairedColumnName} has ${pairedColumns.length}`);
|
|
3013
|
+
}
|
|
3014
|
+
function assertJunctionParentMetadataLength(relation) {
|
|
3015
|
+
assertJunctionMetadataLength(relation, "parentColumns", relation.through.parentColumns, "localColumns", relation.localColumns);
|
|
3016
|
+
}
|
|
3017
|
+
function assertJunctionTargetMetadataLength(relation) {
|
|
3018
|
+
assertJunctionMetadataLength(relation, "childColumns", relation.through.childColumns, "targetColumns", relation.through.targetColumns);
|
|
3019
|
+
}
|
|
3020
|
+
function assertJunctionMetadataShape(relation) {
|
|
3021
|
+
assertJunctionParentMetadataLength(relation);
|
|
3022
|
+
assertJunctionTargetMetadataLength(relation);
|
|
3023
|
+
}
|
|
3024
|
+
function writeJunctionColumn(junctionRow, through, column, value, relationName) {
|
|
3025
|
+
if (Object.hasOwn(junctionRow, column) && !Object.is(junctionRow[column], value)) throw ormError("ORM.RELATION_MUTATION_INVALID", `Cannot write junction "${through.table}": conflicting values for junction column "${column}"`, { meta: {
|
|
3026
|
+
relation: relationName,
|
|
3027
|
+
junction: through.table,
|
|
3028
|
+
column
|
|
3029
|
+
} });
|
|
3030
|
+
junctionRow[column] = value;
|
|
3031
|
+
}
|
|
3032
|
+
async function insertJunctionLink(scope, context, relation, parentPkValues, targetPkValues, mutationKind) {
|
|
3033
|
+
const through = relation.through;
|
|
3034
|
+
const junctionRow = {};
|
|
3035
|
+
for (const [column, value] of parentPkValues.entries()) writeJunctionColumn(junctionRow, through, column, value, relation.relationName);
|
|
3036
|
+
for (const [column, value] of targetPkValues.entries()) writeJunctionColumn(junctionRow, through, column, value, relation.relationName);
|
|
3037
|
+
const applied = context.applyMutationDefaults({
|
|
3038
|
+
op: "create",
|
|
3039
|
+
table: through.table,
|
|
3040
|
+
namespace: through.namespaceId,
|
|
3041
|
+
values: junctionRow
|
|
3042
|
+
});
|
|
3043
|
+
for (const def of applied) junctionRow[def.column] = def.value;
|
|
3044
|
+
const compiled = compileInsertCount(context.contract, through.namespaceId, through.table, [junctionRow]);
|
|
3045
|
+
try {
|
|
3046
|
+
await executeQueryPlan(scope, compiled).toArray();
|
|
3047
|
+
} catch (error) {
|
|
3048
|
+
if (mutationKind === "connect" && isUniqueConstraintViolation(error)) throw ormError("ORM.RELATION_LINK_DUPLICATE", `connect() nested mutation for relation "${relation.relationName}" violated a unique constraint on junction "${through.table}"; the junction link may already be present`, {
|
|
3049
|
+
meta: {
|
|
3050
|
+
relation: relation.relationName,
|
|
3051
|
+
junction: through.table
|
|
3052
|
+
},
|
|
3053
|
+
cause: error
|
|
3054
|
+
});
|
|
3055
|
+
throw error;
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
async function deleteJunctionLink(scope, context, relation, parentPkValues, targetPkValues) {
|
|
3059
|
+
const through = relation.through;
|
|
3060
|
+
const junctionRow = {};
|
|
3061
|
+
for (const [column, value] of parentPkValues.entries()) writeJunctionColumn(junctionRow, through, column, value, relation.relationName);
|
|
3062
|
+
for (const [column, value] of targetPkValues.entries()) writeJunctionColumn(junctionRow, through, column, value, relation.relationName);
|
|
3063
|
+
const exprs = [];
|
|
3064
|
+
for (const [column, value] of Object.entries(junctionRow)) exprs.push(BinaryExpr.eq(ColumnRef.of(through.table, column), LiteralExpr.of(value)));
|
|
3065
|
+
const first = exprs[0];
|
|
3066
|
+
const where = exprs.length === 1 && first !== void 0 ? first : and(...exprs);
|
|
3067
|
+
await executeQueryPlan(scope, compileDeleteCount(context.contract, through.namespaceId, through.table, [where])).toArray();
|
|
3068
|
+
}
|
|
3069
|
+
function readParentColumnValues(contract, parentNamespaceId, parentModelName, relation, parentRow) {
|
|
3070
|
+
const values = /* @__PURE__ */ new Map();
|
|
3071
|
+
for (let i = 0; i < relation.localColumns.length; i++) {
|
|
3072
|
+
const localColumn = relation.localColumns[i];
|
|
3073
|
+
const targetColumn = relation.targetColumns[i];
|
|
3074
|
+
if (!localColumn || !targetColumn) continue;
|
|
3075
|
+
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, localColumn);
|
|
3076
|
+
const parentValue = parentRow[parentFieldName];
|
|
3077
|
+
if (parentValue === void 0) throw new InternalError(`Nested mutation requires parent field "${parentFieldName}" to be present in returned row`);
|
|
3078
|
+
values.set(targetColumn, parentValue);
|
|
3079
|
+
}
|
|
3080
|
+
return values;
|
|
3081
|
+
}
|
|
3082
|
+
function buildChildJoinWhere(relation, childValues) {
|
|
3083
|
+
const exprs = [];
|
|
3084
|
+
for (const [childColumn, parentValue] of childValues.entries()) exprs.push(BinaryExpr.eq(ColumnRef.of(relation.relatedTableName, childColumn), LiteralExpr.of(parentValue)));
|
|
3085
|
+
const first = exprs[0];
|
|
3086
|
+
if (exprs.length === 1 && first !== void 0) return first;
|
|
3087
|
+
return and(...exprs);
|
|
3088
|
+
}
|
|
3089
|
+
async function insertSingleRow(scope, context, namespaceId, modelName, data) {
|
|
3090
|
+
const contract = context.contract;
|
|
3091
|
+
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
3092
|
+
const mappedData = mapModelDataToStorageRow(contract, namespaceId, modelName, data);
|
|
3093
|
+
const applied = context.applyMutationDefaults({
|
|
3094
|
+
op: "create",
|
|
3095
|
+
table: tableName,
|
|
3096
|
+
namespace: namespaceId,
|
|
3097
|
+
values: mappedData
|
|
3098
|
+
});
|
|
3099
|
+
for (const def of applied) mappedData[def.column] = def.value;
|
|
3100
|
+
const firstRow = (await executeQueryPlan(scope, compileInsertReturning(contract, namespaceId, tableName, [mappedData], void 0)).toArray())[0];
|
|
3101
|
+
if (!firstRow) throw ormError("ORM.MUTATION_ROW_MISSING", `Nested create for model "${modelName}" did not return a row`, { meta: {
|
|
3102
|
+
operation: "create",
|
|
3103
|
+
model: modelName,
|
|
3104
|
+
phase: "nested"
|
|
3105
|
+
} });
|
|
3106
|
+
return mapStorageRowToModelFields(contract, namespaceId, modelName, firstRow);
|
|
3107
|
+
}
|
|
3108
|
+
async function findRowByCriterion(scope, context, namespaceId, modelName, criterion) {
|
|
3109
|
+
const contract = context.contract;
|
|
3110
|
+
const whereExpr = shorthandToWhereExpr(context, namespaceId, modelName, castAs(criterion));
|
|
3111
|
+
if (!whereExpr) throw ormError("ORM.RELATION_MUTATION_INVALID", `Nested connect for model "${modelName}" requires non-empty criterion`, { meta: {
|
|
3112
|
+
kind: "connect",
|
|
3113
|
+
model: modelName,
|
|
3114
|
+
problem: "empty-criterion"
|
|
3115
|
+
} });
|
|
3116
|
+
const firstRow = (await executeQueryPlan(scope, compileSelect(contract, namespaceId, resolveModelTableName(contract, namespaceId, modelName), {
|
|
3117
|
+
...emptyState(),
|
|
3118
|
+
filters: [whereExpr],
|
|
3119
|
+
limit: 1
|
|
3120
|
+
})).toArray())[0];
|
|
3121
|
+
if (!firstRow) return null;
|
|
3122
|
+
return mapStorageRowToModelFields(contract, namespaceId, modelName, firstRow);
|
|
3123
|
+
}
|
|
3124
|
+
async function findFirstByFilters(scope, contract, namespaceId, modelName, filters) {
|
|
3125
|
+
const firstRow = (await executeQueryPlan(scope, compileSelect(contract, namespaceId, resolveModelTableName(contract, namespaceId, modelName), {
|
|
3126
|
+
...emptyState(),
|
|
3127
|
+
filters,
|
|
3128
|
+
limit: 1
|
|
3129
|
+
})).toArray())[0];
|
|
3130
|
+
if (!firstRow) return null;
|
|
3131
|
+
return mapStorageRowToModelFields(contract, namespaceId, modelName, firstRow);
|
|
3132
|
+
}
|
|
3133
|
+
async function executeUpdateCount(scope, contract, namespaceId, tableName, setValues, filters) {
|
|
3134
|
+
await executeQueryPlan(scope, compileUpdateCount(contract, namespaceId, tableName, setValues, filters)).toArray();
|
|
3135
|
+
}
|
|
3136
|
+
const relationDefsCache = /* @__PURE__ */ new WeakMap();
|
|
3137
|
+
function getRelationDefinitions(contract, namespaceId, modelName) {
|
|
3138
|
+
let perContract = relationDefsCache.get(contract);
|
|
3139
|
+
if (!perContract) {
|
|
3140
|
+
perContract = /* @__PURE__ */ new Map();
|
|
3141
|
+
relationDefsCache.set(contract, perContract);
|
|
3142
|
+
}
|
|
3143
|
+
const cacheKey = `${namespaceId}\u0000${modelName}`;
|
|
3144
|
+
const cached = perContract.get(cacheKey);
|
|
3145
|
+
if (cached) return cached;
|
|
3146
|
+
const relations = resolveModelRelations(contract, namespaceId, modelName);
|
|
3147
|
+
const definitions = Object.entries(relations).map(([relationName, relation]) => ({
|
|
3148
|
+
relationName,
|
|
3149
|
+
relatedModelName: relation.to,
|
|
3150
|
+
relatedNamespaceId: relation.toNamespace,
|
|
3151
|
+
relatedTableName: resolveModelTableName(contract, relation.toNamespace, relation.to),
|
|
3152
|
+
cardinality: relation.cardinality,
|
|
3153
|
+
localColumns: relation.on.localFields.map((f) => resolveFieldToColumn(contract, namespaceId, modelName, f)),
|
|
3154
|
+
targetColumns: relation.on.targetFields.map((f) => resolveFieldToColumn(contract, relation.toNamespace, relation.to, f)),
|
|
3155
|
+
through: relation.through ? {
|
|
3156
|
+
table: relation.through.table,
|
|
3157
|
+
namespaceId: relation.through.namespaceId,
|
|
3158
|
+
parentColumns: relation.through.parentColumns,
|
|
3159
|
+
childColumns: relation.through.childColumns,
|
|
3160
|
+
targetColumns: relation.through.targetColumns,
|
|
3161
|
+
requiredPayloadColumns: relation.through.requiredPayloadColumns
|
|
3162
|
+
} : void 0
|
|
3163
|
+
}));
|
|
3164
|
+
perContract.set(cacheKey, definitions);
|
|
3165
|
+
return definitions;
|
|
3166
|
+
}
|
|
3167
|
+
function toFieldName(contract, namespaceId, modelName, columnName) {
|
|
3168
|
+
return getColumnToFieldMap(contract, namespaceId, modelName)[columnName] ?? columnName;
|
|
3169
|
+
}
|
|
3170
|
+
function normalizeWhereArg(arg, options) {
|
|
3171
|
+
if (arg === void 0) return;
|
|
3172
|
+
if (arg === null) throw ormError("ORM.ARGUMENT_INVALID", "WhereArg cannot be null. Pass undefined or a valid WhereExpr/ToWhereExpr payload.", { meta: { argument: "where" } });
|
|
3173
|
+
if (isToWhereExpr(arg)) return arg.toWhereExpr();
|
|
3174
|
+
if (options?.contract) return bindWhereExpr(options.contract, arg, options.namespaceId);
|
|
3175
|
+
return arg;
|
|
3176
|
+
}
|
|
3177
|
+
function isToWhereExpr(arg) {
|
|
3178
|
+
return typeof arg === "object" && arg !== null && "toWhereExpr" in arg && !isWhereExpr(arg);
|
|
3179
|
+
}
|
|
3180
|
+
function applyCreateDefaults(ctx, namespaceId, tableName, rows) {
|
|
3181
|
+
const defaultValueCache = rows.length > 1 ? /* @__PURE__ */ new Map() : void 0;
|
|
3182
|
+
for (const row of rows) {
|
|
3183
|
+
const applied = ctx.context.applyMutationDefaults({
|
|
3184
|
+
op: "create",
|
|
3185
|
+
table: tableName,
|
|
3186
|
+
namespace: namespaceId,
|
|
3187
|
+
values: row,
|
|
3188
|
+
...defaultValueCache ? { defaultValueCache } : {}
|
|
3189
|
+
});
|
|
3190
|
+
for (const def of applied) row[def.column] = def.value;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
function applyUpdateDefaults(ctx, namespaceId, tableName, values) {
|
|
3194
|
+
const applied = ctx.context.applyMutationDefaults({
|
|
3195
|
+
op: "update",
|
|
3196
|
+
table: tableName,
|
|
3197
|
+
namespace: namespaceId,
|
|
3198
|
+
values
|
|
3199
|
+
});
|
|
3200
|
+
for (const def of applied) values[def.column] = def.value;
|
|
3201
|
+
}
|
|
3202
|
+
function isToWhereExprInput(value) {
|
|
3203
|
+
return typeof value === "object" && value !== null && "toWhereExpr" in value && typeof value.toWhereExpr === "function";
|
|
3204
|
+
}
|
|
3205
|
+
function isWhereDirectInput(value) {
|
|
3206
|
+
return isWhereExpr(value) && typeof value === "object" && value !== null && "accept" in value && typeof value.accept === "function" || isToWhereExprInput(value);
|
|
3207
|
+
}
|
|
3208
|
+
function isMtiVariantInfo(variant) {
|
|
3209
|
+
return variant?.strategy === "mti";
|
|
3210
|
+
}
|
|
3211
|
+
var Collection = class Collection {
|
|
3212
|
+
/** @internal */
|
|
3213
|
+
ctx;
|
|
3214
|
+
/** @internal */
|
|
3215
|
+
contract;
|
|
3216
|
+
/** @internal */
|
|
3217
|
+
modelName;
|
|
3218
|
+
/** @internal */
|
|
3219
|
+
tableName;
|
|
3220
|
+
/** @internal */
|
|
3221
|
+
namespaceId;
|
|
3222
|
+
/** @internal */
|
|
3223
|
+
state;
|
|
3224
|
+
/** @internal */
|
|
3225
|
+
registry;
|
|
3226
|
+
/** @internal */
|
|
3227
|
+
includeRefinementMode;
|
|
3228
|
+
constructor(ctx, modelName, options) {
|
|
3229
|
+
this.ctx = ctx;
|
|
3230
|
+
this.contract = ctx.context.contract;
|
|
3231
|
+
this.modelName = modelName;
|
|
3232
|
+
this.namespaceId = options.namespaceId;
|
|
3233
|
+
this.tableName = options.tableName ?? resolveModelTableName(this.contract, options.namespaceId, modelName);
|
|
3234
|
+
this.state = options.state ?? emptyState();
|
|
3235
|
+
this.registry = options.registry ?? /* @__PURE__ */ new Map();
|
|
3236
|
+
this.includeRefinementMode = options.includeRefinementMode ?? false;
|
|
3237
|
+
}
|
|
3238
|
+
where(input) {
|
|
3239
|
+
const filter = normalizeWhereArg(typeof input === "function" ? input(createModelAccessor(this.ctx.context, this.namespaceId, this.modelName, this.state.variantName)) : isWhereDirectInput(input) ? input : shorthandToWhereExpr(this.ctx.context, this.namespaceId, this.modelName, input), {
|
|
3240
|
+
contract: this.contract,
|
|
3241
|
+
namespaceId: this.namespaceId
|
|
3242
|
+
});
|
|
3243
|
+
if (!filter) return blindCast(this);
|
|
3244
|
+
return this.#clone({ filters: [...this.state.filters, filter] });
|
|
3245
|
+
}
|
|
3246
|
+
/**
|
|
3247
|
+
* Narrow a polymorphic model to a specific variant. The returned
|
|
3248
|
+
* collection has the variant's row shape and a discriminator filter
|
|
3249
|
+
* is automatically applied. Chaining `.variant(...)` again replaces
|
|
3250
|
+
* the previous variant filter.
|
|
3251
|
+
*
|
|
3252
|
+
* ```typescript
|
|
3253
|
+
* // Read only admin users (STI):
|
|
3254
|
+
* const admins = await db.orm.User.variant('Admin').all();
|
|
3255
|
+
*
|
|
3256
|
+
* // Iterate the rows:
|
|
3257
|
+
* for await (const admin of db.orm.User.variant('Admin').all()) {
|
|
3258
|
+
* console.log(admin.role);
|
|
3259
|
+
* }
|
|
3260
|
+
*
|
|
3261
|
+
* // Insert under a variant — discriminator is injected automatically:
|
|
3262
|
+
* await db.orm.User.variant('Admin').create({ name: 'Ada', role: 'super' });
|
|
3263
|
+
* ```
|
|
3264
|
+
*/
|
|
3265
|
+
variant(variantName) {
|
|
3266
|
+
const model = modelOf(this.contract, this.namespaceId, this.modelName);
|
|
3267
|
+
const discriminator = model?.discriminator;
|
|
3268
|
+
const variants = model?.variants;
|
|
3269
|
+
if (!discriminator || !variants) return blindCast(this);
|
|
3270
|
+
const variantEntry = variants[variantName];
|
|
3271
|
+
if (!variantEntry) return blindCast(this);
|
|
3272
|
+
const columnName = resolveFieldToColumn(this.contract, this.namespaceId, this.modelName, discriminator.field);
|
|
3273
|
+
const filter = BinaryExpr.eq(ColumnRef.of(this.tableName, columnName), LiteralExpr.of(variantEntry.value));
|
|
3274
|
+
const filtersWithoutPreviousVariant = this.state.variantName ? this.state.filters.filter((f) => !(f instanceof BinaryExpr && f.left instanceof ColumnRef && f.left.column === columnName && f.left.table === this.tableName)) : this.state.filters;
|
|
3275
|
+
return this.#cloneWithRow({
|
|
3276
|
+
filters: [...filtersWithoutPreviousVariant, filter],
|
|
3277
|
+
variantName
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
/**
|
|
3281
|
+
* Eagerly load a related model. The relation appears on every
|
|
3282
|
+
* returned row under its declared name; to-one relations are mapped
|
|
3283
|
+
* to a single object (or `null`), to-many relations to an array.
|
|
3284
|
+
*
|
|
3285
|
+
* An optional refinement callback receives a child collection that
|
|
3286
|
+
* can be further constrained, projected, ordered, paginated, or
|
|
3287
|
+
* reduced to scalars via `count()`/`sum()`/etc. or to multiple
|
|
3288
|
+
* sub-aggregates via `combine()`.
|
|
3289
|
+
*
|
|
3290
|
+
* ```typescript
|
|
3291
|
+
* // Simple include — every user comes back with its posts array:
|
|
3292
|
+
* const users = await db.orm.User.include('posts').all();
|
|
3293
|
+
*
|
|
3294
|
+
* // Refine the related collection:
|
|
3295
|
+
* const withRecent = await db.orm.User.include('posts', (posts) =>
|
|
3296
|
+
* posts.where({ published: true }).orderBy((p) => p.createdAt.desc()).take(5),
|
|
3297
|
+
* ).all();
|
|
3298
|
+
*
|
|
3299
|
+
* // Reduce a to-many relation to a scalar value:
|
|
3300
|
+
* const withCounts = await db.orm.User.include('posts', (posts) => posts.count()).all();
|
|
3301
|
+
*
|
|
3302
|
+
* // Multiple sub-views via combine():
|
|
3303
|
+
* const overview = await db.orm.User.include('posts', (posts) =>
|
|
3304
|
+
* posts.combine({ recent: posts.take(3), total: posts.count() }),
|
|
3305
|
+
* ).all();
|
|
3306
|
+
* ```
|
|
3307
|
+
*/
|
|
3308
|
+
include(relationName, refineFn) {
|
|
3309
|
+
const relation = resolveIncludeRelation(this.contract, this.namespaceId, this.modelName, relationName, this.state.variantName);
|
|
3310
|
+
let nestedState = emptyState();
|
|
3311
|
+
let scalarSelector;
|
|
3312
|
+
let combineBranches;
|
|
3313
|
+
if (refineFn) {
|
|
3314
|
+
const refined = refineFn(this.#createCollection(blindCast(relation.relatedModelName), {
|
|
3315
|
+
tableName: relation.relatedTableName,
|
|
3316
|
+
namespaceId: relation.relatedNamespaceId,
|
|
3317
|
+
state: emptyState(),
|
|
3318
|
+
includeRefinementMode: true
|
|
3319
|
+
}));
|
|
3320
|
+
if (isIncludeScalar(refined)) {
|
|
3321
|
+
if (isToOneCardinality(relation.cardinality)) throw ormError("ORM.INCLUDE_UNSUPPORTED", `include('${relationName}') scalar aggregations are only supported for to-many relations`, { meta: {
|
|
3322
|
+
relation: relationName,
|
|
3323
|
+
kind: "scalar"
|
|
3324
|
+
} });
|
|
3325
|
+
scalarSelector = refined;
|
|
3326
|
+
nestedState = refined.state;
|
|
3327
|
+
} else if (isIncludeCombine(refined)) {
|
|
3328
|
+
if (isToOneCardinality(relation.cardinality)) throw ormError("ORM.INCLUDE_UNSUPPORTED", `include('${relationName}') combine() is only supported for to-many relations`, { meta: {
|
|
3329
|
+
relation: relationName,
|
|
3330
|
+
kind: "combine"
|
|
3331
|
+
} });
|
|
3332
|
+
combineBranches = refined.branches;
|
|
3333
|
+
} else if (isCollectionStateCarrier(refined)) nestedState = refined.state;
|
|
3334
|
+
else throw ormError("ORM.INCLUDE_INVALID", `include('${relationName}') refinement must return a collection, include scalar selector, or combine() descriptor`, { meta: { relation: relationName } });
|
|
3335
|
+
}
|
|
3336
|
+
const includeExpr = {
|
|
3337
|
+
relationName,
|
|
3338
|
+
relatedModelName: relation.relatedModelName,
|
|
3339
|
+
relatedNamespaceId: relation.relatedNamespaceId,
|
|
3340
|
+
relatedTableName: relation.relatedTableName,
|
|
3341
|
+
localTableName: relation.localTableName,
|
|
3342
|
+
targetColumn: relation.targetColumn,
|
|
3343
|
+
localColumn: relation.localColumn,
|
|
3344
|
+
cardinality: relation.cardinality,
|
|
3345
|
+
...ifDefined("through", relation.through),
|
|
3346
|
+
nested: nestedState,
|
|
3347
|
+
scalar: scalarSelector,
|
|
3348
|
+
combine: combineBranches
|
|
3349
|
+
};
|
|
3350
|
+
return this.#cloneWithRow({ includes: [...this.state.includes, includeExpr] });
|
|
3351
|
+
}
|
|
3352
|
+
/**
|
|
3353
|
+
* Project the row down to a subset of scalar fields. Previously
|
|
3354
|
+
* included relations are preserved on the resulting row shape; only
|
|
3355
|
+
* scalar columns are narrowed.
|
|
3356
|
+
*
|
|
3357
|
+
* ```typescript
|
|
3358
|
+
* const summaries = await db.orm.User.select('id', 'email').all();
|
|
3359
|
+
* // typeof summaries[number] === { id: ...; email: ... }
|
|
3360
|
+
*
|
|
3361
|
+
* for await (const row of db.orm.User.select('id', 'email').all()) {
|
|
3362
|
+
* console.log(row.id, row.email);
|
|
3363
|
+
* }
|
|
3364
|
+
* ```
|
|
3365
|
+
*/
|
|
3366
|
+
select(...fields) {
|
|
3367
|
+
const selectedFields = mapFieldsToColumns(this.contract, this.namespaceId, this.modelName, fields);
|
|
3368
|
+
return this.#cloneWithRow({ selectedFields });
|
|
3369
|
+
}
|
|
3370
|
+
/**
|
|
3371
|
+
* Append an `ORDER BY` clause. Pass a single selector callback or an
|
|
3372
|
+
* array of callbacks; each receives a typed model accessor whose
|
|
3373
|
+
* columns expose `.asc()` and `.desc()`. Multiple calls append to the
|
|
3374
|
+
* existing list (left-to-right ordering preserved).
|
|
3375
|
+
*
|
|
3376
|
+
* Calling `orderBy(...)` unlocks `cursor(...)` and `distinctOn(...)`,
|
|
3377
|
+
* which both require a defined sort order.
|
|
3378
|
+
*
|
|
3379
|
+
* ```typescript
|
|
3380
|
+
* const newest = await db.orm.User.orderBy((u) => u.createdAt.desc()).all();
|
|
3381
|
+
*
|
|
3382
|
+
* const byName = await db.orm.User
|
|
3383
|
+
* .orderBy([(u) => u.lastName.asc(), (u) => u.firstName.asc()])
|
|
3384
|
+
* .all();
|
|
3385
|
+
* ```
|
|
3386
|
+
*/
|
|
3387
|
+
orderBy(selection) {
|
|
3388
|
+
const accessor = createModelAccessor(this.ctx.context, this.namespaceId, this.modelName, this.state.variantName);
|
|
3389
|
+
const nextOrders = (Array.isArray(selection) ? selection : [selection]).map((selector) => selector(accessor));
|
|
3390
|
+
const existing = this.state.orderBy ?? [];
|
|
3391
|
+
return this.#clone({ orderBy: [...existing, ...nextOrders] });
|
|
3392
|
+
}
|
|
3393
|
+
/**
|
|
3394
|
+
* Switch to grouped-aggregate mode. Returns a `GroupedCollection`
|
|
3395
|
+
* whose `.aggregate(...)` terminal produces one row per group with
|
|
3396
|
+
* the chosen key columns plus the requested aggregates.
|
|
3397
|
+
*
|
|
3398
|
+
* ```typescript
|
|
3399
|
+
* const stats = await db.orm.Post
|
|
3400
|
+
* .where({ published: true })
|
|
3401
|
+
* .groupBy('userId')
|
|
3402
|
+
* .aggregate((agg) => ({ count: agg.count(), totalViews: agg.sum('views') }));
|
|
3403
|
+
* // [{ userId: 1, count: 3, totalViews: 120 }, ...]
|
|
3404
|
+
* ```
|
|
3405
|
+
*/
|
|
3406
|
+
groupBy(...fields) {
|
|
3407
|
+
const groupByColumns = mapFieldsToColumns(this.contract, this.namespaceId, this.modelName, fields);
|
|
3408
|
+
return new GroupedCollection(this.ctx, this.modelName, {
|
|
3409
|
+
tableName: this.tableName,
|
|
3410
|
+
namespaceId: this.namespaceId,
|
|
3411
|
+
baseFilters: this.state.filters,
|
|
3412
|
+
groupByFields: [...fields],
|
|
3413
|
+
groupByColumns,
|
|
3414
|
+
havingFilters: []
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
/**
|
|
3418
|
+
* Scalar reducer — reduces a to-many relation to the number of
|
|
3419
|
+
* related rows. Use inside an `include(...)` refinement callback as
|
|
3420
|
+
* `include(..., (rel) => rel.count())`; throws if called elsewhere.
|
|
3421
|
+
* The parent row's relation field becomes that count instead of an
|
|
3422
|
+
* array.
|
|
3423
|
+
*
|
|
3424
|
+
* ```typescript
|
|
3425
|
+
* const users = await db.orm.User.include('posts', (posts) => posts.count()).all();
|
|
3426
|
+
* // each user row: { ...user, posts: number }
|
|
3427
|
+
* ```
|
|
3428
|
+
*/
|
|
3429
|
+
count() {
|
|
3430
|
+
this.#assertIncludeRefinementMode("count()");
|
|
3431
|
+
return createIncludeScalar("count", this.state);
|
|
3432
|
+
}
|
|
3433
|
+
/**
|
|
3434
|
+
* Scalar reducer — reduces a to-many relation to the sum of `field`
|
|
3435
|
+
* across related rows. Returns `null` when there are no related
|
|
3436
|
+
* rows. Use inside an `include(...)` refinement callback; throws if
|
|
3437
|
+
* called elsewhere.
|
|
3438
|
+
*
|
|
3439
|
+
* ```typescript
|
|
3440
|
+
* const users = await db.orm.User.include('posts', (posts) => posts.sum('views')).all();
|
|
3441
|
+
* // each user row: { ...user, posts: number | null }
|
|
3442
|
+
* ```
|
|
3443
|
+
*/
|
|
3444
|
+
sum(field) {
|
|
3445
|
+
this.#assertIncludeRefinementMode("sum()");
|
|
3446
|
+
const columnName = resolveFieldToColumn(this.contract, this.namespaceId, this.modelName, field);
|
|
3447
|
+
return createIncludeScalar("sum", this.state, columnName);
|
|
3448
|
+
}
|
|
3449
|
+
/**
|
|
3450
|
+
* Scalar reducer — reduces a to-many relation to the average of
|
|
3451
|
+
* `field` across related rows. Returns `null` when there are no
|
|
3452
|
+
* related rows. Use inside an `include(...)` refinement callback;
|
|
3453
|
+
* throws if called elsewhere.
|
|
3454
|
+
*
|
|
3455
|
+
* ```typescript
|
|
3456
|
+
* const users = await db.orm.User.include('posts', (posts) => posts.avg('views')).all();
|
|
3457
|
+
* // each user row: { ...user, posts: number | null }
|
|
3458
|
+
* ```
|
|
3459
|
+
*/
|
|
3460
|
+
avg(field) {
|
|
3461
|
+
this.#assertIncludeRefinementMode("avg()");
|
|
3462
|
+
const columnName = resolveFieldToColumn(this.contract, this.namespaceId, this.modelName, field);
|
|
3463
|
+
return createIncludeScalar("avg", this.state, columnName);
|
|
3464
|
+
}
|
|
3465
|
+
/**
|
|
3466
|
+
* Scalar reducer — reduces a to-many relation to the minimum value
|
|
3467
|
+
* of `field` across related rows. Returns `null` when there are no
|
|
3468
|
+
* related rows. Use inside an `include(...)` refinement callback;
|
|
3469
|
+
* throws if called elsewhere.
|
|
3470
|
+
*
|
|
3471
|
+
* ```typescript
|
|
3472
|
+
* const users = await db.orm.User.include('posts', (posts) => posts.min('views')).all();
|
|
3473
|
+
* ```
|
|
3474
|
+
*/
|
|
3475
|
+
min(field) {
|
|
3476
|
+
this.#assertIncludeRefinementMode("min()");
|
|
3477
|
+
const columnName = resolveFieldToColumn(this.contract, this.namespaceId, this.modelName, field);
|
|
3478
|
+
return createIncludeScalar("min", this.state, columnName);
|
|
3479
|
+
}
|
|
3480
|
+
/**
|
|
3481
|
+
* Scalar reducer — reduces a to-many relation to the maximum value
|
|
3482
|
+
* of `field` across related rows. Returns `null` when there are no
|
|
3483
|
+
* related rows. Use inside an `include(...)` refinement callback;
|
|
3484
|
+
* throws if called elsewhere.
|
|
3485
|
+
*
|
|
3486
|
+
* ```typescript
|
|
3487
|
+
* const users = await db.orm.User.include('posts', (posts) => posts.max('views')).all();
|
|
3488
|
+
* ```
|
|
3489
|
+
*/
|
|
3490
|
+
max(field) {
|
|
3491
|
+
this.#assertIncludeRefinementMode("max()");
|
|
3492
|
+
const columnName = resolveFieldToColumn(this.contract, this.namespaceId, this.modelName, field);
|
|
3493
|
+
return createIncludeScalar("max", this.state, columnName);
|
|
3494
|
+
}
|
|
3495
|
+
/**
|
|
3496
|
+
* Produce multiple named sub-views of a to-many relation in a
|
|
3497
|
+
* single `include(...)`. Each branch is either another refined
|
|
3498
|
+
* collection (mapped to a row array on the parent) or a scalar
|
|
3499
|
+
* reducer such as `count()`/`sum(...)`. Only valid inside an
|
|
3500
|
+
* `include(...)` refinement callback for to-many relations.
|
|
3501
|
+
*
|
|
3502
|
+
* ```typescript
|
|
3503
|
+
* const users = await db.orm.User.include('posts', (posts) =>
|
|
3504
|
+
* posts.combine({
|
|
3505
|
+
* recent: posts.where({ published: true }).take(3),
|
|
3506
|
+
* total: posts.count(),
|
|
3507
|
+
* averageViews: posts.avg('views'),
|
|
3508
|
+
* }),
|
|
3509
|
+
* ).all();
|
|
3510
|
+
* // each user row: {
|
|
3511
|
+
* // ...user,
|
|
3512
|
+
* // posts: { recent: Post[]; total: number; averageViews: number | null };
|
|
3513
|
+
* // }
|
|
3514
|
+
* ```
|
|
3515
|
+
*/
|
|
3516
|
+
combine(spec) {
|
|
3517
|
+
this.#assertIncludeRefinementMode("combine()");
|
|
3518
|
+
const branches = {};
|
|
3519
|
+
for (const [name, value] of Object.entries(spec)) {
|
|
3520
|
+
if (isIncludeScalar(value)) {
|
|
3521
|
+
branches[name] = {
|
|
3522
|
+
kind: "scalar",
|
|
3523
|
+
selector: value
|
|
3524
|
+
};
|
|
3525
|
+
continue;
|
|
3526
|
+
}
|
|
3527
|
+
if (isCollectionStateCarrier(value)) {
|
|
3528
|
+
branches[name] = {
|
|
3529
|
+
kind: "rows",
|
|
3530
|
+
state: value.state
|
|
3531
|
+
};
|
|
3532
|
+
continue;
|
|
3533
|
+
}
|
|
3534
|
+
throw ormError("ORM.INCLUDE_INVALID", `include().combine() branch "${name}" is invalid`, { meta: { branch: name } });
|
|
3535
|
+
}
|
|
3536
|
+
return createIncludeCombine(branches);
|
|
3537
|
+
}
|
|
3538
|
+
/**
|
|
3539
|
+
* Resume pagination from a known cursor position. Requires a prior
|
|
3540
|
+
* `orderBy(...)` so the cursor has a stable basis; provide a value
|
|
3541
|
+
* for every column referenced by the active `orderBy(...)` so each
|
|
3542
|
+
* ordered axis has a defined boundary.
|
|
3543
|
+
*
|
|
3544
|
+
* ```typescript
|
|
3545
|
+
* const page1 = await db.orm.Post
|
|
3546
|
+
* .orderBy((p) => p.createdAt.desc())
|
|
3547
|
+
* .take(20)
|
|
3548
|
+
* .all();
|
|
3549
|
+
*
|
|
3550
|
+
* const last = page1[page1.length - 1]!;
|
|
3551
|
+
* const page2 = await db.orm.Post
|
|
3552
|
+
* .orderBy((p) => p.createdAt.desc())
|
|
3553
|
+
* .cursor({ createdAt: last.createdAt })
|
|
3554
|
+
* .take(20)
|
|
3555
|
+
* .all();
|
|
3556
|
+
* ```
|
|
3557
|
+
*/
|
|
3558
|
+
cursor(cursorValues) {
|
|
3559
|
+
const mappedCursor = mapCursorValuesToColumns(this.contract, this.namespaceId, this.modelName, cursorValues);
|
|
3560
|
+
if (Object.keys(mappedCursor).length === 0) return this;
|
|
3561
|
+
return this.#clone({ cursor: mappedCursor });
|
|
3562
|
+
}
|
|
3563
|
+
/**
|
|
3564
|
+
* Emit `SELECT DISTINCT` keyed on the given fields. Replaces any
|
|
3565
|
+
* previous `distinct(...)` / `distinctOn(...)` selection.
|
|
3566
|
+
*
|
|
3567
|
+
* ```typescript
|
|
3568
|
+
* const groups = await db.orm.User.distinct('country', 'role').all();
|
|
3569
|
+
* ```
|
|
3570
|
+
*/
|
|
3571
|
+
distinct(...fields) {
|
|
3572
|
+
const distinctFields = mapFieldsToColumns(this.contract, this.namespaceId, this.modelName, fields);
|
|
3573
|
+
return this.#clone({
|
|
3574
|
+
distinct: distinctFields,
|
|
3575
|
+
distinctOn: void 0
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
/**
|
|
3579
|
+
* Emit `SELECT DISTINCT ON (fields)` — keep the first row per
|
|
3580
|
+
* distinct key according to the current `orderBy(...)`. Requires a
|
|
3581
|
+
* prior `orderBy(...)`; replaces any previous `distinct(...)` /
|
|
3582
|
+
* `distinctOn(...)` selection.
|
|
3583
|
+
*
|
|
3584
|
+
* ```typescript
|
|
3585
|
+
* // Latest post per user:
|
|
3586
|
+
* const latestPerUser = await db.orm.Post
|
|
3587
|
+
* .orderBy([(p) => p.userId.asc(), (p) => p.createdAt.desc()])
|
|
3588
|
+
* .distinctOn('userId')
|
|
3589
|
+
* .all();
|
|
3590
|
+
* ```
|
|
3591
|
+
*/
|
|
3592
|
+
distinctOn(...fields) {
|
|
3593
|
+
const distinctOnFields = mapFieldsToColumns(this.contract, this.namespaceId, this.modelName, fields);
|
|
3594
|
+
return this.#clone({
|
|
3595
|
+
distinct: void 0,
|
|
3596
|
+
distinctOn: distinctOnFields
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Apply `LIMIT n`. Replaces any previous limit set on this collection.
|
|
3601
|
+
*
|
|
3602
|
+
* ```typescript
|
|
3603
|
+
* const firstTen = await db.orm.User.orderBy((u) => u.id.asc()).take(10).all();
|
|
3604
|
+
* ```
|
|
3605
|
+
*/
|
|
3606
|
+
take(n) {
|
|
3607
|
+
return this.#clone({ limit: n });
|
|
3608
|
+
}
|
|
3609
|
+
/**
|
|
3610
|
+
* Apply `OFFSET n`. Replaces any previous offset set on this collection.
|
|
3611
|
+
*
|
|
3612
|
+
* ```typescript
|
|
3613
|
+
* const page2 = await db.orm.User
|
|
3614
|
+
* .orderBy((u) => u.id.asc())
|
|
3615
|
+
* .skip(10)
|
|
3616
|
+
* .take(10)
|
|
3617
|
+
* .all();
|
|
3618
|
+
* ```
|
|
3619
|
+
*/
|
|
3620
|
+
skip(n) {
|
|
3621
|
+
return this.#clone({ offset: n });
|
|
3622
|
+
}
|
|
3623
|
+
/**
|
|
3624
|
+
* Read terminal: execute the query and stream every matching row.
|
|
3625
|
+
*
|
|
3626
|
+
* The returned `AsyncIterableResult<Row>` is BOTH a thenable that
|
|
3627
|
+
* resolves to `Row[]` (so `await` collects all rows into an array)
|
|
3628
|
+
* AND an async iterable (so `for await` streams rows as they
|
|
3629
|
+
* arrive, without buffering the whole result set in memory). Pick
|
|
3630
|
+
* whichever fits the caller. A single result can only be consumed
|
|
3631
|
+
* once.
|
|
3632
|
+
*
|
|
3633
|
+
* Streaming is the default and the expected execution model. The
|
|
3634
|
+
* only scenarios that fall back to buffering internally before
|
|
3635
|
+
* yielding are drivers that cannot expose a cursor to the
|
|
3636
|
+
* underlying database, and — for queries with `include(...)` —
|
|
3637
|
+
* targets whose SQL dialect supports neither lateral joins nor
|
|
3638
|
+
* correlated subqueries (so child rows cannot be stitched in a
|
|
3639
|
+
* single streaming query). These are implementation details below
|
|
3640
|
+
* the public API; the iteration shape itself is genuinely
|
|
3641
|
+
* streaming whenever the driver and plan allow it.
|
|
3642
|
+
*
|
|
3643
|
+
* ```typescript
|
|
3644
|
+
* // Thenable — collect to an array:
|
|
3645
|
+
* const users = await db.orm.User.all();
|
|
3646
|
+
* for (const user of users) console.log(user.id);
|
|
3647
|
+
*
|
|
3648
|
+
* // Async iterable — stream rows as they arrive:
|
|
3649
|
+
* for await (const user of db.orm.User.all()) {
|
|
3650
|
+
* console.log(user.id);
|
|
3651
|
+
* }
|
|
3652
|
+
* ```
|
|
3653
|
+
*
|
|
3654
|
+
* Accepts an optional `configure` callback that receives a
|
|
3655
|
+
* `MetaBuilder<'read'>` so the caller can attach typed user
|
|
3656
|
+
* annotations to the executed plan. `meta.annotate(...)` enforces
|
|
3657
|
+
* applicability at the type level and at runtime; annotations are
|
|
3658
|
+
* merged into `plan.meta.annotations` at compile time.
|
|
3659
|
+
*
|
|
3660
|
+
* ```typescript
|
|
3661
|
+
* await db.orm.User.all((meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
|
|
3662
|
+
* ```
|
|
3663
|
+
*/
|
|
3664
|
+
all(configure) {
|
|
3665
|
+
return this.#withAnnotationsFromMeta(configure, "all").#dispatch();
|
|
3666
|
+
}
|
|
3667
|
+
async first(filter, configure) {
|
|
3668
|
+
return (await (filter === void 0 ? this : typeof filter === "function" ? this.where(filter) : this.where(filter)).take(1).#withAnnotationsFromMeta(configure, "first").#dispatch().toArray())[0] ?? null;
|
|
3669
|
+
}
|
|
3670
|
+
/**
|
|
3671
|
+
* Read terminal: run an aggregate query (count, sum, avg, min, max)
|
|
3672
|
+
* built via the `AggregateBuilder` callback. Returns one object
|
|
3673
|
+
* with the requested aggregate values keyed by the aliases supplied
|
|
3674
|
+
* in the spec.
|
|
3675
|
+
*
|
|
3676
|
+
* ```typescript
|
|
3677
|
+
* const stats = await db.orm.Post
|
|
3678
|
+
* .where({ published: true })
|
|
3679
|
+
* .aggregate((agg) => ({
|
|
3680
|
+
* total: agg.count(),
|
|
3681
|
+
* averageViews: agg.avg('views'),
|
|
3682
|
+
* maxViews: agg.max('views'),
|
|
3683
|
+
* }));
|
|
3684
|
+
* // { total: 42, averageViews: 17.3, maxViews: 9001 }
|
|
3685
|
+
* ```
|
|
3686
|
+
*
|
|
3687
|
+
* Accepts an optional `configure` callback that receives a
|
|
3688
|
+
* `MetaBuilder<'read'>` for attaching typed annotations.
|
|
3689
|
+
* Annotations are merged into the compiled plan's `meta.annotations`.
|
|
3690
|
+
*/
|
|
3691
|
+
async aggregate(fn, configure) {
|
|
3692
|
+
const aggregateSpec = fn(createAggregateBuilder(this.contract, this.namespaceId, this.modelName));
|
|
3693
|
+
const entries = Object.entries(aggregateSpec);
|
|
3694
|
+
if (entries.length === 0) throw ormError("ORM.AGGREGATE_SELECTOR_MISSING", "aggregate() requires at least one aggregation selector", { meta: {
|
|
3695
|
+
method: "aggregate",
|
|
3696
|
+
model: this.modelName
|
|
3697
|
+
} });
|
|
3698
|
+
for (const [alias, selector] of entries) if (!isAggregateSelector(selector)) throw ormError("ORM.AGGREGATE_SELECTOR_INVALID", `aggregate() selector "${alias}" is invalid`, { meta: {
|
|
3699
|
+
method: "aggregate",
|
|
3700
|
+
model: this.modelName,
|
|
3701
|
+
alias
|
|
3702
|
+
} });
|
|
3703
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "read", "aggregate");
|
|
3704
|
+
const compiled = mergeAnnotations(compileAggregate(this.contract, this.namespaceId, this.tableName, this.state.filters, aggregateSpec), annotationsMap);
|
|
3705
|
+
return normalizeAggregateResult(aggregateSpec, (await executeQueryPlan(this.ctx.runtime, compiled).toArray())[0] ?? {});
|
|
3706
|
+
}
|
|
3707
|
+
async create(data, configure) {
|
|
3708
|
+
assertReturningCapability(this.contract, "create()");
|
|
3709
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "create");
|
|
3710
|
+
if (hasNestedMutationCallbacks(this.contract, this.namespaceId, this.modelName, blindCast(data))) {
|
|
3711
|
+
const createdRow = await executeNestedCreateMutation({
|
|
3712
|
+
context: this.ctx.context,
|
|
3713
|
+
runtime: this.ctx.runtime,
|
|
3714
|
+
namespaceId: this.namespaceId,
|
|
3715
|
+
modelName: this.modelName,
|
|
3716
|
+
data: blindCast(data)
|
|
3717
|
+
});
|
|
3718
|
+
const pkCriterion = buildPrimaryKeyFilterFromRow(this.contract, this.namespaceId, this.modelName, createdRow);
|
|
3719
|
+
const reloaded = await this.#reloadMutationRowByPrimaryKey(pkCriterion);
|
|
3720
|
+
if (!reloaded) throw ormError("ORM.MUTATION_ROW_MISSING", `create() for model "${this.modelName}" did not return a row`, { meta: {
|
|
3721
|
+
operation: "create",
|
|
3722
|
+
model: this.modelName
|
|
3723
|
+
} });
|
|
3724
|
+
return reloaded;
|
|
3725
|
+
}
|
|
3726
|
+
const created = (await this.#createAllWithAnnotations([blindCast(data)], annotationsMap))[0];
|
|
3727
|
+
if (created) return created;
|
|
3728
|
+
throw ormError("ORM.MUTATION_ROW_MISSING", `create() for model "${this.modelName}" did not return a row`, { meta: {
|
|
3729
|
+
operation: "create",
|
|
3730
|
+
model: this.modelName
|
|
3731
|
+
} });
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* Write terminal: insert many rows and stream the inserted rows.
|
|
3735
|
+
*
|
|
3736
|
+
* The returned `AsyncIterableResult<Row>` is BOTH a thenable that
|
|
3737
|
+
* resolves to `Row[]` AND an async iterable that streams inserted
|
|
3738
|
+
* rows as they arrive. Use whichever shape fits the caller — but
|
|
3739
|
+
* only consume it once. Streaming is the default; some
|
|
3740
|
+
* driver/plan combinations may still buffer internally before
|
|
3741
|
+
* yielding.
|
|
3742
|
+
*
|
|
3743
|
+
* ```typescript
|
|
3744
|
+
* // Thenable — collect all inserted rows into an array:
|
|
3745
|
+
* const created = await db.orm.User.createAll([
|
|
3746
|
+
* { email: 'a@example.com' },
|
|
3747
|
+
* { email: 'b@example.com' },
|
|
3748
|
+
* ]);
|
|
3749
|
+
*
|
|
3750
|
+
* // Async iterable — stream inserted rows as they arrive:
|
|
3751
|
+
* for await (const row of db.orm.User.createAll(seedUsers)) {
|
|
3752
|
+
* console.log('inserted', row.id);
|
|
3753
|
+
* }
|
|
3754
|
+
* ```
|
|
3755
|
+
*
|
|
3756
|
+
* Accepts an optional `configure` callback that receives a
|
|
3757
|
+
* `MetaBuilder<'write'>` for attaching typed annotations to the
|
|
3758
|
+
* compiled insert plan.
|
|
3759
|
+
*/
|
|
3760
|
+
createAll(data, configure) {
|
|
3761
|
+
return this.#createAllWithAnnotations(data, this.#collectAnnotationsFromMeta(configure, "write", "createAll"));
|
|
3762
|
+
}
|
|
3763
|
+
#createAllWithAnnotations(data, annotationsMap) {
|
|
3764
|
+
if (data.length === 0) {
|
|
3765
|
+
const generator = async function* () {};
|
|
3766
|
+
return new AsyncIterableResult(generator());
|
|
3767
|
+
}
|
|
3768
|
+
assertReturningCapability(this.contract, "createAll()");
|
|
3769
|
+
const rows = blindCast(data);
|
|
3770
|
+
const mtiContext = this.#resolveMtiCreateContext();
|
|
3771
|
+
if (mtiContext) return this.#executeMtiCreate(rows, mtiContext);
|
|
3772
|
+
const mappedRows = this.#mapCreateRows(rows);
|
|
3773
|
+
applyCreateDefaults(this.ctx, this.namespaceId, this.tableName, mappedRows);
|
|
3774
|
+
const { selectedForQuery: selectedForInsert, hiddenColumns } = this.#augmentMutationSelection();
|
|
3775
|
+
if (this.contract.capabilities?.["sql"]?.["defaultInInsert"] !== true) {
|
|
3776
|
+
const plans = compileInsertReturningSplit(this.contract, this.namespaceId, this.tableName, mappedRows, selectedForInsert).map((plan) => mergeAnnotations(plan, annotationsMap));
|
|
3777
|
+
return dispatchSplitMutationRows({
|
|
3778
|
+
context: this.ctx.context,
|
|
3779
|
+
runtime: this.ctx.runtime,
|
|
3780
|
+
plans,
|
|
3781
|
+
tableName: this.tableName,
|
|
3782
|
+
modelName: this.modelName,
|
|
3783
|
+
namespaceId: this.namespaceId,
|
|
3784
|
+
variantName: this.state.variantName,
|
|
3785
|
+
includes: this.state.includes,
|
|
3786
|
+
selectedFields: this.state.selectedFields,
|
|
3787
|
+
hiddenColumns,
|
|
3788
|
+
mapRow: (mapped) => blindCast(mapped)
|
|
3789
|
+
});
|
|
3790
|
+
}
|
|
3791
|
+
const compiled = mergeAnnotations(compileInsertReturning(this.contract, this.namespaceId, this.tableName, mappedRows, selectedForInsert), annotationsMap);
|
|
3792
|
+
return dispatchMutationRows({
|
|
3793
|
+
context: this.ctx.context,
|
|
3794
|
+
runtime: this.ctx.runtime,
|
|
3795
|
+
compiled,
|
|
3796
|
+
tableName: this.tableName,
|
|
3797
|
+
modelName: this.modelName,
|
|
3798
|
+
namespaceId: this.namespaceId,
|
|
3799
|
+
variantName: this.state.variantName,
|
|
3800
|
+
includes: this.state.includes,
|
|
3801
|
+
selectedFields: this.state.selectedFields,
|
|
3802
|
+
hiddenColumns,
|
|
3803
|
+
mapRow: (mapped) => blindCast(mapped)
|
|
3804
|
+
});
|
|
3805
|
+
}
|
|
3806
|
+
#assertNotMtiVariant(method) {
|
|
3807
|
+
if (this.#resolveMtiCreateContext()) throw ormError("ORM.OPERATION_UNSUPPORTED", `${method} is not supported for MTI variant "${this.state.variantName}" on model "${this.modelName}". Use createAll() instead.`, { meta: {
|
|
3808
|
+
method,
|
|
3809
|
+
model: this.modelName,
|
|
3810
|
+
variant: this.state.variantName,
|
|
3811
|
+
reason: "mti-variant"
|
|
3812
|
+
} });
|
|
3813
|
+
}
|
|
3814
|
+
#resolveMtiCreateContext() {
|
|
3815
|
+
const variantName = this.state.variantName;
|
|
3816
|
+
if (!variantName) return null;
|
|
3817
|
+
const polyInfo = resolvePolymorphismInfo(this.contract, this.namespaceId, this.modelName);
|
|
3818
|
+
if (!polyInfo) return null;
|
|
3819
|
+
const variant = polyInfo.variants.get(variantName);
|
|
3820
|
+
if (!isMtiVariantInfo(variant)) return null;
|
|
3821
|
+
return {
|
|
3822
|
+
polyInfo,
|
|
3823
|
+
variant,
|
|
3824
|
+
baseFieldToColumn: getFieldToColumnMap(this.contract, this.namespaceId, this.modelName),
|
|
3825
|
+
variantFieldToColumn: getFieldToColumnMap(this.contract, this.namespaceId, variant.modelName),
|
|
3826
|
+
pkColumn: resolvePrimaryKeyColumn(this.contract, this.namespaceId, this.tableName)
|
|
3827
|
+
};
|
|
3828
|
+
}
|
|
3829
|
+
#executeMtiCreate(data, mtiCtx) {
|
|
3830
|
+
const { polyInfo, variant, baseFieldToColumn, variantFieldToColumn, pkColumn } = mtiCtx;
|
|
3831
|
+
const contract = this.contract;
|
|
3832
|
+
const collectionCtx = this.ctx;
|
|
3833
|
+
const runtime = collectionCtx.runtime;
|
|
3834
|
+
const tableName = this.tableName;
|
|
3835
|
+
const modelName = this.modelName;
|
|
3836
|
+
const namespaceId = this.namespaceId;
|
|
3837
|
+
const baseFieldColumns = new Set(Object.values(baseFieldToColumn));
|
|
3838
|
+
const variantFieldColumns = new Set(Object.values(variantFieldToColumn));
|
|
3839
|
+
const mergedFieldToColumn = {
|
|
3840
|
+
...baseFieldToColumn,
|
|
3841
|
+
...variantFieldToColumn
|
|
3842
|
+
};
|
|
3843
|
+
const generator = async function* () {
|
|
3844
|
+
for (const row of data) {
|
|
3845
|
+
const allMapped = {};
|
|
3846
|
+
for (const [fieldName, value] of Object.entries(row)) {
|
|
3847
|
+
if (value === void 0) continue;
|
|
3848
|
+
const columnName = mergedFieldToColumn[fieldName] ?? fieldName;
|
|
3849
|
+
allMapped[columnName] = value;
|
|
3850
|
+
}
|
|
3851
|
+
allMapped[polyInfo.discriminatorColumn] = variant.value;
|
|
3852
|
+
const baseRow = {};
|
|
3853
|
+
const variantRow = {};
|
|
3854
|
+
for (const [col, val] of Object.entries(allMapped)) {
|
|
3855
|
+
if (baseFieldColumns.has(col) || col === polyInfo.discriminatorColumn) baseRow[col] = val;
|
|
3856
|
+
if (variantFieldColumns.has(col)) variantRow[col] = val;
|
|
3857
|
+
}
|
|
3858
|
+
yield blindCast(await withMutationScope(runtime, async (scope) => {
|
|
3859
|
+
applyCreateDefaults(collectionCtx, namespaceId, tableName, [baseRow]);
|
|
3860
|
+
const baseCreated = (await executeQueryPlan(scope, compileInsertReturning(contract, namespaceId, tableName, [baseRow], void 0)).toArray())[0];
|
|
3861
|
+
if (!baseCreated) throw ormError("ORM.MUTATION_ROW_MISSING", `MTI base INSERT for model "${modelName}" did not return a row`, { meta: {
|
|
3862
|
+
operation: "create",
|
|
3863
|
+
model: modelName,
|
|
3864
|
+
table: tableName,
|
|
3865
|
+
phase: "mti-base"
|
|
3866
|
+
} });
|
|
3867
|
+
const pkValue = baseCreated[pkColumn];
|
|
3868
|
+
variantRow[pkColumn] = pkValue;
|
|
3869
|
+
applyCreateDefaults(collectionCtx, namespaceId, variant.table, [variantRow]);
|
|
3870
|
+
const variantCreated = (await executeQueryPlan(scope, compileInsertReturning(contract, namespaceId, variant.table, [variantRow], void 0)).toArray())[0];
|
|
3871
|
+
if (!variantCreated) throw ormError("ORM.MUTATION_ROW_MISSING", `MTI variant INSERT for model "${modelName}" into "${variant.table}" did not return a row`, { meta: {
|
|
3872
|
+
operation: "create",
|
|
3873
|
+
model: modelName,
|
|
3874
|
+
table: variant.table,
|
|
3875
|
+
phase: "mti-variant"
|
|
3876
|
+
} });
|
|
3877
|
+
const prefixedVariant = {};
|
|
3878
|
+
for (const [col, val] of Object.entries(variantCreated)) {
|
|
3879
|
+
if (col === pkColumn) continue;
|
|
3880
|
+
prefixedVariant[`${variant.table}__${col}`] = val;
|
|
3881
|
+
}
|
|
3882
|
+
return mapPolymorphicRow(contract, namespaceId, modelName, polyInfo, {
|
|
3883
|
+
...baseCreated,
|
|
3884
|
+
...prefixedVariant
|
|
3885
|
+
}, variant.modelName);
|
|
3886
|
+
}));
|
|
3887
|
+
}
|
|
3888
|
+
};
|
|
3889
|
+
return new AsyncIterableResult(generator());
|
|
3890
|
+
}
|
|
3891
|
+
#mapCreateRows(data) {
|
|
3892
|
+
const variantName = this.state.variantName;
|
|
3893
|
+
if (!variantName) return data.map((row) => mapModelDataToStorageRow(this.contract, this.namespaceId, this.modelName, row));
|
|
3894
|
+
const polyInfo = resolvePolymorphismInfo(this.contract, this.namespaceId, this.modelName);
|
|
3895
|
+
if (!polyInfo) return data.map((row) => mapModelDataToStorageRow(this.contract, this.namespaceId, this.modelName, row));
|
|
3896
|
+
const variant = polyInfo.variants.get(variantName);
|
|
3897
|
+
if (!variant) return data.map((row) => mapModelDataToStorageRow(this.contract, this.namespaceId, this.modelName, row));
|
|
3898
|
+
const baseFieldToColumn = getFieldToColumnMap(this.contract, this.namespaceId, this.modelName);
|
|
3899
|
+
const variantFieldToColumn = getFieldToColumnMap(this.contract, this.namespaceId, variant.modelName);
|
|
3900
|
+
const mergedFieldToColumn = {
|
|
3901
|
+
...baseFieldToColumn,
|
|
3902
|
+
...variantFieldToColumn
|
|
3903
|
+
};
|
|
3904
|
+
return data.map((row) => {
|
|
3905
|
+
const mapped = {};
|
|
3906
|
+
for (const [fieldName, value] of Object.entries(row)) {
|
|
3907
|
+
if (value === void 0) continue;
|
|
3908
|
+
const columnName = mergedFieldToColumn[fieldName] ?? fieldName;
|
|
3909
|
+
mapped[columnName] = value;
|
|
3910
|
+
}
|
|
3911
|
+
mapped[polyInfo.discriminatorColumn] = variant.value;
|
|
3912
|
+
return mapped;
|
|
3913
|
+
});
|
|
3914
|
+
}
|
|
3915
|
+
/**
|
|
3916
|
+
* Write terminal: insert many rows without materializing the
|
|
3917
|
+
* inserted rows, returning the number of inserted records.
|
|
3918
|
+
*
|
|
3919
|
+
* Prefer `createAll(...)` when you need the returned rows; prefer
|
|
3920
|
+
* this when you only need to know how many rows were inserted (the
|
|
3921
|
+
* compiled plan skips `RETURNING`).
|
|
3922
|
+
*
|
|
3923
|
+
* ```typescript
|
|
3924
|
+
* const inserted = await db.orm.User.createAndCount([
|
|
3925
|
+
* { email: 'a@example.com' },
|
|
3926
|
+
* { email: 'b@example.com' },
|
|
3927
|
+
* ]);
|
|
3928
|
+
* // inserted === 2
|
|
3929
|
+
* ```
|
|
3930
|
+
*
|
|
3931
|
+
* Not supported on MTI variants — use `createAll(...)` instead.
|
|
3932
|
+
*/
|
|
3933
|
+
async createAndCount(data, configure) {
|
|
3934
|
+
if (data.length === 0) return 0;
|
|
3935
|
+
this.#assertNotMtiVariant("createAndCount()");
|
|
3936
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "createAndCount");
|
|
3937
|
+
const rows = blindCast(data);
|
|
3938
|
+
const mappedRows = this.#mapCreateRows(rows);
|
|
3939
|
+
applyCreateDefaults(this.ctx, this.namespaceId, this.tableName, mappedRows);
|
|
3940
|
+
if (this.contract.capabilities?.["sql"]?.["defaultInInsert"] !== true) {
|
|
3941
|
+
const plans = compileInsertCountSplit(this.contract, this.namespaceId, this.tableName, mappedRows).map((plan) => mergeAnnotations(plan, annotationsMap));
|
|
3942
|
+
for (const plan of plans) await executeQueryPlan(this.ctx.runtime, plan).toArray();
|
|
3943
|
+
return data.length;
|
|
3944
|
+
}
|
|
3945
|
+
const compiled = mergeAnnotations(compileInsertCount(this.contract, this.namespaceId, this.tableName, mappedRows), annotationsMap);
|
|
3946
|
+
await executeQueryPlan(this.ctx.runtime, compiled).toArray();
|
|
3947
|
+
return data.length;
|
|
3948
|
+
}
|
|
3949
|
+
/**
|
|
3950
|
+
* Write terminal: insert a row, or update the existing row on
|
|
3951
|
+
* conflict. Returns the resulting row (the inserted one or the
|
|
3952
|
+
* updated/existing one).
|
|
3953
|
+
*
|
|
3954
|
+
* `conflictOn` selects which unique constraint drives the conflict
|
|
3955
|
+
* resolution — omit to use the model's primary key.
|
|
3956
|
+
*
|
|
3957
|
+
* ```typescript
|
|
3958
|
+
* // Insert-or-update on email uniqueness:
|
|
3959
|
+
* await db.orm.User.upsert({
|
|
3960
|
+
* create: { email: 'alice@example.com', name: 'Alice' },
|
|
3961
|
+
* update: { name: 'Alice (updated)' },
|
|
3962
|
+
* conflictOn: { email: 'alice@example.com' },
|
|
3963
|
+
* });
|
|
3964
|
+
*
|
|
3965
|
+
* // Conditional create — `update: {}` keeps the existing row
|
|
3966
|
+
* // unchanged. `conflictOn` must reference the constraint that
|
|
3967
|
+
* // makes the row "already exist"; omit only when the conflict is
|
|
3968
|
+
* // on the primary key. On conflict,
|
|
3969
|
+
* // `ON CONFLICT DO NOTHING RETURNING ...` may return zero rows,
|
|
3970
|
+
* // so a follow-up reload is issued to fetch and return the
|
|
3971
|
+
* // existing row.
|
|
3972
|
+
* await db.orm.User.upsert({
|
|
3973
|
+
* create: { email: 'alice@example.com', name: 'Alice' },
|
|
3974
|
+
* update: {},
|
|
3975
|
+
* conflictOn: { email: 'alice@example.com' },
|
|
3976
|
+
* });
|
|
3977
|
+
* ```
|
|
3978
|
+
*
|
|
3979
|
+
* Not supported on MTI variants.
|
|
3980
|
+
*/
|
|
3981
|
+
async upsert(input, configure) {
|
|
3982
|
+
assertReturningCapability(this.contract, "upsert()");
|
|
3983
|
+
this.#assertNotMtiVariant("upsert()");
|
|
3984
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "upsert");
|
|
3985
|
+
const createValues = this.#mapCreateRows([blindCast(input.create)])[0] ?? {};
|
|
3986
|
+
applyCreateDefaults(this.ctx, this.namespaceId, this.tableName, [createValues]);
|
|
3987
|
+
const updateValues = mapModelDataToStorageRow(this.contract, this.namespaceId, this.modelName, input.update);
|
|
3988
|
+
const hasUpdateValues = Object.keys(updateValues).length > 0;
|
|
3989
|
+
if (hasUpdateValues) applyUpdateDefaults(this.ctx, this.namespaceId, this.tableName, updateValues);
|
|
3990
|
+
const conflictColumns = resolveUpsertConflictColumns(this.contract, this.namespaceId, this.modelName, blindCast(input.conflictOn));
|
|
3991
|
+
if (conflictColumns.length === 0) throw ormError("ORM.ARGUMENT_INVALID", `upsert() for model "${this.modelName}" requires conflict columns`, { meta: {
|
|
3992
|
+
method: "upsert",
|
|
3993
|
+
model: this.modelName
|
|
3994
|
+
} });
|
|
3995
|
+
const { selectedForQuery: selectedForUpsert, hiddenColumns } = this.#augmentMutationSelection();
|
|
3996
|
+
const compiled = mergeAnnotations(compileUpsertReturning(this.contract, this.namespaceId, this.tableName, createValues, updateValues, conflictColumns, selectedForUpsert), annotationsMap);
|
|
3997
|
+
const row = await executeMutationReturningSingleRow({
|
|
3998
|
+
context: this.ctx.context,
|
|
3999
|
+
runtime: this.ctx.runtime,
|
|
4000
|
+
compiled,
|
|
4001
|
+
tableName: this.tableName,
|
|
4002
|
+
modelName: this.modelName,
|
|
4003
|
+
namespaceId: this.namespaceId,
|
|
4004
|
+
variantName: this.state.variantName,
|
|
4005
|
+
includes: this.state.includes,
|
|
4006
|
+
selectedFields: this.state.selectedFields,
|
|
4007
|
+
hiddenColumns,
|
|
4008
|
+
mapRow: (mapped) => blindCast(mapped),
|
|
4009
|
+
operation: "upsert",
|
|
4010
|
+
onMissingRowMessage: `upsert() for model "${this.modelName}" did not return a row`
|
|
4011
|
+
});
|
|
4012
|
+
if (row) return row;
|
|
4013
|
+
if (!hasUpdateValues) {
|
|
4014
|
+
const conflictCriterion = this.#buildUpsertConflictCriterion(createValues, conflictColumns);
|
|
4015
|
+
const existing = await this.#reloadMutationRowByCriterion(conflictCriterion, "upsert conflict");
|
|
4016
|
+
if (existing) return existing;
|
|
4017
|
+
}
|
|
4018
|
+
throw ormError("ORM.MUTATION_ROW_MISSING", `upsert() for model "${this.modelName}" did not return a row`, { meta: {
|
|
4019
|
+
operation: "upsert",
|
|
4020
|
+
model: this.modelName
|
|
4021
|
+
} });
|
|
4022
|
+
}
|
|
4023
|
+
/**
|
|
4024
|
+
* Write terminal: update matching rows and return the first one (or
|
|
4025
|
+
* `null` when no row matched). Requires a prior `.where(...)` —
|
|
4026
|
+
* calling `update(...)` on an unfiltered collection is a type error.
|
|
4027
|
+
*
|
|
4028
|
+
* Related rows can be created or relinked through relation
|
|
4029
|
+
* callbacks on parent/child-owned relations (one-to-one or
|
|
4030
|
+
* one-to-many). The callback receives a mutator exposing
|
|
4031
|
+
* `create(...)`, `connect(...)`, and `disconnect(...)`. Nested
|
|
4032
|
+
* updates against existing related rows, and many-to-many relations
|
|
4033
|
+
* as nested-mutation targets, are not supported through this API.
|
|
4034
|
+
*
|
|
4035
|
+
* ```typescript
|
|
4036
|
+
* // Update one row by id:
|
|
4037
|
+
* const updated = await db.orm.User
|
|
4038
|
+
* .where({ id: 1 })
|
|
4039
|
+
* .update({ name: 'Alice Renamed' });
|
|
4040
|
+
*
|
|
4041
|
+
* // Update + relink — runs as a graph of internal mutations:
|
|
4042
|
+
* await db.orm.User
|
|
4043
|
+
* .where({ id: 1 })
|
|
4044
|
+
* .update({
|
|
4045
|
+
* name: 'Alice',
|
|
4046
|
+
* posts: (posts) => posts.connect([{ id: 5 }]),
|
|
4047
|
+
* });
|
|
4048
|
+
* ```
|
|
4049
|
+
*
|
|
4050
|
+
* Accepts an optional `configure` callback that receives a
|
|
4051
|
+
* `MetaBuilder<'write'>` for attaching typed annotations.
|
|
4052
|
+
*
|
|
4053
|
+
* Note: when the input contains nested-mutation callbacks, the
|
|
4054
|
+
* operation is executed as a graph of internal queries via
|
|
4055
|
+
* `withMutationScope`. In that path, annotations apply to the logical
|
|
4056
|
+
* `update()` call but do not currently flow into each constituent SQL
|
|
4057
|
+
* statement issued for the related rows.
|
|
4058
|
+
*/
|
|
4059
|
+
async update(data, configure) {
|
|
4060
|
+
assertReturningCapability(this.contract, "update()");
|
|
4061
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "update");
|
|
4062
|
+
if (hasNestedMutationCallbacks(this.contract, this.namespaceId, this.modelName, blindCast(data))) {
|
|
4063
|
+
const updatedRow = await executeNestedUpdateMutation({
|
|
4064
|
+
context: this.ctx.context,
|
|
4065
|
+
runtime: this.ctx.runtime,
|
|
4066
|
+
namespaceId: this.namespaceId,
|
|
4067
|
+
modelName: this.modelName,
|
|
4068
|
+
filters: this.state.filters,
|
|
4069
|
+
data: blindCast(data)
|
|
4070
|
+
});
|
|
4071
|
+
if (!updatedRow) return null;
|
|
4072
|
+
const pkCriterion = buildPrimaryKeyFilterFromRow(this.contract, this.namespaceId, this.modelName, updatedRow);
|
|
4073
|
+
return this.#reloadMutationRowByPrimaryKey(pkCriterion);
|
|
4074
|
+
}
|
|
4075
|
+
return withMutationScope(this.ctx.runtime, async (scope) => {
|
|
4076
|
+
const scoped = this.#withRuntime(scope);
|
|
4077
|
+
const identityWhere = await scoped.#findFirstMatchingRowIdentityWhere();
|
|
4078
|
+
if (!identityWhere) return null;
|
|
4079
|
+
return (await scoped.#clone({ filters: [identityWhere] }).#updateAllWithAnnotations(blindCast(data), annotationsMap))[0] ?? null;
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
/**
|
|
4083
|
+
* Write terminal: update every matching row and stream the updated
|
|
4084
|
+
* rows. Requires a prior `.where(...)` filter.
|
|
4085
|
+
*
|
|
4086
|
+
* The returned `AsyncIterableResult<Row>` is BOTH a thenable that
|
|
4087
|
+
* resolves to `Row[]` AND an async iterable that streams updated
|
|
4088
|
+
* rows as they arrive. Use whichever fits; a result can only be
|
|
4089
|
+
* consumed once. Streaming is the default; some driver/plan
|
|
4090
|
+
* combinations may still buffer internally before yielding.
|
|
4091
|
+
*
|
|
4092
|
+
* ```typescript
|
|
4093
|
+
* // Thenable — collect updated rows into an array:
|
|
4094
|
+
* const updated = await db.orm.Post
|
|
4095
|
+
* .where({ published: false })
|
|
4096
|
+
* .updateAll({ published: true });
|
|
4097
|
+
*
|
|
4098
|
+
* // Async iterable — stream updated rows as they arrive:
|
|
4099
|
+
* for await (const row of db.orm.Post.where({ draft: true }).updateAll({ draft: false })) {
|
|
4100
|
+
* console.log('published', row.id);
|
|
4101
|
+
* }
|
|
4102
|
+
* ```
|
|
4103
|
+
*
|
|
4104
|
+
* Accepts an optional `configure` callback that receives a
|
|
4105
|
+
* `MetaBuilder<'write'>` for attaching typed annotations.
|
|
4106
|
+
*/
|
|
4107
|
+
updateAll(data, configure) {
|
|
4108
|
+
return this.#updateAllWithAnnotations(data, this.#collectAnnotationsFromMeta(configure, "write", "updateAll"));
|
|
4109
|
+
}
|
|
4110
|
+
#updateAllWithAnnotations(data, annotationsMap) {
|
|
4111
|
+
assertReturningCapability(this.contract, "updateAll()");
|
|
4112
|
+
const mappedData = mapModelDataToStorageRow(this.contract, this.namespaceId, this.modelName, data);
|
|
4113
|
+
if (Object.keys(mappedData).length === 0) {
|
|
4114
|
+
const generator = async function* () {};
|
|
4115
|
+
return new AsyncIterableResult(generator());
|
|
4116
|
+
}
|
|
4117
|
+
applyUpdateDefaults(this.ctx, this.namespaceId, this.tableName, mappedData);
|
|
4118
|
+
const { selectedForQuery: selectedForUpdate, hiddenColumns } = this.#augmentMutationSelection();
|
|
4119
|
+
const compiled = mergeAnnotations(compileUpdateReturning(this.contract, this.namespaceId, this.tableName, mappedData, this.state.filters, selectedForUpdate), annotationsMap);
|
|
4120
|
+
return dispatchMutationRows({
|
|
4121
|
+
context: this.ctx.context,
|
|
4122
|
+
runtime: this.ctx.runtime,
|
|
4123
|
+
compiled,
|
|
4124
|
+
tableName: this.tableName,
|
|
4125
|
+
modelName: this.modelName,
|
|
4126
|
+
namespaceId: this.namespaceId,
|
|
4127
|
+
variantName: this.state.variantName,
|
|
4128
|
+
includes: this.state.includes,
|
|
4129
|
+
selectedFields: this.state.selectedFields,
|
|
4130
|
+
hiddenColumns,
|
|
4131
|
+
mapRow: (mapped) => blindCast(mapped)
|
|
4132
|
+
});
|
|
4133
|
+
}
|
|
4134
|
+
/**
|
|
4135
|
+
* Write terminal: update every matching row without returning them,
|
|
4136
|
+
* resolving to the count of rows that were updated. Requires a prior
|
|
4137
|
+
* `.where(...)` filter.
|
|
4138
|
+
*
|
|
4139
|
+
* Prefer `updateAll(...)` when you need the updated rows; prefer
|
|
4140
|
+
* this when you only need the affected-row count.
|
|
4141
|
+
*
|
|
4142
|
+
* ```typescript
|
|
4143
|
+
* const count = await db.orm.Post
|
|
4144
|
+
* .where({ published: false })
|
|
4145
|
+
* .updateAndCount({ published: true });
|
|
4146
|
+
* ```
|
|
4147
|
+
*/
|
|
4148
|
+
async updateAndCount(data, configure) {
|
|
4149
|
+
const mappedData = mapModelDataToStorageRow(this.contract, this.namespaceId, this.modelName, data);
|
|
4150
|
+
if (Object.keys(mappedData).length === 0) return 0;
|
|
4151
|
+
applyUpdateDefaults(this.ctx, this.namespaceId, this.tableName, mappedData);
|
|
4152
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "updateAndCount");
|
|
4153
|
+
const primaryKeyColumn = resolvePrimaryKeyColumn(this.contract, this.namespaceId, this.tableName);
|
|
4154
|
+
const countState = {
|
|
4155
|
+
...emptyState(),
|
|
4156
|
+
filters: this.state.filters,
|
|
4157
|
+
selectedFields: [primaryKeyColumn],
|
|
4158
|
+
variantName: this.state.variantName
|
|
4159
|
+
};
|
|
4160
|
+
const countCompiled = compileSelect(this.contract, this.namespaceId, this.tableName, countState, this.modelName);
|
|
4161
|
+
const matchingRows = await executeQueryPlan(this.ctx.runtime, countCompiled).toArray();
|
|
4162
|
+
const compiled = mergeAnnotations(compileUpdateCount(this.contract, this.namespaceId, this.tableName, mappedData, this.state.filters, this.state.variantName, this.modelName), annotationsMap);
|
|
4163
|
+
await executeQueryPlan(this.ctx.runtime, compiled).toArray();
|
|
4164
|
+
return matchingRows.length;
|
|
4165
|
+
}
|
|
4166
|
+
/**
|
|
4167
|
+
* Write terminal: delete matching rows and return the first deleted
|
|
4168
|
+
* row (or `null` when no row matched). Requires a prior `.where(...)`
|
|
4169
|
+
* — calling `delete()` on an unfiltered collection is a type error.
|
|
4170
|
+
*
|
|
4171
|
+
* ```typescript
|
|
4172
|
+
* const deleted = await db.orm.User.where({ id: 1 }).delete();
|
|
4173
|
+
* if (deleted) console.log('deleted', deleted.email);
|
|
4174
|
+
* ```
|
|
4175
|
+
*
|
|
4176
|
+
* Accepts an optional `configure` callback that receives a
|
|
4177
|
+
* `MetaBuilder<'write'>` for attaching typed annotations.
|
|
4178
|
+
*/
|
|
4179
|
+
async delete(configure) {
|
|
4180
|
+
assertReturningCapability(this.contract, "delete()");
|
|
4181
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "delete");
|
|
4182
|
+
return withMutationScope(this.ctx.runtime, async (scope) => {
|
|
4183
|
+
const scoped = this.#withRuntime(scope);
|
|
4184
|
+
const identityWhere = await scoped.#findFirstMatchingRowIdentityWhere();
|
|
4185
|
+
if (!identityWhere) return null;
|
|
4186
|
+
return (await scoped.#clone({ filters: [identityWhere] }).#executeDeleteReturning(annotationsMap).toArray())[0] ?? null;
|
|
4187
|
+
});
|
|
4188
|
+
}
|
|
4189
|
+
/**
|
|
4190
|
+
* Write terminal: delete every matching row and stream the deleted
|
|
4191
|
+
* rows. Requires a prior `.where(...)` filter.
|
|
4192
|
+
*
|
|
4193
|
+
* The returned `AsyncIterableResult<Row>` is BOTH a thenable that
|
|
4194
|
+
* resolves to `Row[]` AND an async iterable that streams deleted
|
|
4195
|
+
* rows as they arrive. Use whichever fits; a result can only be
|
|
4196
|
+
* consumed once. Streaming is the default; some driver/plan
|
|
4197
|
+
* combinations may still buffer internally before yielding.
|
|
4198
|
+
*
|
|
4199
|
+
* ```typescript
|
|
4200
|
+
* // Thenable — collect the deleted rows into an array:
|
|
4201
|
+
* const deleted = await db.orm.Post.where({ archived: true }).deleteAll();
|
|
4202
|
+
*
|
|
4203
|
+
* // Async iterable — stream deleted rows as they arrive:
|
|
4204
|
+
* for await (const row of db.orm.Post.where({ archived: true }).deleteAll()) {
|
|
4205
|
+
* console.log('removed', row.id);
|
|
4206
|
+
* }
|
|
4207
|
+
* ```
|
|
4208
|
+
*
|
|
4209
|
+
* Accepts an optional `configure` callback that receives a
|
|
4210
|
+
* `MetaBuilder<'write'>` for attaching typed annotations.
|
|
4211
|
+
*/
|
|
4212
|
+
deleteAll(configure) {
|
|
4213
|
+
return blindCast(this).#deleteAllWithAnnotations(this.#collectAnnotationsFromMeta(configure, "write", "deleteAll"));
|
|
4214
|
+
}
|
|
4215
|
+
#deleteAllWithAnnotations(annotationsMap) {
|
|
4216
|
+
assertReturningCapability(this.contract, "deleteAll()");
|
|
4217
|
+
return this.#executeDeleteReturning(annotationsMap);
|
|
4218
|
+
}
|
|
4219
|
+
#executeDeleteReturning(annotationsMap) {
|
|
4220
|
+
if (this.state.includes.length > 0) return this.#executeDeleteReturningWithIncludes(annotationsMap);
|
|
4221
|
+
const { selectedForQuery: selectedForDelete, hiddenColumns } = this.#augmentMutationSelection();
|
|
4222
|
+
const compiled = mergeAnnotations(compileDeleteReturning(this.contract, this.namespaceId, this.tableName, this.state.filters, selectedForDelete), annotationsMap);
|
|
4223
|
+
return dispatchMutationRows({
|
|
4224
|
+
context: this.ctx.context,
|
|
4225
|
+
runtime: this.ctx.runtime,
|
|
4226
|
+
compiled,
|
|
4227
|
+
tableName: this.tableName,
|
|
4228
|
+
modelName: this.modelName,
|
|
4229
|
+
namespaceId: this.namespaceId,
|
|
4230
|
+
variantName: this.state.variantName,
|
|
4231
|
+
includes: this.state.includes,
|
|
4232
|
+
selectedFields: this.state.selectedFields,
|
|
4233
|
+
hiddenColumns,
|
|
4234
|
+
mapRow: (mapped) => blindCast(mapped)
|
|
4235
|
+
});
|
|
4236
|
+
}
|
|
4237
|
+
/**
|
|
4238
|
+
* Delete read-back with includes.
|
|
4239
|
+
*
|
|
4240
|
+
* A parent-anchored single-query include read can't observe a row
|
|
4241
|
+
* that has already been deleted, so this reads the rows together with
|
|
4242
|
+
* their relations BEFORE issuing the DELETE. The snapshot is fully
|
|
4243
|
+
* drained into a plain array with `.toArray()` while the rows still
|
|
4244
|
+
* exist; only then does the DELETE run. The yielded `for..of` walks
|
|
4245
|
+
* that in-memory array, not a live cursor, so nothing reads from the
|
|
4246
|
+
* deleted rows after the fact. Snapshot read and delete share one
|
|
4247
|
+
* `withMutationScope` so they are atomic; the returned relations
|
|
4248
|
+
* reflect the row's state at delete time.
|
|
4249
|
+
*/
|
|
4250
|
+
#executeDeleteReturningWithIncludes(annotationsMap) {
|
|
4251
|
+
const collection = this;
|
|
4252
|
+
const generator = async function* () {
|
|
4253
|
+
const snapshot = await withMutationScope(collection.ctx.runtime, async (scope) => {
|
|
4254
|
+
const rows = await dispatchCollectionRows({
|
|
4255
|
+
context: collection.ctx.context,
|
|
4256
|
+
runtime: scope,
|
|
4257
|
+
state: collection.state,
|
|
4258
|
+
tableName: collection.tableName,
|
|
4259
|
+
modelName: collection.modelName,
|
|
4260
|
+
namespaceId: collection.namespaceId
|
|
4261
|
+
}).toArray();
|
|
4262
|
+
await executeQueryPlan(scope, mergeAnnotations(compileDeleteCount(collection.contract, collection.namespaceId, collection.tableName, collection.state.filters, collection.state.variantName, collection.modelName), annotationsMap)).toArray();
|
|
4263
|
+
return rows;
|
|
4264
|
+
});
|
|
4265
|
+
for (const row of snapshot) yield row;
|
|
4266
|
+
};
|
|
4267
|
+
return new AsyncIterableResult(generator());
|
|
4268
|
+
}
|
|
4269
|
+
/**
|
|
4270
|
+
* Write terminal: delete every matching row without returning them,
|
|
4271
|
+
* resolving to the count of rows that were deleted. Requires a prior
|
|
4272
|
+
* `.where(...)` filter.
|
|
4273
|
+
*
|
|
4274
|
+
* Prefer `deleteAll(...)` when you need the deleted rows; prefer
|
|
4275
|
+
* this when you only need the affected-row count.
|
|
4276
|
+
*
|
|
4277
|
+
* ```typescript
|
|
4278
|
+
* const removed = await db.orm.Post.where({ archived: true }).deleteAndCount();
|
|
4279
|
+
* ```
|
|
4280
|
+
*/
|
|
4281
|
+
async deleteAndCount(configure) {
|
|
4282
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "deleteAndCount");
|
|
4283
|
+
const primaryKeyColumn = resolvePrimaryKeyColumn(this.contract, this.namespaceId, this.tableName);
|
|
4284
|
+
const countState = {
|
|
4285
|
+
...emptyState(),
|
|
4286
|
+
filters: this.state.filters,
|
|
4287
|
+
selectedFields: [primaryKeyColumn],
|
|
4288
|
+
variantName: this.state.variantName
|
|
4289
|
+
};
|
|
4290
|
+
const countCompiled = compileSelect(this.contract, this.namespaceId, this.tableName, countState, this.modelName);
|
|
4291
|
+
const matchingRows = await executeQueryPlan(this.ctx.runtime, countCompiled).toArray();
|
|
4292
|
+
const compiled = mergeAnnotations(compileDeleteCount(this.contract, this.namespaceId, this.tableName, this.state.filters, this.state.variantName, this.modelName), annotationsMap);
|
|
4293
|
+
await executeQueryPlan(this.ctx.runtime, compiled).toArray();
|
|
4294
|
+
return matchingRows.length;
|
|
4295
|
+
}
|
|
4296
|
+
#buildUpsertConflictCriterion(createValues, conflictColumns) {
|
|
4297
|
+
const columnToField = getColumnToFieldMap(this.contract, this.namespaceId, this.modelName);
|
|
4298
|
+
const criterion = {};
|
|
4299
|
+
for (const columnName of conflictColumns) {
|
|
4300
|
+
if (!(columnName in createValues)) throw ormError("ORM.ARGUMENT_INVALID", `upsert() for model "${this.modelName}" requires create value for conflict column "${columnName}"`, { meta: {
|
|
4301
|
+
method: "upsert",
|
|
4302
|
+
model: this.modelName,
|
|
4303
|
+
column: columnName
|
|
4304
|
+
} });
|
|
4305
|
+
const fieldName = columnToField[columnName] ?? columnName;
|
|
4306
|
+
criterion[fieldName] = createValues[columnName];
|
|
4307
|
+
}
|
|
4308
|
+
return criterion;
|
|
4309
|
+
}
|
|
4310
|
+
/**
|
|
4311
|
+
* Shape the projection for a mutation's `RETURNING` clause.
|
|
4312
|
+
*
|
|
4313
|
+
* Without includes, the mutation returns the caller's projection
|
|
4314
|
+
* directly. With includes, it returns only the row identity columns
|
|
4315
|
+
* (PK / unique): those rows are reloaded through the read path
|
|
4316
|
+
* (`reloadMutationRowsByIdentities`), which re-selects the caller's
|
|
4317
|
+
* projection together with the relations, so the `RETURNING` clause
|
|
4318
|
+
* need only carry enough to key that read-back.
|
|
4319
|
+
*/
|
|
4320
|
+
#augmentMutationSelection() {
|
|
4321
|
+
if (this.state.includes.length > 0) {
|
|
4322
|
+
const identityColumns = resolveRowIdentityColumns(this.contract, this.namespaceId, this.tableName);
|
|
4323
|
+
if (identityColumns.length === 0) throw ormError("ORM.ROW_IDENTITY_MISSING", `Cannot load includes for the mutation result on model "${this.modelName}": table "${this.tableName}" has no primary key or unique constraint to key the include read-back on.`, { meta: {
|
|
4324
|
+
model: this.modelName,
|
|
4325
|
+
table: this.tableName
|
|
4326
|
+
} });
|
|
4327
|
+
return {
|
|
4328
|
+
selectedForQuery: identityColumns,
|
|
4329
|
+
hiddenColumns: []
|
|
4330
|
+
};
|
|
4331
|
+
}
|
|
4332
|
+
return {
|
|
4333
|
+
selectedForQuery: this.state.selectedFields,
|
|
4334
|
+
hiddenColumns: []
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
async #findFirstMatchingRowIdentityWhere() {
|
|
4338
|
+
const identityColumns = resolveRowIdentityColumns(this.contract, this.namespaceId, this.tableName);
|
|
4339
|
+
if (identityColumns.length === 0) throw ormError("ORM.ROW_IDENTITY_MISSING", `update()/delete() on model "${this.modelName}" requires the table to have a primary key or unique constraint`, { meta: {
|
|
4340
|
+
model: this.modelName,
|
|
4341
|
+
table: this.tableName
|
|
4342
|
+
} });
|
|
4343
|
+
const firstRow = await this.#clone({
|
|
4344
|
+
selectedFields: [...identityColumns],
|
|
4345
|
+
includes: []
|
|
4346
|
+
}).first();
|
|
4347
|
+
if (!firstRow) return null;
|
|
4348
|
+
const columnToField = getColumnToFieldMap(this.contract, this.namespaceId, this.modelName);
|
|
4349
|
+
const criterion = {};
|
|
4350
|
+
for (const column of identityColumns) {
|
|
4351
|
+
const fieldName = columnToField[column] ?? column;
|
|
4352
|
+
const value = blindCast(firstRow)[fieldName];
|
|
4353
|
+
if (value === void 0) throw new InternalError(`Missing identity field "${fieldName}" while resolving single-row scope for model "${this.modelName}"`);
|
|
4354
|
+
criterion[fieldName] = value;
|
|
4355
|
+
}
|
|
4356
|
+
return shorthandToWhereExpr(this.ctx.context, this.namespaceId, this.modelName, blindCast(criterion)) ?? null;
|
|
4357
|
+
}
|
|
4358
|
+
async #reloadMutationRowByPrimaryKey(criterion) {
|
|
4359
|
+
return this.#reloadMutationRowByCriterion(criterion, "primary key");
|
|
4360
|
+
}
|
|
4361
|
+
async #reloadMutationRowByCriterion(criterion, criterionLabel) {
|
|
4362
|
+
const whereExpr = shorthandToWhereExpr(this.ctx.context, this.namespaceId, this.modelName, blindCast(criterion));
|
|
4363
|
+
if (!whereExpr) throw new InternalError(`Failed to build ${criterionLabel} filter for mutation result on model "${this.modelName}"`);
|
|
4364
|
+
const resultState = {
|
|
4365
|
+
...emptyState(),
|
|
4366
|
+
filters: [whereExpr],
|
|
4367
|
+
includes: this.state.includes,
|
|
4368
|
+
selectedFields: this.state.selectedFields,
|
|
4369
|
+
limit: 1
|
|
4370
|
+
};
|
|
4371
|
+
return (await dispatchCollectionRows({
|
|
4372
|
+
context: this.ctx.context,
|
|
4373
|
+
runtime: this.ctx.runtime,
|
|
4374
|
+
state: resultState,
|
|
4375
|
+
tableName: this.tableName,
|
|
4376
|
+
modelName: this.modelName,
|
|
4377
|
+
namespaceId: this.namespaceId
|
|
4378
|
+
}))[0] ?? null;
|
|
4379
|
+
}
|
|
4380
|
+
#assertIncludeRefinementMode(action) {
|
|
4381
|
+
if (this.includeRefinementMode) return;
|
|
4382
|
+
throw ormError("ORM.INCLUDE_INVALID", `${action} is only available inside include() refinement callbacks`, { meta: { action } });
|
|
4383
|
+
}
|
|
4384
|
+
#clone(overrides) {
|
|
4385
|
+
return this.#createSelf({
|
|
4386
|
+
...this.state,
|
|
4387
|
+
...overrides
|
|
4388
|
+
});
|
|
4389
|
+
}
|
|
4390
|
+
#withRuntime(runtime) {
|
|
4391
|
+
return blindCast(new (blindCast(this.constructor))({
|
|
4392
|
+
...this.ctx,
|
|
4393
|
+
runtime
|
|
4394
|
+
}, this.modelName, {
|
|
4395
|
+
tableName: this.tableName,
|
|
4396
|
+
namespaceId: this.namespaceId,
|
|
4397
|
+
state: this.state,
|
|
4398
|
+
registry: this.registry,
|
|
4399
|
+
includeRefinementMode: this.includeRefinementMode
|
|
4400
|
+
}));
|
|
4401
|
+
}
|
|
4402
|
+
#cloneWithRow(overrides) {
|
|
4403
|
+
return this.#createSelf({
|
|
4404
|
+
...this.state,
|
|
4405
|
+
...overrides
|
|
4406
|
+
});
|
|
4407
|
+
}
|
|
4408
|
+
#createSelf(state) {
|
|
4409
|
+
return blindCast(new (blindCast(this.constructor))(this.ctx, this.modelName, {
|
|
4410
|
+
tableName: this.tableName,
|
|
4411
|
+
namespaceId: this.namespaceId,
|
|
4412
|
+
state,
|
|
4413
|
+
registry: this.registry,
|
|
4414
|
+
includeRefinementMode: this.includeRefinementMode
|
|
4415
|
+
}));
|
|
4416
|
+
}
|
|
4417
|
+
#createCollection(modelName, options) {
|
|
4418
|
+
return blindCast(new ((this.registry.get(modelName)) ?? (blindCast(Collection)))(this.ctx, modelName, {
|
|
4419
|
+
tableName: options.tableName,
|
|
4420
|
+
namespaceId: options.namespaceId,
|
|
4421
|
+
state: options.state,
|
|
4422
|
+
registry: options.registry ?? this.registry,
|
|
4423
|
+
includeRefinementMode: options.includeRefinementMode ?? this.includeRefinementMode
|
|
4424
|
+
}));
|
|
4425
|
+
}
|
|
4426
|
+
#dispatch() {
|
|
4427
|
+
return dispatchCollectionRows({
|
|
4428
|
+
context: this.ctx.context,
|
|
4429
|
+
runtime: this.ctx.runtime,
|
|
4430
|
+
state: this.state,
|
|
4431
|
+
tableName: this.tableName,
|
|
4432
|
+
modelName: this.modelName,
|
|
4433
|
+
namespaceId: this.namespaceId
|
|
4434
|
+
});
|
|
4435
|
+
}
|
|
4436
|
+
/**
|
|
4437
|
+
* Invokes the user-supplied configurator (if any) against a freshly
|
|
4438
|
+
* constructed read meta builder, and returns a clone whose
|
|
4439
|
+
* `state.annotations` carries the recorded map. Used by read
|
|
4440
|
+
* terminals that flow annotations through state (`all`, `first`).
|
|
4441
|
+
*
|
|
4442
|
+
* Returns the receiver unchanged when no configurator was supplied
|
|
4443
|
+
* or when the configurator did not call `meta.annotate(...)`. The
|
|
4444
|
+
* meta builder's `annotate` method enforces applicability at the
|
|
4445
|
+
* type level and at runtime, so terminal code does not need to
|
|
4446
|
+
* re-validate.
|
|
4447
|
+
*/
|
|
4448
|
+
#withAnnotationsFromMeta(configure, terminalName) {
|
|
4449
|
+
if (configure === void 0) return this;
|
|
4450
|
+
const meta = createMetaBuilder("read", terminalName);
|
|
4451
|
+
configure(meta);
|
|
4452
|
+
if (meta.annotations.size === 0) return this;
|
|
4453
|
+
const next = new Map(this.state.annotations);
|
|
4454
|
+
for (const [namespace, value] of meta.annotations) next.set(namespace, value);
|
|
4455
|
+
return blindCast(this.#clone({ annotations: next }));
|
|
4456
|
+
}
|
|
4457
|
+
/**
|
|
4458
|
+
* Invokes the user-supplied configurator (if any) against a freshly
|
|
4459
|
+
* constructed meta builder of the given operation kind, and returns
|
|
4460
|
+
* the recorded annotation map (or `undefined` when empty). Used by
|
|
4461
|
+
* terminals where annotations don't flow through `state` — the
|
|
4462
|
+
* compiled plan is post-wrapped via `mergeAnnotations` instead.
|
|
4463
|
+
* Read terminals `all` and `first` populate `state.annotations`
|
|
4464
|
+
* via `#withAnnotationsFromMeta` instead; `aggregate` uses this
|
|
4465
|
+
* post-wrap path because its compile function doesn't take `state`.
|
|
4466
|
+
* The meta builder's `annotate` method enforces applicability at the
|
|
4467
|
+
* type level and at runtime.
|
|
4468
|
+
*/
|
|
4469
|
+
#collectAnnotationsFromMeta(configure, kind, terminalName) {
|
|
4470
|
+
if (configure === void 0) return;
|
|
4471
|
+
const meta = createMetaBuilder(kind, terminalName);
|
|
4472
|
+
configure(meta);
|
|
4473
|
+
return meta.annotations.size === 0 ? void 0 : meta.annotations;
|
|
4474
|
+
}
|
|
4475
|
+
};
|
|
4476
|
+
function orm(options) {
|
|
4477
|
+
const { runtime, collections, context } = options;
|
|
4478
|
+
const contract = context.contract;
|
|
4479
|
+
const ctx = {
|
|
4480
|
+
runtime,
|
|
4481
|
+
context
|
|
4482
|
+
};
|
|
4483
|
+
const collectionRegistry = createCollectionRegistry(contract, collections);
|
|
4484
|
+
function buildCollection(namespaceId, modelName, tableName) {
|
|
4485
|
+
return new (blindCast(collectionRegistry.get(modelName) ?? Collection))(ctx, modelName, {
|
|
4486
|
+
registry: collectionRegistry,
|
|
4487
|
+
namespaceId,
|
|
4488
|
+
...tableName !== void 0 ? { tableName } : {}
|
|
4489
|
+
});
|
|
4490
|
+
}
|
|
4491
|
+
const namespaceFacets = /* @__PURE__ */ new Map();
|
|
4492
|
+
function namespaceFacet(namespaceId) {
|
|
4493
|
+
const cached = namespaceFacets.get(namespaceId);
|
|
4494
|
+
if (cached) return cached;
|
|
4495
|
+
const facetModelNames = new Set(domainModelNamesInNamespace(contract, namespaceId));
|
|
4496
|
+
const facetCache = /* @__PURE__ */ new Map();
|
|
4497
|
+
const facet = new Proxy({}, { get(_facetTarget, modelProp) {
|
|
4498
|
+
if (typeof modelProp !== "string") return;
|
|
4499
|
+
if (!facetModelNames.has(modelProp)) return;
|
|
4500
|
+
const hit = facetCache.get(modelProp);
|
|
4501
|
+
if (hit) return hit;
|
|
4502
|
+
const collection = buildCollection(namespaceId, modelProp, domainModelTableInNamespace(contract, namespaceId, modelProp));
|
|
4503
|
+
facetCache.set(modelProp, collection);
|
|
4504
|
+
return collection;
|
|
4505
|
+
} });
|
|
4506
|
+
namespaceFacets.set(namespaceId, facet);
|
|
4507
|
+
return facet;
|
|
4508
|
+
}
|
|
4509
|
+
return new Proxy({}, { get(_target, prop) {
|
|
4510
|
+
if (typeof prop !== "string") return;
|
|
4511
|
+
if (!Object.hasOwn(contract.domain.namespaces, prop)) return;
|
|
4512
|
+
return namespaceFacet(prop);
|
|
4513
|
+
} });
|
|
4514
|
+
}
|
|
4515
|
+
function createCollectionRegistry(contract, collections) {
|
|
4516
|
+
const registry = /* @__PURE__ */ new Map();
|
|
4517
|
+
if (!collections) return registry;
|
|
4518
|
+
const models = domainModelsAtDefaultNamespace(contract.domain);
|
|
4519
|
+
for (const [key, collectionClass] of Object.entries(collections)) {
|
|
4520
|
+
if (!collectionClass) continue;
|
|
4521
|
+
if (!isCollectionClass(collectionClass)) throw ormError("ORM.ARGUMENT_INVALID", `Custom collection '${key}' must be a Collection class (constructor), not an instance`, { meta: {
|
|
4522
|
+
method: "orm",
|
|
4523
|
+
argument: "collections",
|
|
4524
|
+
key
|
|
4525
|
+
} });
|
|
4526
|
+
if (!Object.hasOwn(models, key)) throw ormError("ORM.ARGUMENT_INVALID", `No model found for custom collection '${key}'. Available models: ${Object.keys(models).join(", ")}`, { meta: {
|
|
4527
|
+
method: "orm",
|
|
4528
|
+
argument: "collections",
|
|
4529
|
+
key
|
|
4530
|
+
} });
|
|
4531
|
+
registry.set(key, collectionClass);
|
|
4532
|
+
}
|
|
4533
|
+
return registry;
|
|
4534
|
+
}
|
|
4535
|
+
function isCollectionClass(value) {
|
|
4536
|
+
if (typeof value !== "function") return false;
|
|
4537
|
+
const candidate = value;
|
|
4538
|
+
if (!candidate.prototype || typeof candidate.prototype !== "object") return false;
|
|
4539
|
+
return candidate.prototype instanceof Collection;
|
|
4540
|
+
}
|
|
4541
|
+
//#endregion
|
|
4542
|
+
export { Collection, GroupedCollection, all, and, createModelAccessor, emptyState, not, or, orm };
|
|
4543
|
+
|
|
4544
|
+
//# sourceMappingURL=orm-client.mjs.map
|