From 6f177e7f3a2a0233a83c0e5477638406514fb3db Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sun, 27 Nov 2022 00:42:25 +0200 Subject: [PATCH 01/19] add `UsingNode`. --- src/index.ts | 1 + src/operation-node/operation-node.ts | 1 + src/operation-node/using-node.ts | 33 ++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 src/operation-node/using-node.ts diff --git a/src/index.ts b/src/index.ts index aa8cce749..58f934af7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -174,6 +174,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' diff --git a/src/operation-node/operation-node.ts b/src/operation-node/operation-node.ts index fa4da8ccd..ee19b5cf6 100644 --- a/src/operation-node/operation-node.ts +++ b/src/operation-node/operation-node.ts @@ -76,6 +76,7 @@ export type OperationNodeKind = | 'SetOperationNode' | 'BinaryOperationNode' | 'UnaryOperationNode' + | 'UsingNode' export interface OperationNode { readonly kind: OperationNodeKind diff --git a/src/operation-node/using-node.ts b/src/operation-node/using-node.ts new file mode 100644 index 000000000..d12fdbe51 --- /dev/null +++ b/src/operation-node/using-node.ts @@ -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 froms: ReadonlyArray +} + +/** + * @internal + */ +export const UsingNode = freeze({ + is(node: OperationNode): node is UsingNode { + return node.kind === 'UsingNode' + }, + + create(froms: ReadonlyArray): UsingNode { + return freeze({ + kind: 'UsingNode', + froms: freeze(froms), + }) + }, + + cloneWithFroms( + using: UsingNode, + froms: ReadonlyArray + ): UsingNode { + return freeze({ + ...using, + froms: freeze([...using.froms, ...froms]), + }) + }, +}) From 14fed0b637ee2dadd6765794f35f8f234d24fab4 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sun, 27 Nov 2022 00:43:12 +0200 Subject: [PATCH 02/19] add `using` to `DeleteQueryNode`. --- src/operation-node/delete-query-node.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/operation-node/delete-query-node.ts b/src/operation-node/delete-query-node.ts index 541789607..73af1b72c 100644 --- a/src/operation-node/delete-query-node.ts +++ b/src/operation-node/delete-query-node.ts @@ -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 readonly where?: WhereNode readonly returning?: ReturningNode @@ -69,4 +71,17 @@ export const DeleteQueryNode = freeze({ explain, }) }, + + cloneWithUsing( + deleteNode: DeleteQueryNode, + from: OperationNode[] + ): DeleteQueryNode { + return freeze({ + ...deleteNode, + using: + deleteNode.using !== undefined + ? UsingNode.cloneWithFroms(deleteNode.using, from) + : UsingNode.create(from), + }) + }, }) From 791a7de1ca4097d0c3a980020fd18984e77caee6 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sun, 27 Nov 2022 00:43:45 +0200 Subject: [PATCH 03/19] handle `UsingNode` compilation & transformation. --- src/operation-node/operation-node-transformer.ts | 10 ++++++++++ src/operation-node/operation-node-visitor.ts | 3 +++ src/query-compiler/default-query-compiler.ts | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/src/operation-node/operation-node-transformer.ts b/src/operation-node/operation-node-transformer.ts index 4ae96d787..7bb26bcf5 100644 --- a/src/operation-node/operation-node-transformer.ts +++ b/src/operation-node/operation-node-transformer.ts @@ -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. @@ -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(node: T): T { @@ -364,6 +366,7 @@ export class OperationNodeTransformer { return requireAllProps({ 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), @@ -879,6 +882,13 @@ export class OperationNodeTransformer { }) } + protected transformUsing(node: UsingNode): UsingNode { + return requireAllProps({ + kind: 'UsingNode', + froms: node.froms, + }) + } + protected transformDataType(node: DataTypeNode): DataTypeNode { // An Object.freezed leaf node. No need to clone. return node diff --git a/src/operation-node/operation-node-visitor.ts b/src/operation-node/operation-node-visitor.ts index 84d0eef38..598b764cb 100644 --- a/src/operation-node/operation-node-visitor.ts +++ b/src/operation-node/operation-node-visitor.ts @@ -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[] = [] @@ -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 => { @@ -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 } diff --git a/src/query-compiler/default-query-compiler.ts b/src/query-compiler/default-query-compiler.ts index 7eb18fe56..e063b1c61 100644 --- a/src/query-compiler/default-query-compiler.ts +++ b/src/query-compiler/default-query-compiler.ts @@ -94,6 +94,7 @@ 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 { SimpleReferenceExpressionNode } from '../operation-node/simple-reference-expression-node.js' +import { UsingNode } from '../operation-node/using-node.js' export class DefaultQueryCompiler extends OperationNodeVisitor @@ -341,6 +342,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, ' ') @@ -1266,6 +1272,11 @@ export class DefaultQueryCompiler this.visitNode(node.operand) } + protected override visitUsing(node: UsingNode): void { + this.append('using ') + this.compileList(node.froms) + } + protected append(str: string): void { this.#sql += str } From c8d2f766a9ee9e19949f2e56da3385d338755b92 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sun, 27 Nov 2022 01:13:31 +0200 Subject: [PATCH 04/19] add `using` @ `DeleteQueryBuilder`. --- src/query-builder/delete-query-builder.ts | 29 ++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts index 59a427d79..33f66641b 100644 --- a/src/query-builder/delete-query-builder.ts +++ b/src/query-builder/delete-query-builder.ts @@ -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, @@ -172,6 +178,27 @@ export class DeleteQueryBuilder }) } + /** + * TODO: ... + */ + using>( + from: TE[] + ): DeleteQueryBuilder, FromTables, O> + + using>( + from: TE + ): DeleteQueryBuilder, FromTables, O> + + using(from: TableExpressionOrList): any { + return new DeleteQueryBuilder({ + ...this.#props, + queryNode: DeleteQueryNode.cloneWithUsing( + this.#props.queryNode, + parseTableExpressionOrList(from) + ), + }) + } + /** * Joins another table to the query using an inner join. * From aff56880b5159f827b409db1ec76d242ca4ae636 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Fri, 9 Dec 2022 11:07:07 +0200 Subject: [PATCH 05/19] add `using` unit tests. --- test/node/src/delete.test.ts | 78 +++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/test/node/src/delete.test.ts b/test/node/src/delete.test.ts index 1b7b53fb0..27a7d47d7 100644 --- a/test/node/src/delete.test.ts +++ b/test/node/src/delete.test.ts @@ -1,4 +1,4 @@ -import { DeleteResult } from '../../../' +import { DeleteResult, sql } from '../../../' import { BUILT_IN_DIALECTS, @@ -162,5 +162,81 @@ for (const dialect of BUILT_IN_DIALECTS) { expect(result.last_name).to.equal('Aniston') }) } + + if (dialect === 'postgres') { + it('should delete from t1 using t2', async () => { + const query = ctx.db + .deleteFrom('person') + .using('pet') + .whereRef('pet.owner_id', '=', 'person.id') + .where('pet.species', '=', sql`${'NO_SUCH_SPECIES'}`) + + testSql(query, dialect, { + postgres: { + sql: [ + 'delete from "person"', + 'using "pet"', + 'where "pet"."owner_id" = "person"."id"', + 'and "pet"."species" = $1', + ], + parameters: ['NO_SUCH_SPECIES'], + }, + mysql: NOT_SUPPORTED, + sqlite: NOT_SUPPORTED, + }) + + await query.execute() + }) + } + + if (dialect === 'mysql') { + it('should delete from t1 using t1 inner join t2', async () => { + const query = ctx.db + .deleteFrom('person') + .using('person') + .innerJoin('pet', 'pet.owner_id', 'person.id') + .where('pet.species', '=', sql`${'NO_SUCH_SPECIES'}`) + + testSql(query, dialect, { + postgres: NOT_SUPPORTED, + mysql: { + sql: [ + 'delete from `person`', + 'using `person`', + 'inner join `pet` on `pet`.`owner_id` = `person`.`id`', + 'where `pet`.`species` = ?', + ], + parameters: ['NO_SUCH_SPECIES'], + }, + sqlite: NOT_SUPPORTED, + }) + + await query.execute() + }) + + it('should delete from t1 using t1 left join t2', async () => { + const query = ctx.db + .deleteFrom('person') + .using('person') + .leftJoin('pet', 'pet.owner_id', 'person.id') + .where('pet.species', '=', sql`${'NO_SUCH_SPECIES'}`) + + testSql(query, dialect, { + postgres: NOT_SUPPORTED, + mysql: { + sql: [ + 'delete from `person`', + 'using `person`', + 'left join `pet` on `pet`.`owner_id` = `person`.`id`', + 'where `pet`.`species` = ?', + ], + parameters: ['NO_SUCH_SPECIES'], + }, + sqlite: NOT_SUPPORTED, + }) + + await query.execute() + }) + } }) } From 6c20d9ef21beb3abc88b6179793513f2ac060cff Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Fri, 9 Dec 2022 11:50:33 +0200 Subject: [PATCH 06/19] add `using` unit tests pt.2. --- test/node/src/delete.test.ts | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/node/src/delete.test.ts b/test/node/src/delete.test.ts index 27a7d47d7..343172791 100644 --- a/test/node/src/delete.test.ts +++ b/test/node/src/delete.test.ts @@ -187,6 +187,32 @@ for (const dialect of BUILT_IN_DIALECTS) { await query.execute() }) + + it('should delete from t1 using t2, t3', async () => { + const query = ctx.db + .deleteFrom('person') + .using(['pet', 'toy']) + .whereRef('pet.owner_id', '=', 'person.id') + .whereRef('toy.pet_id', '=', 'pet.id') + .where('toy.price', '=', 0) + + testSql(query, dialect, { + postgres: { + sql: [ + 'delete from "person"', + 'using "pet", "toy"', + 'where "pet"."owner_id" = "person"."id"', + 'and "toy"."pet_id" = "pet"."id"', + 'and "toy"."price" = $1', + ], + parameters: [0], + }, + mysql: NOT_SUPPORTED, + sqlite: NOT_SUPPORTED, + }) + + await query.execute() + }) } if (dialect === 'mysql') { @@ -237,6 +263,28 @@ for (const dialect of BUILT_IN_DIALECTS) { await query.execute() }) + + it('should delete from t1 using t1, t2', async () => { + const query = ctx.db + .deleteFrom('person') + .using(['person', 'pet']) + .where('pet.species', '=', sql`${'NO_SUCH_SPECIES'}`) + + testSql(query, dialect, { + postgres: NOT_SUPPORTED, + mysql: { + sql: [ + 'delete from `person`', + 'using `person`, `pet`', + 'where `pet`.`species` = ?', + ], + parameters: ['NO_SUCH_SPECIES'], + }, + sqlite: NOT_SUPPORTED, + }) + + await query.execute() + }) } }) } From 6d36d260140203041dea2123c3d340a92f6eebf1 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Fri, 9 Dec 2022 16:23:21 +0200 Subject: [PATCH 07/19] add `using` unit tests pt.3. --- test/node/src/delete.test.ts | 76 ++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/node/src/delete.test.ts b/test/node/src/delete.test.ts index 343172791..2e775b992 100644 --- a/test/node/src/delete.test.ts +++ b/test/node/src/delete.test.ts @@ -264,6 +264,34 @@ for (const dialect of BUILT_IN_DIALECTS) { await query.execute() }) + it('should delete from t1 using t1 inner join t2 left join t3', async () => { + const query = ctx.db + .deleteFrom('person') + .using('person') + .innerJoin('pet', 'pet.owner_id', 'person.id') + .leftJoin('toy', 'toy.pet_id', 'pet.id') + .where('pet.species', '=', sql`${'NO_SUCH_SPECIES'}`) + .orWhere('toy.price', '=', 0) + + testSql(query, dialect, { + postgres: NOT_SUPPORTED, + mysql: { + sql: [ + 'delete from `person`', + 'using `person`', + 'inner join `pet` on `pet`.`owner_id` = `person`.`id`', + 'left join `toy` on `toy`.`pet_id` = `pet`.`id`', + 'where `pet`.`species` = ?', + 'or `toy`.`price` = ?', + ], + parameters: ['NO_SUCH_SPECIES', 0], + }, + sqlite: NOT_SUPPORTED, + }) + + await query.execute() + }) + it('should delete from t1 using t1, t2', async () => { const query = ctx.db .deleteFrom('person') @@ -285,6 +313,54 @@ for (const dialect of BUILT_IN_DIALECTS) { await query.execute() }) + + it('should delete from t1 using t1, t2 inner join t3', async () => { + const query = ctx.db + .deleteFrom('person') + .using(['person', 'pet']) + .innerJoin('toy', 'toy.pet_id', 'pet.id') + .where('toy.price', '=', 0) + + testSql(query, dialect, { + postgres: NOT_SUPPORTED, + mysql: { + sql: [ + 'delete from `person`', + 'using `person`, `pet`', + 'inner join `toy` on `toy`.`pet_id` = `pet`.`id`', + 'where `toy`.`price` = ?', + ], + parameters: [0], + }, + sqlite: NOT_SUPPORTED, + }) + + await query.execute() + }) + + it('should delete from t1 using t1, t2 left join t3', async () => { + const query = ctx.db + .deleteFrom('person') + .using(['person', 'pet']) + .leftJoin('toy', 'toy.pet_id', 'pet.id') + .where('toy.price', '=', 0) + + testSql(query, dialect, { + postgres: NOT_SUPPORTED, + mysql: { + sql: [ + 'delete from `person`', + 'using `person`, `pet`', + 'left join `toy` on `toy`.`pet_id` = `pet`.`id`', + 'where `toy`.`price` = ?', + ], + parameters: [0], + }, + sqlite: NOT_SUPPORTED, + }) + + await query.execute() + }) } }) } From e91c4d0cd5dc72e317612cc9b043c65307ab2298 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sat, 10 Dec 2022 00:23:01 +0200 Subject: [PATCH 08/19] add `using` typings tests. --- test/typings/shared.d.ts | 7 ++++++ test/typings/test-d/index.test-d.ts | 33 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/test/typings/shared.d.ts b/test/typings/shared.d.ts index 203f0d26e..0675b5b3f 100644 --- a/test/typings/shared.d.ts +++ b/test/typings/shared.d.ts @@ -7,6 +7,12 @@ export interface Pet { species: 'dog' | 'cat' } +export interface Toy { + id: Generated + price: number + pet_id: string +} + export interface Movie { id: Generated stars: number @@ -23,6 +29,7 @@ export interface Database { movie: Movie 'some_schema.movie': Movie book: Book + toy: Toy } export interface Person { diff --git a/test/typings/test-d/index.test-d.ts b/test/typings/test-d/index.test-d.ts index 7e2dc47e5..0f40c801c 100644 --- a/test/typings/test-d/index.test-d.ts +++ b/test/typings/test-d/index.test-d.ts @@ -816,6 +816,39 @@ async function testUpdate(db: Kysely) { async function testDelete(db: Kysely) { const r1 = await db.deleteFrom('pet').where('id', '=', '1').executeTakeFirst() expectType(r1) + + const r2 = await db + .deleteFrom('person') + .using('pet') + .where('pet.species', '=', 'cat') + .executeTakeFirstOrThrow() + expectType(r2) + + const r3 = await db + .deleteFrom('person') + .using(['pet', 'toy']) + .where('pet.species', '=', 'cat') + .orWhere('toy.price', '=', 0) + .executeTakeFirstOrThrow() + expectType(r3) + + const r4 = await db + .deleteFrom('person') + .using(['person', 'pet']) + .innerJoin('toy', 'toy.pet_id', 'pet.id') + .where('pet.species', '=', 'cat') + .orWhere('toy.price', '=', 0) + .executeTakeFirstOrThrow() + expectType(r4) + + const r5 = await db + .deleteFrom('person') + .using(['person', 'pet']) + .leftJoin('toy', 'toy.pet_id', 'pet.id') + .where('pet.species', '=', 'cat') + .orWhere('toy.price', '=', 0) + .executeTakeFirstOrThrow() + expectType(r5) } async function testOrderBy(db: Kysely) { From 82110055dce3fec66c8f5e76776123e0b827d80c Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sat, 10 Dec 2022 00:32:41 +0200 Subject: [PATCH 09/19] extract `deleteFrom` typings tests to own module. --- .../test-d/delete-query-builder.test-d.ts | 41 +++++++++++++++++++ test/typings/test-d/index.test-d.ts | 38 ----------------- 2 files changed, 41 insertions(+), 38 deletions(-) create mode 100644 test/typings/test-d/delete-query-builder.test-d.ts diff --git a/test/typings/test-d/delete-query-builder.test-d.ts b/test/typings/test-d/delete-query-builder.test-d.ts new file mode 100644 index 000000000..a9da197d3 --- /dev/null +++ b/test/typings/test-d/delete-query-builder.test-d.ts @@ -0,0 +1,41 @@ +import { expectType } from 'tsd' +import { Kysely, DeleteResult } from '..' +import { Database } from '../shared' + +async function testDelete(db: Kysely) { + const r1 = await db.deleteFrom('pet').where('id', '=', '1').executeTakeFirst() + expectType(r1) + + const r2 = await db + .deleteFrom('person') + .using('pet') + .where('pet.species', '=', 'cat') + .executeTakeFirstOrThrow() + expectType(r2) + + const r3 = await db + .deleteFrom('person') + .using(['pet', 'toy']) + .where('pet.species', '=', 'cat') + .orWhere('toy.price', '=', 0) + .executeTakeFirstOrThrow() + expectType(r3) + + const r4 = await db + .deleteFrom('person') + .using(['person', 'pet']) + .innerJoin('toy', 'toy.pet_id', 'pet.id') + .where('pet.species', '=', 'cat') + .orWhere('toy.price', '=', 0) + .executeTakeFirstOrThrow() + expectType(r4) + + const r5 = await db + .deleteFrom('person') + .using(['person', 'john']) + .leftJoin('toy', 'toy.pet_id', 'pet.id') + .where('pet.species', '=', 'cat') + .orWhere('toy.price', '=', 0) + .executeTakeFirstOrThrow() + expectType(r5) +} diff --git a/test/typings/test-d/index.test-d.ts b/test/typings/test-d/index.test-d.ts index 0f40c801c..86a66dda4 100644 --- a/test/typings/test-d/index.test-d.ts +++ b/test/typings/test-d/index.test-d.ts @@ -813,44 +813,6 @@ async function testUpdate(db: Kysely) { db.updateTable('person').set(mutationObject) } -async function testDelete(db: Kysely) { - const r1 = await db.deleteFrom('pet').where('id', '=', '1').executeTakeFirst() - expectType(r1) - - const r2 = await db - .deleteFrom('person') - .using('pet') - .where('pet.species', '=', 'cat') - .executeTakeFirstOrThrow() - expectType(r2) - - const r3 = await db - .deleteFrom('person') - .using(['pet', 'toy']) - .where('pet.species', '=', 'cat') - .orWhere('toy.price', '=', 0) - .executeTakeFirstOrThrow() - expectType(r3) - - const r4 = await db - .deleteFrom('person') - .using(['person', 'pet']) - .innerJoin('toy', 'toy.pet_id', 'pet.id') - .where('pet.species', '=', 'cat') - .orWhere('toy.price', '=', 0) - .executeTakeFirstOrThrow() - expectType(r4) - - const r5 = await db - .deleteFrom('person') - .using(['person', 'pet']) - .leftJoin('toy', 'toy.pet_id', 'pet.id') - .where('pet.species', '=', 'cat') - .orWhere('toy.price', '=', 0) - .executeTakeFirstOrThrow() - expectType(r5) -} - async function testOrderBy(db: Kysely) { const r1 = await db .selectFrom('person') From b1bd2961d6863b66f51e8348c38c19b39905e4d3 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sat, 10 Dec 2022 00:36:02 +0200 Subject: [PATCH 10/19] fix wrong table name in `using` typings test. --- test/typings/test-d/delete-query-builder.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/typings/test-d/delete-query-builder.test-d.ts b/test/typings/test-d/delete-query-builder.test-d.ts index a9da197d3..028dcdb9f 100644 --- a/test/typings/test-d/delete-query-builder.test-d.ts +++ b/test/typings/test-d/delete-query-builder.test-d.ts @@ -32,7 +32,7 @@ async function testDelete(db: Kysely) { const r5 = await db .deleteFrom('person') - .using(['person', 'john']) + .using(['person', 'pet']) .leftJoin('toy', 'toy.pet_id', 'pet.id') .where('pet.species', '=', 'cat') .orWhere('toy.price', '=', 0) From b1bcb5aa0ea739c46dcac664fa1ce2c03e477694 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sat, 10 Dec 2022 00:44:24 +0200 Subject: [PATCH 11/19] fix `using` list types @ `DeleteQueryBuilder`. --- src/query-builder/delete-query-builder.ts | 130 +++++++++++++++++++--- 1 file changed, 115 insertions(+), 15 deletions(-) diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts index 33f66641b..61badae69 100644 --- a/src/query-builder/delete-query-builder.ts +++ b/src/query-builder/delete-query-builder.ts @@ -6,8 +6,6 @@ import { parseJoin, } from '../parser/join-parser.js' import { - From, - FromTables, parseTableExpressionOrList, TableExpression, TableExpressionOrList, @@ -181,13 +179,13 @@ export class DeleteQueryBuilder /** * TODO: ... */ - using>( - from: TE[] - ): DeleteQueryBuilder, FromTables, O> + using, ...TableExpression[]]>( + from: TE + ): DeleteQueryBuilderWithUsingList using>( from: TE - ): DeleteQueryBuilder, FromTables, O> + ): DeleteQueryBuilderWithUsingOrInnerJoin using(from: TableExpressionOrList): any { return new DeleteQueryBuilder({ @@ -312,12 +310,19 @@ export class DeleteQueryBuilder TE extends TableExpression, K1 extends JoinReferenceExpression, K2 extends JoinReferenceExpression - >(table: TE, k1: K1, k2: K2): DeleteQueryBuilderWithInnerJoin + >( + table: TE, + k1: K1, + k2: K2 + ): DeleteQueryBuilderWithUsingOrInnerJoin innerJoin< TE extends TableExpression, FN extends JoinCallbackExpression - >(table: TE, callback: FN): DeleteQueryBuilderWithInnerJoin + >( + table: TE, + callback: FN + ): DeleteQueryBuilderWithUsingOrInnerJoin innerJoin(...args: any): any { return new DeleteQueryBuilder({ @@ -712,35 +717,130 @@ export interface DeleteQueryBuilderProps { readonly executor: QueryExecutor } -export type DeleteQueryBuilderWithInnerJoin< +export type DeleteQueryBuilderWithUsingList< + DB, + TB extends keyof DB, + O, + TE extends TableExpression[] +> = TE extends [] + ? DeleteQueryBuilder + : TE extends [infer TEL extends TableExpression, ...infer TER] + ? TEL extends `${infer T} as ${infer A}` + ? T extends keyof DB + ? TER extends TableExpression[] + ? DeleteQueryBuilderWithUsingListAndAliasedTableExpression< + DB, + TB, + O, + TER, + A, + DB[T] + > + : never + : never + : TEL extends keyof DB + ? TER extends TableExpression[] + ? DeleteQueryBuilderWithUsingList + : never + : TEL extends AliasedExpression + ? TER extends TableExpression[] + ? DeleteQueryBuilderWithUsingListAndAliasedTableExpression< + DB, + TB, + O, + TER, + QA, + QO + > + : never + : TEL extends (qb: any) => AliasedExpression + ? TER extends TableExpression[] + ? DeleteQueryBuilderWithUsingListAndAliasedTableExpression< + DB, + TB, + O, + TER, + QA, + QO + > + : never + : never + : never + +type DeleteQueryBuilderWithUsingListAndAliasedTableExpression< + DB, + TB extends keyof DB, + O, + TE extends TableExpression[], + A extends string, + R +> = A extends keyof DB + ? TE extends TableExpression< + DBWithUsingOrInnerJoinToAliasedTable, + TB | A + >[] + ? DeleteQueryBuilderWithUsingList< + DBWithUsingOrInnerJoinToAliasedTable, + TB | A, + O, + TE + > + : never + : TE extends TableExpression, TB | A>[] + ? DeleteQueryBuilderWithUsingList, TB | A, O, TE> + : never + +export type DeleteQueryBuilderWithUsingOrInnerJoin< DB, TB extends keyof DB, O, TE extends TableExpression > = TE extends `${infer T} as ${infer A}` ? T extends keyof DB - ? InnerJoinedBuilder + ? DeleteQueryBuilderWithUsingOrInnerJoinToAliasedTableExpression< + DB, + TB, + O, + A, + DB[T] + > : never : TE extends keyof DB ? DeleteQueryBuilder : TE extends AliasedExpression - ? InnerJoinedBuilder + ? DeleteQueryBuilderWithUsingOrInnerJoinToAliasedTableExpression< + DB, + TB, + O, + QA, + QO + > : TE extends (qb: any) => AliasedExpression - ? InnerJoinedBuilder + ? DeleteQueryBuilderWithUsingOrInnerJoinToAliasedTableExpression< + DB, + TB, + O, + QA, + QO + > : never -type InnerJoinedBuilder< +type DeleteQueryBuilderWithUsingOrInnerJoinToAliasedTableExpression< DB, TB extends keyof DB, O, A extends string, R > = A extends keyof DB - ? DeleteQueryBuilder, TB | A, O> + ? DeleteQueryBuilder< + DBWithUsingOrInnerJoinToAliasedTable, + TB | A, + O + > : // Much faster non-recursive solution for the simple case. DeleteQueryBuilder, TB | A, O> -type InnerJoinedDB = { +type DBWithUsingOrInnerJoinToAliasedTable = { [C in keyof DB | A]: C extends A ? R : C extends keyof DB ? DB[C] : never } From ae161136f4f4e6c635a26005b093304d8223852e Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sat, 10 Dec 2022 14:33:51 +0200 Subject: [PATCH 12/19] remove unnecessary extends. --- src/query-builder/delete-query-builder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts index 61badae69..5e0472ce7 100644 --- a/src/query-builder/delete-query-builder.ts +++ b/src/query-builder/delete-query-builder.ts @@ -724,7 +724,7 @@ export type DeleteQueryBuilderWithUsingList< TE extends TableExpression[] > = TE extends [] ? DeleteQueryBuilder - : TE extends [infer TEL extends TableExpression, ...infer TER] + : TE extends [infer TEL, ...infer TER] ? TEL extends `${infer T} as ${infer A}` ? T extends keyof DB ? TER extends TableExpression[] From 1fcf3350b42a8335e17c1f3857f8ee89e334a211 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sat, 10 Dec 2022 16:10:09 +0200 Subject: [PATCH 13/19] add `using` ts docs. --- src/query-builder/delete-query-builder.ts | 80 +++++++++++++++++++++-- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts index 5e0472ce7..8956bb8ba 100644 --- a/src/query-builder/delete-query-builder.ts +++ b/src/query-builder/delete-query-builder.ts @@ -177,22 +177,92 @@ export class DeleteQueryBuilder } /** - * TODO: ... + * 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 tables after the `from` clause to be named after the `using` + * clause. 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, ...TableExpression[]]>( - from: TE + tables: TE ): DeleteQueryBuilderWithUsingList using>( - from: TE + table: TE ): DeleteQueryBuilderWithUsingOrInnerJoin - using(from: TableExpressionOrList): any { + using(tables: TableExpressionOrList): any { return new DeleteQueryBuilder({ ...this.#props, queryNode: DeleteQueryNode.cloneWithUsing( this.#props.queryNode, - parseTableExpressionOrList(from) + parseTableExpressionOrList(tables) ), }) } From 170e0dc5de6b2f0e4d31fa723eda01ad07b7cec2 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sat, 10 Dec 2022 16:26:54 +0200 Subject: [PATCH 14/19] rename using node froms to tables. --- src/operation-node/delete-query-node.ts | 2 +- src/operation-node/operation-node-transformer.ts | 2 +- src/operation-node/using-node.ts | 12 ++++++------ src/query-compiler/default-query-compiler.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/operation-node/delete-query-node.ts b/src/operation-node/delete-query-node.ts index 73af1b72c..021053283 100644 --- a/src/operation-node/delete-query-node.ts +++ b/src/operation-node/delete-query-node.ts @@ -80,7 +80,7 @@ export const DeleteQueryNode = freeze({ ...deleteNode, using: deleteNode.using !== undefined - ? UsingNode.cloneWithFroms(deleteNode.using, from) + ? UsingNode.cloneWithTables(deleteNode.using, from) : UsingNode.create(from), }) }, diff --git a/src/operation-node/operation-node-transformer.ts b/src/operation-node/operation-node-transformer.ts index c2f6e24c7..b7bb31c2f 100644 --- a/src/operation-node/operation-node-transformer.ts +++ b/src/operation-node/operation-node-transformer.ts @@ -881,7 +881,7 @@ export class OperationNodeTransformer { protected transformUsing(node: UsingNode): UsingNode { return requireAllProps({ kind: 'UsingNode', - froms: node.froms, + tables: node.tables, }) } diff --git a/src/operation-node/using-node.ts b/src/operation-node/using-node.ts index d12fdbe51..cdef79798 100644 --- a/src/operation-node/using-node.ts +++ b/src/operation-node/using-node.ts @@ -3,7 +3,7 @@ import { OperationNode } from './operation-node.js' export interface UsingNode extends OperationNode { readonly kind: 'UsingNode' - readonly froms: ReadonlyArray + readonly tables: ReadonlyArray } /** @@ -14,20 +14,20 @@ export const UsingNode = freeze({ return node.kind === 'UsingNode' }, - create(froms: ReadonlyArray): UsingNode { + create(tables: ReadonlyArray): UsingNode { return freeze({ kind: 'UsingNode', - froms: freeze(froms), + tables: freeze(tables), }) }, - cloneWithFroms( + cloneWithTables( using: UsingNode, - froms: ReadonlyArray + tables: ReadonlyArray ): UsingNode { return freeze({ ...using, - froms: freeze([...using.froms, ...froms]), + tables: freeze([...using.tables, ...tables]), }) }, }) diff --git a/src/query-compiler/default-query-compiler.ts b/src/query-compiler/default-query-compiler.ts index d62b730e8..ee2a8e013 100644 --- a/src/query-compiler/default-query-compiler.ts +++ b/src/query-compiler/default-query-compiler.ts @@ -1258,7 +1258,7 @@ export class DefaultQueryCompiler protected override visitUsing(node: UsingNode): void { this.append('using ') - this.compileList(node.froms) + this.compileList(node.tables) } protected append(str: string): void { From 46ad77a8b7e9356e5b71ed007180e9f6d0173ee6 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sat, 10 Dec 2022 16:39:51 +0200 Subject: [PATCH 15/19] add `using` unit test with `returning` clause. --- test/node/src/delete.test.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/node/src/delete.test.ts b/test/node/src/delete.test.ts index 50fe25fa4..c266e224f 100644 --- a/test/node/src/delete.test.ts +++ b/test/node/src/delete.test.ts @@ -216,6 +216,34 @@ for (const dialect of BUILT_IN_DIALECTS) { await query.execute() }) + + it('should delete from t1 using t2, t3 returning t2.column', async () => { + const query = ctx.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') + + testSql(query, dialect, { + postgres: { + 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"', + ], + parameters: ['Bob'], + }, + mysql: NOT_SUPPORTED, + sqlite: NOT_SUPPORTED, + }) + + await query.execute() + }) } if (dialect === 'mysql') { From 131aed73bb46e3308d1bff96c62f6f44f0f3b11b Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Thu, 15 Dec 2022 02:22:33 +0200 Subject: [PATCH 16/19] rename `DeleteQueryNode.cloneWithUsing` 2nd argument to `tables`. Co-authored-by: Naor Peled --- src/operation-node/delete-query-node.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/operation-node/delete-query-node.ts b/src/operation-node/delete-query-node.ts index 021053283..54a3ac673 100644 --- a/src/operation-node/delete-query-node.ts +++ b/src/operation-node/delete-query-node.ts @@ -74,14 +74,14 @@ export const DeleteQueryNode = freeze({ cloneWithUsing( deleteNode: DeleteQueryNode, - from: OperationNode[] + tables: OperationNode[] ): DeleteQueryNode { return freeze({ ...deleteNode, using: deleteNode.using !== undefined - ? UsingNode.cloneWithTables(deleteNode.using, from) - : UsingNode.create(from), + ? UsingNode.cloneWithTables(deleteNode.using, tables) + : UsingNode.create(tables), }) }, }) From 0ebc249034e7b9b203633b9fc67f7def6b3ba47e Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Thu, 15 Dec 2022 02:26:26 +0200 Subject: [PATCH 17/19] ts doc grammer. --- src/query-builder/delete-query-builder.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts index 81488a990..0e883dbf0 100644 --- a/src/query-builder/delete-query-builder.ts +++ b/src/query-builder/delete-query-builder.ts @@ -202,9 +202,10 @@ export class DeleteQueryBuilder * and "person"."first_name" = $1 * ``` * - * On supported databases such as MySQL, this clause allows using joins, but requires at - * least one of tables after the `from` clause to be named after the `using` - * clause. See also {@link innerJoin}, {@link leftJoin}, {@link rightJoin} and {@link fullJoin}. + * On supported databases such as MySQL, this clause allows using joins, but requires + * at least one of the tables after the `from` clause to be also named after + * the `using` clause. See also {@link innerJoin}, {@link leftJoin}, {@link rightJoin} + * and {@link fullJoin}. * * ```ts * await db From 6ef98d4629b0e641ee4a713f6744c22dcbcf6016 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Fri, 30 Dec 2022 11:42:10 +0200 Subject: [PATCH 18/19] make using types just like `.selectFrom`. --- src/query-builder/delete-query-builder.ts | 132 +++------------------- 1 file changed, 16 insertions(+), 116 deletions(-) diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts index 36ee960e8..83f4b96f2 100644 --- a/src/query-builder/delete-query-builder.ts +++ b/src/query-builder/delete-query-builder.ts @@ -6,6 +6,8 @@ import { parseJoin, } from '../parser/join-parser.js' import { + From, + FromTables, parseTableExpressionOrList, TableExpression, TableExpressionOrList, @@ -258,13 +260,13 @@ export class DeleteQueryBuilder * returning "pet"."name" * ``` */ - using, ...TableExpression[]]>( - tables: TE - ): DeleteQueryBuilderWithUsingList + using>( + tables: TE[] + ): DeleteQueryBuilder, FromTables, O> - using>( + using>( table: TE - ): DeleteQueryBuilderWithUsingOrInnerJoin + ): DeleteQueryBuilder, FromTables, O> using(tables: TableExpressionOrList): any { return new DeleteQueryBuilder({ @@ -389,19 +391,12 @@ export class DeleteQueryBuilder TE extends TableExpression, K1 extends JoinReferenceExpression, K2 extends JoinReferenceExpression - >( - table: TE, - k1: K1, - k2: K2 - ): DeleteQueryBuilderWithUsingOrInnerJoin + >(table: TE, k1: K1, k2: K2): DeleteQueryBuilderWithInnerJoin innerJoin< TE extends TableExpression, FN extends JoinCallbackExpression - >( - table: TE, - callback: FN - ): DeleteQueryBuilderWithUsingOrInnerJoin + >(table: TE, callback: FN): DeleteQueryBuilderWithInnerJoin innerJoin(...args: any): any { return new DeleteQueryBuilder({ @@ -889,130 +884,35 @@ export interface DeleteQueryBuilderProps { readonly executor: QueryExecutor } -export type DeleteQueryBuilderWithUsingList< - DB, - TB extends keyof DB, - O, - TE extends TableExpression[] -> = TE extends [] - ? DeleteQueryBuilder - : TE extends [infer TEL, ...infer TER] - ? TEL extends `${infer T} as ${infer A}` - ? T extends keyof DB - ? TER extends TableExpression[] - ? DeleteQueryBuilderWithUsingListAndAliasedTableExpression< - DB, - TB, - O, - TER, - A, - DB[T] - > - : never - : never - : TEL extends keyof DB - ? TER extends TableExpression[] - ? DeleteQueryBuilderWithUsingList - : never - : TEL extends AliasedExpression - ? TER extends TableExpression[] - ? DeleteQueryBuilderWithUsingListAndAliasedTableExpression< - DB, - TB, - O, - TER, - QA, - QO - > - : never - : TEL extends (qb: any) => AliasedExpression - ? TER extends TableExpression[] - ? DeleteQueryBuilderWithUsingListAndAliasedTableExpression< - DB, - TB, - O, - TER, - QA, - QO - > - : never - : never - : never - -type DeleteQueryBuilderWithUsingListAndAliasedTableExpression< - DB, - TB extends keyof DB, - O, - TE extends TableExpression[], - A extends string, - R -> = A extends keyof DB - ? TE extends TableExpression< - DBWithUsingOrInnerJoinToAliasedTable, - TB | A - >[] - ? DeleteQueryBuilderWithUsingList< - DBWithUsingOrInnerJoinToAliasedTable, - TB | A, - O, - TE - > - : never - : TE extends TableExpression, TB | A>[] - ? DeleteQueryBuilderWithUsingList, TB | A, O, TE> - : never - -export type DeleteQueryBuilderWithUsingOrInnerJoin< +export type DeleteQueryBuilderWithInnerJoin< DB, TB extends keyof DB, O, TE extends TableExpression > = TE extends `${infer T} as ${infer A}` ? T extends keyof DB - ? DeleteQueryBuilderWithUsingOrInnerJoinToAliasedTableExpression< - DB, - TB, - O, - A, - DB[T] - > + ? InnerJoinedBuilder : never : TE extends keyof DB ? DeleteQueryBuilder : TE extends AliasedExpression - ? DeleteQueryBuilderWithUsingOrInnerJoinToAliasedTableExpression< - DB, - TB, - O, - QA, - QO - > + ? InnerJoinedBuilder : TE extends (qb: any) => AliasedExpression - ? DeleteQueryBuilderWithUsingOrInnerJoinToAliasedTableExpression< - DB, - TB, - O, - QA, - QO - > + ? InnerJoinedBuilder : never -type DeleteQueryBuilderWithUsingOrInnerJoinToAliasedTableExpression< +type InnerJoinedBuilder< DB, TB extends keyof DB, O, A extends string, R > = A extends keyof DB - ? DeleteQueryBuilder< - DBWithUsingOrInnerJoinToAliasedTable, - TB | A, - O - > + ? DeleteQueryBuilder, TB | A, O> : // Much faster non-recursive solution for the simple case. DeleteQueryBuilder, TB | A, O> -type DBWithUsingOrInnerJoinToAliasedTable = { +type InnerJoinedDB = { [C in keyof DB | A]: C extends A ? R : C extends keyof DB ? DB[C] : never } From f4ed010e98dc6fa891939b9bc7b05a2a9b362c2f Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Fri, 30 Dec 2022 11:45:49 +0200 Subject: [PATCH 19/19] change 'clause' to 'keyword' in `using` docs. --- src/query-builder/delete-query-builder.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts index 83f4b96f2..510aa460a 100644 --- a/src/query-builder/delete-query-builder.ts +++ b/src/query-builder/delete-query-builder.ts @@ -213,8 +213,8 @@ export class DeleteQueryBuilder * ``` * * On supported databases such as MySQL, this clause allows using joins, but requires - * at least one of the tables after the `from` clause to be also named after - * the `using` clause. See also {@link innerJoin}, {@link leftJoin}, {@link rightJoin} + * 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