Skip to content

Commit

Permalink
fix(core): Fix EntityHydrator error on long table names (#2959)
Browse files Browse the repository at this point in the history
Fixes #2899
  • Loading branch information
jnugh authored Aug 14, 2024
1 parent 1edb5e1 commit bcfcf7d
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 6 deletions.
49 changes: 47 additions & 2 deletions packages/core/e2e/entity-hydrator.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { initialData } from '../../../e2e-common/e2e-initial-data';
import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';

import { AdditionalConfig, HydrationTestPlugin } from './fixtures/test-plugins/hydration-test-plugin';
import {
AdditionalConfig,
HydrationTestPlugin,
TreeEntity,
} from './fixtures/test-plugins/hydration-test-plugin';
import { UpdateChannelMutation, UpdateChannelMutationVariables } from './graphql/generated-e2e-admin-types';
import {
AddItemToOrderDocument,
Expand Down Expand Up @@ -54,11 +58,17 @@ describe('Entity hydration', () => {

const connection = server.app.get(TransactionalConnection).rawConnection;
const asset = await connection.getRepository(Asset).findOne({ where: {} });
await connection.getRepository(AdditionalConfig).save(
const additionalConfig = await connection.getRepository(AdditionalConfig).save(
new AdditionalConfig({
backgroundImage: asset,
}),
);
const parent = await connection
.getRepository(TreeEntity)
.save(new TreeEntity({ additionalConfig, image1: asset, image2: asset }));
await connection
.getRepository(TreeEntity)
.save(new TreeEntity({ parent, image1: asset, image2: asset }));
}, TEST_SETUP_TIMEOUT_MS);

afterAll(async () => {
Expand Down Expand Up @@ -382,6 +392,35 @@ describe('Entity hydration', () => {
expect(line.productVariantId).toBe(line.productVariant.id);
}
});

/*
* Postgres has a character limit for alias names which can cause issues when joining
* multiple aliases with the same prefix
* https://github.com/vendure-ecommerce/vendure/issues/2899
*/
it('Hydrates properties with very long names', async () => {
await adminClient.query<UpdateChannelMutation, UpdateChannelMutationVariables>(UPDATE_CHANNEL, {
input: {
id: 'T_1',
customFields: {
additionalConfigId: 'T_1',
},
},
});

const { hydrateChannelWithVeryLongPropertyName } = await adminClient.query<{
hydrateChannelWithVeryLongPropertyName: any;
}>(GET_HYDRATED_CHANNEL_LONG_ALIAS, {
id: 'T_1',
});

const entity = (
hydrateChannelWithVeryLongPropertyName.customFields.additionalConfig as AdditionalConfig
).treeEntity[0];
const child = entity.childrenPropertyWithAVeryLongNameThatExceedsPostgresLimitsEasilyByItself[0];
expect(child.image1).toBeDefined();
expect(child.image2).toBeDefined();
});
});

function getVariantWithName(product: Product, name: string) {
Expand Down Expand Up @@ -432,3 +471,9 @@ const GET_HYDRATED_CHANNEL_NESTED = gql`
hydrateChannelWithNestedRelation(id: $id)
}
`;

const GET_HYDRATED_CHANNEL_LONG_ALIAS = gql`
query GetHydratedChannelNested($id: ID!) {
hydrateChannelWithVeryLongPropertyName(id: $id)
}
`;
60 changes: 58 additions & 2 deletions packages/core/e2e/fixtures/test-plugins/hydration-test-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
VendurePlugin,
} from '@vendure/core';
import gql from 'graphql-tag';
import { Entity, ManyToOne } from 'typeorm';
import { Entity, ManyToOne, OneToMany } from 'typeorm';

@Resolver()
export class TestAdminPluginResolver {
Expand Down Expand Up @@ -141,6 +141,30 @@ export class TestAdminPluginResolver {
});
return channel;
}

@Query()
async hydrateChannelWithVeryLongPropertyName(@Ctx() ctx: RequestContext, @Args() args: { id: ID }) {
const channel = await this.channelService.findOne(ctx, args.id);
await this.entityHydrator.hydrate(ctx, channel!, {
relations: ['customFields.additionalConfig.treeEntity'],
});

// Make sure we start on a tree entity to make use of tree-relations-qb-joiner.ts
await Promise.all(
((channel!.customFields as any).additionalConfig.treeEntity as TreeEntity[]).map(treeEntity =>
this.entityHydrator.hydrate(ctx, treeEntity, {
relations: [
'childrenPropertyWithAVeryLongNameThatExceedsPostgresLimitsEasilyByItself',
'childrenPropertyWithAVeryLongNameThatExceedsPostgresLimitsEasilyByItself',
'childrenPropertyWithAVeryLongNameThatExceedsPostgresLimitsEasilyByItself.image1',
'childrenPropertyWithAVeryLongNameThatExceedsPostgresLimitsEasilyByItself.image2',
],
}),
),
);

return channel;
}
}

