Skip to content

Commit

Permalink
Add support for WHERE LIKE clauses in SQL drivers via native regexps
Browse files Browse the repository at this point in the history
  • Loading branch information
B4nan committed Feb 12, 2019
1 parent 475b790 commit 2ad681e
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 1 deletion.
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@
- debugging section in docs (add logger, set debug mode, query logger)
- test request context is properly garbage collected or we need some clean manual up
- single table inheritance
- support for LIKE clauses in SQL drivers
15 changes: 15 additions & 0 deletions docs/usage-with-sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,21 @@ EntityManager.transactional(cb: (em: EntityManager) => Promise<any>): Promise<an

Keep in mind transactions are supported only in MySQL driver currently.

## LIKE queries

SQL do support LIKE queries via native JS regular expressions:

```typescript
const author1 = new Author2('Author 1', '[email protected]');
const author2 = new Author2('Author 2', '[email protected]');
const author3 = new Author2('Author 3', '[email protected]');
await orm.em.persist([author1, author2, author3]);

// finds authors with email like '%exa%le.c_m'
const authors = await orm.em.find(Author2, { email: /exa.*le\.c.m$/ });
console.log(authors); // all 3 authors found
```

## Native collection methods

Sometimes you need to perform some bulk operation, or you just want to populate your
Expand Down
27 changes: 27 additions & 0 deletions lib/QueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ export class QueryBuilder {
Object.values(this._cond).forEach(cond => {
if (Utils.isObject(cond) && cond.$in) {
return ret.push(...cond.$in);
} else if (cond instanceof RegExp) {
return ret.push(this.getRegExpParam(cond));
}

ret.push(cond);
Expand All @@ -222,6 +224,8 @@ export class QueryBuilder {

if (value && Utils.isObject(value) && value.$in) {
ret += ` IN (${Object.keys(value.$in).map(() => '?').join(', ')})`;
} else if (value instanceof RegExp) {
ret += ' LIKE ?';
} else if (value) {
ret += ' = ?';
}
Expand Down Expand Up @@ -330,6 +334,29 @@ export class QueryBuilder {
return this.metadata[entityName] ? this.metadata[entityName].collection : entityName;
}

private getRegExpParam(re: RegExp): string {
const value = re.source
.replace(/\.\*/g, '%') // .* -> %
.replace(/\./g, '_') // . -> _
.replace(/\\_/g, '.') // \. -> .
.replace(/^\^/g, '') // remove ^ from start
.replace(/\$$/g, ''); // remove $ from end

if (re.source.startsWith('^') && re.source.endsWith('$')) {
return value;
}

if (re.source.startsWith('^')) {
return value + '%';
}

if (re.source.endsWith('$')) {
return '%' + value;
}

return `%${value}%`;
}

}

export enum QueryType {
Expand Down
14 changes: 14 additions & 0 deletions tests/EntityManager.mysql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,20 @@ describe('EntityManagerMySql', () => {
expect(ref.isInitialized()).toBe(true);
});

test('findOne supports regexps', async () => {
const author1 = new Author2('Author 1', '[email protected]');
const author2 = new Author2('Author 2', '[email protected]');
const author3 = new Author2('Author 3', '[email protected]');
await orm.em.persist([author1, author2, author3]);
orm.em.clear();

const authors = await orm.em.find(Author2, { email: /exa.*le\.c.m$/ });
expect(authors.length).toBe(3);
expect(authors[0].name).toBe('Author 1');
expect(authors[1].name).toBe('Author 2');
expect(authors[2].name).toBe('Author 3');
});

test('stable results of serialization', async () => {
const god = new Author2('God', '[email protected]');
const bible = new Book2('Bible', god);
Expand Down
14 changes: 14 additions & 0 deletions tests/EntityManager.sqlite.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,20 @@ describe('EntityManagerSqlite', () => {
expect(ref.isInitialized()).toBe(true);
});

test('findOne supports regexps', async () => {
const author1 = new Author2('Author 1', '[email protected]');
const author2 = new Author2('Author 2', '[email protected]');
const author3 = new Author2('Author 3', '[email protected]');
await orm.em.persist([author1, author2, author3]);
orm.em.clear();

const authors = await orm.em.find(Author2, { email: /exa.*le\.c.m$/ });
expect(authors.length).toBe(3);
expect(authors[0].name).toBe('Author 1');
expect(authors[1].name).toBe('Author 2');
expect(authors[2].name).toBe('Author 3');
});

test('stable results of serialization', async () => {
const god = new Author2('God', '[email protected]');
const bible = new Book2('Bible', god);
Expand Down
22 changes: 22 additions & 0 deletions tests/QueryBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ describe('QueryBuilder', () => {
expect(qb.getParams()).toEqual(['test 123', 'lol 321', PublisherType.GLOBAL, 2, 1]);
});

test('select by regexp', async () => {
let qb = orm.em.createQueryBuilder(Publisher2.name);
qb.select('*').where({ name: /test/ });
expect(qb.getQuery()).toEqual('SELECT `e0`.* FROM `publisher2` AS `e0` WHERE `e0`.`name` LIKE ?');
expect(qb.getParams()).toEqual(['%test%']);

qb = orm.em.createQueryBuilder(Publisher2.name);
qb.select('*').where({ name: /^test/ });
expect(qb.getQuery()).toEqual('SELECT `e0`.* FROM `publisher2` AS `e0` WHERE `e0`.`name` LIKE ?');
expect(qb.getParams()).toEqual(['test%']);

qb = orm.em.createQueryBuilder(Publisher2.name);
qb.select('*').where({ name: /t.st$/ });
expect(qb.getQuery()).toEqual('SELECT `e0`.* FROM `publisher2` AS `e0` WHERE `e0`.`name` LIKE ?');
expect(qb.getParams()).toEqual(['%t_st']);

qb = orm.em.createQueryBuilder(Publisher2.name);
qb.select('*').where({ name: /^c.o.*l-te.*st\.c.m$/ });
expect(qb.getQuery()).toEqual('SELECT `e0`.* FROM `publisher2` AS `e0` WHERE `e0`.`name` LIKE ?');
expect(qb.getParams()).toEqual(['c_o%l-te%st.c_m']);
});

test('select by m:1', async () => {
const qb = orm.em.createQueryBuilder(Author2.name);
qb.select('*').where({ favouriteBook: 123 });
Expand Down

0 comments on commit 2ad681e

Please sign in to comment.