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 TOP clause support. #821

Merged
merged 25 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2dd1e6a
implement compilation.
igalklebanov Dec 29, 2023
bc28062
simplify top modifiers.
igalklebanov Dec 29, 2023
296ffe5
add to select query builder.
igalklebanov Dec 29, 2023
dd9a9e2
forgot to handle it in select visitor.
igalklebanov Dec 29, 2023
4b7c4fd
select test cases.
igalklebanov Dec 29, 2023
343fc30
use .top instead of raw in tests.
igalklebanov Dec 29, 2023
582e049
add to delete query builder.
igalklebanov Dec 29, 2023
132f3e7
add to update query builder.
igalklebanov Dec 29, 2023
d26ac31
add to insert query builder.
igalklebanov Dec 29, 2023
69a2c0e
Merge branch 'master' into feat-top
igalklebanov Dec 29, 2023
eea88a9
removed unused cloneWithTop.
igalklebanov Dec 29, 2023
effbe99
Merge branch 'master' into feat-top
igalklebanov Dec 29, 2023
f1c1005
Merge branch 'master' into feat-top
igalklebanov Dec 30, 2023
8da9cdd
Merge branch 'master' into feat-top
igalklebanov Jan 9, 2024
5d865de
Merge branch 'master' into feat-top
igalklebanov Feb 15, 2024
136dba5
Merge branch 'master' into feat-top
igalklebanov Feb 21, 2024
7199c12
add top-parser.
igalklebanov Feb 21, 2024
4ed4bf2
trailing commas.
igalklebanov Feb 21, 2024
f70b30a
fix top parser.
igalklebanov Feb 21, 2024
e269502
remove unused imports.
igalklebanov Feb 21, 2024
d0a5fc9
add top to merge query builder.
igalklebanov Feb 21, 2024
cb6a57f
add top to WheneableMergeQueryBuilder.
igalklebanov Feb 21, 2024
a10136a
add merge test cases.
igalklebanov Feb 21, 2024
12bc33c
Merge branch 'master' into feat-top
igalklebanov Feb 24, 2024
7f88abc
re-add select test cases.
igalklebanov Feb 24, 2024
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
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/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
14 changes: 14 additions & 0 deletions src/operation-node/operation-node-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import { TupleNode } from './tuple-node.js'
import { MergeQueryNode } from './merge-query-node.js'
import { MatchedNode } from './matched-node.js'
import { AddIndexNode } from './add-index-node.js'
import { TopNode } from './top-node.js'

