Skip to content

Commit

Permalink
Speedup types with huge databases. Fixes #867
Browse files Browse the repository at this point in the history
  • Loading branch information
koskimas committed Jul 15, 2024
1 parent 9ae6948 commit dd1c732
Show file tree
Hide file tree
Showing 10 changed files with 3,369 additions and 153 deletions.
31 changes: 3 additions & 28 deletions src/expression/expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ import {
import { SelectQueryNode } from '../operation-node/select-query-node.js'
import {
parseTableExpressionOrList,
TableExpression,
From,
TableExpressionOrList,
FromTables,
ExtractTableAlias,
AnyAliasedTable,
PickTableWithAlias,
parseTable,
} from '../parser/table-parser.js'
import { WithSchemaPlugin } from '../plugin/with-schema/with-schema-plugin.js'
Expand Down Expand Up @@ -83,6 +77,7 @@ import {
parseDataTypeExpression,
} from '../parser/data-type-parser.js'
import { CastNode } from '../operation-node/cast-node.js'
import { SelectFrom } from '../parser/select-from-parser.js'

export interface ExpressionBuilder<DB, TB extends keyof DB> {
/**
Expand Down Expand Up @@ -277,29 +272,9 @@ export interface ExpressionBuilder<DB, TB extends keyof DB> {
* that case Kysely typings wouldn't allow you to reference `pet.owner_id`
* because `pet` is not joined to that query.
*/
selectFrom<TE extends keyof DB & string>(
from: TE[],
): SelectQueryBuilder<DB, TB | ExtractTableAlias<DB, TE>, {}>

selectFrom<TE extends TableExpression<DB, TB>>(
from: TE[],
): SelectQueryBuilder<From<DB, TE>, FromTables<DB, TB, TE>, {}>

selectFrom<TE extends keyof DB & string>(
from: TE,
): SelectQueryBuilder<DB, TB | ExtractTableAlias<DB, TE>, {}>

selectFrom<TE extends AnyAliasedTable<DB>>(
from: TE,
): SelectQueryBuilder<
DB & PickTableWithAlias<DB, TE>,
TB | ExtractTableAlias<DB & PickTableWithAlias<DB, TE>, TE>,
{}
>

selectFrom<TE extends TableExpression<DB, TB>>(
selectFrom<TE extends TableExpressionOrList<DB, TB>>(
from: TE,
): SelectQueryBuilder<From<DB, TE>, FromTables<DB, TB, TE>, {}>
): SelectFrom<DB, TB, TE>

/**
* Creates a `case` statement/operator.
Expand Down
30 changes: 30 additions & 0 deletions src/parser/delete-from-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { DeleteQueryBuilder } from '../query-builder/delete-query-builder.js'
import type { DeleteResult } from '../query-builder/delete-result.js'
import type { ShallowRecord } from '../util/type-utils.js'
import type {
ExtractTableAlias,
From,
FromTables,
TableExpressionOrList,
} from './table-parser.js'

export type DeleteFrom<DB, TE extends TableExpressionOrList<DB, never>> =
TE extends ReadonlyArray<infer T>
? DeleteQueryBuilder<From<DB, T>, FromTables<DB, never, T>, DeleteResult>
: TE extends keyof DB & string
? // This branch creates a good-looking type for the most common case:
// deleteFrom('person') --> DeleteQueryBuilder<DB, 'person', {}>.
// ExtractTableAlias is needed for the case where DB == any. Without it:
// deleteFrom('person as p') --> DeleteQueryBuilder<DB, 'person as p', {}>
DeleteQueryBuilder<DB, ExtractTableAlias<DB, TE>, DeleteResult>
: // This branch creates a good-looking type for common aliased case:
// deleteFrom('person as p') --> DeleteQueryBuilder<DB & { p: Person }, 'p', {}>.
TE extends `${infer T} as ${infer A}`
? T extends keyof DB
? DeleteQueryBuilder<DB & ShallowRecord<A, DB[T]>, A, DeleteResult>
: never
: DeleteQueryBuilder<
From<DB, TE>,
FromTables<DB, never, TE>,
DeleteResult
>
15 changes: 15 additions & 0 deletions src/parser/merge-into-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { MergeQueryBuilder } from '../query-builder/merge-query-builder.js'
import type { MergeResult } from '../query-builder/merge-result.js'
import type { ShallowRecord } from '../util/type-utils.js'
import type { ExtractTableAlias, SimpleTableReference } from './table-parser.js'

export type MergeInto<
DB,
TE extends SimpleTableReference<DB>,
> = TE extends keyof DB & string
? MergeQueryBuilder<DB, ExtractTableAlias<DB, TE>, MergeResult>
: TE extends `${infer T} as ${infer A}`
? T extends keyof DB
? MergeQueryBuilder<DB & ShallowRecord<A, DB[T]>, A, MergeResult>
: never
: never
29 changes: 29 additions & 0 deletions src/parser/select-from-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { SelectQueryBuilder } from '../query-builder/select-query-builder.js'
import type { ShallowRecord } from '../util/type-utils.js'
import type {
ExtractTableAlias,
From,
FromTables,
TableExpressionOrList,
} from './table-parser.js'

export type SelectFrom<
DB,
TB extends keyof DB,
TE extends TableExpressionOrList<DB, TB>,
> =
TE extends ReadonlyArray<infer T>
? SelectQueryBuilder<From<DB, T>, FromTables<DB, TB, T>, {}>
: TE extends keyof DB & string
? // This branch creates a good-looking type for the most common case:
// selectFrom('person') --> SelectQueryBuilder<DB, 'person', {}>.
// ExtractTableAlias is needed for the case where DB == any. Without it:
// selectFrom('person as p') --> SelectQueryBuilder<DB, 'person as p', {}>
SelectQueryBuilder<DB, TB | ExtractTableAlias<DB, TE>, {}>
: // This branch creates a good-looking type for common aliased case:
// selectFrom('person as p') --> SelectQueryBuilder<DB & { p: Person }, 'p', {}>.
TE extends `${infer T} as ${infer A}`
? T extends keyof DB
? SelectQueryBuilder<DB & ShallowRecord<A, DB[T]>, TB | A, {}>
: never
: SelectQueryBuilder<From<DB, TE>, FromTables<DB, TB, TE>, {}>
23 changes: 2 additions & 21 deletions src/parser/table-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { IdentifierNode } from '../operation-node/identifier-node.js'
import { OperationNode } from '../operation-node/operation-node.js'
import { AliasedExpression } from '../expression/expression.js'
import { DrainOuterGeneric, ShallowRecord } from '../util/type-utils.js'
import { DrainOuterGeneric } from '../util/type-utils.js'

export type TableExpression<DB, TB extends keyof DB> =
| AnyAliasedTable<DB>
Expand All @@ -19,17 +19,9 @@ export type TableExpressionOrList<DB, TB extends keyof DB> =
| TableExpression<DB, TB>
| ReadonlyArray<TableExpression<DB, TB>>

export type TableReference<DB> =
| SimpleTableReference<DB>
| AliasedExpression<any, any>

export type SimpleTableReference<DB> = AnyAliasedTable<DB> | AnyTable<DB>

export type AnyAliasedTable<DB> = `${AnyTable<DB>} as ${string}`

export type TableReferenceOrList<DB> =
| TableReference<DB>
| ReadonlyArray<TableReference<DB>>
export type AnyTable<DB> = keyof DB & string

export type From<DB, TE> = DrainOuterGeneric<{
[C in
Expand All @@ -56,15 +48,6 @@ export type ExtractTableAlias<DB, TE> = TE extends `${string} as ${infer TA}`
? TE
: never

export type PickTableWithAlias<
DB,
T extends AnyAliasedTable<DB>,
> = T extends `${infer TB} as ${infer A}`
? TB extends keyof DB
? ShallowRecord<A, DB[TB]>
: never
: never

type ExtractAliasFromTableExpression<DB, TE> = TE extends string
? TE extends `${string} as ${infer TA}`
? TA
Expand Down Expand Up @@ -101,8 +84,6 @@ type ExtractRowTypeFromTableExpression<
: never
: never

type AnyTable<DB> = keyof DB & string

export function parseTableExpressionOrList(
table: TableExpressionOrList<any, any>,
): OperationNode[] {
Expand Down
41 changes: 41 additions & 0 deletions src/parser/update-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { UpdateQueryBuilder } from '../query-builder/update-query-builder.js'
import type { UpdateResult } from '../query-builder/update-result.js'
import type { ShallowRecord } from '../util/type-utils.js'
import type {
ExtractTableAlias,
From,
FromTables,
TableExpressionOrList,
} from './table-parser.js'

export type UpdateTable<DB, TE extends TableExpressionOrList<DB, never>> =
TE extends ReadonlyArray<infer T>
? UpdateQueryBuilder<
From<DB, T>,
FromTables<DB, never, T>,
FromTables<DB, never, T>,
UpdateResult
>
: TE extends keyof DB & string
? // This branch creates a good-looking type for the most common case:
// updateTable('person') --> UpdateQueryBuilder<DB, 'person', 'person', {}>.
// ExtractTableAlias is needed for the case where DB == any. Without it:
// updateTable('person as p') --> UpdateQueryBuilder<DB, 'person as p', 'person as p', {}>
UpdateQueryBuilder<
DB,
ExtractTableAlias<DB, TE>,
ExtractTableAlias<DB, TE>,
UpdateResult
>
: // This branch creates a good-looking type for common aliased case:
// updateTable('person as p') --> UpdateQueryBuilder<DB & { p: Person }, 'p', 'p', {}>.
TE extends `${infer T} as ${infer A}`
? T extends keyof DB
? UpdateQueryBuilder<DB & ShallowRecord<A, DB[T]>, A, A, UpdateResult>
: never
: UpdateQueryBuilder<
From<DB, TE>,
FromTables<DB, never, TE>,
FromTables<DB, never, TE>,
UpdateResult
>
105 changes: 14 additions & 91 deletions src/query-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,8 @@ import { SelectQueryNode } from './operation-node/select-query-node.js'
import { UpdateQueryNode } from './operation-node/update-query-node.js'
import {
parseTable,
parseTableExpression,
parseTableExpressionOrList,
TableExpression,
From,
TableExpressionOrList,
FromTables,
TableReference,
TableReferenceOrList,
ExtractTableAlias,
AnyAliasedTable,
PickTableWithAlias,
SimpleTableReference,
parseAliasedTable,
} from './parser/table-parser.js'
Expand Down Expand Up @@ -52,6 +43,10 @@ import {
import { MergeQueryBuilder } from './query-builder/merge-query-builder.js'
import { MergeQueryNode } from './operation-node/merge-query-node.js'
import { MergeResult } from './query-builder/merge-result.js'
import { SelectFrom } from './parser/select-from-parser.js'
import { DeleteFrom } from './parser/delete-from-parser.js'
import { UpdateTable } from './parser/update-parser.js'
import { MergeInto } from './parser/merge-into-parser.js'

export class QueryCreator<DB> {
readonly #props: QueryCreatorProps
Expand Down Expand Up @@ -167,29 +162,9 @@ export class QueryCreator<DB> {
* (select 1 as one) as "q"
* ```
*/
selectFrom<TE extends keyof DB & string>(
from: TE[],
): SelectQueryBuilder<DB, ExtractTableAlias<DB, TE>, {}>

selectFrom<TE extends TableExpression<DB, never>>(
from: TE[],
): SelectQueryBuilder<From<DB, TE>, FromTables<DB, never, TE>, {}>

selectFrom<TE extends keyof DB & string>(
from: TE,
): SelectQueryBuilder<DB, ExtractTableAlias<DB, TE>, {}>

selectFrom<TE extends AnyAliasedTable<DB>>(
selectFrom<TE extends TableExpressionOrList<DB, never>>(
from: TE,
): SelectQueryBuilder<
DB & PickTableWithAlias<DB, TE>,
ExtractTableAlias<DB & PickTableWithAlias<DB, TE>, TE>,
{}
>

selectFrom<TE extends TableExpression<DB, never>>(
from: TE,
): SelectQueryBuilder<From<DB, TE>, FromTables<DB, never, TE>, {}>
): SelectFrom<DB, never, TE>

selectFrom(from: TableExpressionOrList<any, any>): any {
return createSelectQueryBuilder({
Expand Down Expand Up @@ -414,23 +389,11 @@ export class QueryCreator<DB> {
* where `person`.`id` = ?
* ```
*/
deleteFrom<TR extends keyof DB & string>(
from: TR[],
): DeleteQueryBuilder<DB, ExtractTableAlias<DB, TR>, DeleteResult>

deleteFrom<TR extends TableReference<DB>>(
tables: TR[],
): DeleteQueryBuilder<From<DB, TR>, FromTables<DB, never, TR>, DeleteResult>

deleteFrom<TR extends keyof DB & string>(
from: TR,
): DeleteQueryBuilder<DB, ExtractTableAlias<DB, TR>, DeleteResult>

deleteFrom<TR extends TableReference<DB>>(
table: TR,
): DeleteQueryBuilder<From<DB, TR>, FromTables<DB, never, TR>, DeleteResult>
deleteFrom<TE extends TableExpressionOrList<DB, never>>(
from: TE,
): DeleteFrom<DB, TE>

deleteFrom(tables: TableReferenceOrList<DB>): any {
deleteFrom(tables: TableExpressionOrList<any, any>): any {
return new DeleteQueryBuilder({
queryId: createQueryId(),
executor: this.#props.executor,
Expand Down Expand Up @@ -464,41 +427,9 @@ export class QueryCreator<DB> {
* console.log(result.numUpdatedRows)
* ```
*/
updateTable<TE extends keyof DB & string>(
from: TE[],
): UpdateQueryBuilder<
DB,
ExtractTableAlias<DB, TE>,
ExtractTableAlias<DB, TE>,
UpdateResult
>

updateTable<TE extends TableExpression<DB, never>>(
from: TE[],
): UpdateQueryBuilder<
From<DB, TE>,
FromTables<DB, never, TE>,
FromTables<DB, never, TE>,
UpdateResult
>

updateTable<TE extends keyof DB & string>(
updateTable<TE extends TableExpressionOrList<DB, never>>(
from: TE,
): UpdateQueryBuilder<
DB,
ExtractTableAlias<DB, TE>,
ExtractTableAlias<DB, TE>,
UpdateResult
>

updateTable<TE extends AnyAliasedTable<DB>>(
from: TE,
): UpdateQueryBuilder<
DB & PickTableWithAlias<DB, TE>,
ExtractTableAlias<DB & PickTableWithAlias<DB, TE>, TE>,
ExtractTableAlias<DB & PickTableWithAlias<DB, TE>, TE>,
UpdateResult
>
): UpdateTable<DB, TE>

updateTable(tables: TableExpressionOrList<any, any>): any {
return new UpdateQueryBuilder({
Expand Down Expand Up @@ -545,17 +476,9 @@ export class QueryCreator<DB> {
* do nothing
* ```
*/
mergeInto<TR extends keyof DB & string>(
targetTable: TR,
): MergeQueryBuilder<DB, TR, MergeResult>

mergeInto<TR extends AnyAliasedTable<DB>>(
mergeInto<TR extends SimpleTableReference<DB>>(
targetTable: TR,
): MergeQueryBuilder<
DB & PickTableWithAlias<DB, TR>,
ExtractTableAlias<DB & PickTableWithAlias<DB, TR>, TR>,
MergeResult
>
): MergeInto<DB, TR>

mergeInto<TR extends SimpleTableReference<DB>>(targetTable: TR): any {
return new MergeQueryBuilder({
Expand Down
12 changes: 0 additions & 12 deletions test/typings/test-d/generic.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,6 @@ async function testGenericSelectHelper() {
.execute()
}

async function testGenericSelect<T extends keyof Database>(
db: Kysely<Database>,
table: T,
) {
const r1 = await db.selectFrom(table).select('id').executeTakeFirstOrThrow()
expectAssignable<string | number>(r1.id)
}

async function testGenericUpdate(db: Kysely<Database>, table: 'pet' | 'movie') {
await db.updateTable(table).set({ id: '123' }).execute()
}

async function testSelectsInVariable(db: Kysely<Database>) {
const selects = [
'first_name',
Expand Down
Loading

0 comments on commit dd1c732

Please sign in to comment.