@Entity()
Expand All @@ -151,11 +175,42 @@ export class AdditionalConfig extends VendureEntity {

@ManyToOne(() => Asset, { onDelete: 'SET NULL', nullable: true })
backgroundImage: Asset;

@OneToMany(() => TreeEntity, entity => entity.additionalConfig)
treeEntity: TreeEntity[];
}

@Entity()
export class TreeEntity extends VendureEntity {
constructor(input?: DeepPartial<TreeEntity>) {
super(input);
}

@ManyToOne(() => AdditionalConfig, e => e.treeEntity, { nullable: true })
additionalConfig: AdditionalConfig;

@OneToMany(() => TreeEntity, entity => entity.parent)
childrenPropertyWithAVeryLongNameThatExceedsPostgresLimitsEasilyByItself: TreeEntity[];

@ManyToOne(
() => TreeEntity,
entity => entity.childrenPropertyWithAVeryLongNameThatExceedsPostgresLimitsEasilyByItself,
{
nullable: true,
},
)
parent: TreeEntity;

@ManyToOne(() => Asset)
image1: Asset;

@ManyToOne(() => Asset)
image2: Asset;
}

@VendurePlugin({
imports: [PluginCommonModule],
entities: [AdditionalConfig],
entities: [AdditionalConfig, TreeEntity],
adminApiExtensions: {
resolvers: [TestAdminPluginResolver],
schema: gql`
Expand All @@ -168,6 +223,7 @@ export class AdditionalConfig extends VendureEntity {
hydrateOrderReturnQuantities(id: ID!): JSON
hydrateChannel(id: ID!): JSON
hydrateChannelWithNestedRelation(id: ID!): JSON
hydrateChannelWithVeryLongPropertyName(id: ID!): JSON
}
`,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EntityMetadata, FindOneOptions, SelectQueryBuilder } from 'typeorm';
import { EntityTarget } from 'typeorm/common/EntityTarget';
import { FindOptionsRelationByString, FindOptionsRelations } from 'typeorm/find-options/FindOptionsRelations';
import { DriverUtils } from 'typeorm/driver/DriverUtils';

import { findOptionsObjectToArray } from '../../../connection/find-options-object-to-array';
import { VendureEntity } from '../../../entity';
Expand Down Expand Up @@ -108,7 +108,12 @@ export function joinTreeRelationsDynamically<T extends VendureEntity>(
if (relationMetadata.isEager) {
joinConnector = '__';
}
const nextAlias = `${currentAlias}${joinConnector}${part.replace(/\./g, '_')}`;
const nextAlias = DriverUtils.buildAlias(
qb.connection.driver,
{ shorten: false },
currentAlias,
part.replace(/\./g, '_'),
);
const nextPath = parts.join('.');
const fullPath = [...(parentPath || []), part].join('.');
if (!qb.expressionMap.joinAttributes.some(ja => ja.alias.name === nextAlias)) {
Expand Down

0 comments on commit bcfcf7d

Please sign in to comment.