* Builds a UNION ALL (or UNION) subquery from `unionWhere` branches and merges it * into the main WHERE as `pk IN (branch_1 UNION ALL branch_2 ...)`. * Each branch is planned independently by the database, enabling per-table index usage.
(
meta: EntityMetadata<T>,
where: ObjectQuery<T>,
options: FindOptions<T, any, any, any> | CountOptions<T> | NativeInsertUpdateOptions<T> | DeleteOptions<T>,
forDml = false,
)
| 3138 | * Each branch is planned independently by the database, enabling per-table index usage. |
| 3139 | */ |
| 3140 | protected async applyUnionWhere<T extends object>( |
| 3141 | meta: EntityMetadata<T>, |
| 3142 | where: ObjectQuery<T>, |
| 3143 | options: FindOptions<T, any, any, any> | CountOptions<T> | NativeInsertUpdateOptions<T> | DeleteOptions<T>, |
| 3144 | forDml = false, |
| 3145 | ): Promise<ObjectQuery<T>> { |
| 3146 | const unionWhere = (options as FindOptions<T>).unionWhere!; |
| 3147 | const strategy = (options as FindOptions<T>).unionWhereStrategy ?? 'union-all'; |
| 3148 | const schema = this.getSchemaName(meta, options); |
| 3149 | const connectionType = this.resolveConnectionType({ |
| 3150 | ctx: options.ctx, |
| 3151 | connectionType: (options as FindOptions<T>).connectionType, |
| 3152 | }); |
| 3153 | |
| 3154 | const branchQbs: QueryBuilder<any>[] = []; |
| 3155 | |
| 3156 | for (const branch of unionWhere) { |
| 3157 | const qb = this.createQueryBuilder<T>( |
| 3158 | meta.class, |
| 3159 | options.ctx, |
| 3160 | connectionType, |
| 3161 | false, |
| 3162 | (options as FindOptions<T>).logging, |
| 3163 | ).withSchema(schema); |
| 3164 | |
| 3165 | const pkFields = meta.primaryKeys.map(pk => { |
| 3166 | const prop = meta.properties[pk]; |
| 3167 | return `${qb.alias}.${prop.fieldNames[0]}`; |
| 3168 | }); |
| 3169 | |
| 3170 | qb.select(pkFields as any).where(branch as any); |
| 3171 | |
| 3172 | if (options.em) { |
| 3173 | await qb.applyJoinedFilters(options.em, options.filters); |
| 3174 | } |
| 3175 | |
| 3176 | branchQbs.push(qb); |
| 3177 | } |
| 3178 | |
| 3179 | const [first, ...rest] = branchQbs; |
| 3180 | const unionQb = strategy === 'union' ? first.union(...rest) : first.unionAll(...rest); |
| 3181 | const pkHash = Utils.getPrimaryKeyHash(meta.primaryKeys); |
| 3182 | |
| 3183 | // MySQL does not allow referencing the target table in a subquery |
| 3184 | // for UPDATE/DELETE, so we wrap the union in a derived table. |
| 3185 | if (forDml) { |
| 3186 | const { sql, params } = unionQb.toQuery(); |
| 3187 | return { |
| 3188 | $and: [where, { [pkHash]: { $in: raw(`select * from (${sql}) as __u`, params) } }], |
| 3189 | } as ObjectQuery<T>; |
| 3190 | } |
| 3191 | |
| 3192 | return { |
| 3193 | $and: [where, { [pkHash]: { $in: unionQb.toRaw() } }], |
| 3194 | } as ObjectQuery<T>; |
| 3195 | } |
| 3196 | |
| 3197 | protected buildOrderBy<T extends object>( |
nothing calls this directly
no test coverage detected