Skip to content

Commit b90a8ca

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 8213f69 commit b90a8ca

File tree

8 files changed

+60
-4
lines changed

8 files changed

+60
-4
lines changed

CHANGELOG-Unreleased.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- [adapters] Adapter objects now returns `dbName`
1818
- [TypeScript] Add unsafeExecute method
1919
- [TypeScript] Add localStorage property to Database
20+
- [Query] Add `Q.sortBy({column:'columnName', table:'tableName'})` to support sorting on joined tables
2021

2122
### Performance
2223

src/QueryDescription/index.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ export type On = $RE<{
4949
export type SortOrder = 'asc' | 'desc'
5050
export const asc: SortOrder
5151
export const desc: SortOrder
52+
export type SortColumn = ColumnName | $RE<{column: ColumnName, table: TableName<any>}>
5253
export type SortBy = $RE<{
5354
type: 'sortBy',
5455
sortColumn: ColumnName,
5556
sortOrder: SortOrder,
57+
table: TableName<any>,
5658
}>
5759
export type Take = $RE<{
5860
type: 'take',
@@ -193,7 +195,7 @@ export function and(...clauses: Where[]): And
193195

194196
export function or(...clauses: Where[]): Or
195197

196-
export function sortBy(sortColumn: ColumnName, sortOrder: SortOrder): SortBy
198+
export function sortBy(sortColumn: SortColumn, sortOrder: SortOrder): SortBy
197199

198200
export function take(count: number): Take
199201

src/QueryDescription/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ export type On = $RE<{
5959
export type SortOrder = 'asc' | 'desc'
6060
export const asc: SortOrder = 'asc'
6161
export const desc: SortOrder = 'desc'
62+
export type SortColumn = ColumnName | $RE<{ column: ColumnName, table: TableName<any> }>
6263
export type SortBy = $RE<{
6364
type: 'sortBy',
6465
sortColumn: ColumnName,
6566
sortOrder: SortOrder,
67+
table?: TableName<any>,
6668
}>
6769
export type Take = $RE<{
6870
type: 'take',
@@ -319,12 +321,16 @@ export function or(...clauses: Where[]): Or {
319321
return { type: 'or', conditions: clauses }
320322
}
321323

322-
export function sortBy(sortColumn: ColumnName, sortOrder: SortOrder = asc): SortBy {
324+
export function sortBy(sortColumn: SortColumn, sortOrder: SortOrder = asc): SortBy {
323325
invariant(
324326
sortOrder === 'asc' || sortOrder === 'desc',
325327
`Invalid sortOrder argument received in Q.sortBy (valid: asc, desc)`,
326328
)
327-
return { type: 'sortBy', sortColumn: checkName(sortColumn), sortOrder }
329+
330+
const sortCol = typeof sortColumn === 'object' ? sortColumn.column : sortColumn
331+
const table = typeof sortColumn === 'object' ? sortColumn.table : undefined
332+
333+
return { type: 'sortBy', sortColumn: checkName(sortCol), sortOrder, table }
328334
}
329335

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

src/QueryDescription/test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,19 @@ describe('buildQueryDescription', () => {
477477
sortBy: [{ type: 'sortBy', sortColumn: 'sortable_column', sortOrder: 'desc' }],
478478
})
479479
})
480+
it('supports sorting query on joined table', () => {
481+
const query = Q.buildQueryDescription([
482+
Q.sortBy({ column: 'sortable_column', table: 'joinedTable' }, Q.desc),
483+
])
484+
expect(query).toEqual({
485+
where: [],
486+
joinTables: [],
487+
nestedJoinTables: [],
488+
sortBy: [
489+
{ type: 'sortBy', sortColumn: 'sortable_column', sortOrder: 'desc', table: 'joinedTable' },
490+
],
491+
})
492+
})
480493
it('does not support skip operator without take operator', () => {
481494
expect(() => {
482495
Q.buildQueryDescription([Q.skip(100)])

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
@@ -196,7 +196,7 @@ const encodeOrderBy = (table: TableName<any>, sortBys: SortBy[]) => {
196196
}
197197
const orderBys = sortBys
198198
.map((sortBy) => {
199-
return `"${table}"."${sortBy.sortColumn}" ${sortBy.sortOrder}`
199+
return `"${sortBy.table ?? table}"."${sortBy.sortColumn}" ${sortBy.sortOrder}`
200200
})
201201
.join(', ')
202202
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)