Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add returning support in MERGE queries. (#1171)
Browse files Browse the repository at this point in the history
igalklebanov authored Dec 1, 2024
1 parent 1c5e03a commit 09d7ab0
Showing 11 changed files with 503 additions and 89 deletions.
1 change: 1 addition & 0 deletions deno.check.d.ts
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import type {
export interface Database {
audit: AuditTable
person: PersonTable
person_backup: PersonTable
pet: PetTable
toy: ToyTable
wine: WineTable
52 changes: 52 additions & 0 deletions src/helpers/postgres.ts
Original file line number Diff line number Diff line change
@@ -169,3 +169,55 @@ export function jsonBuildObject<O extends Record<string, Expression<unknown>>>(
Object.keys(obj).flatMap((k) => [sql.lit(k), obj[k]]),
)})`
}

export type MergeAction = 'INSERT' | 'UPDATE' | 'DELETE'

/**
* The PostgreSQL `merge_action` function.
*
* This function can be used in a `returning` clause to get the action that was
* performed in a `mergeInto` query. The function returns one of the following
* strings: `'INSERT'`, `'UPDATE'`, or `'DELETE'`.
*
* ### Examples
*
* ```ts
* import { mergeAction } from 'kysely/helpers/postgres'
*
* const result = await db
* .mergeInto('person as p')
* .using('person_backup as pb', 'p.id', 'pb.id')
* .whenMatched()
* .thenUpdateSet(({ ref }) => ({
* first_name: ref('pb.first_name'),
* updated_at: ref('pb.updated_at').$castTo<string | null>(),
* }))
* .whenNotMatched()
* .thenInsertValues(({ ref}) => ({
* id: ref('pb.id'),
* first_name: ref('pb.first_name'),
* created_at: ref('pb.updated_at'),
* updated_at: ref('pb.updated_at').$castTo<string | null>(),
* }))
* .returning([mergeAction().as('action'), 'p.id', 'p.updated_at'])
* .execute()
*
* result[0].action
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* merge into "person" as "p"
* using "person_backup" as "pb" on "p"."id" = "pb"."id"
* when matched then update set
* "first_name" = "pb"."first_name",
* "updated_at" = "pb"."updated_at"::text
* when not matched then insert values ("id", "first_name", "created_at", "updated_at")
* values ("pb"."id", "pb"."first_name", "pb"."updated_at", "pb"."updated_at")
* returning merge_action() as "action", "p"."id", "p"."updated_at"
* ```
*/
export function mergeAction(): RawBuilder<MergeAction> {
return sql`merge_action()`
}
2 changes: 2 additions & 0 deletions src/operation-node/merge-query-node.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { AliasNode } from './alias-node.js'
import { JoinNode } from './join-node.js'
import { OperationNode } from './operation-node.js'
import { OutputNode } from './output-node.js'
import { ReturningNode } from './returning-node.js'
import { TableNode } from './table-node.js'
import { TopNode } from './top-node.js'
import { WhenNode } from './when-node.js'
@@ -15,6 +16,7 @@ export interface MergeQueryNode extends OperationNode {
readonly whens?: ReadonlyArray<WhenNode>
readonly with?: WithNode
readonly top?: TopNode
readonly returning?: ReturningNode
readonly output?: OutputNode
readonly endModifiers?: ReadonlyArray<OperationNode>
}
1 change: 1 addition & 0 deletions src/operation-node/operation-node-transformer.ts
Original file line number Diff line number Diff line change
@@ -1039,6 +1039,7 @@ export class OperationNodeTransformer {
top: this.transformNode(node.top),
endModifiers: this.transformNodeList(node.endModifiers),
output: this.transformNode(node.output),
returning: this.transformNode(node.returning),
})
}

4 changes: 2 additions & 2 deletions src/query-builder/delete-query-builder.ts
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ import { QueryId } from '../util/query-id.js'
import { freeze } from '../util/object-utils.js'
import { KyselyPlugin } from '../plugin/kysely-plugin.js'
import { WhereInterface } from './where-interface.js'
import { ReturningInterface } from './returning-interface.js'
import { MultiTableReturningInterface } from './returning-interface.js'
import {
isNoResultErrorConstructor,
NoResultError,
@@ -82,7 +82,7 @@ import {
export class DeleteQueryBuilder<DB, TB extends keyof DB, O>
implements
WhereInterface<DB, TB>,
ReturningInterface<DB, TB, O>,
MultiTableReturningInterface<DB, TB, O>,
OutputInterface<DB, TB, O, 'deleted'>,
OperationNodeSource,
Compilable<O>,
117 changes: 111 additions & 6 deletions src/query-builder/merge-query-builder.ts
Original file line number Diff line number Diff line change
@@ -22,8 +22,17 @@ import {
} from '../parser/join-parser.js'
import { parseMergeThen, parseMergeWhen } from '../parser/merge-parser.js'
import { ReferenceExpression } from '../parser/reference-parser.js'
import { ReturningAllRow, ReturningRow } from '../parser/returning-parser.js'
import { parseSelectAll, parseSelectArg } from '../parser/select-parser.js'
import {
ReturningAllRow,
ReturningCallbackRow,
ReturningRow,
} from '../parser/returning-parser.js'
import {
parseSelectAll,
parseSelectArg,
SelectCallback,
SelectExpression,
} from '../parser/select-parser.js'
import { TableExpression } from '../parser/table-parser.js'
import { parseTop } from '../parser/top-parser.js'
import {
@@ -58,10 +67,13 @@ import {
SelectExpressionFromOutputCallback,
SelectExpressionFromOutputExpression,
} from './output-interface.js'
import { MultiTableReturningInterface } from './returning-interface.js'
import { UpdateQueryBuilder } from './update-query-builder.js'

export class MergeQueryBuilder<DB, TT extends keyof DB, O>
implements OutputInterface<DB, TT, O>
implements
MultiTableReturningInterface<DB, TT, O>,
OutputInterface<DB, TT, O>
{
readonly #props: MergeQueryBuilderProps

@@ -215,6 +227,44 @@ export class MergeQueryBuilder<DB, TT extends keyof DB, O>
})
}

returning<SE extends SelectExpression<DB, TT>>(
selections: ReadonlyArray<SE>,
): MergeQueryBuilder<DB, TT, ReturningRow<DB, TT, O, SE>>

returning<CB extends SelectCallback<DB, TT>>(
callback: CB,
): MergeQueryBuilder<DB, TT, ReturningCallbackRow<DB, TT, O, CB>>

returning<SE extends SelectExpression<DB, TT>>(
selection: SE,
): MergeQueryBuilder<DB, TT, ReturningRow<DB, TT, O, SE>>

returning(args: any): any {
return new MergeQueryBuilder({
...this.#props,
queryNode: QueryNode.cloneWithReturning(
this.#props.queryNode,
parseSelectArg(args),
),
})
}

returningAll<T extends TT>(
table: T,
): MergeQueryBuilder<DB, TT, ReturningAllRow<DB, T, O>>

returningAll(): MergeQueryBuilder<DB, TT, ReturningAllRow<DB, TT, O>>

returningAll(table?: any): any {
return new MergeQueryBuilder({
...this.#props,
queryNode: QueryNode.cloneWithReturning(
this.#props.queryNode,
parseSelectAll(table),
),
})
}

output<OE extends OutputExpression<DB, TT>>(
selections: readonly OE[],
): MergeQueryBuilder<
@@ -274,7 +324,11 @@ export class WheneableMergeQueryBuilder<
ST extends keyof DB,
O,
>
implements Compilable<O>, OutputInterface<DB, TT, O>, OperationNodeSource
implements
Compilable<O>,
MultiTableReturningInterface<DB, TT | ST, O>,
OutputInterface<DB, TT, O>,
OperationNodeSource
{
readonly #props: MergeQueryBuilderProps

@@ -608,6 +662,54 @@ export class WheneableMergeQueryBuilder<
return this.#whenNotMatched([lhs, op, rhs], true, true)
}

returning<SE extends SelectExpression<DB, TT | ST>>(
selections: ReadonlyArray<SE>,
): WheneableMergeQueryBuilder<DB, TT, ST, ReturningRow<DB, TT | ST, O, SE>>

returning<CB extends SelectCallback<DB, TT | ST>>(
callback: CB,
): WheneableMergeQueryBuilder<
DB,
TT,
ST,
ReturningCallbackRow<DB, TT | ST, O, CB>
>

returning<SE extends SelectExpression<DB, TT | ST>>(
selection: SE,
): WheneableMergeQueryBuilder<DB, TT, ST, ReturningRow<DB, TT | ST, O, SE>>

returning(args: any): any {
return new WheneableMergeQueryBuilder({
...this.#props,
queryNode: QueryNode.cloneWithReturning(
this.#props.queryNode,
parseSelectArg(args),
),
})
}

returningAll<T extends TT | ST>(
table: T,
): WheneableMergeQueryBuilder<DB, TT, ST, ReturningAllRow<DB, T, O>>

returningAll(): WheneableMergeQueryBuilder<
DB,
TT,
ST,
ReturningAllRow<DB, TT | ST, O>
>

returningAll(table?: any): any {
return new WheneableMergeQueryBuilder({
...this.#props,
queryNode: QueryNode.cloneWithReturning(
this.#props.queryNode,
parseSelectAll(table),
),
})
}

output<OE extends OutputExpression<DB, TT>>(
selections: readonly OE[],
): WheneableMergeQueryBuilder<
@@ -788,9 +890,12 @@ export class WheneableMergeQueryBuilder<
this.#props.queryId,
)

const { adapter } = this.#props.executor
const query = compiledQuery.query as MergeQueryNode

if (
(compiledQuery.query as MergeQueryNode).output &&
this.#props.executor.adapter.supportsOutput
(query.returning && adapter.supportsReturning) ||
(query.output && adapter.supportsOutput)
) {
return result.rows as any
}
24 changes: 22 additions & 2 deletions src/query-builder/returning-interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ReturningAllRow,
ReturningCallbackRow,
ReturningRow,
} from '../parser/returning-parser.js'
@@ -10,7 +11,7 @@ export interface ReturningInterface<DB, TB extends keyof DB, O> {
* Allows you to return data from modified rows.
*
* On supported databases like PostgreSQL, this method can be chained to
* `insert`, `update` and `delete` queries to return data.
* `insert`, `update`, `delete` and `merge` queries to return data.
*
* Note that on SQLite you need to give aliases for the expressions to avoid
* [this bug](https://sqlite.org/forum/forumpost/033daf0b32) in SQLite.
@@ -78,10 +79,29 @@ export interface ReturningInterface<DB, TB extends keyof DB, O> {
): ReturningInterface<DB, TB, ReturningRow<DB, TB, O, SE>>

/**
* Adds a `returning *` to an insert/update/delete query on databases
* Adds a `returning *` to an insert/update/delete/merge query on databases
* that support `returning` such as PostgreSQL.
*
* Also see the {@link returning} method.
*/
returningAll(): ReturningInterface<DB, TB, Selectable<DB[TB]>>
}

export interface MultiTableReturningInterface<DB, TB extends keyof DB, O>
extends ReturningInterface<DB, TB, O> {
/**
* Adds a `returning *` or `returning table.*` to an insert/update/delete/merge
* query on databases that support `returning` such as PostgreSQL.
*
* Also see the {@link returning} method.
*/
returningAll<T extends TB>(
tables: ReadonlyArray<T>,
): MultiTableReturningInterface<DB, TB, ReturningAllRow<DB, T, O>>

returningAll<T extends TB>(
table: T,
): MultiTableReturningInterface<DB, TB, ReturningAllRow<DB, T, O>>

returningAll(): ReturningInterface<DB, TB, Selectable<DB[TB]>>
}
4 changes: 2 additions & 2 deletions src/query-builder/update-query-builder.ts
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ import { freeze } from '../util/object-utils.js'
import { UpdateResult } from './update-result.js'
import { KyselyPlugin } from '../plugin/kysely-plugin.js'
import { WhereInterface } from './where-interface.js'
import { ReturningInterface } from './returning-interface.js'
import { MultiTableReturningInterface } from './returning-interface.js'
import {
isNoResultErrorConstructor,
NoResultError,
@@ -83,7 +83,7 @@ import {
export class UpdateQueryBuilder<DB, UT extends keyof DB, TB extends keyof DB, O>
implements
WhereInterface<DB, TB>,
ReturningInterface<DB, TB, O>,
MultiTableReturningInterface<DB, TB, O>,
OutputInterface<DB, TB, O>,
OperationNodeSource,
Compilable<O>,
5 changes: 5 additions & 0 deletions src/query-compiler/default-query-compiler.ts
Original file line number Diff line number Diff line change
@@ -1585,6 +1585,11 @@ export class DefaultQueryCompiler
this.compileList(node.whens, ' ')
}

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

if (node.output) {
this.append(' ')
this.visitNode(node.output)
201 changes: 201 additions & 0 deletions test/node/src/merge.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MergeResult, sql } from '../../..'
import { mergeAction } from '../../../helpers/postgres'
import {
DIALECTS,
NOT_SUPPORTED,
@@ -1013,6 +1014,206 @@ for (const dialect of DIALECTS.filter(
})
})

if (dialect === 'postgres') {
it('should perform a merge...using table simple on...when matched then delete returning id query', async () => {
const expected = await ctx.db.selectFrom('pet').select('id').execute()

const query = ctx.db
.mergeInto('pet')
.using('person', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.returning('pet.id')

testSql(query, dialect, {
postgres: {
sql: 'merge into "pet" using "person" on "pet"."owner_id" = "person"."id" when matched then delete returning "pet"."id"',
parameters: [],
},
mysql: NOT_SUPPORTED,
mssql: NOT_SUPPORTED,
sqlite: NOT_SUPPORTED,
})

const result = await query.execute()

expect(result).to.eql(expected)
})

it('should perform a merge...using table simple on...when matched then update set name returning {target}.name, {source}.first_name query', async () => {
const query = ctx.db
.mergeInto('pet')
.using('person', 'pet.owner_id', 'person.id')
.whenMatched()
.thenUpdateSet((eb) => ({
name: sql`${eb.ref('person.first_name')} || '''s pet'`,
}))
.returning([
'pet.name as pet_name',
'person.first_name as owner_name',
])

testSql(query, dialect, {
postgres: {
sql: 'merge into "pet" using "person" on "pet"."owner_id" = "person"."id" when matched then update set "name" = "person"."first_name" || \'\'\'s pet\' returning "pet"."name" as "pet_name", "person"."first_name" as "owner_name"',
parameters: [],
},
mysql: NOT_SUPPORTED,
mssql: NOT_SUPPORTED,
sqlite: NOT_SUPPORTED,
})

const result = await query.execute()

expect(result).to.eql([
{ owner_name: 'Jennifer', pet_name: "Jennifer's pet" },
{ owner_name: 'Arnold', pet_name: "Arnold's pet" },
{ owner_name: 'Sylvester', pet_name: "Sylvester's pet" },
])
})

it('should perform a merge...using table simple on...when matched then delete returning * query', async () => {
const expected = await ctx.db
.selectFrom('pet')
.innerJoin('person', 'pet.owner_id', 'person.id')
.selectAll()
.execute()

const query = ctx.db
.mergeInto('pet')
.using('person', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.returningAll()

testSql(query, dialect, {
postgres: {
sql: 'merge into "pet" using "person" on "pet"."owner_id" = "person"."id" when matched then delete returning *',
parameters: [],
},
mysql: NOT_SUPPORTED,
mssql: NOT_SUPPORTED,
sqlite: NOT_SUPPORTED,
})

const result = await query.execute()

expect(result).to.eql(expected)
})

it('should perform a merge...using table simple on...when matched then delete returning {target}.* query', async () => {
const expected = await ctx.db.selectFrom('pet').selectAll().execute()

const query = ctx.db
.mergeInto('pet')
.using('person', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.returningAll('pet')

testSql(query, dialect, {
postgres: {
sql: 'merge into "pet" using "person" on "pet"."owner_id" = "person"."id" when matched then delete returning "pet".*',
parameters: [],
},
mysql: NOT_SUPPORTED,
mssql: NOT_SUPPORTED,
sqlite: NOT_SUPPORTED,
})

const result = await query.execute()

expect(result).to.eql(expected)
})

it('should perform a merge...using table simple on...when matched then delete returning {source}.* query', async () => {
const expected = await ctx.db
.selectFrom('pet')
.innerJoin('person', 'pet.owner_id', 'person.id')
.selectAll('person')
.execute()

const query = ctx.db
.mergeInto('pet')
.using('person', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.returningAll('person')

testSql(query, dialect, {
postgres: {
sql: 'merge into "pet" using "person" on "pet"."owner_id" = "person"."id" when matched then delete returning "person".*',
parameters: [],
},
mysql: NOT_SUPPORTED,
mssql: NOT_SUPPORTED,
sqlite: NOT_SUPPORTED,
})

const result = await query.execute()

expect(result).to.eql(expected)
})

it('should perform a merge...using table simple on...when matched then delete returning merge_action(), {target}.name', async () => {
await ctx.db.connection().execute(async (db) => {
await ctx.db
.insertInto('person')
.values({ first_name: 'Moshe', gender: 'other' })
.execute()

await sql`SET session_replication_role = 'replica'`.execute(db)
await db
.insertInto('pet')
.values({
name: 'Ralph',
owner_id: 9999,
species: 'hamster',
})
.execute()
await sql`SET session_replication_role = 'origin'`.execute(db)
})

const query = ctx.db
.mergeInto('pet')
.using('person', 'pet.owner_id', 'person.id')
.whenMatched()
.thenUpdateSet(
'name',
(eb) => sql`${eb.ref('person.first_name')} || '''s pet'`,
)
.whenNotMatched()
.thenInsertValues((eb) => ({
name: sql`${eb.ref('person.first_name')} || '''s pet'`,
owner_id: eb.ref('person.id'),
species: 'hamster',
}))
.whenNotMatchedBySource()
.thenDelete()
.returning([mergeAction().as('action'), 'pet.name'])

