Skip to content

Commit 42dcad1

Browse files
committed
feat: support sortBy on joined table
feat: update ts definition for sortBy method fix: throw error when sort by is used on joined table with loki test: add integration test on sort on joined table
1 parent 825512e commit 42dcad1

File tree

10 files changed

+63
-5
lines changed

10 files changed

+63
-5
lines changed

CHANGELOG-Unreleased.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
### Deprecations
66

77
### New features
8-
8+
- [Query] Add `Q.sortBy({column:'columnName', table:'tableName'})` to support sorting on joined tables
99
### Fixes
1010

1111
### Performance

src/QueryDescription/__tests__/test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,19 @@ describe('buildQueryDescription', () => {
461461
sortBy: [{ type: 'sortBy', sortColumn: 'sortable_column', sortOrder: 'desc' }],
462462
})
463463
})
464+
it('supports sorting query on joined table', () => {
465+
const query = Q.buildQueryDescription([
466+
Q.sortBy({ column: 'sortable_column', table: 'joinedTable' }, Q.desc),
467+
])
468+
expect(query).toEqual({
469+
where: [],
470+
joinTables: [],
471+
nestedJoinTables: [],
472+
sortBy: [
473+
{ type: 'sortBy', sortColumn: 'sortable_column', sortOrder: 'desc', table: 'joinedTable' },
474+
],
475+
})
476+
})
464477
it('does not support skip operator without take operator', () => {
465478
expect(() => {
466479
Q.buildQueryDescription([Q.skip(100)])

src/QueryDescription/operators.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
On,
1616
SortOrder,
1717
SortBy,
18+
SortColumn,
1819
Take,
1920
Skip,
2021
JoinTables,
@@ -114,7 +115,7 @@ export const or: ArrayOrSpreadFn<Where, Or>
114115
export const asc: SortOrder
115116
export const desc: SortOrder
116117

117-
export function sortBy(sortColumn: ColumnName, sortOrder?: SortOrder): SortBy
118+
export function sortBy(sortColumn: SortColumn, sortOrder?: SortOrder): SortBy
118119

119120
export function take(count: number): Take
120121

src/QueryDescription/operators.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
On,
2424
SortOrder,
2525
SortBy,
26+
SortColumn,
2627
Take,
2728
Skip,
2829
JoinTables,
@@ -212,12 +213,16 @@ export const or: ArrayOrSpreadFn<Where, Or> = (...args): Or => {
212213
export const asc: SortOrder = 'asc'
213214
export const desc: SortOrder = 'desc'
214215

215-
export function sortBy(sortColumn: ColumnName, sortOrder: SortOrder = asc): SortBy {
216+
export function sortBy(sortColumn: SortColumn, sortOrder: SortOrder = asc): SortBy {
216217
invariant(
217218
sortOrder === 'asc' || sortOrder === 'desc',
218219
`Invalid sortOrder argument received in Q.sortBy (valid: asc, desc)`,
219220
)
220-
return { type: 'sortBy', sortColumn: checkName(sortColumn), sortOrder }
221+
222+
const sortCol = typeof sortColumn === 'object' ? sortColumn.column : sortColumn
223+
const table = typeof sortColumn === 'object' ? sortColumn.table : undefined
224+
225+
return { type: 'sortBy', sortColumn: checkName(sortCol), sortOrder, table }
221226
}
222227

223228
export function take(count: number): Take {

src/QueryDescription/type.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ export type On = $RE<{
5050
export type SortOrder = 'asc' | 'desc'
5151
export const asc: SortOrder
5252
export const desc: SortOrder
53+
export type SortColumn = ColumnName | $RE<{column: ColumnName, table: TableName<any>}>
5354
export type SortBy = $RE<{
5455
type: 'sortBy'
5556
sortColumn: ColumnName
5657
sortOrder: SortOrder
58+
table?: TableName<any>
5759
}>
5860
export type Take = $RE<{
5961
type: 'take'

src/QueryDescription/type.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,13 @@ export type On = $RE<{
4949
conditions: Where[],
5050
}>
5151
export type SortOrder = 'asc' | 'desc'
52+
53+
export type SortColumn = ColumnName | $RE<{column: ColumnName, table: TableName<any>}>
5254
export type SortBy = $RE<{
5355
type: 'sortBy',
5456
sortColumn: ColumnName,
5557
sortOrder: SortOrder,
58+
table?: TableName<any>,
5659
}>
5760
export type Take = $RE<{
5861
type: 'take',

src/__tests__/databaseTests.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,7 @@ function joinTest(
991991
skipCount?: boolean,
992992
skipLoki?: boolean,
993993
skipSqlite?: boolean,
994+
checkOrder?: boolean,
994995
}>,
995996
): void {
996997
joinTests.push(options)
@@ -1140,6 +1141,28 @@ joinTest({
11401141
{ id: 'n6' }, // bad TT
11411142
],
11421143
})
1144+
joinTest({
1145+
name: `can perform Q.sort on joined table`,
1146+
query: [
1147+
Q.experimentalJoinTables(['tag_assignments']),
1148+
Q.sortBy({ column: 'text1', table: 'tag_assignments' }),
1149+
],
1150+
extraRecords: {
1151+
tag_assignments: [
1152+
{ id: 'tt1', text1: 'z', task_id: 'm6' },
1153+
{ id: 'tt2', text1: 'y', task_id: 'm8' },
1154+
{ id: 'tt3', text1: 'x', task_id: 'm7' },
1155+
{ id: 'tt4', text1: 'w', task_id: 'n5' },
1156+
{ id: 'tt5', text1: 'v', task_id: 'n6' },
1157+
{ id: 'tt6', text1: 'u', task_id: 'm2' },
1158+
{ id: 'tt7', text1: 'z', task_id: 'm2' },
1159+
],
1160+
},
1161+
matching: [{ id: 'm2' }, { id: 'n6' }, { id: 'n5' }, { id: 'm7' }, { id: 'm8' }, { id: 'm6' }],
1162+
nonMatching: [],
1163+
checkOrder: true,
1164+
skipLoki: true,
1165+
})
11431166
joinTest({
11441167
name: `can perform Q.on's nested in Q.on`,
11451168
query: [

src/adapters/lokijs/worker/executeQuery.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// @flow
22

3+
import { invariant } from '../../../utils/common'
4+
35
import type { SerializedQuery } from '../../../Query'
46

57
import type { DirtyRaw } from '../../../RawRecord'
@@ -31,6 +33,10 @@ function performQuery(query: SerializedQuery, loki: Loki): LokiResultset {
3133
// Step three: sort, skip, take
3234
const { sortBy, take, skip } = query.description
3335
if (sortBy.length) {
36+
if (process.env.NODE_ENV !== 'production') {
37+
invariant(!sortBy.some((sort) => sort.table), 'sortBy is not supported on joined table')
38+
}
39+
3440
resultset = resultset.compoundsort(
3541
sortBy.map(({ sortColumn, sortOrder }) => [sortColumn, sortOrder === 'desc']),
3642
)

src/adapters/sqlite/encodeQuery/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ const encodeOrderBy = (table: TableName<any>, sortBys: SortBy[]) => {
194194
}
195195
const orderBys = sortBys
196196
.map((sortBy) => {
197-
return `"${table}"."${sortBy.sortColumn}" ${sortBy.sortOrder}`
197+
return `"${sortBy.table ?? table}"."${sortBy.sortColumn}" ${sortBy.sortOrder}`
198198
})
199199
.join(', ')
200200
return ` order by ${orderBys}`

src/adapters/sqlite/encodeQuery/test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ describe('SQLite encodeQuery', () => {
256256
`select "tasks".* from "tasks" where "tasks"."_status" is not 'deleted' order by "tasks"."sortable_column" desc, "tasks"."sortable_column2" asc`,
257257
)
258258
})
259+
it('encodes order by clause on table', () => {
260+
expect(encoded([Q.sortBy({ column: 'sortable_column', table: 'table' }, Q.desc)])).toBe(
261+
`select "tasks".* from "tasks" where "tasks"."_status" is not 'deleted' order by "table"."sortable_column" desc`,
262+
)
263+
})
259264
it('encodes limit clause', () => {
260265
expect(encoded([Q.take(100)])).toBe(
261266
`select "tasks".* from "tasks" where "tasks"."_status" is not 'deleted' limit 100`,

0 commit comments

Comments
 (0)