Skip to content

Commit

Permalink
Merge pull request #3 from Terminal-Systems/feature/aggregate-filters
Browse files Browse the repository at this point in the history
Feature/aggregate filters
  • Loading branch information
dmerrill6 authored Apr 1, 2019
2 parents b92382e + 372f4f4 commit e12da85
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 6 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ knexFlexFilter(baseQuery, where, opts).then(result => console.log(result));
// Will produce a query like whereRaw("myJsonbColumn->>'a' > ?")
```

### isAggregateFn

Use this function when trying to filter over a query that has an aggregate function (sum, count, etc.). It receives the column name and must return true if it's an aggregate column and false otherwise. Must be used together with `preprocessor`, as aggregate functions are filtered using `having`, which takes the operation instead of the alias.

For example:

```javascript
const baseQuery = knex.table('entities').sum('ownerId as ownerIdSum').groupBy('id');
const isAggregateFn = column => column === 'ownerIdSum';
const preprocessor = column => (column === 'ownerIdSum' ? 'sum("ownerId")' : column);

const query = knexFlexFilter(
aggregatedQuery,
{ ownerIdSum_eq: 1 },
{ castFn, isAggregateFn, preprocessor }
);
```

## Contributing

Make sure all the tests pass before sending a PR. To run the test suite, run `yarn test`. Please note that the codebase is using `dotenv` package to connect to a test db, so, to connect to your own, add a `.env` file inside the `tests` folder with the following structure:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "knex-flex-filter",
"version": "0.2.0",
"version": "0.2.1",
"description": "Flexible filtering and search for Knex queries",
"main": "dist/index.js",
"repository": "https://github.com/Terminal-Systems/knex-flex-filter",
Expand Down
11 changes: 9 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,20 @@ const processFilter = (filterQS, castFn, preprocessor) => {


export const knexFlexFilter = (originalQuery, where = {}, opts = {}) => {
const { castFn, preprocessor = defaultPreprocessor() } = opts;
const { castFn, preprocessor = defaultPreprocessor(), isAggregateFn } = opts;

let result = originalQuery;

Object.keys(where).forEach((key) => {
const query = processFilter(key, castFn, preprocessor);
result = result.whereRaw(query, [where[key]]);
const { column } = splitColumnAndCondition(key);
let queryFn = 'whereRaw';
if (isAggregateFn) {
if (isAggregateFn(column)) {
queryFn = 'havingRaw';
}
}
result = result[queryFn](query, [where[key]]);
});

return result;
Expand Down
104 changes: 102 additions & 2 deletions tests/knex-flex-filter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ describe('knex-flex-filter', () => {
return 'bigint';
case 'lastBuyBlockNumber':
return 'bigint';

default:
return undefined;
return '';
}
};
done();
Expand Down Expand Up @@ -136,6 +135,107 @@ describe('knex-flex-filter', () => {
});
});

describe('when filtering an aggregate column', () => {
let aggregatedQuery;
let isAggregateFn;
let preprocessor;
beforeEach(() => {
aggregatedQuery = knex.table('entities').sum('ownerId as ownerIdSum').groupBy('id');
isAggregateFn = column => column === 'ownerIdSum';
preprocessor = column => (column === 'ownerIdSum' ? 'sum("ownerId")' : column);
});

it('correctly filters by _eq', async (done) => {
const query = knexFlexFilter(aggregatedQuery, { ownerIdSum_eq: 1 }, { castFn, isAggregateFn, preprocessor });

expect(query._statements[2].value.sql).toEqual('sum("ownerId") = ?');
expect(query._statements[2].value.bindings).toEqual([1]);

const result = await query;
expect(parseInt(result[0].ownerIdSum, 10)).toEqual(1);
done();
});

it('correctly filters by _gt', async (done) => {
const query = knexFlexFilter(aggregatedQuery, { ownerIdSum_gt: 0 }, { castFn, isAggregateFn, preprocessor });

expect(query._statements[2].value.sql).toEqual('sum("ownerId") > ?');
expect(query._statements[2].value.bindings).toEqual([0]);

const result = await query;
expect(parseInt(result[0].ownerIdSum, 10)).toBeGreaterThan(0);
done();
});

it('correctly filters by _lt', async (done) => {
const query = knexFlexFilter(aggregatedQuery, { ownerIdSum_lt: 2 }, { castFn, isAggregateFn, preprocessor });

expect(query._statements[2].value.sql).toEqual('sum("ownerId") < ?');
expect(query._statements[2].value.bindings).toEqual([2]);

const result = await query;
expect(parseInt(result[0].ownerIdSum, 10)).toBeLessThan(2);
done();
});

it('correctly filters by _in', async (done) => {
const query = knexFlexFilter(aggregatedQuery, { ownerIdSum_in: [1, 2] }, { castFn, isAggregateFn, preprocessor });

expect(query._statements[2].value.sql).toEqual('sum("ownerId") = ANY(?)');
expect(query._statements[2].value.bindings).toEqual([[1, 2]]);

const result = await query;
expect([1, 2]).toContain(parseInt(result[0].ownerIdSum, 10));
done();
});

it('correctly filters by _not', async (done) => {
const query = knexFlexFilter(aggregatedQuery, { ownerIdSum_not: 2 }, { castFn, isAggregateFn, preprocessor });

expect(query._statements[2].value.sql).toEqual('sum("ownerId") <> ?');
expect(query._statements[2].value.bindings).toEqual([2]);

const result = await query;
expect(result.map(_schema => parseInt(_schema.ownerIdSum, 10))).not.toContain(2);
done();
});

it('correctly filters by _gte', async (done) => {
const query = knexFlexFilter(aggregatedQuery, { ownerIdSum_gte: 1 }, { castFn, isAggregateFn, preprocessor });

expect(query._statements[2].value.sql).toEqual('sum("ownerId") >= ?');
expect(query._statements[2].value.bindings).toEqual([1]);

const result = await query;
expect(parseInt(result[0].ownerIdSum, 10)).toBeGreaterThanOrEqual(1);
done();
});

it('correctly filters by _lte', async (done) => {
const query = knexFlexFilter(aggregatedQuery, { ownerIdSum_lte: 1 }, { castFn, isAggregateFn, preprocessor });

expect(query._statements[2].value.sql).toEqual('sum("ownerId") <= ?');
expect(query._statements[2].value.bindings).toEqual([1]);

const result = await query;
expect(parseInt(result[0].ownerIdSum, 10)).toBeLessThanOrEqual(1);
done();
});

// TODO @dmerrill6: Fix this test as it's not passing
xit('correctly filters by _not_in', async (done) => {
const query = knexFlexFilter(aggregatedQuery, { ownerIdSum_not_in: [2, 3] }, { castFn, isAggregateFn, preprocessor });
console.log(query.toString());
expect(query._statements[2].value.sql).toEqual('sum("ownerId") <> ANY(?)');
expect(query._statements[2].value.bindings).toEqual([[2, 3]]);

const result = await query;
console.log('result', result);
expect([2, 3]).not.toContain(parseInt(result[0].ownerIdSum, 10));
done();
});
});

describe('when filtering using the jsonb preprocessor', () => {
const BLOCK_NUMBER = 5000;

Expand Down
2 changes: 1 addition & 1 deletion tests/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "knex-flex-filter-tests",
"version": "0.1.0",
"version": "0.2.0",
"description": "knex-flex-filter test suite",
"repository": "https://github.com/Terminal-Systems/knex-flex-filter",
"author": "Daniel Merrill <[email protected]>",
Expand Down

0 comments on commit e12da85

Please sign in to comment.