Skip to content

Commit 06ffa1f

Browse files
igalklebanovthecodrr
authored andcommitted
fix: onConflict..doUpdateSet using select types instead of update types. (kysely-org#792)
* make OnConflictDatabase use Updateable types of tables. * extract & add complex type use case 4 insert on conflict do update set.
1 parent 356b384 commit 06ffa1f

8 files changed

+205
-195
lines changed

Diff for: src/query-builder/on-conflict-builder.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
UpdateObjectExpression,
1616
parseUpdateObjectExpression,
1717
} from '../parser/update-set-parser.js'
18+
import { Updateable } from '../util/column-type.js'
1819
import { freeze } from '../util/object-utils.js'
1920
import { preventAwait } from '../util/prevent-await.js'
2021
import { AnyColumn, SqlBool } from '../util/type-utils.js'
@@ -257,7 +258,7 @@ export interface OnConflictBuilderProps {
257258
preventAwait(OnConflictBuilder, "don't await OnConflictBuilder instances.")
258259

259260
export type OnConflictDatabase<DB, TB extends keyof DB> = {
260-
[K in keyof DB | 'excluded']: K extends keyof DB ? DB[K] : DB[TB]
261+
[K in keyof DB | 'excluded']: Updateable<K extends keyof DB ? DB[K] : DB[TB]>
261262
}
262263

263264
export type OnConflictTables<TB> = TB | 'excluded'

Diff for: test/typings/shared.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export interface Person {
6464
// we never want the user to be able to insert or
6565
// update.
6666
modified_at: ColumnType<Date, never, never>
67+
// A column that cannot be inserted, but can be updated.
68+
deleted_at: ColumnType<Date | null, never, string | undefined>
6769
}
6870

6971
export interface PersonMetadata {

Diff for: test/typings/test-d/delete-query-builder.test-d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ async function testDelete(db: Kysely<Database>) {
111111
gender: 'male' | 'female' | 'other'
112112
modified_at: Date
113113
marital_status: 'single' | 'married' | 'divorced' | 'widowed' | null
114+
deleted_at: Date | null
114115

115116
name: string
116117
owner_id: number
@@ -136,6 +137,7 @@ async function testDelete(db: Kysely<Database>) {
136137
gender: 'male' | 'female' | 'other'
137138
modified_at: Date
138139
marital_status: 'single' | 'married' | 'divorced' | 'widowed' | null
140+
deleted_at: Date | null
139141

140142
name: string
141143
owner_id: number
@@ -173,6 +175,7 @@ async function testDelete(db: Kysely<Database>) {
173175
gender: 'male' | 'female' | 'other'
174176
modified_at: Date
175177
marital_status: 'single' | 'married' | 'divorced' | 'widowed' | null
178+
deleted_at: Date | null
176179

177180
name: string
178181
owner_id: number

Diff for: test/typings/test-d/index.test-d.ts

+2-194
Original file line numberDiff line numberDiff line change
@@ -7,202 +7,10 @@
77
* happy, but we can catch it here.
88
*/
99

10-
import {
11-
Kysely,
12-
Transaction,
13-
InsertResult,
14-
UpdateResult,
15-
Selectable,
16-
sql,
17-
ExpressionBuilder,
18-
} from '..'
10+
import { Kysely, Transaction, InsertResult, UpdateResult, Selectable } from '..'
1911

2012
import { Database, Person } from '../shared'
21-
import { expectType, expectError, expectAssignable } from 'tsd'
22-
23-
async function testInsert(db: Kysely<Database>) {
24-
const person = {
25-
first_name: 'Jennifer',
26-
last_name: 'Aniston',
27-
gender: 'other' as const,
28-
age: 30,
29-
}
30-
31-
// Insert one row
32-
const r1 = await db.insertInto('person').values(person).execute()
33-
34-
expectType<InsertResult[]>(r1)
35-
36-
// Should be able to leave out nullable columns like last_name
37-
const r2 = await db
38-
.insertInto('person')
39-
.values({ first_name: 'fname', age: 10, gender: 'other' })
40-
.executeTakeFirst()
41-
42-
expectType<InsertResult>(r2)
43-
44-
// The result type is correct when executeTakeFirstOrThrow is used
45-
const r3 = await db
46-
.insertInto('person')
47-
.values(person)
48-
.executeTakeFirstOrThrow()
49-
50-
expectType<InsertResult>(r3)
51-
52-
// Insert values from a CTE
53-
const r4 = await db
54-
.with('foo', (db) =>
55-
db.selectFrom('person').select('id').where('person.id', '=', 1)
56-
)
57-
.insertInto('movie')
58-
.values({
59-
stars: (eb) => eb.selectFrom('foo').select('foo.id'),
60-
})
61-
.executeTakeFirst()
62-
63-
expectType<InsertResult>(r4)
64-
65-
// Insert with an on conflict statement
66-
const r5 = await db
67-
.insertInto('person')
68-
.values(person)
69-
.onConflict((oc) =>
70-
oc.column('id').doUpdateSet({
71-
// Should be able to reference the `excluded` "table"
72-
first_name: (eb) => eb.ref('excluded.first_name'),
73-
last_name: (eb) => eb.ref('last_name'),
74-
})
75-
)
76-
.executeTakeFirst()
77-
78-
expectType<InsertResult>(r5)
79-
80-
// Non-existent table
81-
expectError(db.insertInto('doesnt_exists'))
82-
83-
// Non-existent column
84-
expectError(db.insertInto('person').values({ not_column: 'foo' }))
85-
86-
// Wrong type for a column
87-
expectError(
88-
db.insertInto('person').values({ first_name: 10, age: 10, gender: 'other' })
89-
)
90-
91-
// Missing required columns
92-
expectError(db.insertInto('person').values({ first_name: 'Jennifer' }))
93-
94-
// Explicitly excluded column
95-
expectError(db.insertInto('person').values({ modified_at: new Date() }))
96-
97-
// Non-existent column in a `doUpdateSet` call.
98-
expectError(
99-
db
100-
.insertInto('person')
101-
.values(person)
102-
.onConflict((oc) =>
103-
oc.column('id').doUpdateSet({
104-
first_name: (eb) => eb.ref('doesnt_exist'),
105-
})
106-
)
107-
)
108-
109-
// GeneratedAlways column is not allowed to be inserted
110-
expectError(db.insertInto('book').values({ id: 1, name: 'foo' }))
111-
112-
// Wrong subquery return value type
113-
expectError(
114-
db.insertInto('person').values({
115-
first_name: 'what',
116-
gender: 'male',
117-
age: (eb) => eb.selectFrom('pet').select('pet.name'),
118-
})
119-
)
120-
121-
// Nullable column as undefined
122-
const insertObject: {
123-
first_name: string
124-
last_name: string | undefined
125-
age: number
126-
gender: 'male' | 'female' | 'other'
127-
} = {
128-
first_name: 'emily',
129-
last_name: 'smith',
130-
age: 25,
131-
gender: 'female',
132-
}
133-
134-
db.insertInto('person').values(insertObject)
135-
}
136-
137-
async function testReturning(db: Kysely<Database>) {
138-
const person = {
139-
first_name: 'Jennifer',
140-
last_name: 'Aniston',
141-
gender: 'other' as const,
142-
age: 30,
143-
}
144-
145-
// One returning expression
146-
const r1 = await db
147-
.insertInto('person')
148-
.values(person)
149-
.returning('id')
150-
.executeTakeFirst()
151-
152-
expectType<
153-
| {
154-
id: number
155-
}
156-
| undefined
157-
>(r1)
158-
159-
// Multiple returning expressions
160-
const r2 = await db
161-
.insertInto('person')
162-
.values(person)
163-
.returning(['id', 'person.first_name as fn'])
164-
.execute()
165-
166-
expectType<
167-
{
168-
id: number
169-
fn: string
170-
}[]
171-
>(r2)
172-
173-
// Non-column reference returning expressions
174-
const r3 = await db
175-
.insertInto('person')
176-
.values(person)
177-
.returning([
178-
'id',
179-
sql<string>`concat(first_name, ' ', last_name)`.as('full_name'),
180-
(qb) => qb.selectFrom('pet').select('pet.id').as('sub'),
181-
])
182-
.execute()
183-
184-
expectType<
185-
{
186-
id: number
187-
full_name: string
188-
sub: string | null
189-
}[]
190-
>(r3)
191-
192-
const r4 = await db
193-
.insertInto('movie')
194-
.values({ stars: 5 })
195-
.returningAll()
196-
.executeTakeFirstOrThrow()
197-
198-
expectType<{
199-
id: string
200-
stars: number
201-
}>(r4)
202-
203-
// Non-existent column
204-
expectError(db.insertInto('person').values(person).returning('not_column'))
205-
}
13+
import { expectType, expectError } from 'tsd'
20614

20715
async function testUpdate(db: Kysely<Database>) {
20816
const r1 = await db

0 commit comments

Comments
 (0)