From 4736c58da5de67f6868b4552351a093e67715e4b Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Thu, 18 Sep 2025 17:50:10 +0200 Subject: [PATCH] fix: Prevent promotion filtering to exceed psql limits (#13540) * fix(): Prevent promotion filtering to exceed psql limits * Create sour-rockets-grin.md --- .changeset/sour-rockets-grin.md | 5 +++ .../promotion-module/compute-actions.spec.ts | 2 +- .../src/services/promotion-module.ts | 5 ++- ...romotion-rule-query-filter-from-context.ts | 37 +++++++++++++++++-- 4 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 .changeset/sour-rockets-grin.md diff --git a/.changeset/sour-rockets-grin.md b/.changeset/sour-rockets-grin.md new file mode 100644 index 0000000000..3959693087 --- /dev/null +++ b/.changeset/sour-rockets-grin.md @@ -0,0 +1,5 @@ +--- +"@medusajs/promotion": patch +--- + +fix(): Prevent promotion filtering to exceed psql limits diff --git a/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts b/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts index b14d9fe9a9..f826b70396 100644 --- a/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts +++ b/packages/modules/promotion/integration-tests/__tests__/services/promotion-module/compute-actions.spec.ts @@ -353,7 +353,7 @@ moduleIntegrationTestRunner({ value: 100, target_rules: [ { - attribute: "product.id", + attribute: "items.product.id", operator: "eq", values: ["prod_tshirt0"], // Only applies to product 0 }, diff --git a/packages/modules/promotion/src/services/promotion-module.ts b/packages/modules/promotion/src/services/promotion-module.ts index 03e81d1cf2..241935f9bd 100644 --- a/packages/modules/promotion/src/services/promotion-module.ts +++ b/packages/modules/promotion/src/services/promotion-module.ts @@ -466,7 +466,10 @@ export default class PromotionModuleService if (!preventAutoPromotions) { const rulePrefilteringFilters = - buildPromotionRuleQueryFilterFromContext(applicationContext) + await buildPromotionRuleQueryFilterFromContext( + applicationContext, + sharedContext + ) let prefilteredAutomaticPromotionIds: string[] = [] diff --git a/packages/modules/promotion/src/utils/compute-actions/build-promotion-rule-query-filter-from-context.ts b/packages/modules/promotion/src/utils/compute-actions/build-promotion-rule-query-filter-from-context.ts index 5ad1dba346..6c3fe1e0ff 100644 --- a/packages/modules/promotion/src/utils/compute-actions/build-promotion-rule-query-filter-from-context.ts +++ b/packages/modules/promotion/src/utils/compute-actions/build-promotion-rule-query-filter-from-context.ts @@ -2,11 +2,12 @@ import { ComputeActionContext, ComputeActionItemLine, ComputeActionShippingLine, + Context, DAL, PromotionTypes, } from "@medusajs/framework/types" import { flattenObjectToKeyValuePairs } from "@medusajs/framework/utils" -import { raw } from "@mikro-orm/postgresql" +import { raw, SqlEntityManager } from "@mikro-orm/postgresql" /** * Builds a query filter for promotion rules based on the context. @@ -17,9 +18,10 @@ import { raw } from "@mikro-orm/postgresql" * @param context * @returns */ -export function buildPromotionRuleQueryFilterFromContext( - context: PromotionTypes.ComputeActionContext -): DAL.FilterQuery | null { +export async function buildPromotionRuleQueryFilterFromContext( + context: PromotionTypes.ComputeActionContext, + sharedContext: Context +): Promise | null> { const { items = [], shipping_methods: shippingMethods = [], @@ -67,6 +69,33 @@ export function buildPromotionRuleQueryFilterFromContext( }) }) + // count the number of attributes in the map + const numberOfAttributes = attributeValueMap.size + if (numberOfAttributes > 10) { + const manager = (sharedContext.transactionManager ?? + sharedContext.manager) as SqlEntityManager + const knex = manager.getKnex() + + const { rows } = await knex.raw( + ` + SELECT DISTINCT attribute + FROM promotion_rule + WHERE deleted_at IS NULL + ` + ) + + const dbAvailableAttributes = new Set( + rows.map(({ attribute }) => attribute) + ) + + // update the attribute in the map to remove the one that are not in the db + attributeValueMap.forEach((valueSet, attribute) => { + if (!dbAvailableAttributes.has(attribute)) { + attributeValueMap.delete(attribute) + } + }) + } + // Build conditions for a NOT EXISTS subquery to exclude promotions with unsatisfiable rules const sqlConditions: string[] = []