Skip to content

Commit e4c82e8

Browse files
author
Lars-Erik Roald
committed
DISTINCT solves #119
1 parent 15868ae commit e4c82e8

File tree

9 files changed

+104
-12
lines changed

9 files changed

+104
-12
lines changed

src/client/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ function rdbClient(options = {}) {
212212
count,
213213
getMany,
214214
aggregate: groupBy,
215+
distinct,
215216
getAll,
216217
getOne,
217218
getById,
@@ -267,9 +268,17 @@ function rdbClient(options = {}) {
267268
}
268269

269270
async function groupBy(strategy) {
271+
return executeGroupBy('aggregate', strategy);
272+
}
273+
274+
async function distinct(strategy) {
275+
return executeGroupBy('distinct', strategy);
276+
}
277+
278+
async function executeGroupBy(path, strategy) {
270279
let args = negotiateGroupBy(null, strategy);
271280
let body = stringify({
272-
path: 'aggregate',
281+
path,
273282
args
274283
});
275284
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });

src/getManyDto/query/newSingleQuery.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@ var newJoinSql = require('../../table/query/singleQuery/newJoinSql');
44
var newParameterized = require('../../table/query/newParameterized');
55
var getSessionSingleton = require('../../table/getSessionSingleton');
66

7-
function _new(context,table,filter,span, alias,orderBy,limit,offset) {
7+
function _new(context,table,filter,span, alias,orderBy,limit,offset,distinct = false) {
88
var quote = getSessionSingleton(context, 'quote');
99
var name = quote(table._dbName);
1010
var columnSql = newColumnSql(context,table,span,alias,true);
1111
var joinSql = newJoinSql(context, span, alias);
1212
var whereSql = newWhereSql(context,table,filter,alias);
1313
if (limit)
1414
limit = limit + ' ';
15+
const selectClause = distinct ? 'select distinct ' : 'select ';
1516

16-
return newParameterized('select ' + limit + columnSql + ' from ' + name + ' ' + quote(alias)).append(joinSql).append(whereSql).append(orderBy + offset);
17+
return newParameterized(selectClause + limit + columnSql + ' from ' + name + ' ' + quote(alias)).append(joinSql).append(whereSql).append(orderBy + offset);
1718

1819
}
1920

20-
module.exports = _new;
21+
module.exports = _new;

src/hostExpress/executePath.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ function _executePath(context, ...rest) {
7676

7777
async function executePath({ table, JSONFilter, baseFilter, customFilters = {}, request, response, readonly, disableBulkDeletes, isHttp, client }) {
7878
let allowedOps = { ..._allowedOps, insert: !readonly, ...extractRelations(getMeta(table)) };
79-
let ops = { ..._ops, ...getCustomFilterPaths(customFilters), getManyDto, getMany, aggregate, count, delete: _delete, cascadeDelete, update, replace };
79+
let ops = { ..._ops, ...getCustomFilterPaths(customFilters), getManyDto, getMany, aggregate, distinct, count, delete: _delete, cascadeDelete, update, replace };
8080

8181
let res = await parseFilter(JSONFilter, table);
8282
if (res === undefined)
@@ -372,6 +372,17 @@ function _executePath(context, ...rest) {
372372
return table.aggregate.apply(null, args);
373373
}
374374

375+
async function distinct(filter, strategy) {
376+
validateStrategy(table, strategy);
377+
filter = negotiateFilter(filter);
378+
const _baseFilter = await invokeBaseFilter();
379+
if (_baseFilter)
380+
filter = filter.and(context, _baseFilter);
381+
let args = [context, filter].concat(Array.prototype.slice.call(arguments).slice(1));
382+
await negotiateWhereAndAggregate(strategy);
383+
return table.distinct.apply(null, args);
384+
}
385+
375386

376387

377388
async function negotiateWhereAndAggregate(strategy) {

src/hostLocal.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ let executeQuery = require('./query');
55
let executeSqliteFunction = require('./sqliteFunction');
66
let hostExpress = require('./hostExpress');
77
let hostHono = require('./hostHono');
8-
const readonlyOps = ['getManyDto', 'getMany', 'aggregate', 'count'];
8+
const readonlyOps = ['getManyDto', 'getMany', 'aggregate', 'distinct', 'count'];
99
// { db, table, defaultConcurrency,
1010
// concurrency,
1111
// customFilters,

src/map2.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,7 @@ export type TableClient<M extends Record<string, TableDefinition<M>>, K extends
696696

697697
// Aggregate methods - return plain objects (no active record methods)
698698
aggregate<strategy extends AggregateStrategy<M, K>>(strategy: strategy): Promise<Array<DeepExpand<AggregateCustomSelectorProperties<M, K, strategy>>>>;
699+
distinct<strategy extends AggregateStrategy<M, K>>(strategy: strategy): Promise<Array<DeepExpand<AggregateCustomSelectorProperties<M, K, strategy>>>>;
699700

700701
// Single item methods - return individual objects with individual active record methods
701702
getOne<strategy extends FetchStrategy<M, K>>(

src/table.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ function _new(tableName) {
7979
const args = [context, table, ...rest];
8080
return groupBy.apply(null, args);
8181
};
82+
table.distinct = function(context, ...rest) {
83+
const args = [context, table, ...rest, { distinct: true }];
84+
return groupBy.apply(null, args);
85+
};
8286

8387
table.getMany.exclusive = function(context, ...rest) {
8488
const args = [context, table, ...rest];

src/table/groupBy.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const negotiateRawSqlFilter = require('./column/negotiateRawSqlFilter');
33
const strategyToSpan = require('./strategyToSpan');
44
const executeQueries = require('./executeQueries');
55

6-
async function groupBy(context, table, filter, strategy) {
6+
async function groupBy(context, table, filter, strategy, options) {
77
filter = negotiateRawSqlFilter(context, filter, table);
88
if (strategy && strategy.where) {
99
let arg = typeof strategy.where === 'function' ? strategy.where(table) : strategy.where;
@@ -15,7 +15,7 @@ async function groupBy(context, table, filter, strategy) {
1515

1616
let alias = table._dbName;
1717

18-
const query = newQuery(context, table, filter, span, alias);
18+
const query = newQuery(context, table, filter, span, alias, options);
1919
const res = await executeQueries(context, [query]);
2020
return decode(context, span, await res[0]);
2121
}
@@ -62,4 +62,4 @@ async function decode(context, span, rows, keys = rows.length > 0 ? Object.keys(
6262
return outRows;
6363
}
6464

65-
module.exports = groupBy;
65+
module.exports = groupBy;

src/table/groupBy/newQuery.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ var extractLimit = require('../query/extractLimit');
44
var newParameterized = require('../query/newParameterized');
55
var extractOffset = require('../query/extractOffset');
66

7-
function newQuery(context, table,filter,span,alias) {
7+
function newQuery(context, table,filter,span,alias,options = {}) {
88
filter = extractFilter(filter);
99
var orderBy = '';
1010
var limit = extractLimit(context, span);
1111
var offset = extractOffset(context, span);
12+
const useDistinct = options.distinct && canUseDistinct(span);
13+
14+
var query = newSingleQuery(context, table,filter,span,alias,orderBy,limit,offset,useDistinct);
15+
if (useDistinct)
16+
return query;
1217

13-
var query = newSingleQuery(context, table,filter,span,alias,orderBy,limit,offset);
1418
const groupClause = groupBy(span);
1519
return newParameterized(query.sql(), query.parameters).append(groupClause);
1620
}
@@ -22,4 +26,9 @@ function groupBy(span) {
2226
return ' GROUP BY ' + keys.map(key => span.aggregates[key].groupBy).join(',');
2327
}
2428

25-
module.exports = newQuery;
29+
function canUseDistinct(span) {
30+
const keys = Object.keys(span.aggregates);
31+
return keys.every(key => !!span.aggregates[key].column);
32+
}
33+
34+
module.exports = newQuery;

tests/aggregate.test.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,63 @@ describe('count empty primary key array', () => {
209209
}
210210
});
211211

212+
describe('distinct', () => {
213+
test('pg', async () => await verify('pg'));
214+
test('pglite', async () => await verify('pglite'));
215+
test('oracle', async () => await verify('oracle'));
216+
test('mssql', async () => await verify('mssql'));
217+
if (major === 18)
218+
test('mssqlNative', async () => await verify('mssqlNative'));
219+
test('mysql', async () => await verify('mysql'));
220+
test('sqlite', async () => await verify('sqlite'));
221+
test('sap', async () => await verify('sap'));
222+
test('http', async () => await verify('http'));
223+
224+
async function verify(dbName) {
225+
const { db } = getDb(dbName);
226+
227+
const rows = await db.orderLine.distinct({
228+
orderId: x => x.orderId,
229+
});
230+
231+
rows.sort((a, b) => a.orderId - b.orderId);
232+
233+
expect(rows).toEqual([
234+
{ orderId: 1 },
235+
{ orderId: 2 }
236+
]);
237+
}
238+
});
239+
240+
describe('distinct fallback group by', () => {
241+
test('pg', async () => await verify('pg'));
242+
test('pglite', async () => await verify('pglite'));
243+
test('oracle', async () => await verify('oracle'));
244+
test('mssql', async () => await verify('mssql'));
245+
if (major === 18)
246+
test('mssqlNative', async () => await verify('mssqlNative'));
247+
test('mysql', async () => await verify('mysql'));
248+
test('sqlite', async () => await verify('sqlite'));
249+
test('sap', async () => await verify('sap'));
250+
test('http', async () => await verify('http'));
251+
252+
async function verify(dbName) {
253+
const { db } = getDb(dbName);
254+
255+
const rows = await db.orderLine.distinct({
256+
orderId: x => x.orderId,
257+
count: x => x.count(x => x.id),
258+
});
259+
260+
rows.sort((a, b) => a.orderId - b.orderId);
261+
262+
expect(rows).toEqual([
263+
{ orderId: 1, count: 2 },
264+
{ orderId: 2, count: 1 }
265+
]);
266+
}
267+
});
268+
212269
const pathSegments = __filename.split('/');
213270
const lastSegment = pathSegments[pathSegments.length - 1];
214271
const fileNameWithoutExtension = lastSegment.split('.')[0];

0 commit comments

Comments
 (0)