From c36a360710a47764afa955d06f5ec62549421d00 Mon Sep 17 00:00:00 2001 From: carathorys Date: Fri, 28 Jun 2024 13:45:32 +0200 Subject: [PATCH] fix(core): Update relationLoadStrategy --- .../indexer/indexer.controller.ts | 235 ++++++++++-------- 1 file changed, 126 insertions(+), 109 deletions(-) diff --git a/packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts b/packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts index 7cd4d2a3e2..92fcca37af 100644 --- a/packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts +++ b/packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts @@ -4,7 +4,7 @@ import { ID } from '@vendure/common/lib/shared-types'; import { notNullOrUndefined } from '@vendure/common/lib/shared-utils'; import { unique } from '@vendure/common/lib/unique'; import { Observable } from 'rxjs'; -import { FindManyOptions, FindOptionsRelations, FindOptionsWhere, In, IsNull } from 'typeorm'; +import { FindManyOptions, In } from 'typeorm'; import { RequestContext } from '../../../api/common/request-context'; import { RequestContextCacheService } from '../../../cache/request-context-cache.service'; @@ -39,28 +39,6 @@ import { import { MutableRequestContext } from './mutable-request-context'; export const BATCH_SIZE = 1000; -export const productRelations = [ - 'translations', - 'featuredAsset', - 'facetValues', - 'facetValues.facet', - 'variants', - 'channels', -]; -export const variantRelations = [ - 'translations', - 'taxCategory', - 'productVariantPrices', - 'channels', - 'channels.defaultTaxZone', - 'product', - 'product.translations', - 'product.channels', - 'facetValues', - 'facetValues.facet', - 'collections', - 'collections.translations', -]; export const workerLoggerCtx = 'DefaultSearchPlugin Worker'; @@ -84,12 +62,8 @@ export class IndexerController { return asyncObservable(async observer => { const timeStart = Date.now(); const channel = ctx.channel ?? (await this.loadChannel(ctx, ctx.channelId)); - const [, count] = await this.getSearchIndexQueryBuilder( - ctx, - undefined, - { take: 1, skip: 0 }, - channel, - ); + const qb = this.getSearchIndexQueryBuilder(ctx, channel); + const count = await qb.getCount(); Logger.verbose(`Reindexing ${count} variants for channel ${ctx.channel.code}`, workerLoggerCtx); const batches = Math.ceil(count / BATCH_SIZE); @@ -101,12 +75,10 @@ export class IndexerController { throw new Error('reindex job was cancelled'); } Logger.verbose(`Processing batch ${i + 1} of ${batches}`, workerLoggerCtx); - const [variants] = await this.getSearchIndexQueryBuilder( - ctx, - undefined, - { take: BATCH_SIZE, skip: i * BATCH_SIZE }, - channel, - ); + const variants = await qb + .take(BATCH_SIZE) + .skip(i * BATCH_SIZE) + .getMany(); await this.saveVariants(ctx, variants); observer.next({ total: count, @@ -142,12 +114,12 @@ export class IndexerController { const end = begin + BATCH_SIZE; Logger.verbose(`Updating ids from index ${begin} to ${end}`); const batchIds = ids.slice(begin, end); - const [batch] = await this.getSearchIndexQueryBuilder( + const batch = await this.getSearchIndexQueryBuilder( ctx, - { id: In(batchIds) }, - undefined, ...(await this.getAllChannels(ctx)), - ); + ) + .where('variants.deletedAt IS NULL AND variants.id IN (:...ids)', { ids: batchIds }) + .getMany(); await this.saveVariants(ctx, batch); observer.next({ @@ -263,19 +235,19 @@ export class IndexerController { ): Promise { const channel = await this.loadChannel(ctx, channelId); ctx.setChannel(channel); - const product = await this.getProductInChannelQueryBuilder(ctx, productId, undefined, channel); + const product = await this.getProductInChannelQueryBuilder(ctx, productId, channel).getOneOrFail(); if (product) { const affectedChannels = await this.getAllChannels(ctx, { where: { availableLanguageCodes: In(product.translations.map(t => t.languageCode)), }, }); - const [updatedVariants] = await this.getSearchIndexQueryBuilder( + const updatedVariants = await this.getSearchIndexQueryBuilder( ctx, - { productId }, - undefined, ...unique(affectedChannels.concat(channel)), - ); + ) + .andWhere('variants.productId = :productId', { productId }) + .getMany(); if (updatedVariants.length === 0) { const clone = new Product({ id: product.id }); await this.entityHydrator.hydrate(ctx, clone, { relations: ['translations' as never] }); @@ -305,12 +277,9 @@ export class IndexerController { ): Promise { const channel = await this.loadChannel(ctx, channelId); ctx.setChannel(channel); - const [variants] = await this.getSearchIndexQueryBuilder( - ctx, - { id: In(variantIds) }, - undefined, - channel, - ); + const variants = await this.getSearchIndexQueryBuilder(ctx, channel) + .andWhere('variants.deletedAt IS NULL AND variants.id IN (:...variantIds)', { variantIds }) + .getMany(); if (variants) { Logger.verbose(`Updating ${variants.length} variants`, workerLoggerCtx); @@ -325,7 +294,10 @@ export class IndexerController { channelIds: ID[], ): Promise { const channels = await Promise.all(channelIds.map(channelId => this.loadChannel(ctx, channelId))); - const product = await this.getProductInChannelQueryBuilder(ctx, productId, undefined, ...channels); + const product = await this.getProductInChannelQueryBuilder(ctx, productId, ...channels) + .leftJoinAndSelect('product.variants', 'variants') + .leftJoinAndSelect('variants.translations', 'variants_translations') + .getOne(); if (product) { const removedVariantIds = product.variants.map(v => v.id); @@ -347,78 +319,124 @@ export class IndexerController { return await this.connection.getRepository(ctx, Channel).find(options); } - private async getSearchIndexQueryBuilder( - ctx: RequestContext, - additionalFilters?: FindOptionsWhere, - { take, skip }: { take?: number; skip?: number } = {}, - ...channels: Channel[] - ) { + private getSearchIndexQueryBuilder(ctx: RequestContext, ...channels: Channel[]) { const channelLanguages = unique( channels.flatMap(c => c.availableLanguageCodes).concat(this.configService.defaultLanguageCode), ); - const qb = await this.connection.getRepository(ctx, ProductVariant).findAndCount({ - loadEagerRelations: false, - relations: variantRelations, - where: { - ...additionalFilters, - deletedAt: IsNull(), - channels: { - id: In(channels.map(x => x.id)), + const qb = this.connection + .getRepository(ctx, ProductVariant) + .createQueryBuilder('variants') + .setFindOptions({ + loadEagerRelations: false, + relationLoadStrategy: 'query', + }) + .leftJoinAndSelect( + 'variants.channels', + 'variant_channels', + 'variant_channels.id IN (:...channelId)', + { + channelId: channels.map(x => x.id), }, - collections: { - channels: { - id: In(channels.map(x => x.id)), - }, - translations: { - languageCode: In(channelLanguages), - }, + ) + .leftJoinAndSelect('variant_channels.defaultTaxZone', 'variant_channel_tax_zone') + .leftJoinAndSelect('variants.taxCategory', 'variant_tax_category') + .leftJoinAndSelect( + 'variants.productVariantPrices', + 'product_variant_prices', + 'product_variant_prices.channelId IN (:...channelId)', + { channelId: channels.map(x => x.id) }, + ) + .leftJoinAndSelect( + 'variants.translations', + 'product_variant_translation', + 'product_variant_translation.baseId = variants.id AND product_variant_translation.languageCode IN (:...channelLanguages)', + { + channelLanguages, }, - translations: { - languageCode: In(channelLanguages), + ) + .leftJoin('variants.product', 'product') + .leftJoinAndSelect('variants.facetValues', 'variant_facet_values') + .leftJoinAndSelect( + 'variant_facet_values.translations', + 'variant_facet_value_translations', + 'variant_facet_value_translations.languageCode IN (:...channelLanguages)', + { + channelLanguages, }, - productVariantPrices: { - channelId: In(channels.map(x => x.id)), + ) + .leftJoinAndSelect('variant_facet_values.facet', 'facet_values_facet') + .leftJoinAndSelect( + 'facet_values_facet.translations', + 'facet_values_facet_translations', + 'facet_values_facet_translations.languageCode IN (:...channelLanguages)', + { + channelLanguages, }, - product: { - deletedAt: IsNull(), - translations: { - languageCode: In(channelLanguages), - }, + ) + .leftJoinAndSelect('variants.collections', 'collections') + .leftJoinAndSelect( + 'collections.channels', + 'collection_channels', + 'collection_channels.id IN (:...channelId)', + { channelId: channels.map(x => x.id) }, + ) + .leftJoinAndSelect( + 'collections.translations', + 'collection_translations', + 'collection_translations.languageCode IN (:...channelLanguages)', + { + channelLanguages, }, - }, - take, - skip, - relationLoadStrategy: 'query', - }); + ) + .leftJoin('product.channels', 'channel') + .where('channel.id IN (:...channelId)', { channelId: channels.map(x => x.id) }) + .andWhere('product.deletedAt IS NULL') + .andWhere('variants.deletedAt IS NULL'); return qb; } - private getProductInChannelQueryBuilder( - ctx: RequestContext, - productId: ID, - additionalFilters?: FindOptionsWhere, - ...channels: Channel[] - ) { + private getProductInChannelQueryBuilder(ctx: RequestContext, productId: ID, ...channels: Channel[]) { const channelLanguages = unique( channels.flatMap(c => c.availableLanguageCodes).concat(this.configService.defaultLanguageCode), ); - - return this.connection.getRepository(ctx, Product).findOneOrFail({ - loadEagerRelations: false, - relations: productRelations, - relationLoadStrategy: 'query', - where: { - ...additionalFilters, - id: productId, - deletedAt: IsNull(), - channels: { - id: In(channels.map(x => x.id)), + return this.connection + .getRepository(ctx, Product) + .createQueryBuilder('product') + .setFindOptions({ + loadEagerRelations: false, + relationLoadStrategy: 'query', + }) + .leftJoinAndSelect( + 'product.translations', + 'translations', + 'translations.languageCode IN (:...channelLanguages)', + { + channelLanguages, }, - translations: { - languageCode: In(channelLanguages), + ) + .leftJoinAndSelect('product.featuredAsset', 'product_featured_asset') + .leftJoinAndSelect('product.facetValues', 'product_facet_values') + .leftJoinAndSelect( + 'product_facet_values.translations', + 'product_facet_value_translations', + 'product_facet_value_translations.languageCode IN (:...channelLanguages)', + { + channelLanguages, }, - }, - }); + ) + .leftJoinAndSelect('product_facet_values.facet', 'product_facet') + .leftJoinAndSelect( + 'product_facet.translations', + 'product_facet_translations', + 'product_facet_translations.languageCode IN (:...channelLanguages)', + { + channelLanguages, + }, + ) + .leftJoinAndSelect('product.channels', 'channel', 'channel.id IN (:...channelId)', { + channelId: channels.map(x => x.id), + }) + .where('product.id = :productId', { productId }); } private async saveVariants(ctx: MutableRequestContext, variants: ProductVariant[]) { @@ -433,9 +451,8 @@ export class IndexerController { product = await this.getProductInChannelQueryBuilder( ctx, variant.productId, - undefined, ctx.channel, - ); + ).getOneOrFail(); productMap.set(variant.productId, product); } const availableLanguageCodes = unique(ctx.channel.availableLanguageCodes);