diff --git a/packages/query-typeorm/.eslintrc.js b/packages/query-typeorm/.eslintrc.js index 5d0af5eca..d625d1781 100644 --- a/packages/query-typeorm/.eslintrc.js +++ b/packages/query-typeorm/.eslintrc.js @@ -20,6 +20,8 @@ module.exports = { 'assertUpdateSQL', 'assertDeleteSQL', 'assertSoftDeleteSQL', + 'assertManyToOneUniDirectionalSQL', + 'assertManyToManyUniDirectionalSQL' ], }, ], diff --git a/packages/query-typeorm/__tests__/__fixtures__/seeds.ts b/packages/query-typeorm/__tests__/__fixtures__/seeds.ts index 198539a12..a6f071a2c 100644 --- a/packages/query-typeorm/__tests__/__fixtures__/seeds.ts +++ b/packages/query-typeorm/__tests__/__fixtures__/seeds.ts @@ -29,16 +29,19 @@ export const TEST_RELATIONS: TestRelation[] = TEST_ENTITIES.reduce((relations, t testRelationPk: `test-relations-${te.testEntityPk}-1`, relationName: `${te.stringType}-test-relation-one`, testEntityId: te.testEntityPk, + uniDirectionalTestEntityId: te.testEntityPk, }, { testRelationPk: `test-relations-${te.testEntityPk}-2`, relationName: `${te.stringType}-test-relation-two`, testEntityId: te.testEntityPk, + uniDirectionalTestEntityId: te.testEntityPk, }, { testRelationPk: `test-relations-${te.testEntityPk}-3`, relationName: `${te.stringType}-test-relation-three`, testEntityId: te.testEntityPk, + uniDirectionalTestEntityId: te.testEntityPk, }, ]; }, [] as TestRelation[]); @@ -56,6 +59,14 @@ export const seed = async (connection: Connection = getConnection()): Promise { // eslint-disable-next-line no-param-reassign te.oneTestRelation = testRelations.find((tr) => tr.testRelationPk === `test-relations-${te.testEntityPk}-1`); + if (te.numberType % 2 === 0) { + // eslint-disable-next-line no-param-reassign + te.manyTestRelations = testRelations.filter((tr) => tr.relationName.endsWith('two')); + } + if (te.numberType % 3 === 0) { + // eslint-disable-next-line no-param-reassign + te.manyToManyUniDirectional = testRelations.filter((tr) => tr.relationName.endsWith('three')); + } return testEntityRepo.save(te); }), ); diff --git a/packages/query-typeorm/__tests__/__fixtures__/test-relation.entity.ts b/packages/query-typeorm/__tests__/__fixtures__/test-relation.entity.ts index bd721071a..704353d86 100644 --- a/packages/query-typeorm/__tests__/__fixtures__/test-relation.entity.ts +++ b/packages/query-typeorm/__tests__/__fixtures__/test-relation.entity.ts @@ -13,10 +13,17 @@ export class TestRelation { @Column({ name: 'test_entity_id', nullable: true }) testEntityId?: string; + @Column({ name: 'uni_directional_test_entity_id', nullable: true }) + uniDirectionalTestEntityId?: string; + @ManyToOne(() => TestEntity, (te) => te.testRelations, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'test_entity_id' }) testEntity?: TestEntity; + @ManyToOne(() => TestEntity, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'uni_directional_test_entity_id' }) + testEntityUniDirectional?: TestEntity; + @ManyToMany(() => TestEntity, (te) => te.manyTestRelations, { onDelete: 'CASCADE', nullable: false }) manyTestEntities?: TestEntity[]; diff --git a/packages/query-typeorm/__tests__/__fixtures__/test.entity.ts b/packages/query-typeorm/__tests__/__fixtures__/test.entity.ts index 1115bb73a..632d46693 100644 --- a/packages/query-typeorm/__tests__/__fixtures__/test.entity.ts +++ b/packages/query-typeorm/__tests__/__fixtures__/test.entity.ts @@ -26,6 +26,10 @@ export class TestEntity { @JoinTable() manyTestRelations?: TestRelation[]; + @ManyToMany(() => TestRelation, { onDelete: 'CASCADE', nullable: false }) + @JoinTable() + manyToManyUniDirectional?: TestRelation[]; + @OneToOne(() => TestRelation, (relation) => relation.oneTestEntity) @JoinColumn() oneTestRelation?: TestRelation; diff --git a/packages/query-typeorm/__tests__/query/relation-query.builder.spec.ts b/packages/query-typeorm/__tests__/query/relation-query.builder.spec.ts index 440d5fa09..087570188 100644 --- a/packages/query-typeorm/__tests__/query/relation-query.builder.spec.ts +++ b/packages/query-typeorm/__tests__/query/relation-query.builder.spec.ts @@ -21,6 +21,17 @@ describe('RelationQueryBuilder', (): void => { ` INNER JOIN "test_relation" "TestRelation" ON "TestRelation"."test_entity_id" = "testEntity"."test_entity_pk"` + ` WHERE ("TestRelation"."test_relation_pk" = ?)`; + const manyToOneSelectUniDirectional = + 'SELECT "testEntityUniDirectional"."test_entity_pk" AS "testEntityUniDirectional_test_entity_pk",' + + ' "testEntityUniDirectional"."string_type" AS "testEntityUniDirectional_string_type",' + + ' "testEntityUniDirectional"."bool_type" AS "testEntityUniDirectional_bool_type",' + + ' "testEntityUniDirectional"."number_type" AS "testEntityUniDirectional_number_type",' + + ' "testEntityUniDirectional"."date_type" AS "testEntityUniDirectional_date_type",' + + ' "testEntityUniDirectional"."oneTestRelationTestRelationPk" AS "testEntityUniDirectional_oneTestRelationTestRelationPk"' + + ' FROM "test_entity" "testEntityUniDirectional"' + + ' INNER JOIN "test_relation" "TestRelation" ON "TestRelation"."uni_directional_test_entity_id" = "testEntityUniDirectional"."test_entity_pk"' + + ' WHERE ("TestRelation"."test_relation_pk" = ?)'; + const manyToManyNonOwnerSelectQuery = `SELECT` + ` "manyTestEntities"."test_entity_pk" AS "manyTestEntities_test_entity_pk",` + @@ -37,7 +48,8 @@ describe('RelationQueryBuilder', (): void => { `SELECT` + ` "testRelations"."test_relation_pk" AS "testRelations_test_relation_pk",` + ` "testRelations"."relation_name" AS "testRelations_relation_name",` + - ` "testRelations"."test_entity_id" AS "testRelations_test_entity_id"` + + ` "testRelations"."test_entity_id" AS "testRelations_test_entity_id",` + + ` "testRelations"."uni_directional_test_entity_id" AS "testRelations_uni_directional_test_entity_id"` + ` FROM "test_relation" "testRelations"` + ' WHERE ("testRelations"."test_entity_id" = ?)'; @@ -45,7 +57,8 @@ describe('RelationQueryBuilder', (): void => { 'SELECT ' + `"manyTestRelations"."test_relation_pk" AS "manyTestRelations_test_relation_pk",` + ' "manyTestRelations"."relation_name" AS "manyTestRelations_relation_name",' + - ' "manyTestRelations"."test_entity_id" AS "manyTestRelations_test_entity_id"' + + ' "manyTestRelations"."test_entity_id" AS "manyTestRelations_test_entity_id",' + + ' "manyTestRelations"."uni_directional_test_entity_id" AS "manyTestRelations_uni_directional_test_entity_id"' + ` FROM "test_relation" "manyTestRelations"` + ` INNER JOIN "test_entity_many_test_relations_test_relation" "test_entity_many_test_relations_test_relation" ON "test_entity_many_test_relations_test_relation"."testRelationTestRelationPk" = "manyTestRelations"."test_relation_pk"` + ' WHERE ("test_entity_many_test_relations_test_relation"."testEntityTestEntityPk" = ?)'; @@ -54,7 +67,8 @@ describe('RelationQueryBuilder', (): void => { `SELECT` + ` "oneTestRelation"."test_relation_pk" AS "oneTestRelation_test_relation_pk",` + ` "oneTestRelation"."relation_name" AS "oneTestRelation_relation_name",` + - ` "oneTestRelation"."test_entity_id" AS "oneTestRelation_test_entity_id"` + + ` "oneTestRelation"."test_entity_id" AS "oneTestRelation_test_entity_id",` + + ` "oneTestRelation"."uni_directional_test_entity_id" AS "oneTestRelation_uni_directional_test_entity_id"` + ` FROM "test_relation" "oneTestRelation"` + ` INNER JOIN "test_entity" "TestEntity" ON "TestEntity"."oneTestRelationTestRelationPk" = "oneTestRelation"."test_relation_pk"` + ' WHERE ("TestEntity"."test_entity_pk" = ?)'; @@ -77,6 +91,16 @@ describe('RelationQueryBuilder', (): void => { ` FROM "test_entity_relation_entity" "testEntityRelation"` + ` WHERE ("testEntityRelation"."test_entity_id" = ?)`; + const manyToManyUniDirectionalSelect = + 'SELECT' + + ' "manyToManyUniDirectional"."test_relation_pk" AS "manyToManyUniDirectional_test_relation_pk",' + + ' "manyToManyUniDirectional"."relation_name" AS "manyToManyUniDirectional_relation_name",' + + ' "manyToManyUniDirectional"."test_entity_id" AS "manyToManyUniDirectional_test_entity_id",' + + ' "manyToManyUniDirectional"."uni_directional_test_entity_id" AS "manyToManyUniDirectional_uni_directional_test_entity_id"' + + ' FROM "test_relation" "manyToManyUniDirectional" ' + + 'INNER JOIN "test_entity_many_to_many_uni_directional_test_relation" "test_entity_many_to_many_uni_directional_test_relation" ON "test_entity_many_to_many_uni_directional_test_relation"."testRelationTestRelationPk" = "manyToManyUniDirectional"."test_relation_pk" ' + + 'WHERE ("test_entity_many_to_many_uni_directional_test_relation"."testEntityTestEntityPk" = ?)'; + const getRelationQueryBuilder = ( EntityClass: Class, relationName: string, @@ -105,6 +129,7 @@ describe('RelationQueryBuilder', (): void => { const assertManyToManyOwnerSQL = createSQLAsserter(TestEntity, manyToManyOwnerSelect); const assertManyToOneSQL = createSQLAsserter(TestRelation, manyToOneSelect); + const assertManyToOneUniDirectionalSQL = createSQLAsserter(TestRelation, manyToOneSelectUniDirectional); const assertManyToManyNonOwnerSQL = createSQLAsserter(TestRelation, manyToManyNonOwnerSelectQuery); @@ -113,6 +138,7 @@ describe('RelationQueryBuilder', (): void => { const assertOneToOneNonOwnerSQL = createSQLAsserter(TestRelation, oneToOneNonOwnerSelect); const assertManyToManyCustomJoinSQL = createSQLAsserter(TestEntity, manyToManyCustomJoinSelect); + const assertManyToManyUniDirectionalSQL = createSQLAsserter(TestEntity, manyToManyUniDirectionalSelect); describe('#select', () => { const testEntity: TestEntity = { @@ -144,6 +170,12 @@ describe('RelationQueryBuilder', (): void => { it('should work with one entity', () => { assertManyToOneSQL(testRelation, 'testEntity', {}, ``, [testRelation.testRelationPk]); }); + + it('should work with a uni-directional relationship', () => { + assertManyToOneUniDirectionalSQL(testRelation, 'testEntityUniDirectional', {}, ``, [ + testRelation.testRelationPk, + ]); + }); }); describe('many to many', () => { @@ -164,6 +196,12 @@ describe('RelationQueryBuilder', (): void => { assertManyToManyCustomJoinSQL(testEntity, 'testEntityRelation', {}, ``, [testEntity.testEntityPk]); }); }); + + describe('uni-directional many to many', () => { + it('should create the correct sql', () => { + assertManyToManyUniDirectionalSQL(testEntity, 'manyToManyUniDirectional', {}, ``, [testEntity.testEntityPk]); + }); + }); }); describe('one to one', () => { diff --git a/packages/query-typeorm/__tests__/services/typeorm-query.service.spec.ts b/packages/query-typeorm/__tests__/services/typeorm-query.service.spec.ts index 8879d7689..53ed58a09 100644 --- a/packages/query-typeorm/__tests__/services/typeorm-query.service.spec.ts +++ b/packages/query-typeorm/__tests__/services/typeorm-query.service.spec.ts @@ -97,6 +97,20 @@ describe('TypeOrmQueryService', (): void => { }); expect(queryResults).toEqual(TEST_RELATIONS.slice(0, 6)); }); + + it('should allow filtering on a uni directional many to one relation', async () => { + const queryService = moduleRef.get(TestRelationService); + const queryResults = await queryService.query({ + filter: { + testEntityUniDirectional: { + testEntityPk: { + in: [TEST_ENTITIES[0].testEntityPk, TEST_ENTITIES[1].testEntityPk], + }, + }, + }, + }); + expect(queryResults).toEqual(TEST_RELATIONS.slice(0, 6)); + }); }); describe('oneToMany', () => { @@ -115,6 +129,42 @@ describe('TypeOrmQueryService', (): void => { expect(queryResult).toEqual([entity]); }); }); + + describe('manyToMany', () => { + it('should allow filtering on a many to many relation', async () => { + const queryService = moduleRef.get(TestEntityService); + const queryResult = await queryService.query({ + filter: { + manyTestRelations: { + relationName: { + in: [TEST_RELATIONS[1].relationName, TEST_RELATIONS[4].relationName], + }, + }, + }, + }); + expect(queryResult).toEqual([ + TEST_ENTITIES[1], + TEST_ENTITIES[3], + TEST_ENTITIES[5], + TEST_ENTITIES[7], + TEST_ENTITIES[9], + ]); + }); + + it('should allow filtering on a many to many uni-directional relation', async () => { + const queryService = moduleRef.get(TestEntityService); + const queryResult = await queryService.query({ + filter: { + manyToManyUniDirectional: { + relationName: { + in: [TEST_RELATIONS[2].relationName, TEST_RELATIONS[5].relationName], + }, + }, + }, + }); + expect(queryResult).toEqual([TEST_ENTITIES[2], TEST_ENTITIES[5], TEST_ENTITIES[8]]); + }); + }); }); }); @@ -281,6 +331,17 @@ describe('TypeOrmQueryService', (): void => { TEST_RELATIONS[2].testRelationPk, ]); }); + + describe('manyToMany', () => { + it('call select and return the with a uni-directional relation', async () => { + const entity = TEST_ENTITIES[2]; + const queryService = moduleRef.get(TestEntityService); + const queryResult = await queryService.queryRelations(TestRelation, 'manyToManyUniDirectional', entity, {}); + TEST_RELATIONS.filter((tr) => tr.relationName.endsWith('three')).forEach((tr) => { + expect(queryResult).toContainEqual(tr); + }); + }); + }); }); describe('with multiple entities', () => { @@ -660,6 +721,16 @@ describe('TypeOrmQueryService', (): void => { 'Unable to find relation badRelation on TestEntity', ); }); + + describe('manyToOne', () => { + it('call select and return the with a uni-directional relation', async () => { + const entity = TEST_RELATIONS[0]; + const queryService = moduleRef.get(TestRelationService); + const queryResult = await queryService.findRelation(TestEntity, 'testEntityUniDirectional', entity); + + expect(queryResult).toEqual(TEST_ENTITIES[0]); + }); + }); }); describe('with multiple entities', () => { diff --git a/packages/query-typeorm/src/query/relation-query.builder.ts b/packages/query-typeorm/src/query/relation-query.builder.ts index fb9cfee20..245d6225e 100644 --- a/packages/query-typeorm/src/query/relation-query.builder.ts +++ b/packages/query-typeorm/src/query/relation-query.builder.ts @@ -221,7 +221,7 @@ export class RelationQueryBuilder { })), }, ]; - const fromPrimaryKeys = relation.inverseRelation!.entityMetadata.primaryColumns.map((pk) => ({ + const fromPrimaryKeys = relation.inverseEntityMetadata.primaryColumns.map((pk) => ({ selectPath: `${relation.propertyName}.${pk.propertyName}`, databasePath: pk.databasePath, propertyName: pk.propertyName, @@ -249,7 +249,7 @@ export class RelationQueryBuilder { getOneToManyOrOneToOneNotOwnerMeta(relation: RelationMetadata): RelationQuery { const aliasName = relation.propertyName; const columns = relation.inverseRelation!.joinColumns; - const fromPrimaryKeys: PrimaryKey[] = relation.inverseRelation!.entityMetadata.primaryColumns.map((pk) => ({ + const fromPrimaryKeys: PrimaryKey[] = relation.inverseEntityMetadata.primaryColumns.map((pk) => ({ selectPath: `${aliasName}.${pk.propertyName}`, databasePath: pk.databasePath, propertyName: pk.propertyName, @@ -287,7 +287,7 @@ export class RelationQueryBuilder { })), }, ]; - const fromPrimaryKeys = relation.inverseRelation!.entityMetadata.primaryColumns.map((pk) => ({ + const fromPrimaryKeys = relation.inverseEntityMetadata.primaryColumns.map((pk) => ({ selectPath: `${mainAlias}.${pk.propertyName}`, databasePath: pk.databasePath, propertyName: pk.propertyName, @@ -325,7 +325,7 @@ export class RelationQueryBuilder { })), }, ]; - const fromPrimaryKeys = relation.inverseRelation!.entityMetadata.primaryColumns.map((pk) => ({ + const fromPrimaryKeys = relation.inverseEntityMetadata.primaryColumns.map((pk) => ({ selectPath: `${mainAlias}.${pk.propertyName}`, databasePath: pk.databasePath, propertyName: pk.propertyName,