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 using clause support @ DeleteQueryBuilder. #241

Merged
merged 25 commits into from
Jan 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6f177e7
add `UsingNode`.
igalklebanov Nov 26, 2022
14fed0b
add `using` to `DeleteQueryNode`.
igalklebanov Nov 26, 2022
791a7de
handle `UsingNode` compilation & transformation.
igalklebanov Nov 26, 2022
c8d2f76
add `using` @ `DeleteQueryBuilder`.
igalklebanov Nov 26, 2022
2fe17e0
Merge branch 'master' into delete-from-using
igalklebanov Dec 9, 2022
aff5688
add `using` unit tests.
igalklebanov Dec 9, 2022
6c20d9e
add `using` unit tests pt.2.
igalklebanov Dec 9, 2022
6d36d26
add `using` unit tests pt.3.
igalklebanov Dec 9, 2022
e91c4d0
add `using` typings tests.
igalklebanov Dec 9, 2022
8211005
extract `deleteFrom` typings tests to own module.
igalklebanov Dec 9, 2022
b1bd296
fix wrong table name in `using` typings test.
igalklebanov Dec 9, 2022
b1bcb5a
fix `using` list types @ `DeleteQueryBuilder`.
igalklebanov Dec 9, 2022
49c4b13
Merge branch 'master' into delete-from-using
igalklebanov Dec 10, 2022
ae16113
remove unnecessary extends.
igalklebanov Dec 10, 2022
1fcf335
add `using` ts docs.
igalklebanov Dec 10, 2022
9e070cf
Merge branch 'master' into delete-from-using
igalklebanov Dec 10, 2022
170e0dc
rename using node froms to tables.
igalklebanov Dec 10, 2022
46ad77a
add `using` unit test with `returning` clause.
igalklebanov Dec 10, 2022
131aed7
rename `DeleteQueryNode.cloneWithUsing` 2nd argument to `tables`.
igalklebanov Dec 15, 2022
0b2cc5d
Merge branch 'master' into delete-from-using
igalklebanov Dec 15, 2022
0ebc249
ts doc grammer.
igalklebanov Dec 15, 2022
96debd1
Merge branch 'master' into delete-from-using
igalklebanov Dec 16, 2022
9518721
Merge branch 'master' into delete-from-using
igalklebanov Dec 30, 2022
6ef98d4
make using types just like `.selectFrom`.
igalklebanov Dec 30, 2022
f4ed010
change 'clause' to 'keyword' in `using` docs.
igalklebanov Dec 30, 2022
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 @@ -175,6 +175,7 @@ export * from './operation-node/partition-by-item-node.js'
export * from './operation-node/set-operation-node.js'
export * from './operation-node/binary-operation-node.js'
export * from './operation-node/unary-operation-node.js'
export * from './operation-node/using-node.js'

export * from './util/column-type.js'
export * from './util/compilable.js'
Expand Down
15 changes: 15 additions & 0 deletions src/operation-node/delete-query-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { LimitNode } from './limit-node.js'
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'

export interface DeleteQueryNode extends OperationNode {
readonly kind: 'DeleteQueryNode'
readonly from: FromNode
readonly using?: UsingNode
readonly joins?: ReadonlyArray<JoinNode>
readonly where?: WhereNode
readonly returning?: ReturningNode
Expand Down Expand Up @@ -69,4 +71,17 @@ export const DeleteQueryNode = freeze({
explain,
})
},

cloneWithUsing(
deleteNode: DeleteQueryNode,
tables: OperationNode[]
): DeleteQueryNode {
return freeze({
...deleteNode,
using:
deleteNode.using !== undefined
? UsingNode.cloneWithTables(deleteNode.using, tables)
: UsingNode.create(tables),
})
},
})
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 @@ -78,6 +78,7 @@ import { PartitionByItemNode } from './partition-by-item-node.js'
import { SetOperationNode } from './set-operation-node.js'
import { BinaryOperationNode } from './binary-operation-node.js'
import { UnaryOperationNode } from './unary-operation-node.js'
import { UsingNode } from './using-node.js'