/**
* Transforms an operation node tree into another one.
Expand Down Expand Up @@ -214,6 +215,7 @@ export class OperationNodeTransformer {
MergeQueryNode: this.transformMergeQuery.bind(this),
MatchedNode: this.transformMatched.bind(this),
AddIndexNode: this.transformAddIndex.bind(this),
TopNode: this.transformTop.bind(this),
})

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

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

Expand All @@ -397,6 +401,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 @@ -500,6 +505,7 @@ export class OperationNodeTransformer {
returning: this.transformNode(node.returning),
with: this.transformNode(node.with),
explain: this.transformNode(node.explain),
top: this.transformNode(node.top),
})
}

Expand Down Expand Up @@ -1015,6 +1021,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
13 changes: 8 additions & 5 deletions src/operation-node/operation-node-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import { TupleNode } from './tuple-node.js'
import { MergeQueryNode } from './merge-query-node.js'
import { MatchedNode } from './matched-node.js'
import { AddIndexNode } from './add-index-node.js'
import { TopNode } from './top-node.js'

export abstract class OperationNodeVisitor {
protected readonly nodeStack: OperationNode[] = []
Expand Down Expand Up @@ -191,6 +192,7 @@ export abstract class OperationNodeVisitor {
MergeQueryNode: this.visitMergeQuery.bind(this),
MatchedNode: this.visitMatched.bind(this),
AddIndexNode: this.visitAddIndex.bind(this),
TopNode: this.visitTop.bind(this),
})

protected readonly visitNode = (node: OperationNode): void => {
Expand Down Expand Up @@ -234,17 +236,17 @@ export abstract class OperationNodeVisitor {
protected abstract visitDropIndex(node: DropIndexNode): void
protected abstract visitList(node: ListNode): void
protected abstract visitPrimaryKeyConstraint(
node: PrimaryKeyConstraintNode
node: PrimaryKeyConstraintNode,
): void
protected abstract visitUniqueConstraint(node: UniqueConstraintNode): void
protected abstract visitReferences(node: ReferencesNode): void
protected abstract visitCheckConstraint(node: CheckConstraintNode): void
protected abstract visitWith(node: WithNode): void
protected abstract visitCommonTableExpression(
node: CommonTableExpressionNode
node: CommonTableExpressionNode,
): void
protected abstract visitCommonTableExpressionName(
node: CommonTableExpressionNameNode
node: CommonTableExpressionNameNode,
): void
protected abstract visitHaving(node: HavingNode): void
protected abstract visitCreateSchema(node: CreateSchemaNode): void
Expand All @@ -257,13 +259,13 @@ export abstract class OperationNodeVisitor {
protected abstract visitAddConstraint(node: AddConstraintNode): void
protected abstract visitDropConstraint(node: DropConstraintNode): void
protected abstract visitForeignKeyConstraint(
node: ForeignKeyConstraintNode
node: ForeignKeyConstraintNode,
): void
protected abstract visitDataType(node: DataTypeNode): void
protected abstract visitSelectAll(node: SelectAllNode): void
protected abstract visitIdentifier(node: IdentifierNode): void
protected abstract visitSchemableIdentifier(
node: SchemableIdentifierNode
node: SchemableIdentifierNode,
): void
protected abstract visitValue(node: ValueNode): void
protected abstract visitPrimitiveValueList(node: PrimitiveValueListNode): void
Expand Down Expand Up @@ -298,4 +300,5 @@ export abstract class OperationNodeVisitor {
protected abstract visitMergeQuery(node: MergeQueryNode): void
protected abstract visitMatched(node: MatchedNode): void
protected abstract visitAddIndex(node: AddIndexNode): 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 @@ -88,6 +88,7 @@ export type OperationNodeKind =
| 'MergeQueryNode'
| 'MatchedNode'
| 'AddIndexNode'
| 'TopNode'

export interface OperationNode {
readonly kind: OperationNodeKind
Expand Down
13 changes: 13 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 { TopModifier, 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 @@ -84,4 +86,15 @@ export const QueryNode = freeze({
explain: ExplainNode.create(format, options?.toOperationNode()),
})
},

cloneWithTop<T extends HasTop>(
node: T,
expression: number | bigint,
modifiers?: TopModifier
): T {
return freeze({
...node,
top: TopNode.create(expression, modifiers),
})
},
})
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 @@ -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 { TopNode } from './top-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 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 @@ -9,6 +9,7 @@ import { WhereNode } from './where-node.js'
import { WithNode } from './with-node.js'
import { FromNode } from './from-node.js'
import { ExplainNode } from './explain-node.js'
import { TopNode } from './top-node.js'

export type UpdateValuesNode = ValueListNode | PrimitiveValueListNode

Expand All @@ -22,6 +23,7 @@ export interface UpdateQueryNode extends OperationNode {
readonly returning?: ReturningNode
readonly with?: WithNode
readonly explain?: ExplainNode
readonly top?: TopNode
}

/**
Expand Down
50 changes: 50 additions & 0 deletions src/query-builder/delete-query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
ValueExpression,
parseValueExpression,
} from '../parser/value-parser.js'
import { TopNode } from '../operation-node/top-node.js'

export class DeleteQueryBuilder<DB, TB extends keyof DB, O>
implements
Expand Down Expand Up @@ -134,6 +135,55 @@ export class DeleteQueryBuilder<DB, TB extends keyof DB, O>
})
}

/**
* Changes a `delete from` query into a `delete top from` query.
*
* `top` clause is only supported by some dialects like MS SQL Server.
*
* ### Examples
*
* Delete the first 5 rows:
*
* ```ts
* await db
* .deleteFrom('person')
* .top(5)
* .where('age', '>', 18)
* .executeTakeFirstOrThrow()
* ```
*
* The generated SQL (MS SQL Server):
*
* ```sql
* delete top(5) from "person" where "age" > @1
* ```
*
* Delete the first 50% of rows:
*
* ```ts
* await db
* .deleteFrom('person')
* .top(50, 'percent')
* .where('age', '>', 18)
* .executeTakeFirstOrThrow()
* ```
*
* The generated SQL (MS SQL Server):
*
* ```sql
* delete top(50) percent from "person" where "age" > @1
* ```
*/
top(
top: number | bigint,
modifiers?: 'percent'
): DeleteQueryBuilder<DB, TB, O> {
return new DeleteQueryBuilder({
...this.#props,
queryNode: QueryNode.cloneWithTop(this.#props.queryNode, top, modifiers),
})
}

/**
* Adds a `using` clause to the query.
*
Expand Down
57 changes: 57 additions & 0 deletions src/query-builder/insert-query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,63 @@ export class InsertQueryBuilder<DB, TB extends keyof DB, O>
})
}

/**
* Changes an `insert into` query to an `insert top into` query.
*
* `top` clause is only supported by some dialects like MS SQL Server.
*
* ### Examples
*
* Insert the first 5 rows:
*
* ```ts
* await db.insertInto('person')
* .top(5)
* .columns(['first_name', 'gender'])
* .expression(
* (eb) => eb.selectFrom('pet').select(['name', sql.lit('other').as('gender')])
* )
* .execute()
* ```
*
* The generated SQL (MS SQL Server):
*
* ```sql
* insert top(5) into "person" ("first_name", "gender") select "name", 'other' as "gender" from "pet"
* ```
*
* Insert the first 50 percent of rows:
*
* ```ts
* await db.insertInto('person')
* .top(50, 'percent')
* .columns(['first_name', 'gender'])
* .expression(
* (eb) => eb.selectFrom('pet').select(['name', sql.lit('other').as('gender')])
* )
* .execute()
* ```
*
* The generated SQL (MS SQL Server):
*
* ```sql
* insert top(50) percent into "person" ("first_name", "gender") select "name", 'other' as "gender" from "pet"
* ```
*/
top(
expression: number | bigint,
modifiers?: 'percent'
): InsertQueryBuilder<DB, TB, O> {
return new InsertQueryBuilder({
...this.#props,
queryNode: QueryNode.cloneWithTop(
this.#props.queryNode,
expression,
modifiers
),
})
}

/**
* Adds an `on conflict` clause to the query.
*
Expand Down
Loading
Loading