Skip to content

Commit

Permalink
fix: mongodb repository.find filters soft deleted rows (#8581)
Browse files Browse the repository at this point in the history
Closes: #7113
  • Loading branch information
spotykatch committed Feb 15, 2022
1 parent 600bd4e commit f7c1f7d
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 1 deletion.
32 changes: 31 additions & 1 deletion src/entity-manager/MongoEntityManager.ts
Expand Up @@ -52,6 +52,7 @@ import { UpdateResult } from "../query-builder/result/UpdateResult";
import { DeleteResult } from "../query-builder/result/DeleteResult";
import { EntityMetadata } from "../metadata/EntityMetadata";
import { FindConditions } from "../find-options/FindConditions";
import { ColumnMetadata } from "../metadata/ColumnMetadata";

/**
* Entity manager supposed to work with any entity, automatically find its repository and call its methods,
Expand Down Expand Up @@ -83,6 +84,8 @@ export class MongoEntityManager extends EntityManager {
async find<Entity>(entityClassOrName: EntityTarget<Entity>, optionsOrConditions?: FindManyOptions<Entity> | Partial<Entity>): Promise<Entity[]> {
const query = this.convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions);
const cursor = await this.createEntityCursor(entityClassOrName, query);
const deleteDateColumn = this.connection.getMetadata(entityClassOrName).deleteDateColumn;

if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
if (optionsOrConditions.select)
cursor.project(this.convertFindOptionsSelectToProjectCriteria(optionsOrConditions.select));
Expand All @@ -92,6 +95,11 @@ export class MongoEntityManager extends EntityManager {
cursor.limit(optionsOrConditions.take);
if (optionsOrConditions.order)
cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order));
if (deleteDateColumn && !optionsOrConditions.withDeleted) {
this.filterSoftDeleted(cursor, deleteDateColumn);
}
} else if(deleteDateColumn) {
this.filterSoftDeleted(cursor, deleteDateColumn);
}
return cursor.toArray();
}
Expand All @@ -104,6 +112,8 @@ export class MongoEntityManager extends EntityManager {
async findAndCount<Entity>(entityClassOrName: EntityTarget<Entity>, optionsOrConditions?: FindManyOptions<Entity> | Partial<Entity>): Promise<[Entity[], number]> {
const query = this.convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions);
const cursor = await this.createEntityCursor(entityClassOrName, query);
const deleteDateColumn = this.connection.getMetadata(entityClassOrName).deleteDateColumn;

if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
if (optionsOrConditions.select)
cursor.project(this.convertFindOptionsSelectToProjectCriteria(optionsOrConditions.select));
Expand All @@ -113,7 +123,11 @@ export class MongoEntityManager extends EntityManager {
cursor.limit(optionsOrConditions.take);
if (optionsOrConditions.order)
cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order));

if (deleteDateColumn && !optionsOrConditions.withDeleted) {
this.filterSoftDeleted(cursor, deleteDateColumn);
}
} else if(deleteDateColumn) {
this.filterSoftDeleted(cursor, deleteDateColumn);
}
const [results, count] = await Promise.all<any>([
cursor.toArray(),
Expand Down Expand Up @@ -151,6 +165,7 @@ export class MongoEntityManager extends EntityManager {
};

const cursor = await this.createEntityCursor(entityClassOrName, query);
const deleteDateColumn = this.connection.getMetadata(entityClassOrName).deleteDateColumn;
if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
if (optionsOrConditions.select)
cursor.project(this.convertFindOptionsSelectToProjectCriteria(optionsOrConditions.select));
Expand All @@ -160,6 +175,11 @@ export class MongoEntityManager extends EntityManager {
cursor.limit(optionsOrConditions.take);
if (optionsOrConditions.order)
cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order));
if (deleteDateColumn && !optionsOrConditions.withDeleted) {
this.filterSoftDeleted(cursor, deleteDateColumn);
}
} else if(deleteDateColumn) {
this.filterSoftDeleted(cursor, deleteDateColumn);
}
return await cursor.toArray();
}
Expand All @@ -178,11 +198,17 @@ export class MongoEntityManager extends EntityManager {
query["_id"] = (id instanceof objectIdInstance) ? id : new objectIdInstance(id);
}
const cursor = await this.createEntityCursor(entityClassOrName, query);
const deleteDateColumn = this.connection.getMetadata(entityClassOrName).deleteDateColumn;
if (FindOptionsUtils.isFindOneOptions(findOneOptionsOrConditions)) {
if (findOneOptionsOrConditions.select)
cursor.project(this.convertFindOptionsSelectToProjectCriteria(findOneOptionsOrConditions.select));
if (findOneOptionsOrConditions.order)
cursor.sort(this.convertFindOptionsOrderToOrderCriteria(findOneOptionsOrConditions.order));
if (deleteDateColumn && !findOneOptionsOrConditions.withDeleted) {
this.filterSoftDeleted(cursor, deleteDateColumn);
}
} else if(deleteDateColumn) {
this.filterSoftDeleted(cursor, deleteDateColumn);
}

// const result = await cursor.limit(1).next();
Expand Down Expand Up @@ -744,4 +770,8 @@ export class MongoEntityManager extends EntityManager {
};
}

protected filterSoftDeleted<Entity>(cursor: Cursor<Entity>, deleteDateColumn: ColumnMetadata) {
cursor.filter({$where: `this.${deleteDateColumn.propertyName}==null`});
}

}
10 changes: 10 additions & 0 deletions test/github-issues/7113/entity/Configuration.ts
@@ -0,0 +1,10 @@
import { DeleteDateColumn, Entity, ObjectID, ObjectIdColumn } from "../../../../src";

@Entity()
export class Configuration {
@ObjectIdColumn()
_id: ObjectID;

@DeleteDateColumn()
deletedAt?: Date;
}
104 changes: 104 additions & 0 deletions test/github-issues/7113/issue-7113.ts
@@ -0,0 +1,104 @@
import "reflect-metadata";
import { createTestingConnections, closeTestingConnections, reloadTestingDatabases } from "../../utils/test-utils";
import { Connection } from "../../../src/connection/Connection";
import { expect } from "chai";
import { Configuration } from "./entity/Configuration";
import { ConfigurationRepository } from "./repository/ConfigurationRepository";

describe("github issues > #7113 Soft deleted docs still being pulled in Mongodb", () => {

let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchema: true,
enabledDrivers: ["mongodb"],
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));

it("should not pull soft deleted docs with find", () => Promise.all(connections.map(async connection => {

const repository = connection.getCustomRepository(ConfigurationRepository);
const configuration = new Configuration();

await repository.save(configuration);

await repository.deleteConfiguration(configuration);

const withoutDeleted = await repository.findAllConfigurations();
expect(withoutDeleted.length).to.be.eq(0);

const withDeleted = await repository.find({ withDeleted: true });
expect(withDeleted.length).to.be.eq(1);

const withOtherOption = await repository.find({order: { _id: "ASC" }});
expect(withOtherOption.length).to.be.eq(0);

})));

it("should not pull soft deleted docs with findAndCount", () => Promise.all(connections.map(async connection => {

const repository = connection.getCustomRepository(ConfigurationRepository);
const configuration = new Configuration();

await repository.save(configuration);

await repository.softRemove(configuration);

const withoutDeletedAndCount = await repository.findAndCount();
expect(withoutDeletedAndCount[0].length).to.be.eq(0);

const withDeletedAndCount = await repository.findAndCount({ withDeleted: true });
expect(withDeletedAndCount[0].length).to.be.eq(1);

const withOtherOptionAndCount = await repository.findAndCount({order: { _id: "ASC" }});
expect(withOtherOptionAndCount[0].length).to.be.eq(0);

})));

it("should not pull soft deleted docs with findByIds", () => Promise.all(connections.map(async connection => {

const repository = connection.getCustomRepository(ConfigurationRepository);
const configuration = new Configuration();

await repository.save(configuration);

await repository.softRemove(configuration);

const withoutDeletedById = await repository.findByIds([configuration._id]);
expect(withoutDeletedById.length).to.be.eq(0);

const withDeletedById = await repository.findByIds([configuration._id],
{ withDeleted: true });
expect(withDeletedById.length).to.be.eq(1);

const withOtherOptionById = await repository.findByIds([configuration._id],
{ cache: true });
expect(withOtherOptionById.length).to.be.eq(0);

})));

it("should not pull soft deleted docs with findOne", () => Promise.all(connections.map(async connection => {

const repository = connection.getCustomRepository(ConfigurationRepository);
const configuration = new Configuration();

await repository.save(configuration);

await repository.softRemove(configuration);

const withoutDeletedOne = await repository.findOne(configuration._id);
expect(withoutDeletedOne).to.be.undefined;

const withDeletedOne = await repository.findOne(configuration._id,
{ withDeleted: true });
expect(withDeletedOne).not.to.be.undefined;

const withOtherOptionOne = await repository.findOne(configuration._id,
{ cache: true });
expect(withOtherOptionOne).to.be.undefined;

})));

});
18 changes: 18 additions & 0 deletions test/github-issues/7113/repository/ConfigurationRepository.ts
@@ -0,0 +1,18 @@
import { EntityRepository, MongoRepository, ObjectID } from "../../../../src";
import { Configuration } from "../entity/Configuration";

@EntityRepository(Configuration)
export class ConfigurationRepository extends MongoRepository<Configuration> {

async findAllConfigurations(): Promise<Configuration[]> {
const configurations = await this.find();
return configurations;
}

async deleteConfiguration(
configuration: Configuration
): Promise<ObjectID> {
await this.softRemove(configuration);
return configuration._id;
}
}

0 comments on commit f7c1f7d

Please sign in to comment.