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

add FETCH clause support. #822

Merged
merged 17 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions src/dialect/mssql/mssql-query-compiler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AddColumnNode } from '../../operation-node/add-column-node.js'
import { AlterTableColumnAlterationNode } from '../../operation-node/alter-table-node.js'
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'

Expand All @@ -9,6 +10,11 @@ export class MssqlQueryCompiler extends DefaultQueryCompiler {
return `@${this.numParameters}`
}

protected override visitOffset(node: OffsetNode): void {
super.visitOffset(node)
this.append(' rows')
}

// mssql allows multi-column alterations in a single statement,
// but you can only use the command keyword/s once.
// it also doesn't support multiple kinds of commands in the same
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export * from './operation-node/json-operator-chain-node.js'
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 './util/column-type.js'
export * from './util/compilable.js'
Expand Down
27 changes: 27 additions & 0 deletions src/operation-node/fetch-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { OperationNode } from './operation-node.js'
import { ValueNode } from './value-node.js'

export type FetchModifier = 'only' | 'with ties'

export interface FetchNode extends OperationNode {
readonly kind: 'FetchNode'
readonly rowCount: ValueNode
readonly modifier: FetchModifier
}

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

create(rowCount: number | bigint, modifier: FetchModifier): FetchNode {
return {
kind: 'FetchNode',
rowCount: ValueNode.create(rowCount),
modifier,
}
},
}
11 changes: 11 additions & 0 deletions src/operation-node/operation-node-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { MergeQueryNode } from './merge-query-node.js'
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'

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

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

Expand Down Expand Up @@ -1027,6 +1030,14 @@ export class OperationNodeTransformer {
})
}

protected transformFetch(node: FetchNode): FetchNode {
return requireAllProps<FetchNode>({
kind: 'FetchNode',
rowCount: this.transformNode(node.rowCount),
modifier: node.modifier,
})
}

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 @@ -93,6 +93,7 @@ import { MergeQueryNode } from './merge-query-node.js'
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'

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

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

