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

EW-1059 fix slow Query by account searchByUsername #5339

Merged
merged 5 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -343,30 +343,6 @@ describe('account repo', () => {
});
});

describe('When searching by username', () => {
const setup = async () => {
const originalUsername = '[email protected]';
const partialLowerCaseUsername = '[email protected]';
const lowercaseUsername = '[email protected]';
const account = accountFactory.build({ username: originalUsername });
await em.persistAndFlush([account]);
em.clear();
return { originalUsername, partialLowerCaseUsername, lowercaseUsername, account };
};

it('should find account by user name, ignoring case', async () => {
const { originalUsername, partialLowerCaseUsername, lowercaseUsername } = await setup();

let [accounts] = await repo.searchByUsernameExactMatch(partialLowerCaseUsername);
expect(accounts).toHaveLength(1);
expect(accounts[0]).toEqual(expect.objectContaining({ username: originalUsername }));

[accounts] = await repo.searchByUsernameExactMatch(lowercaseUsername);
expect(accounts).toHaveLength(1);
expect(accounts[0]).toEqual(expect.objectContaining({ username: originalUsername }));
});
});

describe('When using wildcard', () => {
const setup = async () => {
const originalUsername = '[email protected]';
Expand Down
70 changes: 39 additions & 31 deletions apps/server/src/modules/account/repo/micro-orm/account.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,47 @@ export class AccountRepo {
await this.em.flush();
}

public async searchByUsernameExactMatch(username: string, skip = 0, limit = 1): Promise<Counted<Account[]>> {
return this.searchByUsername(username, skip, limit, true);
public async searchByUsernameExactMatch(username: string, offset = 0, limit = 1): Promise<Counted<Account[]>> {
const [entities, count] = await this.em.findAndCount(
this.entityName,
{
username,
},
{
offset,
limit,
orderBy: { username: 1 },
}
);
const accounts = AccountEntityToDoMapper.mapEntitiesToDos(entities);

return [accounts, count];
}

public async searchByUsernamePartialMatch(username: string, skip = 0, limit = 10): Promise<Counted<Account[]>> {
return this.searchByUsername(username, skip, limit, false);
/**
* @deprecated we want to discourage the usage of this function, because it uses a regular expression
* for partial matching and this can have some serious performance implications. Use it with caution.
*/
public async searchByUsernamePartialMatch(username: string, offset = 0, limit = 10): Promise<Counted<Account[]>> {
const escapedUsername = username.replace(/[^(\p{L}\p{N})]/gu, '\\$&');
const [entities, count] = await this.em.findAndCount(
this.entityName,
{
// NOTE: The default behavior of the MongoDB driver allows
// to pass regular expressions directly into the where clause
// without the need of using the $re operator, this will NOT
// work with SQL drivers.
username: new RegExp(escapedUsername, 'i'),
},
{
offset,
limit,
orderBy: { username: 1 },
}
);
const accounts = AccountEntityToDoMapper.mapEntitiesToDos(entities);

return [accounts, count];
}

public async deleteById(accountId: EntityId | ObjectId): Promise<void> {
Expand Down Expand Up @@ -154,31 +189,4 @@ export class AccountRepo {

return result;
}

private async searchByUsername(
username: string,
offset: number,
limit: number,
exactMatch: boolean
): Promise<Counted<Account[]>> {
const escapedUsername = username.replace(/[^(\p{L}\p{N})]/gu, '\\$&');
const searchUsername = exactMatch ? `^${escapedUsername}$` : escapedUsername;
const [entities, count] = await this.em.findAndCount(
this.entityName,
{
// NOTE: The default behavior of the MongoDB driver allows
// to pass regular expressions directly into the where clause
// without the need of using the $re operator, this will NOT
// work with SQL drivers
username: new RegExp(searchUsername, 'i'),
},
{
offset,
limit,
orderBy: { username: 1 },
}
);
const accounts = AccountEntityToDoMapper.mapEntitiesToDos(entities);
return [accounts, count];
}
}
Loading