Skip to content

Commit

Permalink
add multi-column invocations & ${ref} ${direction} @ .orderBy. (#423
Browse files Browse the repository at this point in the history
)
  • Loading branch information
igalklebanov authored Jul 18, 2023
1 parent d01932d commit fe85562
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 78 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions src/operation-node/delete-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ export const DeleteQueryNode = freeze({
})
},

cloneWithOrderByItem(
cloneWithOrderByItems(
deleteNode: DeleteQueryNode,
item: OrderByItemNode
items: ReadonlyArray<OrderByItemNode>
): DeleteQueryNode {
return freeze({
...deleteNode,
orderBy: deleteNode.orderBy
? OrderByNode.cloneWithItem(deleteNode.orderBy, item)
: OrderByNode.create(item),
? OrderByNode.cloneWithItems(deleteNode.orderBy, items)
: OrderByNode.create(items),
})
},

Expand Down
11 changes: 7 additions & 4 deletions src/operation-node/order-by-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ export const OrderByNode = freeze({
return node.kind === 'OrderByNode'
},

create(item: OrderByItemNode): OrderByNode {
create(items: ReadonlyArray<OrderByItemNode>): OrderByNode {
return freeze({
kind: 'OrderByNode',
items: freeze([item]),
items: freeze([...items]),
})
},

cloneWithItem(orderBy: OrderByNode, item: OrderByItemNode): OrderByNode {
cloneWithItems(
orderBy: OrderByNode,
items: ReadonlyArray<OrderByItemNode>
): OrderByNode {
return freeze({
...orderBy,
items: freeze([...orderBy.items, item]),
items: freeze([...orderBy.items, ...items]),
})
},
})
9 changes: 6 additions & 3 deletions src/operation-node/over-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ export const OverNode = freeze({
})
},

cloneWithOrderByItem(overNode: OverNode, item: OrderByItemNode): OverNode {
cloneWithOrderByItems(
overNode: OverNode,
items: ReadonlyArray<OrderByItemNode>
): OverNode {
return freeze({
...overNode,
orderBy: overNode.orderBy
? OrderByNode.cloneWithItem(overNode.orderBy, item)
: OrderByNode.create(item),
? OrderByNode.cloneWithItems(overNode.orderBy, items)
: OrderByNode.create(items),
})
},

Expand Down
8 changes: 4 additions & 4 deletions src/operation-node/select-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ export const SelectQueryNode = freeze({
})
},

cloneWithOrderByItem(
cloneWithOrderByItems(
selectNode: SelectQueryNode,
item: OrderByItemNode
items: ReadonlyArray<OrderByItemNode>
): SelectQueryNode {
return freeze({
...selectNode,
orderBy: selectNode.orderBy
? OrderByNode.cloneWithItem(selectNode.orderBy, item)
: OrderByNode.create(item),
? OrderByNode.cloneWithItems(selectNode.orderBy, items)
: OrderByNode.create(items),
})
},

Expand Down
81 changes: 70 additions & 11 deletions src/parser/order-by-parser.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,97 @@
import { isDynamicReferenceBuilder } from '../dynamic/dynamic-reference-builder.js'
import { Expression } from '../expression/expression.js'
import { OperationNode } from '../operation-node/operation-node.js'
import { OrderByItemNode } from '../operation-node/order-by-item-node.js'
import { RawNode } from '../operation-node/raw-node.js'
import {
parseReferenceExpression,
ReferenceExpression,
} from './reference-parser.js'
import { isExpressionOrFactory, parseExpression } from './expression-parser.js'
import { StringReference, parseStringReference } from './reference-parser.js'
import { ReferenceExpression } from './reference-parser.js'

export type OrderByDirection = 'asc' | 'desc'

export function isOrderByDirection(thing: unknown): thing is OrderByDirection {
return thing === 'asc' || thing === 'desc'
}

export type OrderByExpression<DB, TB extends keyof DB, O> =
export type DirectedOrderByStringReference<DB, TB extends keyof DB, O> = `${
| StringReference<DB, TB>
| (keyof O & string)} ${OrderByDirection}`

export type UndirectedOrderByExpression<DB, TB extends keyof DB, O> =
| ReferenceExpression<DB, TB>
| (keyof O & string)

export type OrderByExpression<DB, TB extends keyof DB, O> =
| UndirectedOrderByExpression<DB, TB, O>
| DirectedOrderByStringReference<DB, TB, O>

export type OrderByDirectionExpression = OrderByDirection | Expression<any>

export function parseOrderBy(
orderBy: OrderByExpression<any, any, any>,
export function parseOrderBy(args: any[]): OrderByItemNode[] {
if (args.length === 2) {
return [parseOrderByItem(args[0], args[1])]
}

if (args.length === 1) {
const [orderBy] = args

if (Array.isArray(orderBy)) {
return orderBy.map((item) => parseOrderByItem(item))
}

return [parseOrderByItem(orderBy)]
}

throw new Error(
`Invalid number of arguments at order by! expected 1-2, received ${args.length}`
)
}

export function parseOrderByItem(
ref: ReferenceExpression<any, any>,
direction?: OrderByDirectionExpression
): OrderByItemNode {
const parsedRef = parseOrderByExpression(ref)

if (OrderByItemNode.is(parsedRef)) {
if (direction) {
throw new Error('Cannot specify direction twice!')
}

return parsedRef
}

return OrderByItemNode.create(
parseOrderByExpression(orderBy),
parsedRef,
parseOrderByDirectionExpression(direction)
)
}

function parseOrderByExpression(
expr: OrderByExpression<any, any, any>
): OperationNode {
return parseReferenceExpression(expr)
if (isExpressionOrFactory(expr)) {
return parseExpression(expr)
}

if (isDynamicReferenceBuilder(expr)) {
return expr.toOperationNode()
}

const [ref, direction] = expr.split(' ')

if (direction) {
if (!isOrderByDirection(direction)) {
throw new Error(`Invalid order by direction: ${direction}`)
}

return OrderByItemNode.create(
parseStringReference(ref),
parseOrderByDirectionExpression(direction as any)
)
}

return parseStringReference(expr)
}

function parseOrderByDirectionExpression(
Expand All @@ -44,7 +103,7 @@ function parseOrderByDirectionExpression(

if (expr === 'asc' || expr === 'desc') {
return RawNode.createWithSql(expr)
} else {
return expr.toOperationNode()
}

return expr.toOperationNode()
}
2 changes: 1 addition & 1 deletion src/parser/reference-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export function parseOrderedColumnName(column: string): OperationNode {
)
}

return parseOrderBy(columnName, order)
return parseOrderBy([columnName, order])[0]
} else {
return parseColumnName(column)
}
Expand Down
4 changes: 2 additions & 2 deletions src/query-builder/delete-query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,9 +577,9 @@ export class DeleteQueryBuilder<DB, TB extends keyof DB, O>
): DeleteQueryBuilder<DB, TB, O> {
return new DeleteQueryBuilder({
...this.#props,
queryNode: DeleteQueryNode.cloneWithOrderByItem(
queryNode: DeleteQueryNode.cloneWithOrderByItems(
this.#props.queryNode,
parseOrderBy(orderBy, direction)
parseOrderBy([orderBy, direction])
),
})
}
Expand Down
4 changes: 2 additions & 2 deletions src/query-builder/over-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ export class OverBuilder<DB, TB extends keyof DB>
direction?: OrderByDirectionExpression
): OverBuilder<DB, TB> {
return new OverBuilder({
overNode: OverNode.cloneWithOrderByItem(
overNode: OverNode.cloneWithOrderByItems(
this.#props.overNode,
parseOrderBy(orderBy, direction)
parseOrderBy([orderBy, direction])
),
})
}
Expand Down
53 changes: 42 additions & 11 deletions src/query-builder/select-query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
import {
OrderByDirectionExpression,
OrderByExpression,
DirectedOrderByStringReference,
UndirectedOrderByExpression,
parseOrderBy,
} from '../parser/order-by-parser.js'
import { preventAwait } from '../util/prevent-await.js'
Expand Down Expand Up @@ -765,14 +767,28 @@ export interface SelectQueryBuilder<DB, TB extends keyof DB, O>
/**
* Adds an `order by` clause to the query.
*
* `orderBy` calls are additive. To order by multiple columns, call `orderBy`
* multiple times.
* `orderBy` calls are additive. Meaning, additional `orderBy` calls append to
* the existing order by clause.
*
* The first argument is the expression to order by and the second is the
* order (`asc` or `desc`).
* In a single call you can add a single column/expression or multiple columns/expressions.
*
* Single column/expression calls can have 1-2 arguments. The first argument is
* the expression to order by (optionally including the direction) while the second
* optional argument is the direction (`asc` or `desc`).
*
* ### Examples
*
* Single column/expression per call:
*
* ```ts
* await db
* .selectFrom('person')
* .select('person.first_name as fn')
* .orderBy('id')
* .orderBy('fn desc')
* .execute()
* ```
*
* ```ts
* await db
* .selectFrom('person')
Expand All @@ -790,6 +806,16 @@ export interface SelectQueryBuilder<DB, TB extends keyof DB, O>
* order by "id" asc, "fn" desc
* ```
*
* Multiple columns/expressions per call:
*
* ```ts
* await db
* .selectFrom('person')
* .select('person.first_name as fn')
* .orderBy(['id', 'fn desc'])
* .execute()
* ```
*
* The order by expression can also be a raw sql expression or a subquery
* in addition to column references:
*
Expand Down Expand Up @@ -850,10 +876,18 @@ export interface SelectQueryBuilder<DB, TB extends keyof DB, O>
* ```
*/
orderBy(
orderBy: OrderByExpression<DB, TB, O>,
orderBy: UndirectedOrderByExpression<DB, TB, O>,
direction?: OrderByDirectionExpression
): SelectQueryBuilder<DB, TB, O>

orderBy(
ref: DirectedOrderByStringReference<DB, TB, O>
): SelectQueryBuilder<DB, TB, O>

orderBy(
refs: ReadonlyArray<OrderByExpression<DB, TB, O>>
): SelectQueryBuilder<DB, TB, O>

/**
* Adds a `group by` clause to the query.
*
Expand Down Expand Up @@ -1750,15 +1784,12 @@ class SelectQueryBuilderImpl<DB, TB extends keyof DB, O>
})
}

orderBy(
orderBy: OrderByExpression<DB, TB, O>,
direction?: OrderByDirectionExpression
): SelectQueryBuilder<DB, TB, O> {
orderBy(...args: any[]): SelectQueryBuilder<DB, TB, O> {
return new SelectQueryBuilderImpl({
...this.#props,
queryNode: SelectQueryNode.cloneWithOrderByItem(
queryNode: SelectQueryNode.cloneWithOrderByItems(
this.#props.queryNode,
parseOrderBy(orderBy, direction)
parseOrderBy(args)
),
})
}
Expand Down
Loading

1 comment on commit fe85562

@vercel
Copy link

@vercel vercel bot commented on fe85562 Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

kysely – ./

kysely-git-master-kysely-team.vercel.app
kysely-kysely-team.vercel.app
www.kysely.dev
kysely.dev

Please sign in to comment.