/**
* Transforms an operation node tree into another one.
Expand Down Expand Up @@ -190,6 +191,7 @@ export class OperationNodeTransformer {
SetOperationNode: this.transformSetOperation.bind(this),
BinaryOperationNode: this.transformBinaryOperation.bind(this),
UnaryOperationNode: this.transformUnaryOperation.bind(this),
UsingNode: this.transformUsing.bind(this),
})

transformNode<T extends OperationNode | undefined>(node: T): T {
Expand Down Expand Up @@ -364,6 +366,7 @@ export class OperationNodeTransformer {
return requireAllProps<DeleteQueryNode>({
kind: 'DeleteQueryNode',
from: this.transformNode(node.from),
using: this.transformNode(node.using),
joins: this.transformNodeList(node.joins),
where: this.transformNode(node.where),
returning: this.transformNode(node.returning),
Expand Down Expand Up @@ -877,6 +880,13 @@ export class OperationNodeTransformer {
})
}

protected transformUsing(node: UsingNode): UsingNode {
return requireAllProps<UsingNode>({
kind: 'UsingNode',
tables: node.tables,
})
}

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 @@ -80,6 +80,7 @@ import { PartitionByItemNode } from './partition-by-item-node.js'
import { SetOperationNode } from './set-operation-node.js'
import { BinaryOperationNode } from './binary-operation-node.js'
import { UnaryOperationNode } from './unary-operation-node.js'
import { UsingNode } from './using-node.js'

export abstract class OperationNodeVisitor {
protected readonly nodeStack: OperationNode[] = []
Expand Down Expand Up @@ -167,6 +168,7 @@ export abstract class OperationNodeVisitor {
SetOperationNode: this.visitSetOperation.bind(this),
BinaryOperationNode: this.visitBinaryOperation.bind(this),
UnaryOperationNode: this.visitUnaryOperation.bind(this),
UsingNode: this.visitUsing.bind(this),
})

protected readonly visitNode = (node: OperationNode): void => {
Expand Down Expand Up @@ -262,4 +264,5 @@ export abstract class OperationNodeVisitor {
protected abstract visitSetOperation(node: SetOperationNode): void
protected abstract visitBinaryOperation(node: BinaryOperationNode): void
protected abstract visitUnaryOperation(node: UnaryOperationNode): void
protected abstract visitUsing(node: UsingNode): 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 @@ -76,6 +76,7 @@ export type OperationNodeKind =
| 'SetOperationNode'
| 'BinaryOperationNode'
| 'UnaryOperationNode'
| 'UsingNode'

export interface OperationNode {
readonly kind: OperationNodeKind
Expand Down
33 changes: 33 additions & 0 deletions src/operation-node/using-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { freeze } from '../util/object-utils.js'
import { OperationNode } from './operation-node.js'

export interface UsingNode extends OperationNode {
readonly kind: 'UsingNode'
readonly tables: ReadonlyArray<OperationNode>
}

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

create(tables: ReadonlyArray<OperationNode>): UsingNode {
return freeze({
kind: 'UsingNode',
tables: freeze(tables),
})
},

cloneWithTables(
using: UsingNode,
tables: ReadonlyArray<OperationNode>
): UsingNode {
return freeze({
...using,
tables: freeze([...using.tables, ...tables]),
})
},
})
100 changes: 99 additions & 1 deletion src/query-builder/delete-query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import {
JoinReferenceExpression,
parseJoin,
} from '../parser/join-parser.js'
import { TableExpression } from '../parser/table-parser.js'
import {
From,
FromTables,
parseTableExpressionOrList,
TableExpression,
TableExpressionOrList,
} from '../parser/table-parser.js'
import {
parseSelectExpressionOrList,
parseSelectAll,
Expand Down Expand Up @@ -180,6 +186,98 @@ export class DeleteQueryBuilder<DB, TB extends keyof DB, O>
})
}

/**
* Adds a `using` clause to the query.
*
* This clause allows adding additional tables to the query for filtering/returning
* only. Usually a non-standard syntactic-sugar alternative to a `where` with a sub-query.
*
* ### Examples:
*
* ```ts
* await db
* .deleteFrom('pet')
* .using('person')
* .whereRef('pet.owner_id', '=', 'person.id')
* .where('person.first_name', '=', 'Bob')
* .executeTakeFirstOrThrow()
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* delete from "pet"
* using "person"
* where "pet"."owner_id" = "person"."id"
* and "person"."first_name" = $1
* ```
*
* On supported databases such as MySQL, this clause allows using joins, but requires
* at least one of the tables after the `from` keyword to be also named after
* the `using` keyword. See also {@link innerJoin}, {@link leftJoin}, {@link rightJoin}
* and {@link fullJoin}.
*
* ```ts
* await db
* .deleteFrom('pet')
* .using('pet')
* .leftJoin('person', 'person.id', 'pet.owner_id')
* .where('person.first_name', '=', 'Bob')
* .executeTakeFirstOrThrow()
* ```
*
* The generated SQL (MySQL):
*
* ```sql
* delete from `pet`
* using `pet`
* left join `person` on `person`.`id` = `pet`.`owner_id`
* where `person`.`first_name` = ?
* ```
*
* You can also chain multiple invocations of this method, or pass an array to
* a single invocation to name multiple tables.
*
* ```ts
* await db
* .deleteFrom('toy')
* .using(['pet', 'person'])
* .whereRef('toy.pet_id', '=', 'pet.id')
* .whereRef('pet.owner_id', '=', 'person.id')
* .where('person.first_name', '=', 'Bob')
* .returning('pet.name')
* .executeTakeFirstOrThrow()
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* delete from "toy"
* using "pet", "person"
* where "toy"."pet_id" = "pet"."id"
* and "pet"."owner_id" = "person"."id"
* and "person"."first_name" = $1
* returning "pet"."name"
* ```
*/
using<TE extends TableExpression<DB, keyof DB>>(
tables: TE[]
): DeleteQueryBuilder<From<DB, TE>, FromTables<DB, TB, TE>, O>

