Skip to content

cache is stored without proper identifier/key when using eager:true or relations  #5983

@kateile

Description

@kateile

Issue type:
[x] bug report

Database system/driver:
[x] mysql

TypeORM version:
[x] 0.x.x (0.2.24)

Steps to reproduce or a small repository showing the problem:
I am trying to cache users by their username, and everything works fine when eager:false. When true cache is stored with the whole query as Redis key. This bug could provide incorrect result when cache is enabled and eager:true is used. I have managed to reproduce it has below.

User.ts

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({nullable: true, unique: true})
  username?: string;

  @OneToOne(
    () => Profile,
    (profile) => profile.user,
    {eager: true}
  )
  @JoinColumn()
  profile: Profile;
}

Profile.ts

@Entity()
export class Profile {
  @PrimaryGeneratedColumn()
  id: number;

  @OneToOne(
    () => User,
    (user) => user.profile
  )
  user: User;
}

UserRepository .ts

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  async findByUsername(username: string): Promise<User> {
    const user = await this.findOne({
      where: {username},
      cache: {
        id: `username:${username}`,
        milliseconds: 24 * 60 * 60 * 1000
      }
    });

    if (!user) {
      throw new Error(`user with ${username} was not found!!!`);
    }

    return user;
  }

}

UserRepository.test.ts

describe('User Tests', () => {
  it('should save cache with identifier when eager:true', async () => {
    const userRepository = getCustomRepository(UserRepository);

    const username = 'mwanza';

    try {
      await userRepository.findByUsername(username);
      expect(1).toEqual(5); //In case above passed.
    } catch (e) {
      expect(e.message).toMatch(new RegExp(username));
    }

    const redisKey = await redis.get(`username:${username}`); //Passing cache key as used above.
    expect(redisKey).not.toBeNull(); //Received: null
  });
});

In above expected cache key/identifier to be username:mwanza instead it stores the whole query as key/identifier. This don't give us granular control of cache using this way

await connection.queryResultCache.remove([`username:mwanza`]);

So only way to clear cache is to wait until it expires and as result it leads to incorrect results.

Nux-20200501-184448

key: SELECT DISTINCT `distinctAlias`.`User_id` as "ids_User_id" FROM (SELECT `User`.`id` AS `User_id`, `User`.`username` AS `User_username`, `User`.`profileId` AS `User_profileId`, `User_profile`.`id` AS `User_profile_id` FROM `user` `User` LEFT JOIN `profile` `User_profile` ON `User_profile`.`id`=`User`.`profileId` WHERE `User`.`username` = ?) `distinctAlias` ORDER BY `User_id` ASC LIMIT 1 -- PARAMETERS: ["mwanza"]
value { "query": "SELECT DISTINCT `distinctAlias`.`User_id` as \"ids_User_id\" FROM (SELECT `User`.`id` AS `User_id`, `User`.`username` AS `User_username`, `User`.`profileId` AS `User_profileId`, `User_profile`.`id` AS `User_profile_id` FROM `user` `User` LEFT JOIN `profile` `User_profile` ON `User_profile`.`id`=`User`.`profileId` WHERE `User`.`username` = ?) `distinctAlias` ORDER BY `User_id` ASC LIMIT 1 -- PARAMETERS: [\"mwanza\"]", "time": 1588347815582, "duration": 86400000, "result": "[]" }

When eager:false everything goes well.
Nux-20200501-184948

key: username:mwanza

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions