Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revisiting orderBy - deprecations, new order by item builder (nullFirst(), nullsLast(), collate()). #1326

Merged
merged 32 commits into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
09abcc7
add order by benchmark.
igalklebanov Jan 19, 2025
1251c77
update benches.
igalklebanov Jan 20, 2025
c4ca80e
order by interface and item builder.
igalklebanov Jan 20, 2025
0fa9058
add orderBy stuff @ query-node.
igalklebanov Jan 20, 2025
1bcb9b6
deprecate direction as expression.
igalklebanov Jan 20, 2025
634362d
fix toOperationNode return type.
igalklebanov Jan 20, 2025
4aaf773
add collate node.
igalklebanov Jan 20, 2025
7e87088
fix index.
igalklebanov Jan 20, 2025
256ffa9
update order by parser.
igalklebanov Jan 20, 2025
50963b3
update compilation.
igalklebanov Jan 20, 2025
60f9d20
update order-by-item-node.
igalklebanov Jan 20, 2025
600b28d
update select.
igalklebanov Jan 20, 2025
8b11dc1
align delete query builder.
igalklebanov Jan 20, 2025
e80d95a
add order by @ update query builder.
igalklebanov Jan 20, 2025
d5bdb1b
fix test setup.
igalklebanov Jan 20, 2025
97b8679
fix signatures.
igalklebanov Jan 20, 2025
6b304bb
update benches.
igalklebanov Jan 20, 2025
e4f1957
add clearOrderBy to order by interface.
igalklebanov Jan 20, 2025
ab79066
fix transformer.
igalklebanov Jan 20, 2025
71c033d
align over builder.
igalklebanov Jan 20, 2025
e770332
jsdoc links.
igalklebanov Jan 20, 2025
36fff96
align aggregate function builder pt. 1.
igalklebanov Jan 20, 2025
4dbf349
make typings tests pass.
igalklebanov Jan 25, 2025
d099e0d
make DeleteQueryNode backwards compatible til 0.29.
igalklebanov Jan 25, 2025
5708ee5
make SelectQueryNode backwards compatible til 0.29.
igalklebanov Jan 25, 2025
6cd1be5
make order-by-parser backwards compatible.
igalklebanov Jan 25, 2025
1e60331
align withinGroupOrderBy.
igalklebanov Jan 25, 2025
c4b9c8b
fix build.
igalklebanov Jan 25, 2025
5d68749
export OrderByModifiers.
igalklebanov Jan 25, 2025
5ea6d4c
..
igalklebanov Jan 25, 2025
e5cde56
fix collate.
igalklebanov Jan 25, 2025
9225bc8
test update order by.
igalklebanov Jan 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/dialect/mssql/mssql-query-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { DropColumnNode } from '../../operation-node/drop-column-node.js'
import { OffsetNode } from '../../operation-node/offset-node.js'
import { MergeQueryNode } from '../../operation-node/merge-query-node.js'
import { DefaultQueryCompiler } from '../../query-compiler/default-query-compiler.js'
import { CollateNode } from '../../operation-node/collate-node.js'

const COLLATION_CHAR_REGEX = /^[a-z0-9_]$/i