using<TE extends TableExpression<DB, keyof DB>>(
table: TE
): DeleteQueryBuilder<From<DB, TE>, FromTables<DB, TB, TE>, O>

using(tables: TableExpressionOrList<any, any>): any {
return new DeleteQueryBuilder({
...this.#props,
queryNode: DeleteQueryNode.cloneWithUsing(
this.#props.queryNode,
parseTableExpressionOrList(tables)
),
})
}

/**
* Joins another table to the query using an inner join.
*
Expand Down
11 changes: 11 additions & 0 deletions src/query-compiler/default-query-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import { PartitionByItemNode } from '../operation-node/partition-by-item-node.js
import { SetOperationNode } from '../operation-node/set-operation-node.js'
import { BinaryOperationNode } from '../operation-node/binary-operation-node.js'
import { UnaryOperationNode } from '../operation-node/unary-operation-node.js'
import { UsingNode } from '../operation-node/using-node.js'

export class DefaultQueryCompiler
extends OperationNodeVisitor
Expand Down Expand Up @@ -338,6 +339,11 @@ export class DefaultQueryCompiler
this.append('delete ')
this.visitNode(node.from)

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

if (node.joins) {
this.append(' ')
this.compileList(node.joins, ' ')
Expand Down Expand Up @@ -1258,6 +1264,11 @@ export class DefaultQueryCompiler
this.visitNode(node.operand)
}

protected override visitUsing(node: UsingNode): void {
this.append('using ')
this.compileList(node.tables)
}

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