Skip to content

Commit

Permalink
feat(sql): add groupBy to FindOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
B4nan committed Aug 9, 2020
1 parent d13451f commit 2f6687a
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 8 deletions.
2 changes: 1 addition & 1 deletion ROADMAP.md
Expand Up @@ -30,13 +30,13 @@ discuss specifics.
- [x] Nested conditions in `em.remove()` via subqueries (#492)
- [x] Use custom errors for specific cases (unique constraint violation, db not accessible, ...)
- [x] Paginator helper or something similar ([doctrine docs](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/pagination.html))
- [x] Add `groupBy` and `distinct` to `FindOptions` and `FindOneOptions`
- [ ] Lazy scalar properties (allow having props that won't be loaded by default, but can be populated)
- [ ] Support computed properties
- [ ] Association scopes/filters ([hibernate docs](https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/filters.html))
- [ ] Support external hooks when using EntitySchema (hooks outside of entity)
- [ ] Cache metadata only with ts-morph provider
- [ ] Diffing entity level indexes in schema generator
- [ ] Add `groupBy` and `distinct` to `FindOptions` and `FindOneOptions`
- [ ] Add custom types for blob, array, json
- [ ] Seeds (#251)
- [ ] Static checking of population (#214)
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/drivers/IDatabaseDriver.ts
Expand Up @@ -83,11 +83,13 @@ export interface FindOptions {
fields?: string[];
schema?: string;
flags?: QueryFlag[];
groupBy?: string | string[];
}

export interface FindOneOptions {
populate?: string[] | boolean;
orderBy?: QueryOrderMap;
groupBy?: string | string[];
lockMode?: LockMode;
lockVersion?: number | Date;
refresh?: boolean;
Expand Down
8 changes: 7 additions & 1 deletion packages/knex/src/AbstractSqlDriver.ts
Expand Up @@ -39,7 +39,12 @@ export abstract class AbstractSqlDriver<C extends AbstractSqlConnection = Abstra
}

const qb = this.createQueryBuilder(entityName, ctx, !!ctx);
qb.select(options.fields || '*').populate(options.populate).where(where as Dictionary).orderBy(options.orderBy!).withSchema(options.schema);
qb.select(options.fields || '*')
.populate(options.populate)
.where(where as Dictionary)
.orderBy(options.orderBy!)
.groupBy(options.groupBy!)
.withSchema(options.schema);

if (options.limit !== undefined) {
qb.limit(options.limit, options.offset);
Expand Down Expand Up @@ -69,6 +74,7 @@ export abstract class AbstractSqlDriver<C extends AbstractSqlConnection = Abstra
.populate(options.populate)
.where(where as Dictionary)
.orderBy(options.orderBy!)
.groupBy(options.groupBy!)
.limit(1)
.setLockMode(options.lockMode)
.withSchema(options.schema);
Expand Down
20 changes: 14 additions & 6 deletions tests/EntityManager.mysql.test.ts
Expand Up @@ -3,7 +3,7 @@ import chalk from 'chalk';

import {
Collection, Configuration, EntityManager, LockMode, MikroORM, QueryFlag, QueryOrder, Reference, Utils, Logger, ValidationError, wrap,
UniqueConstraintViolationException, TableNotFoundException, NotNullConstraintViolationException, TableExistsException, SyntaxErrorException,
UniqueConstraintViolationException, TableNotFoundException, TableExistsException, SyntaxErrorException,
NonUniqueFieldNameException, InvalidFieldNameException,
} from '@mikro-orm/core';
import { MySqlDriver, MySqlConnection } from '@mikro-orm/mysql';
Expand Down Expand Up @@ -1977,18 +1977,26 @@ describe('EntityManagerMySql', () => {
await orm.em.flush();
orm.em.clear();

const mock = jest.fn();
const logger = new Logger(mock, true);
Object.assign(orm.em.config, { logger });

// without paginate flag it fails to get 5 records
const res1 = await orm.em.find(Author2, { books: { title: /^Bible/ } }, {
orderBy: { name: QueryOrder.ASC, books: { title: QueryOrder.ASC } },
limit: 5,
groupBy: ['id', 'name', 'e1.title'],
});

expect(res1).toHaveLength(2);
expect(res1.map(a => a.name)).toEqual(['God 01', 'God 02']);

const mock = jest.fn();
const logger = new Logger(mock, true);
Object.assign(orm.em.config, { logger });
expect(mock.mock.calls[0][0]).toMatch('select `e0`.*, `e2`.`author_id` as `address_author_id` ' +
'from `author2` as `e0` ' +
'left join `book2` as `e1` on `e0`.`id` = `e1`.`author_id` ' +
'left join `address2` as `e2` on `e0`.`id` = `e2`.`author_id` ' +
'where `e1`.`title` like ? ' +
'group by `e0`.`id`, `e0`.`name`, `e1`.`title` ' +
'order by `e0`.`name` asc, `e1`.`title` asc limit ?');

// with paginate flag (and a bit of dark sql magic) we get what we want
const res2 = await orm.em.find(Author2, { books: { title: /^Bible/ } }, {
Expand All @@ -2000,7 +2008,7 @@ describe('EntityManagerMySql', () => {

expect(res2).toHaveLength(5);
expect(res2.map(a => a.name)).toEqual(['God 04', 'God 05', 'God 06', 'God 07', 'God 08']);
expect(mock.mock.calls[0][0]).toMatch('select `e0`.*, `e2`.`author_id` as `address_author_id` ' +
expect(mock.mock.calls[1][0]).toMatch('select `e0`.*, `e2`.`author_id` as `address_author_id` ' +
'from `author2` as `e0` ' +
'left join `book2` as `e1` on `e0`.`id` = `e1`.`author_id` ' +
'left join `address2` as `e2` on `e0`.`id` = `e2`.`author_id` where `e0`.`id` in (select `e0`.`id` ' +
Expand Down

0 comments on commit 2f6687a

Please sign in to comment.