testSql(query, dialect, {
postgres: {
sql: 'merge into "pet" using "person" on "pet"."owner_id" = "person"."id" when matched then update set "name" = "person"."first_name" || \'\'\'s pet\' when not matched then insert ("name", "owner_id", "species") values ("person"."first_name" || \'\'\'s pet\', "person"."id", $1) when not matched by source then delete returning merge_action() as "action", "pet"."name"',
parameters: ['hamster'],
},
mysql: NOT_SUPPORTED,
mssql: NOT_SUPPORTED,
sqlite: NOT_SUPPORTED,
})

const result = await query.execute()

expect(result).to.eql([
{ action: 'UPDATE', name: "Jennifer's pet" },
{ action: 'UPDATE', name: "Arnold's pet" },
{ action: 'UPDATE', name: "Sylvester's pet" },
{ action: 'DELETE', name: 'Ralph' },
{ action: 'INSERT', name: "Moshe's pet" },
])
})
}

if (dialect === 'mssql') {
it('should perform a merge top...using table simple on...when matched then delete query', async () => {
const query = ctx.db
181 changes: 104 additions & 77 deletions test/typings/test-d/merge.test-d.ts
Original file line number Diff line number Diff line change
@@ -7,12 +7,14 @@ import {
MergeQueryBuilder,
MergeResult,
NotMatchedThenableMergeQueryBuilder,
SelectType,
Selectable,
UpdateQueryBuilder,
WheneableMergeQueryBuilder,
mergeAction,
sql,
} from '..'
import { Database, Person } from '../shared'
import { Database, Person, Pet } from '../shared'

async function testMergeInto(db: Kysely<Database>) {
db.mergeInto('person')
@@ -422,35 +424,105 @@ async function testThenInsert(
)
}

async function testOutput(db: Kysely<Database>) {
// One returning expression
const r1 = await db
.mergeInto('person')
.using('pet', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.output('deleted.id')
.executeTakeFirst()
async function testReturning(
baseQuery: WheneableMergeQueryBuilder<Database, 'person', 'pet', MergeResult>,
) {
// One returning expression, target table
const r1 = await baseQuery.returning('person.id').execute()

expectType<{ id: number }[]>(r1)

expectType<{ id: number } | undefined>(r1)
// One returning expression, source table
const r2 = await baseQuery.returning('pet.name').execute()

expectType<{ name: string }[]>(r2)

// Multiple returning expressions
const r2 = await db
.mergeInto('person')
.using('pet', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.output(['deleted.id', 'deleted.first_name as fn'])
const r3 = await baseQuery
.returning(['person.id', 'pet.name as pet_name'])
.execute()

expectType<{ id: number; fn: string }[]>(r2)
expectType<{ id: number; pet_name: string }[]>(r3)

// Non-column reference returning expressions
const r3 = await db
.mergeInto('person')
.using('pet', 'pet.owner_id', 'person.id')
.whenMatched()
.thenUpdateSet('age', (eb) => eb(eb.ref('age'), '+', 20))
const r4 = await baseQuery
.returning([
'person.age',
sql<string>`concat(person.first_name, ' ', person.last_name)`.as(
'full_name',
),
])
.execute()

expectType<{ age: number; full_name: string }[]>(r4)

// Return all columns
const r5 = await baseQuery.returningAll().executeTakeFirstOrThrow()

expectType<{
[K in keyof Person | keyof Pet]:
| (K extends keyof Person ? SelectType<Person[K]> : never)
| (K extends keyof Pet ? SelectType<Pet[K]> : never)
}>(r5)

// Return all target columns
const r6 = await baseQuery.returningAll('person').executeTakeFirstOrThrow()

expectType<Selectable<Person>>(r6)

// Return all source columns
const r7 = await baseQuery.returningAll('pet').executeTakeFirstOrThrow()

expectType<Selectable<Pet>>(r7)

// Return single merge_action
const r8 = await baseQuery.returning(mergeAction().as('action')).execute()

expectType<{ action: 'INSERT' | 'UPDATE' | 'DELETE' }[]>(r8)

// Return multi merge_action
const r9 = await baseQuery
.returning([mergeAction().as('action'), 'person.id'])
.execute()

expectType<{ action: 'INSERT' | 'UPDATE' | 'DELETE'; id: number }[]>(r9)

// Non-existent column
expectError(baseQuery.returning('not_column'))
expectError(baseQuery.returning('person.not_column'))
expectError(baseQuery.returning('pet.not_column'))

// Non-existent prefix
expectError(baseQuery.returning('foo.age'))
expectError(baseQuery.returningAll('foo'))

// unaliased merge_action
expectError(baseQuery.returning(mergeAction()).execute())
expectError(baseQuery.returning([mergeAction(), 'person.id']).execute())
}

async function testOutput(
baseQuery: WheneableMergeQueryBuilder<Database, 'person', 'pet', MergeResult>,
) {
// One returning expression, deleted values
const r1 = await baseQuery.output('deleted.id').execute()

expectType<{ id: number }[]>(r1)

// One returning expression, inserted values
const r2 = await baseQuery.output('inserted.id').execute()

expectType<{ id: number }[]>(r2)

// Multiple returning expressions
const r3 = await baseQuery
.output(['deleted.id', 'inserted.first_name as fn'])
.execute()

expectType<{ id: number; fn: string }[]>(r3)

// Non-column reference returning expressions
const r4 = await baseQuery
.output([
'inserted.age',
sql<string>`concat(deleted.first_name, ' ', deleted.last_name)`.as(
@@ -459,66 +531,21 @@ async function testOutput(db: Kysely<Database>) {
])
.execute()

expectType<{ age: number; full_name: string }[]>(r3)
expectType<{ age: number; full_name: string }[]>(r4)

// Return all columns
const r4 = await db
.mergeInto('person')
.using('pet', 'person.id', 'pet.owner_id')
.whenNotMatched()
.thenInsertValues({
gender: 'female',
age: 15,
first_name: 'Jane',
})
.outputAll('inserted')
.executeTakeFirstOrThrow()

expectType<Selectable<Person>>(r4)
const r5 = await baseQuery.outputAll('inserted').executeTakeFirstOrThrow()

expectType<Selectable<Person>>(r5)

// Non-existent column
expectError(
db
.mergeInto('person')
.using('pet', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.output('inserted.not_column'),
)
expectError(baseQuery.output('inserted.not_column'))

// Without prefix
expectError(
db
.mergeInto('person')
.using('pet', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.output('age'),
)
expectError(
db
.mergeInto('person')
.using('pet', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.outputAll(),
)
expectError(baseQuery.output('age'))
expectError(baseQuery.outputAll())

// Non-existent prefix
expectError(
db
.mergeInto('person')
.using('pet', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.output('foo.age'),
)
expectError(
db
.mergeInto('person')
.using('pet', 'pet.owner_id', 'person.id')
.whenMatched()
.thenDelete()
.outputAll('foo'),
)
expectError(baseQuery.output('foo.age'))
expectError(baseQuery.outputAll('foo'))
}

0 comments on commit 09d7ab0

Please sign in to comment.