export class MssqlQueryCompiler extends DefaultQueryCompiler {
protected override getCurrentParameterPlaceholder(): string {
Expand Down Expand Up @@ -85,6 +88,20 @@ export class MssqlQueryCompiler extends DefaultQueryCompiler {
this.append(';')
}

protected override visitCollate(node: CollateNode): void {
this.append('collate ')

const { name } = node.collation

for (const char of name) {
if (!COLLATION_CHAR_REGEX.test(char)) {
throw new Error(`Invalid collation: ${name}`)
}
}

this.append(name)
}

protected override announcesNewColumnDataType(): boolean {
return false
}
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './query-builder/where-interface.js'
export * from './query-builder/returning-interface.js'
export * from './query-builder/output-interface.js'
export * from './query-builder/having-interface.js'
export * from './query-builder/order-by-interface.js'
export * from './query-builder/select-query-builder.js'
export * from './query-builder/insert-query-builder.js'
export * from './query-builder/update-query-builder.js'
Expand All @@ -28,6 +29,7 @@ export * from './query-builder/case-builder.js'
export * from './query-builder/json-path-builder.js'
export * from './query-builder/merge-query-builder.js'
export * from './query-builder/merge-result.js'
export * from './query-builder/order-by-item-builder.js'

export * from './raw-builder/raw-builder.js'
export * from './raw-builder/sql.js'
Expand Down Expand Up @@ -125,6 +127,7 @@ export * from './operation-node/binary-operation-node.js'
export * from './operation-node/case-node.js'
export * from './operation-node/cast-node.js'
export * from './operation-node/check-constraint-node.js'
export * from './operation-node/collate-node.js'
export * from './operation-node/column-definition-node.js'
export * from './operation-node/column-node.js'
export * from './operation-node/column-update-node.js'
Expand Down Expand Up @@ -269,6 +272,9 @@ export { UpdateObject } from './parser/update-set-parser.js'
export {
OrderByExpression,
OrderByDirectionExpression,
OrderByModifiers,
OrderByDirection,
OrderByModifiersCallbackExpression,
} from './parser/order-by-parser.js'
export {
ComparisonOperatorExpression,
Expand All @@ -281,3 +287,4 @@ export {
OperandExpression,
ExpressionOrFactory,
} from './parser/expression-parser.js'
export { Collation } from './parser/collate-parser.js'
24 changes: 24 additions & 0 deletions src/operation-node/collate-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { freeze } from '../util/object-utils.js'
import { IdentifierNode } from './identifier-node.js'
import { OperationNode } from './operation-node.js'

export interface CollateNode extends OperationNode {
readonly kind: 'CollateNode'
readonly collation: IdentifierNode
}

/**
* @internal
*/
export const CollateNode = {
is(node: OperationNode): node is CollateNode {
return node.kind === 'CollateNode'
},

create(collation: string): CollateNode {
return freeze({
kind: 'CollateNode',
collation: IdentifierNode.create(collation),
})
},
}
30 changes: 14 additions & 16 deletions src/operation-node/delete-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ExplainNode } from './explain-node.js'
import { UsingNode } from './using-node.js'
import { TopNode } from './top-node.js'
import { OutputNode } from './output-node.js'
import { QueryNode } from './query-node.js'

export interface DeleteQueryNode extends OperationNode {
readonly kind: 'DeleteQueryNode'
Expand Down Expand Up @@ -45,24 +46,21 @@ export const DeleteQueryNode = freeze({
})
},

cloneWithOrderByItems(
deleteNode: DeleteQueryNode,
// TODO: remove in v0.29
/**
* @deprecated Use `QueryNode.cloneWithoutOrderBy` instead.
*/
cloneWithOrderByItems: (
node: DeleteQueryNode,
items: ReadonlyArray<OrderByItemNode>,
): DeleteQueryNode {
return freeze({
...deleteNode,
orderBy: deleteNode.orderBy
? OrderByNode.cloneWithItems(deleteNode.orderBy, items)
: OrderByNode.create(items),
})
},
) => QueryNode.cloneWithOrderByItems(node, items),

cloneWithoutOrderBy(deleteNode: DeleteQueryNode): DeleteQueryNode {
return freeze({
...deleteNode,
orderBy: undefined,
})
},
// TODO: remove in v0.29
/**
* @deprecated Use `QueryNode.cloneWithoutOrderBy` instead.
*/
cloneWithoutOrderBy: (node: DeleteQueryNode) =>
QueryNode.cloneWithoutOrderBy(node),

cloneWithLimit(
deleteNode: DeleteQueryNode,
Expand Down
10 changes: 10 additions & 0 deletions src/operation-node/operation-node-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ import { TopNode } from './top-node.js'
import { OutputNode } from './output-node.js'
import { RefreshMaterializedViewNode } from './refresh-materialized-view-node.js'
import { OrActionNode } from './or-action-node.js'
import { CollateNode } from './collate-node.js'

/**
* Transforms an operation node tree into another one.
Expand Down Expand Up @@ -233,6 +234,7 @@ export class OperationNodeTransformer {
TopNode: this.transformTop.bind(this),
OutputNode: this.transformOutput.bind(this),
OrActionNode: this.transformOrAction.bind(this),
CollateNode: this.transformCollate.bind(this),
})

transformNode<T extends OperationNode | undefined>(node: T): T {
Expand Down Expand Up @@ -502,6 +504,8 @@ export class OperationNodeTransformer {
kind: 'OrderByItemNode',
orderBy: this.transformNode(node.orderBy),
direction: this.transformNode(node.direction),
collation: this.transformNode(node.collation),
nulls: node.nulls,
})
}

Expand Down Expand Up @@ -534,6 +538,7 @@ export class OperationNodeTransformer {
limit: this.transformNode(node.limit),
top: this.transformNode(node.top),
output: this.transformNode(node.output),
orderBy: this.transformNode(node.orderBy),
})
}

Expand Down Expand Up @@ -1140,4 +1145,9 @@ export class OperationNodeTransformer {
// An Object.freezed leaf node. No need to clone.
return node
}

protected transformCollate(node: CollateNode): CollateNode {
// An Object.freezed leaf node. No need to clone.
return node
}
}
3 changes: 3 additions & 0 deletions src/operation-node/operation-node-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import { TopNode } from './top-node.js'
import { OutputNode } from './output-node.js'
import { RefreshMaterializedViewNode } from './refresh-materialized-view-node.js'
import { OrActionNode } from './or-action-node.js'
import { CollateNode } from './collate-node.js'

export abstract class OperationNodeVisitor {
protected readonly nodeStack: OperationNode[] = []
Expand Down Expand Up @@ -203,6 +204,7 @@ export abstract class OperationNodeVisitor {
TopNode: this.visitTop.bind(this),
OutputNode: this.visitOutput.bind(this),
OrActionNode: this.visitOrAction.bind(this),
CollateNode: this.visitCollate.bind(this),
})

protected readonly visitNode = (node: OperationNode): void => {
Expand Down Expand Up @@ -318,4 +320,5 @@ export abstract class OperationNodeVisitor {
protected abstract visitTop(node: TopNode): void
protected abstract visitOutput(node: OutputNode): void
protected abstract visitOrAction(node: OrActionNode): void
protected abstract visitCollate(node: CollateNode): void
}
1 change: 1 addition & 0 deletions src/operation-node/operation-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export type OperationNodeKind =
| 'TopNode'
| 'OutputNode'
| 'OrActionNode'
| 'CollateNode'

export interface OperationNode {
readonly kind: OperationNodeKind
Expand Down
16 changes: 15 additions & 1 deletion src/operation-node/order-by-item-node.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { freeze } from '../util/object-utils.js'
import { CollateNode } from './collate-node.js'
import { OperationNode } from './operation-node.js'

export type OrderByItemNodeProps = Omit<OrderByItemNode, 'kind' | 'orderBy'>

export interface OrderByItemNode extends OperationNode {
readonly kind: 'OrderByItemNode'
readonly orderBy: OperationNode
// TODO(samiko): Do we need an OrderByDirectionNode for consistency?
readonly direction?: OperationNode
readonly nulls?: 'first' | 'last'
readonly collation?: CollateNode
}

/**
Expand All @@ -23,4 +27,14 @@ export const OrderByItemNode = freeze({
direction,
})
},

cloneWith(
node: OrderByItemNode,
props: OrderByItemNodeProps,
): OrderByItemNode {
return freeze({
...node,
...props,
})
},
})
22 changes: 22 additions & 0 deletions src/operation-node/query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { Expression } from '../expression/expression.js'
import { MergeQueryNode } from './merge-query-node.js'
import { TopNode } from './top-node.js'
import { OutputNode } from './output-node.js'
import { OrderByNode } from './order-by-node.js'
import { OrderByItemNode } from './order-by-item-node.js'

export type QueryNode =
| SelectQueryNode
Expand All @@ -29,6 +31,7 @@ type HasExplain = { explain?: ExplainNode }
type HasTop = { top?: TopNode }
type HasOutput = { output?: OutputNode }
type HasEndModifiers = { endModifiers?: ReadonlyArray<OperationNode> }
type HasOrderBy = { orderBy?: OrderByNode }

/**
* @internal
Expand Down Expand Up @@ -127,4 +130,23 @@ export const QueryNode = freeze({
: OutputNode.create(selections),
})
},

cloneWithOrderByItems<T extends HasOrderBy>(
node: T,
items: ReadonlyArray<OrderByItemNode>,
): T {
return freeze({
...node,
orderBy: node.orderBy
? OrderByNode.cloneWithItems(node.orderBy, items)
: OrderByNode.create(items),
})
},

cloneWithoutOrderBy<T extends HasOrderBy>(node: T): T {
return freeze({
...node,
orderBy: undefined,
})
},
})
30 changes: 14 additions & 16 deletions src/operation-node/select-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ExplainNode } from './explain-node.js'
import { SetOperationNode } from './set-operation-node.js'
import { FetchNode } from './fetch-node.js'
import { TopNode } from './top-node.js'
import { QueryNode } from './query-node.js'

export interface SelectQueryNode extends OperationNode {
readonly kind: 'SelectQueryNode'
Expand Down Expand Up @@ -101,17 +102,14 @@ export const SelectQueryNode = freeze({
})
},

cloneWithOrderByItems(
selectNode: SelectQueryNode,
// TODO: remove in v0.29
/**
* @deprecated Use `QueryNode.cloneWithoutOrderBy` instead.
*/
cloneWithOrderByItems: (
node: SelectQueryNode,
items: ReadonlyArray<OrderByItemNode>,
): SelectQueryNode {
return freeze({
...selectNode,
orderBy: selectNode.orderBy
? OrderByNode.cloneWithItems(selectNode.orderBy, items)
: OrderByNode.create(items),
})
},
) => QueryNode.cloneWithOrderByItems(node, items),

cloneWithGroupByItems(
selectNode: SelectQueryNode,
Expand Down Expand Up @@ -200,12 +198,12 @@ export const SelectQueryNode = freeze({
})
},

cloneWithoutOrderBy(select: SelectQueryNode): SelectQueryNode {
return freeze({
...select,
orderBy: undefined,
})
},
// TODO: remove in v0.29
/**
* @deprecated Use `QueryNode.cloneWithoutOrderBy` instead.
*/
cloneWithoutOrderBy: (node: SelectQueryNode) =>
QueryNode.cloneWithoutOrderBy(node),

cloneWithoutGroupBy(select: SelectQueryNode): SelectQueryNode {
return freeze({
Expand Down
2 changes: 2 additions & 0 deletions src/operation-node/update-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LimitNode } from './limit-node.js'
import { TopNode } from './top-node.js'
import { OutputNode } from './output-node.js'
import { ListNode } from './list-node.js'
import { OrderByNode } from './order-by-node.js'

export type UpdateValuesNode = ValueListNode | PrimitiveValueListNode

Expand All @@ -30,6 +31,7 @@ export interface UpdateQueryNode extends OperationNode {
readonly limit?: LimitNode
readonly top?: TopNode
readonly output?: OutputNode
readonly orderBy?: OrderByNode
}

/**
Expand Down
8 changes: 8 additions & 0 deletions src/parser/collate-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type Collation =
// anything super common or simple should be added here.
// https://sqlite.org/datatype3.html#collating_sequences
| 'nocase'
| 'binary'
| 'rtrim'
// otherwise, allow any string, while still providing autocompletion.
| (string & {})
Loading
Loading