Skip to content

Commit

Permalink
add TOP clause support. (#821)
Browse files Browse the repository at this point in the history
  • Loading branch information
igalklebanov authored Feb 24, 2024
1 parent eb4eb56 commit d1c1322
Show file tree
Hide file tree
Showing 27 changed files with 847 additions and 119 deletions.
5 changes: 3 additions & 2 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"semi": false,
"singleQuote": true
}
"singleQuote": true,
"trailingComma": "all"
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export * from './operation-node/tuple-node.js'
export * from './operation-node/merge-query-node.js'
export * from './operation-node/matched-node.js'
export * from './operation-node/fetch-node.js'
export * from './operation-node/top-node.js'

export * from './util/column-type.js'
export * from './util/compilable.js'
Expand Down
2 changes: 2 additions & 0 deletions src/operation-node/delete-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { OrderByNode } from './order-by-node.js'
import { OrderByItemNode } from './order-by-item-node.js'
import { ExplainNode } from './explain-node.js'
import { UsingNode } from './using-node.js'
import { TopNode } from './top-node.js'

export interface DeleteQueryNode extends OperationNode {
readonly kind: 'DeleteQueryNode'
Expand All @@ -22,6 +23,7 @@ export interface DeleteQueryNode extends OperationNode {
readonly orderBy?: OrderByNode
readonly limit?: LimitNode
readonly explain?: ExplainNode
readonly top?: TopNode
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/operation-node/insert-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { OnDuplicateKeyNode } from './on-duplicate-key-node.js'
import { OperationNode } from './operation-node.js'
import { ReturningNode } from './returning-node.js'
import { TableNode } from './table-node.js'
import { TopNode } from './top-node.js'
import { WithNode } from './with-node.js'

export type InsertQueryNodeProps = Omit<InsertQueryNode, 'kind' | 'into'>
Expand All @@ -23,6 +24,7 @@ export interface InsertQueryNode extends OperationNode {
readonly replace?: boolean
readonly explain?: ExplainNode
readonly defaultValues?: boolean
readonly top?: TopNode
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/operation-node/merge-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AliasNode } from './alias-node.js'
import { JoinNode } from './join-node.js'
import { OperationNode } from './operation-node.js'
import { TableNode } from './table-node.js'
import { TopNode } from './top-node.js'
import { WhenNode } from './when-node.js'
import { WithNode } from './with-node.js'

Expand All @@ -12,6 +13,7 @@ export interface MergeQueryNode extends OperationNode {
readonly using?: JoinNode
readonly whens?: ReadonlyArray<WhenNode>
readonly with?: WithNode
readonly top?: TopNode
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/operation-node/operation-node-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import { MatchedNode } from './matched-node.js'
import { AddIndexNode } from './add-index-node.js'
import { CastNode } from './cast-node.js'
import { FetchNode } from './fetch-node.js'
import { TopNode } from './top-node.js'

/**
* Transforms an operation node tree into another one.
Expand Down Expand Up @@ -218,6 +219,7 @@ export class OperationNodeTransformer {
AddIndexNode: this.transformAddIndex.bind(this),
CastNode: this.transformCast.bind(this),
FetchNode: this.transformFetch.bind(this),
TopNode: this.transformTop.bind(this),
})

transformNode<T extends OperationNode | undefined>(node: T): T {
Expand Down Expand Up @@ -265,6 +267,7 @@ export class OperationNodeTransformer {
explain: this.transformNode(node.explain),
setOperations: this.transformNodeList(node.setOperations),
fetch: this.transformNode(node.fetch),
top: this.transformNode(node.top),
})
}

Expand Down Expand Up @@ -380,6 +383,7 @@ export class OperationNodeTransformer {
replace: node.replace,
explain: this.transformNode(node.explain),
defaultValues: node.defaultValues,
top: this.transformNode(node.top),
})
}

Expand All @@ -402,6 +406,7 @@ export class OperationNodeTransformer {
orderBy: this.transformNode(node.orderBy),
limit: this.transformNode(node.limit),
explain: this.transformNode(node.explain),
top: this.transformNode(node.top),
})
}

Expand Down Expand Up @@ -507,6 +512,7 @@ export class OperationNodeTransformer {
with: this.transformNode(node.with),
explain: this.transformNode(node.explain),
limit: this.transformNode(node.limit),
top: this.transformNode(node.top),
})
}

Expand Down Expand Up @@ -1000,6 +1006,7 @@ export class OperationNodeTransformer {
using: this.transformNode(node.using),
whens: this.transformNodeList(node.whens),
with: this.transformNode(node.with),
top: this.transformNode(node.top),
})
}

Expand Down Expand Up @@ -1038,6 +1045,14 @@ export class OperationNodeTransformer {
})
}

protected transformTop(node: TopNode): TopNode {
return requireAllProps<TopNode>({
kind: 'TopNode',
expression: node.expression,
modifiers: node.modifiers,
})
}

protected transformDataType(node: DataTypeNode): DataTypeNode {
// An Object.freezed leaf node. No need to clone.
return node
Expand Down
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 @@ -94,6 +94,7 @@ import { MatchedNode } from './matched-node.js'
import { AddIndexNode } from './add-index-node.js'
import { CastNode } from './cast-node.js'
import { FetchNode } from './fetch-node.js'
import { TopNode } from './top-node.js'

export abstract class OperationNodeVisitor {
protected readonly nodeStack: OperationNode[] = []
Expand Down Expand Up @@ -195,6 +196,7 @@ export abstract class OperationNodeVisitor {
AddIndexNode: this.visitAddIndex.bind(this),
CastNode: this.visitCast.bind(this),
FetchNode: this.visitFetch.bind(this),
TopNode: this.visitTop.bind(this),
})

protected readonly visitNode = (node: OperationNode): void => {
Expand Down Expand Up @@ -304,4 +306,5 @@ export abstract class OperationNodeVisitor {
protected abstract visitAddIndex(node: AddIndexNode): void
protected abstract visitCast(node: CastNode): void
protected abstract visitFetch(node: FetchNode): void
protected abstract visitTop(node: TopNode): 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 @@ -90,6 +90,7 @@ export type OperationNodeKind =
| 'AddIndexNode'
| 'CastNode'
| 'FetchNode'
| 'TopNode'

export interface OperationNode {
readonly kind: OperationNodeKind
Expand Down
9 changes: 9 additions & 0 deletions src/operation-node/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 { ExplainFormat } from '../util/explainable.js'
import { Expression } from '../expression/expression.js'
import { MergeQueryNode } from './merge-query-node.js'
import { TopNode } from './top-node.js'

export type QueryNode =
| SelectQueryNode
Expand All @@ -24,6 +25,7 @@ type HasJoins = { joins?: ReadonlyArray<JoinNode> }
type HasWhere = { where?: WhereNode }
type HasReturning = { returning?: ReturningNode }
type HasExplain = { explain?: ExplainNode }
type HasTop = { top?: TopNode }

/**
* @internal
Expand Down Expand Up @@ -91,4 +93,11 @@ export const QueryNode = freeze({
explain: ExplainNode.create(format, options?.toOperationNode()),
})
},

cloneWithTop<T extends HasTop>(node: T, top: TopNode): T {
return freeze({
...node,
top,
})
},
})
2 changes: 2 additions & 0 deletions src/operation-node/select-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SelectModifierNode } from './select-modifier-node.js'
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'

export interface SelectQueryNode extends OperationNode {
readonly kind: 'SelectQueryNode'
Expand All @@ -35,6 +36,7 @@ export interface SelectQueryNode extends OperationNode {
readonly explain?: ExplainNode
readonly setOperations?: ReadonlyArray<SetOperationNode>
readonly fetch?: FetchNode
readonly top?: TopNode
}

/**
Expand Down
27 changes: 27 additions & 0 deletions src/operation-node/top-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { freeze } from '../util/object-utils.js'
import { OperationNode } from './operation-node.js'

export type TopModifier = 'percent' | 'with ties' | 'percent with ties'

export interface TopNode extends OperationNode {
readonly kind: 'TopNode'
readonly expression: number | bigint
readonly modifiers?: TopModifier
}

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

create(expression: number | bigint, modifiers?: TopModifier): TopNode {
return freeze({
kind: 'TopNode',
expression,
modifiers,
})
},
})
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 @@ -10,6 +10,7 @@ import { WithNode } from './with-node.js'
import { FromNode } from './from-node.js'
import { ExplainNode } from './explain-node.js'
import { LimitNode } from './limit-node.js'
import { TopNode } from './top-node.js'

export type UpdateValuesNode = ValueListNode | PrimitiveValueListNode

Expand All @@ -24,6 +25,7 @@ export interface UpdateQueryNode extends OperationNode {
readonly with?: WithNode
readonly explain?: ExplainNode
readonly limit?: LimitNode
readonly top?: TopNode
}

/**
Expand Down
25 changes: 25 additions & 0 deletions src/parser/top-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TopModifier, TopNode } from '../operation-node/top-node.js'
import { isBigInt, isNumber, isUndefined } from '../util/object-utils.js'

export function parseTop(
expression: number | bigint,
modifiers?: TopModifier,
): TopNode {
if (!isNumber(expression) && !isBigInt(expression)) {
throw new Error(`Invalid top expression: ${expression}`)
}

if (!isUndefined(modifiers) && !isTopModifiers(modifiers)) {
throw new Error(`Invalid top modifiers: ${modifiers}`)
}

return TopNode.create(expression, modifiers)
}

function isTopModifiers(modifiers: string): modifiers is TopModifier {
return (
modifiers === 'percent' ||
modifiers === 'with ties' ||
modifiers === 'percent with ties'
)
}
Loading

0 comments on commit d1c1322

Please sign in to comment.