Skip to content

Commit

Permalink
feat(sql): add callback signature to expr() with alias parameter
Browse files Browse the repository at this point in the history
```ts
const qb1 = orm.em.createQueryBuilder(Book2);
qb1.select('*').where({
  // using the alias of joined author via expr()
  author: { [expr(alias => `lower(${alias}.name)`)]: '...' } },
});
```

Closes #2405
  • Loading branch information
B4nan committed Nov 13, 2021
1 parent 0b37654 commit 48702c7
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 4 deletions.
7 changes: 6 additions & 1 deletion packages/core/src/utils/QueryHelper.ts
Expand Up @@ -253,8 +253,13 @@ export class QueryHelper {
/**
* Helper for escaping string types, e.g. `keyof T -> string`.
* We can also pass array of strings to allow tuple comparison in SQL drivers.
* Another alternative is to use callback signature, which will give us the current alias in its parameter.
*/
export function expr<T = unknown>(sql: (keyof T & string) | (keyof T & string)[]): string {
export function expr<T = unknown>(sql: (keyof T & string) | (keyof T & string)[] | ((alias: string) => string)): string {
if (sql instanceof Function) {
return sql('[::alias::]');
}

if (Array.isArray(sql)) {
return Utils.getPrimaryKeyHash(sql);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/knex/src/query/ObjectCriteriaNode.ts
Expand Up @@ -36,7 +36,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
} else if (childNode.shouldRename(payload)) {
o[childNode.renameFieldToPK(qb)] = payload;
} else if (primaryKey || virtual || operator || customExpression || field.includes('.') || ![QueryType.SELECT, QueryType.COUNT].includes(qb.type ?? QueryType.SELECT)) {
o[field] = payload;
o[field.replace(/\[::alias::]/g, alias!)] = payload;
} else {
o[`${alias}.${field}`] = payload;
}
Expand Down
18 changes: 16 additions & 2 deletions tests/QueryBuilder.test.ts
@@ -1,5 +1,5 @@
import { inspect } from 'util';
import { LockMode, MikroORM, QueryFlag, QueryOrder } from '@mikro-orm/core';
import { expr, LockMode, MikroORM, QueryFlag, QueryOrder } from '@mikro-orm/core';
import type { PostgreSqlDriver } from '@mikro-orm/postgresql';
import { CriteriaNode } from '@mikro-orm/knex';
import { MySqlDriver } from '@mikro-orm/mysql';
Expand Down Expand Up @@ -420,7 +420,7 @@ describe('QueryBuilder', () => {
expect(qb1.getParams()).toEqual(['{"foo":"bar"}']);

const qb2 = orm.em.createQueryBuilder(Book2);
qb2.select('*').where({ 'json_contains(`e0`.`meta`, ?) = ?': [{ foo: 'baz' }, false] });
qb2.select('*').where({ [expr(a => `json_contains(\`${a}\`.\`meta\`, ?) = ?`)]: [{ foo: 'baz' }, false] });
expect(qb2.getQuery()).toEqual('select `e0`.*, `e0`.price * 1.19 as `price_taxed` from `book2` as `e0` where json_contains(`e0`.`meta`, ?) = ?');
expect(qb2.getParams()).toEqual(['{"foo":"baz"}', false]);
});
Expand Down Expand Up @@ -1644,6 +1644,20 @@ describe('QueryBuilder', () => {
'where (`e1`.`name` = ? or not (`e1`.`name` = ?))');
});

test('select with auto-joining and alias replacement via expr()', async () => {
const qb1 = orm.em.createQueryBuilder(Book2, 'a');
qb1.select('*').where({
$or: [
{ author: { name: 'test' } },
{ author: { [expr(a => `lower(${a}.name)`)]: 'wut' } },
],
});
expect(qb1.getQuery()).toEqual('select `a`.*, `a`.price * 1.19 as `price_taxed` ' +
'from `book2` as `a` ' +
'left join `author2` as `e1` on `a`.`author_id` = `e1`.`id` ' +
'where (`e1`.`name` = ? or lower(e1.name) = ?)');
});

test('select by PK via operator', async () => {
const qb1 = orm.em.createQueryBuilder(Author2, 'a');
qb1.select('*').where({ $in: [1, 2] });
Expand Down

0 comments on commit 48702c7

Please sign in to comment.