export interface OperationNode {
readonly kind: OperationNodeKind
Expand Down
12 changes: 12 additions & 0 deletions src/operation-node/select-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { WithNode } from './with-node.js'
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'

export interface SelectQueryNode extends OperationNode {
readonly kind: 'SelectQueryNode'
Expand All @@ -33,6 +34,7 @@ export interface SelectQueryNode extends OperationNode {
readonly having?: HavingNode
readonly explain?: ExplainNode
readonly setOperations?: ReadonlyArray<SetOperationNode>
readonly fetch?: FetchNode
}

/**
Expand Down Expand Up @@ -153,6 +155,16 @@ export const SelectQueryNode = freeze({
})
},

cloneWithFetch(
selectNode: SelectQueryNode,
fetch: FetchNode
): SelectQueryNode {
return freeze({
...selectNode,
fetch,
})
},

cloneWithHaving(
selectNode: SelectQueryNode,
operation: OperationNode,
Expand Down
21 changes: 21 additions & 0 deletions src/parser/fetch-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { FetchModifier, FetchNode } from '../operation-node/fetch-node.js'
import { isBigInt, isNumber } from '../util/object-utils.js'

export function parseFetch(
rowCount: number | bigint,
modifier: FetchModifier,
): FetchNode {
if (!isNumber(rowCount) && !isBigInt(rowCount)) {
throw new Error(`Invalid fetch row count: ${rowCount}`)
}

if (!isFetchModifier(modifier)) {
throw new Error(`Invalid fetch modifier: ${modifier}`)
}

return FetchNode.create(rowCount, modifier)
}

function isFetchModifier(value: any): value is FetchModifier {
return value === 'only' || value === 'with ties'
}
65 changes: 59 additions & 6 deletions src/query-builder/select-query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ import {
ValueExpression,
parseValueExpression,
} from '../parser/value-parser.js'
import { FetchModifier } from '../operation-node/fetch-node.js'
import { parseFetch } from '../parser/fetch-parser.js'

export interface SelectQueryBuilder<DB, TB extends keyof DB, O>
extends WhereInterface<DB, TB>,
Expand Down Expand Up @@ -1045,10 +1047,12 @@ export interface SelectQueryBuilder<DB, TB extends keyof DB, O>
* .limit(10)
* ```
*/
limit(limit: ValueExpression<DB, TB, number>): SelectQueryBuilder<DB, TB, O>
limit(
limit: ValueExpression<DB, TB, number | bigint>,
): SelectQueryBuilder<DB, TB, O>

/**
* Adds an offset clause to the query.
* Adds an `offset` clause to the query.
*
* ### Examples
*
Expand All @@ -1058,11 +1062,45 @@ export interface SelectQueryBuilder<DB, TB extends keyof DB, O>
* return await db
* .selectFrom('person')
* .select('first_name')
* .offset(10)
* .limit(10)
* .offset(10)
* ```
*/
offset(offset: ValueExpression<DB, TB, number>): SelectQueryBuilder<DB, TB, O>
offset(
offset: ValueExpression<DB, TB, number | bigint>,
): SelectQueryBuilder<DB, TB, O>

/**
* Adds a `fetch` clause to the query.
*
* This clause is only supported by some dialects like PostgreSQL or MS SQL Server.
*
* ### Examples
*
* ```ts
* return await db
* .selectFrom('person')
* .select('first_name')
* .orderBy('first_name')
* .offset(0)
* .fetch(10)
* .execute()
* ```
*
* The generated SQL (MS SQL Server):
*
* ```sql
* select "first_name"
* from "person"
* order by "first_name"
* offset 0 rows
* fetch next 10 rows only
* ```
*/
fetch(
rowCount: number | bigint,
modifier?: FetchModifier,
): SelectQueryBuilder<DB, TB, O>

/**
* Combines another select query or raw expression to this query using `union`.
Expand Down Expand Up @@ -1966,7 +2004,9 @@ class SelectQueryBuilderImpl<DB, TB extends keyof DB, O>
})
}

limit(limit: ValueExpression<DB, TB, number>): SelectQueryBuilder<DB, TB, O> {
limit(
limit: ValueExpression<DB, TB, number | bigint>,
): SelectQueryBuilder<DB, TB, O> {
return new SelectQueryBuilderImpl({
...this.#props,
queryNode: SelectQueryNode.cloneWithLimit(
Expand All @@ -1977,7 +2017,7 @@ class SelectQueryBuilderImpl<DB, TB extends keyof DB, O>
}

offset(
offset: ValueExpression<DB, TB, number>,
offset: ValueExpression<DB, TB, number | bigint>,
): SelectQueryBuilder<DB, TB, O> {
return new SelectQueryBuilderImpl({
...this.#props,
Expand All @@ -1988,6 +2028,19 @@ class SelectQueryBuilderImpl<DB, TB extends keyof DB, O>
})
}

fetch(
rowCount: number | bigint,
modifier: FetchModifier = 'only',
): SelectQueryBuilder<DB, TB, O> {
return new SelectQueryBuilderImpl({
...this.#props,
queryNode: SelectQueryNode.cloneWithFetch(
this.#props.queryNode,
parseFetch(rowCount, modifier),
),
})
}

union(
expression: SetOperandExpression<DB, O>,
): SelectQueryBuilder<DB, TB, O> {
Expand Down
12 changes: 12 additions & 0 deletions src/query-compiler/default-query-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ import { MergeQueryNode } from '../operation-node/merge-query-node.js'
import { MatchedNode } from '../operation-node/matched-node.js'
import { AddIndexNode } from '../operation-node/add-index-node.js'
import { CastNode } from '../operation-node/cast-node.js'
import { FetchNode } from '../operation-node/fetch-node.js'

export class DefaultQueryCompiler
extends OperationNodeVisitor
Expand Down Expand Up @@ -222,6 +223,11 @@ export class DefaultQueryCompiler
this.visitNode(node.offset)
}

if (node.fetch) {
this.append(' ')
this.visitNode(node.fetch)
}

if (node.endModifiers?.length) {
this.append(' ')
this.compileList(this.sortSelectModifiers([...node.endModifiers]), ' ')
Expand Down Expand Up @@ -1526,6 +1532,12 @@ export class DefaultQueryCompiler
this.visitNode(node.dataType)
this.append(')')
}

protected override visitFetch(node: FetchNode): void {
this.append('fetch next ')
this.visitNode(node.rowCount)
this.append(` rows ${node.modifier}`)
igalklebanov marked this conversation as resolved.
Show resolved Hide resolved
}

protected append(str: string): void {
this.#sql += str
Expand Down
Loading
Loading