Skip to content

Commit 799752f

Browse files
committed
Speed up SelectQueryBuilder.if type checking
1 parent dabc8e9 commit 799752f

6 files changed

Lines changed: 423 additions & 4 deletions

File tree

src/query-builder/select-query-builder.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,7 +1847,7 @@ export interface SelectQueryBuilder<DB, TB extends keyof DB, O>
18471847
*/
18481848
$if<O2>(
18491849
condition: boolean,
1850-
func: (qb: this) => SelectQueryBuilder<any, any, O & O2>,
1850+
func: (qb: this) => SelectQueryBuilderExpression<O & O2>,
18511851
): SelectQueryBuilder<DB, TB, O & Partial<Omit<O2, keyof O>>>
18521852

18531853
/**
@@ -2589,10 +2589,10 @@ class SelectQueryBuilderImpl<
25892589

25902590
$if<O2>(
25912591
condition: boolean,
2592-
func: (qb: this) => SelectQueryBuilder<any, any, O & O2>,
2592+
func: (qb: this) => SelectQueryBuilderExpression<O & O2>,
25932593
): SelectQueryBuilder<DB, TB, O & Partial<Omit<O2, keyof O>>> {
25942594
if (condition) {
2595-
return func(this)
2595+
return func(this) as any
25962596
}
25972597

25982598
return new SelectQueryBuilderImpl({

test/ts-benchmarks/generic.bench.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
DeleteQueryBuilder,
55
ExpressionBuilder,
66
Generated,
7+
InsertQueryBuilder,
78
Kysely,
89
MergeQueryBuilder,
910
Nullable,
@@ -77,6 +78,15 @@ declare function acceptsNarrowDeleteQueryBuilder(
7778
qb: DeleteQueryBuilder<{ a: A; b: B }, 'a', unknown>,
7879
): void
7980

81+
declare const wideInsertQueryBuilder: InsertQueryBuilder<
82+
{ a: A; b: B },
83+
'a',
84+
{ a: number }
85+
>
86+
declare function acceptsNarrowInsertQueryBuilder(
87+
qb: InsertQueryBuilder<{ a: A; b: B }, 'a', unknown>,
88+
): void
89+
8090
declare const wideUpdateQueryBuilder: UpdateQueryBuilder<
8191
{ a: A; b: B },
8292
'a',
@@ -164,6 +174,10 @@ bench('DeleteQueryBuilder assignable to narrower DeleteQueryBuilder', () => {
164174
return acceptsNarrowDeleteQueryBuilder(wideDeleteQueryBuilder)
165175
}).types([10919, 'instantiations'])
166176

177+
bench('InsertQueryBuilder assignable to narrower InsertQueryBuilder', () => {
178+
return acceptsNarrowInsertQueryBuilder(wideInsertQueryBuilder)
179+
}).types([2614, 'instantiations'])
180+
167181
bench('UpdateQueryBuilder assignable to narrower UpdateQueryBuilder', () => {
168182
return acceptsNarrowUpdateQueryBuilder(wideUpdateQueryBuilder)
169183
}).types([91178, 'instantiations'])

test/ts-benchmarks/if.bench.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { bench } from '@ark/attest'
2+
import type { DB } from '../typings/test-d/huge-db.test-d.js'
3+
import type { Kysely } from '../../dist/index.js'
4+
5+
// `$if` is how conditional filters, optional selections and optional joins get
6+
// built in application code, so it tends to appear in every non-trivial query.
7+
//
8+
// Each of these numbers includes the one-time cost of relating two
9+
// instantiations of the builder under test, because that is what a `$if` call
10+
// site makes the compiler do: it has to match the callback's return type
11+
// against the builder type in the signature. Keep the callback return types
12+
// pointed at the smallest interface that still identifies the builder - see the
13+
// comment on `SelectQueryBuilder.$if`.
14+
15+
declare const kysely: Kysely<DB>
16+
declare const condition: boolean
17+
18+
console.log('if.bench.ts:\n')
19+
20+
bench.baseline(() => {})
21+
22+
bench('kysely..select..$if(qb => qb.select(column))', () => {
23+
return kysely
24+
.selectFrom('my_table')
25+
.select('my_table.id')
26+
.$if(condition, (qb) =>
27+
qb.select('my_table.col_2e66a5c7e24d1d066230f368ce8b094e'),
28+
)
29+
}).types([7027, 'instantiations'])
30+
31+
bench('kysely..select..$if(qb => qb.where(...))', () => {
32+
return kysely
33+
.selectFrom('my_table')
34+
.select('my_table.id')
35+
.$if(condition, (qb) =>
36+
qb.where('my_table.col_1d726898491fbca9a8dac855d2be1be8', '=', 1),
37+
)
38+
}).types([3509, 'instantiations'])
39+
40+
bench('kysely..select..$if(qb => qb.innerJoin(...))', () => {
41+
return kysely
42+
.selectFrom('my_table')
43+
.select('my_table.id')
44+
.$if(condition, (qb) =>
45+
qb.innerJoin(
46+
'table_0b5ac72e03509e06683edcba4b3887ab',
47+
'table_0b5ac72e03509e06683edcba4b3887ab.id',
48+
'my_table.id',
49+
),
50+
)
51+
}).types([8742, 'instantiations'])
52+
53+
bench('kysely..select..$if x3', () => {
54+
return kysely
55+
.selectFrom('my_table')
56+
.select('my_table.id')
57+
.$if(condition, (qb) =>
58+
qb.where('my_table.col_1d726898491fbca9a8dac855d2be1be8', '=', 1),
59+
)
60+
.$if(condition, (qb) =>
61+
qb.select('my_table.col_2e66a5c7e24d1d066230f368ce8b094e'),
62+
)
63+
.$if(condition, (qb) =>
64+
qb.select('my_table.col_4f84013a8b5e4c2b7529058c8fafcaa8'),
65+
)
66+
}).types([10196, 'instantiations'])
67+
68+
bench('kysely..update..$if(qb => qb.returning(column))', () => {
69+
return kysely
70+
.updateTable('my_table')
71+
.set('my_table.col_2e66a5c7e24d1d066230f368ce8b094e', 'x')
72+
.returning('my_table.id')
73+
.$if(condition, (qb) =>
74+
qb.returning('my_table.col_2e66a5c7e24d1d066230f368ce8b094e'),
75+
)
76+
}).types([27600, 'instantiations'])
77+
78+
bench('kysely..delete..$if(qb => qb.returning(column))', () => {
79+
return kysely
80+
.deleteFrom('my_table')
81+
.returning('my_table.id')
82+
.$if(condition, (qb) =>
83+
qb.returning('my_table.col_2e66a5c7e24d1d066230f368ce8b094e'),
84+
)
85+
}).types([12710, 'instantiations'])
86+
87+
bench('kysely..insert..$if(qb => qb.returning(column))', () => {
88+
return kysely
89+
.insertInto('my_table')
90+
.defaultValues()
91+
.returning('my_table.id')
92+
.$if(condition, (qb) =>
93+
qb.returning('my_table.col_2e66a5c7e24d1d066230f368ce8b094e'),
94+
)
95+
}).types([4083, 'instantiations'])
96+
97+
bench('kysely..$call(qb => qb.select(column))', () => {
98+
return kysely
99+
.selectFrom('my_table')
100+
.select('my_table.id')
101+
.$call((qb) => qb.select('my_table.col_2e66a5c7e24d1d066230f368ce8b094e'))
102+
}).types([968, 'instantiations'])
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { bench } from '@ark/attest'
2+
import type { DB } from '../typings/test-d/huge-db.test-d.js'
3+
import type { Kysely } from '../../dist/index.js'
4+
5+
// `insert-into.bench.ts` only covers picking the table. These cover the parts
6+
// that carry data - the values themselves, upserts and `set` - which is where a
7+
// row type gets matched against the table's insertable columns. Bulk inserts
8+
// are worth watching in particular: the cost should stay flat in the number of
9+
// rows, since every row is checked against the same type.
10+
11+
declare const kysely: Kysely<DB>
12+
declare const kyselyAny: Kysely<any>
13+
14+
const row = {
15+
col_1d726898491fbca9a8dac855d2be1be8: 1,
16+
col_2e66a5c7e24d1d066230f368ce8b094e: 'a',
17+
col_2f76db193eac6ad0f152563313673ac9: new Date(),
18+
col_4f84013a8b5e4c2b7529058c8fafcaa8: 'b',
19+
col_d2508118d0d39e198d1129d87d692d59: new Date(),
20+
col_454ff479a3b5a9ef082d9be9ac02a6f4: 'c',
21+
col_3917508388f24a50271f7088b657123c: 'd',
22+
}
23+
24+
declare const rows: (typeof row)[]
25+
26+
console.log('insert-values.bench.ts:\n')
27+
28+
bench.baseline(() => {
29+
return kysely.insertInto('my_table')
30+
})
31+
32+
bench('kysely.insertInto(table).values(row)', () => {
33+
return kysely.insertInto('my_table').values(row)
34+
}).types([1038, 'instantiations'])
35+
36+
bench('kysely.insertInto(table).values(~row)', () => {
37+
// @ts-expect-error
38+
return kysely.insertInto('my_table').values({ ...row, no_such_column: 1 })
39+
}).types([1171, 'instantiations'])
40+
41+
bench('kysely.insertInto(table).values([row])', () => {
42+
return kysely.insertInto('my_table').values([row])
43+
}).types([1047, 'instantiations'])
44+
45+
bench('kysely.insertInto(table).values([row x5])', () => {
46+
return kysely.insertInto('my_table').values([row, row, row, row, row])
47+
}).types([1047, 'instantiations'])
48+
49+
bench('kysely.insertInto(table).values([row x10])', () => {
50+
return kysely
51+
.insertInto('my_table')
52+
.values([row, row, row, row, row, row, row, row, row, row])
53+
}).types([1047, 'instantiations'])
54+
55+
bench('kysely.insertInto(table).values(row[])', () => {
56+
return kysely.insertInto('my_table').values(rows)
57+
}).types([1045, 'instantiations'])
58+
59+
bench('kysely.insertInto(table).values(eb => row)', () => {
60+
return kysely.insertInto('my_table').values((eb) => ({
61+
...row,
62+
col_1d726898491fbca9a8dac855d2be1be8: eb
63+
.selectFrom('my_table as t2')
64+
.select('t2.col_1d726898491fbca9a8dac855d2be1be8')
65+
.limit(1),
66+
}))
67+
}).types([2522, 'instantiations'])
68+
69+
bench('kysely..onConflict(oc => oc.column(column).doNothing())', () => {
70+
return kysely
71+
.insertInto('my_table')
72+
.values(row)
73+
.onConflict((oc) =>
74+
oc.column('col_1d726898491fbca9a8dac855d2be1be8').doNothing(),
75+
)
76+
}).types([1102, 'instantiations'])
77+
78+
bench('kysely..onConflict(oc => oc.column(column).doUpdateSet(row))', () => {
79+
return kysely
80+
.insertInto('my_table')
81+
.values(row)
82+
.onConflict((oc) =>
83+
oc.column('col_1d726898491fbca9a8dac855d2be1be8').doUpdateSet(row),
84+
)
85+
}).types([2131, 'instantiations'])
86+
87+
bench('kysely..onConflict(oc => oc..doUpdateSet(eb => excluded ref))', () => {
88+
return kysely
89+
.insertInto('my_table')
90+
.values(row)
91+
.onConflict((oc) =>
92+
oc.column('col_1d726898491fbca9a8dac855d2be1be8').doUpdateSet({
93+
col_2e66a5c7e24d1d066230f368ce8b094e: (eb) =>
94+
eb.ref('excluded.col_2e66a5c7e24d1d066230f368ce8b094e'),
95+
}),
96+
)
97+
}).types([4458, 'instantiations'])
98+
99+
bench('kysely.updateTable(table).set(row)', () => {
100+
return kysely.updateTable('my_table').set(row)
101+
}).types([848, 'instantiations'])
102+
103+
bench('kysely.updateTable(table).set(column, value)', () => {
104+
return kysely
105+
.updateTable('my_table')
106+
.set('my_table.col_2e66a5c7e24d1d066230f368ce8b094e', 'x')
107+
}).types([3078, 'instantiations'])
108+
109+
bench('kysely.updateTable(table).set(eb => row)', () => {
110+
return kysely.updateTable('my_table').set((eb) => ({
111+
col_1d726898491fbca9a8dac855d2be1be8: eb(
112+
'col_1d726898491fbca9a8dac855d2be1be8',
113+
'+',
114+
1,
115+
),
116+
}))
117+
}).types([3520, 'instantiations'])
118+
119+
bench('kyselyAny.insertInto(table).values(row)', () => {
120+
return kyselyAny.insertInto('my_table').values(row)
121+
}).types([421, 'instantiations'])
122+
123+
bench('kyselyAny.updateTable(table).set(row)', () => {
124+
return kyselyAny.updateTable('my_table').set(row)
125+
}).types([333, 'instantiations'])

0 commit comments

Comments
 (0)