Skip to content

Commit

Permalink
feat(core): allow filtering in Collection.loadCount() (#3958)
Browse files Browse the repository at this point in the history
Closes #3527
  • Loading branch information
manu-urba committed Jan 23, 2023
1 parent 2dcca5f commit 08ea320
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 9 deletions.
30 changes: 21 additions & 9 deletions packages/core/src/entity/Collection.ts
Expand Up @@ -55,25 +55,32 @@ export class Collection<T extends object, O extends object = object> extends Arr

/**
* Gets the count of collection items from database instead of counting loaded items.
* The value is cached, use `refresh = true` to force reload it.
* The value is cached (unless you use the `where` option), use `refresh: true` to force reload it.
*/
async loadCount(refresh = false): Promise<number> {
if (!refresh && Utils.isDefined(this._count)) {
async loadCount(options: LoadCountOptions<T> | boolean = {}): Promise<number> {
options = typeof options === 'boolean' ? { refresh: options } : options;

if (!options.refresh && !options.where && Utils.isDefined(this._count)) {
return this._count!;
}

const em = this.getEntityManager();
const pivotMeta = em.getMetadata().find(this.property.pivotEntity)!;

if (!em.getPlatform().usesPivotTable() && this.property.reference === ReferenceType.MANY_TO_MANY) {
this._count = this.length;
return this._count = this.length;
} else if (this.property.pivotTable && !(this.property.inversedBy || this.property.mappedBy)) {
this._count = await em.count(this.property.type, this.createLoadCountCondition({} as FilterQuery<T>, pivotMeta), { populate: [{ field: this.property.pivotEntity }] });
} else {
this._count = await em.count(this.property.type, this.createLoadCountCondition({} as FilterQuery<T>, pivotMeta));
const count = await em.count(this.property.type, this.createLoadCountCondition(options.where ?? {} as FilterQuery<T>, pivotMeta), { populate: [{ field: this.property.pivotEntity }] });
if (!options.where) {
this._count = count;
}
return count;
}

return this._count!;
const count = await em.count(this.property.type, this.createLoadCountCondition(options.where ?? {} as FilterQuery<T>, pivotMeta));
if (!options.where) {
this._count = count;
}
return count;
}

async matching<P extends string = never>(options: MatchingOptions<T, P>): Promise<Loaded<T, P>[]> {
Expand Down Expand Up @@ -435,3 +442,8 @@ export interface InitOptions<T, P extends string = never> {
lockMode?: Exclude<LockMode, LockMode.OPTIMISTIC>;
connectionType?: ConnectionType;
}

export interface LoadCountOptions<T> {
refresh?: boolean;
where?: FilterQuery<T>;
}
91 changes: 91 additions & 0 deletions tests/features/load-count-where.test.ts
@@ -0,0 +1,91 @@
import {
Collection,
Entity, ManyToOne,
OneToMany,
PrimaryKey,
Property,
} from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/sqlite';

@Entity()
export class EndUser {

@PrimaryKey()
id!: number;

@Property()
name!: string;

// eslint-disable-next-line @typescript-eslint/no-use-before-define
@OneToMany(() => Booking, booking => booking.endUser)
bookings = new Collection<Booking>(this);

}

@Entity()
export class Event {

@PrimaryKey()
id!: number;

@Property()
name!: string;

@OneToMany(
// eslint-disable-next-line @typescript-eslint/no-use-before-define
() => Booking,
booking => booking.event,
)
bookings = new Collection<Booking>(this);

}

@Entity()
export class Booking {

@PrimaryKey()
id!: number;

@ManyToOne(() => EndUser)
endUser!: EndUser;

// eslint-disable-next-line @typescript-eslint/no-use-before-define
@ManyToOne(() => Event)
event!: Event;

}

describe('Collection.loadCount where option', () => {

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
entities: [EndUser, Booking, Event],
dbName: ':memory:',
});
await orm.getSchemaGenerator().refreshDatabase();
});

afterAll(() => orm.close(true));

test('Collection.loadCount with where option should work', async () => {
const endUser1 = orm.em.create(EndUser, { id: 1, name: 'Revò' });
const event1 = orm.em.create(Event, { id: 1, name: 'MikroORM party' });
const event2 = orm.em.create(Event, { id: 2, name: 'MikroORM second party!' });
await orm.em.persistAndFlush([endUser1, event1, event2]);
const endUser = await orm.em.findOneOrFail(EndUser, { id: endUser1.id });
let bookingCount = await endUser.bookings.loadCount(true);
expect(bookingCount).toBe(0);
const booking1 = orm.em.create(Booking, { id: 1, endUser, event: event1.id });
await orm.em.persistAndFlush(booking1);
bookingCount = await endUser.bookings.loadCount(true);
expect(bookingCount).toBe(1);
const booking2 = orm.em.create(Booking, { id: 2, endUser, event: event2.id });
await orm.em.persistAndFlush(booking2);
bookingCount = await endUser.bookings.loadCount(true);
expect(bookingCount).toBe(2);
const userFirstPartyBookingCount = await endUser.bookings.loadCount({ where: { event: event1 } });
expect(userFirstPartyBookingCount).toBe(1);
});
});

0 comments on commit 08ea320

Please sign in to comment.