Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/related entities loading each in a separate query #1510

Merged
17 changes: 13 additions & 4 deletions src/packages/core/src/base-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type LoaderMap = { [key: string]: DataLoader<string, unknown> };
type LoadOneOptions<G = unknown> = {
gqlEntityType: { new (...args: any[]): G };
id: string;
filter?: Filter<G>;
};

type LoadByRelatedIdOptions<G = unknown, D = unknown> = {
Expand All @@ -39,12 +40,14 @@ const getGqlEntityName = (gqlEntityType: any) => {

const getBaseLoadOneLoader = <G = unknown, D = unknown>({
gqlEntityType,
filter,
keyStore,
}: {
gqlEntityType: {
new (...args: any[]): G;
};
keyStore: LoaderMap;
filter?: Filter<G>;
}) => {
const gqlTypeName = getGqlEntityName(gqlEntityType);
if (!keyStore[gqlTypeName]) {
Expand All @@ -59,11 +62,17 @@ const getBaseLoadOneLoader = <G = unknown, D = unknown>({
);
const primaryKeyField = graphweaverMetadata.primaryKeyFieldForEntity(entity) as keyof D;

const listFilter = {
let listFilter = {
[`${String(primaryKeyField)}_in`]: keys,
// Note: Typecast here shouldn't be necessary, but FilterEntity<G> doesn't like this.
} as Filter<G>;

if (filter) {
listFilter = {
_and: [listFilter, filter],
} as Filter<G>;
}

const backendFilter =
isTransformableGraphQLEntityClass(entity.target) && entity.target.toBackendEntityFilter
? entity.target.toBackendEntityFilter(listFilter)
Expand Down Expand Up @@ -214,9 +223,9 @@ export class BaseLoader {
private loadOneLoaderMap: LoaderMap = {};
private relatedIdLoaderMap: LoaderMap = {};

public loadOne<G = unknown, D = unknown>({ gqlEntityType, id }: LoadOneOptions<G>) {
const loader = getBaseLoadOneLoader<G, D>({ gqlEntityType, keyStore: this.loadOneLoaderMap });
return loader.load(id);
public loadOne<G = unknown, D = unknown>(args: LoadOneOptions<G>) {
const loader = getBaseLoadOneLoader<G, D>({ ...args, keyStore: this.loadOneLoaderMap });
return loader.load(args.id);
}

public loadByRelatedId<G = unknown, D = unknown>(args: LoadByRelatedIdOptions<G, D>) {
Expand Down
3 changes: 2 additions & 1 deletion src/packages/core/src/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ const _listRelationshipField = async <G, D, R, C extends BaseContext>(
gqlEntityType,
relatedField: field.relationshipInfo.relatedField as keyof D & string,
id: String(source[sourcePrimaryKeyField]),
filter: relatedEntityFilter as Filter<typeof gqlEntityType>,
filter,
});
} else if (idValue) {
logger.trace('Loading with loadOne');
Expand All @@ -705,6 +705,7 @@ const _listRelationshipField = async <G, D, R, C extends BaseContext>(
BaseLoaders.loadOne<R, D>({
gqlEntityType,
id: String(id),
filter,
})
)
);
Expand Down
25 changes: 21 additions & 4 deletions src/packages/mikroorm/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
graphweaverMetadata,
} from '@exogee/graphweaver';
import { logger } from '@exogee/logger';
import { Reference, RequestContext, sql } from '@mikro-orm/core';
import { LoadStrategy, Reference, RequestContext, sql } from '@mikro-orm/core';
import { AutoPath, PopulateHint } from '@mikro-orm/postgresql';
import { pluginManager, apolloPluginManager } from '@exogee/graphweaver-server';

Expand Down Expand Up @@ -401,13 +401,30 @@ export class MikroBackendProvider<D> implements BackendProvider<D> {
trace?: TraceOptions
): Promise<D[]> {
trace?.span.updateName(`Mikro-Orm - findByRelatedId ${this.entityType.name}`);
const queryFilter = {
$and: [{ [relatedField]: { $in: relatedFieldIds } }, ...[gqlToMikro(filter) ?? []]],
};

// Any is the actual type from MikroORM, sorry folks.
let queryFilter: any = { [relatedField]: { $in: relatedFieldIds } };

if (filter) {
// Since the user has supplied a filter, we need to and it in.
queryFilter = {
$and: [queryFilter, ...[gqlToMikro(filter)]],
};
}

const populate = [relatedField as AutoPath<typeof entity, PopulateHint>];
const result = await this.database.em.find(entity, queryFilter, {
// We only need one result per entity.
flags: [QueryFlag.DISTINCT],

// We do want to populate the relation, however, see below.
populate,

// We'd love to use the default joined loading strategy, but it doesn't work with the populateWhere option.
strategy: LoadStrategy.SELECT_IN,

// This tells MikroORM we only need to load the related entities if they match the filter specified above.
populateWhere: PopulateHint.INFER,
});

return result as D[];
Expand Down
Loading