Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: increase fault tolerance to deep types error #483

Merged
merged 2 commits into from
May 16, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/dialect/mysql/mysql-dialect-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DatabaseConnection } from '../../driver/database-connection.js'
import { ShallowRecord } from '../../util/type-utils.js'

/**
* Config for the MySQL dialect.
@@ -63,4 +64,4 @@ export interface MysqlOkPacket {
insertId: number
}

export type MysqlQueryResult = MysqlOkPacket | Record<string, unknown>[]
export type MysqlQueryResult = MysqlOkPacket | ShallowRecord<string, unknown>[]
7 changes: 4 additions & 3 deletions src/parser/expression-parser.ts
Original file line number Diff line number Diff line change
@@ -13,14 +13,15 @@ import {
} from '../expression/expression-builder.js'
import { SelectQueryBuilder } from '../query-builder/select-query-builder.js'
import { isFunction } from '../util/object-utils.js'
import { ShallowRecord } from '../util/type-utils.js'

export type ExpressionOrFactory<DB, TB extends keyof DB, V> =
// SQL treats a subquery with a single selection as a scalar. That's
// why we need to explicitly allow `SelectQueryBuilder` here with a
// `Record<string, V>` output type, even though `SelectQueryBuilder`
// `ShallowRecord<string, V>` output type, even though `SelectQueryBuilder`
// is also an `Expression`.
| SelectQueryBuilder<any, any, Record<string, V>>
| SelectQueryBuilderFactory<DB, TB, Record<string, V>>
| SelectQueryBuilder<any, any, ShallowRecord<string, V>>
| SelectQueryBuilderFactory<DB, TB, ShallowRecord<string, V>>
| Expression<V>
| ExpressionFactory<DB, TB, V>

17 changes: 10 additions & 7 deletions src/parser/select-parser.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import {
AnyAliasedColumnWithTable,
AnyColumn,
AnyColumnWithTable,
DrainOuterGeneric,
ExtractColumnType,
} from '../util/type-utils.js'
import { parseAliasedStringReference } from './reference-parser.js'
@@ -42,11 +43,11 @@ export type SelectArg<
| ReadonlyArray<SE>
| ((eb: ExpressionBuilder<DB, TB>) => ReadonlyArray<SE>)

export type Selection<DB, TB extends keyof DB, SE> = {
export type Selection<DB, TB extends keyof DB, SE> = DrainOuterGeneric<{
[A in ExtractAliasFromSelectExpression<SE>]: SelectType<
ExtractTypeFromSelectExpression<DB, TB, SE, A>
>
}
}>

type ExtractAliasFromSelectExpression<SE> = SE extends string
? ExtractAliasFromStringSelectExpression<SE>
@@ -151,11 +152,13 @@ type ExtractTypeFromStringSelectExpression<
: never
: never

export type AllSelection<DB, TB extends keyof DB> = Selectable<{
[C in AnyColumn<DB, TB>]: {
[T in TB]: C extends keyof DB[T] ? DB[T][C] : never
}[TB]
}>
export type AllSelection<DB, TB extends keyof DB> = DrainOuterGeneric<
Selectable<{
[C in AnyColumn<DB, TB>]: {
[T in TB]: C extends keyof DB[T] ? DB[T][C] : never
}[TB]
}>
>

export function parseSelectArg(
selection: SelectArg<any, any, SelectExpression<any, any>>
13 changes: 7 additions & 6 deletions src/parser/table-parser.ts
Original file line number Diff line number Diff line change
@@ -8,6 +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'

export type TableExpression<DB, TB extends keyof DB> =
| AnyAliasedTable<DB>
@@ -29,7 +30,7 @@ export type TableReferenceOrList<DB> =
| TableReference<DB>
| ReadonlyArray<TableReference<DB>>

export type From<DB, TE> = {
export type From<DB, TE> = DrainOuterGeneric<{
[C in
| keyof DB
| ExtractAliasFromTableExpression<
@@ -40,11 +41,11 @@ export type From<DB, TE> = {
: C extends keyof DB
? DB[C]
: never
}
}>

export type FromTables<DB, TB extends keyof DB, TE> =
| TB
| ExtractAliasFromTableExpression<DB, TE>
export type FromTables<DB, TB extends keyof DB, TE> = DrainOuterGeneric<
TB | ExtractAliasFromTableExpression<DB, TE>
>

export type ExtractTableAlias<DB, TE> = TE extends `${string} as ${infer TA}`
? TA
@@ -57,7 +58,7 @@ export type PickTableWithAlias<
T extends AnyAliasedTable<DB>
> = T extends `${infer TB} as ${infer A}`
? TB extends keyof DB
? Record<A, DB[TB]>
? ShallowRecord<A, DB[TB]>
: never
: never

19 changes: 11 additions & 8 deletions src/parser/with-parser.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { CommonTableExpressionNameNode } from '../operation-node/common-table-ex
import { QueryCreator } from '../query-creator.js'
import { createQueryCreator } from './parse-utils.js'
import { Expression } from '../expression/expression.js'
import { DrainOuterGeneric, ShallowRecord } from '../util/type-utils.js'

export type CommonTableExpression<DB, CN extends string> = (
creator: QueryCreator<DB>
@@ -15,7 +16,7 @@ export type RecursiveCommonTableExpression<DB, CN extends string> = (
creator: QueryCreator<
DB &
// Recursive CTE can select from itself.
Record<
ShallowRecord<
ExtractTableFromCommonTableExpressionName<CN>,
ExtractRowFromCommonTableExpressionName<CN>
>
@@ -26,12 +27,14 @@ export type QueryCreatorWithCommonTableExpression<
DB,
CN extends string,
CTE
> = QueryCreator<
DB &
Record<
ExtractTableFromCommonTableExpressionName<CN>,
ExtractRowFromCommonTableExpression<CTE>
>
> = DrainOuterGeneric<
QueryCreator<
DB &
ShallowRecord<
ExtractTableFromCommonTableExpressionName<CN>,
ExtractRowFromCommonTableExpression<CTE>
>
>
>

type CommonTableExpressionOutput<DB, CN extends string> =
@@ -83,7 +86,7 @@ type ExtractTableFromCommonTableExpressionName<CN extends string> =
type ExtractRowFromCommonTableExpressionName<CN extends string> =
CN extends `${string}(${infer CL})`
? { [C in ExtractColumnNamesFromColumnList<CL>]: any }
: Record<string, any>
: ShallowRecord<string, any>

/**
* Parses a string like 'id, first_name' into a type 'id' | 'first_name'
32 changes: 22 additions & 10 deletions src/query-builder/delete-query-builder.ts
Original file line number Diff line number Diff line change
@@ -22,9 +22,11 @@ import { ReturningAllRow, ReturningRow } from '../parser/returning-parser.js'
import { ReferenceExpression } from '../parser/reference-parser.js'
import { QueryNode } from '../operation-node/query-node.js'
import {
DrainOuterGeneric,
MergePartial,
NarrowPartial,
Nullable,
ShallowRecord,
SimplifyResult,
SimplifySingleResult,
} from '../util/type-utils.js'
@@ -1070,11 +1072,11 @@ type InnerJoinedBuilder<
> = A extends keyof DB
? DeleteQueryBuilder<InnerJoinedDB<DB, A, R>, TB | A, O>
: // Much faster non-recursive solution for the simple case.
DeleteQueryBuilder<DB & Record<A, R>, TB | A, O>
DeleteQueryBuilder<DB & ShallowRecord<A, R>, TB | A, O>

type InnerJoinedDB<DB, A extends string, R> = {
type InnerJoinedDB<DB, A extends string, R> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A ? R : C extends keyof DB ? DB[C] : never
}
}>

export type DeleteQueryBuilderWithLeftJoin<
DB,
@@ -1102,15 +1104,15 @@ type LeftJoinedBuilder<
> = A extends keyof DB
? DeleteQueryBuilder<LeftJoinedDB<DB, A, R>, TB | A, O>
: // Much faster non-recursive solution for the simple case.
DeleteQueryBuilder<DB & Record<A, Nullable<R>>, TB | A, O>
DeleteQueryBuilder<DB & ShallowRecord<A, Nullable<R>>, TB | A, O>

type LeftJoinedDB<DB, A extends keyof any, R> = {
type LeftJoinedDB<DB, A extends keyof any, R> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? Nullable<R>
: C extends keyof DB
? DB[C]
: never
}
}>

export type DeleteQueryBuilderWithRightJoin<
DB,
@@ -1137,15 +1139,20 @@ type RightJoinedBuilder<
R
> = DeleteQueryBuilder<RightJoinedDB<DB, TB, A, R>, TB | A, O>

type RightJoinedDB<DB, TB extends keyof DB, A extends keyof any, R> = {
type RightJoinedDB<
DB,
TB extends keyof DB,
A extends keyof any,
R
> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? R
: C extends TB
? Nullable<DB[C]>
: C extends keyof DB
? DB[C]
: never
}
}>

export type DeleteQueryBuilderWithFullJoin<
DB,
@@ -1172,12 +1179,17 @@ type OuterJoinedBuilder<
R
> = DeleteQueryBuilder<OuterJoinedBuilderDB<DB, TB, A, R>, TB | A, O>

type OuterJoinedBuilderDB<DB, TB extends keyof DB, A extends keyof any, R> = {
type OuterJoinedBuilderDB<
DB,
TB extends keyof DB,
A extends keyof any,
R
> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? Nullable<R>
: C extends TB
? Nullable<DB[C]>
: C extends keyof DB
? DB[C]
: never
}
}>
32 changes: 22 additions & 10 deletions src/query-builder/select-query-builder.ts
Original file line number Diff line number Diff line change
@@ -23,9 +23,11 @@ import {
import { SelectQueryNode } from '../operation-node/select-query-node.js'
import { QueryNode } from '../operation-node/query-node.js'
import {
DrainOuterGeneric,
MergePartial,
NarrowPartial,
Nullable,
ShallowRecord,
Simplify,
SimplifySingleResult,
} from '../util/type-utils.js'
@@ -2050,11 +2052,11 @@ type InnerJoinedBuilder<
> = A extends keyof DB
? SelectQueryBuilder<InnerJoinedDB<DB, A, R>, TB | A, O>
: // Much faster non-recursive solution for the simple case.
SelectQueryBuilder<DB & Record<A, R>, TB | A, O>
SelectQueryBuilder<DB & ShallowRecord<A, R>, TB | A, O>

type InnerJoinedDB<DB, A extends string, R> = {
type InnerJoinedDB<DB, A extends string, R> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A ? R : C extends keyof DB ? DB[C] : never
}
}>

export type SelectQueryBuilderWithLeftJoin<
DB,
@@ -2082,15 +2084,15 @@ type LeftJoinedBuilder<
> = A extends keyof DB
? SelectQueryBuilder<LeftJoinedDB<DB, A, R>, TB | A, O>
: // Much faster non-recursive solution for the simple case.
SelectQueryBuilder<DB & Record<A, Nullable<R>>, TB | A, O>
SelectQueryBuilder<DB & ShallowRecord<A, Nullable<R>>, TB | A, O>

type LeftJoinedDB<DB, A extends keyof any, R> = {
type LeftJoinedDB<DB, A extends keyof any, R> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? Nullable<R>
: C extends keyof DB
? DB[C]
: never
}
}>

export type SelectQueryBuilderWithRightJoin<
DB,
@@ -2117,15 +2119,20 @@ type RightJoinedBuilder<
R
> = SelectQueryBuilder<RightJoinedDB<DB, TB, A, R>, TB | A, O>

type RightJoinedDB<DB, TB extends keyof DB, A extends keyof any, R> = {
type RightJoinedDB<
DB,
TB extends keyof DB,
A extends keyof any,
R
> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? R
: C extends TB
? Nullable<DB[C]>
: C extends keyof DB
? DB[C]
: never
}
}>

export type SelectQueryBuilderWithFullJoin<
DB,
@@ -2152,12 +2159,17 @@ type OuterJoinedBuilder<
R
> = SelectQueryBuilder<OuterJoinedBuilderDB<DB, TB, A, R>, TB | A, O>

type OuterJoinedBuilderDB<DB, TB extends keyof DB, A extends keyof any, R> = {
type OuterJoinedBuilderDB<
DB,
TB extends keyof DB,
A extends keyof any,
R
> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? Nullable<R>
: C extends TB
? Nullable<DB[C]>
: C extends keyof DB
? DB[C]
: never
}
}>
32 changes: 22 additions & 10 deletions src/query-builder/update-query-builder.ts
Original file line number Diff line number Diff line change
@@ -22,9 +22,11 @@ import { ReturningRow } from '../parser/returning-parser.js'
import { ReferenceExpression } from '../parser/reference-parser.js'
import { QueryNode } from '../operation-node/query-node.js'
import {
DrainOuterGeneric,
MergePartial,
NarrowPartial,
Nullable,
ShallowRecord,
SimplifyResult,
SimplifySingleResult,
} from '../util/type-utils.js'
@@ -991,11 +993,11 @@ type InnerJoinedBuilder<
> = A extends keyof DB
? UpdateQueryBuilder<InnerJoinedDB<DB, A, R>, UT, TB | A, O>
: // Much faster non-recursive solution for the simple case.
UpdateQueryBuilder<DB & Record<A, R>, UT, TB | A, O>
UpdateQueryBuilder<DB & ShallowRecord<A, R>, UT, TB | A, O>

type InnerJoinedDB<DB, A extends string, R> = {
type InnerJoinedDB<DB, A extends string, R> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A ? R : C extends keyof DB ? DB[C] : never
}
}>

export type UpdateQueryBuilderWithLeftJoin<
DB,
@@ -1025,15 +1027,15 @@ type LeftJoinedBuilder<
> = A extends keyof DB
? UpdateQueryBuilder<LeftJoinedDB<DB, A, R>, UT, TB | A, O>
: // Much faster non-recursive solution for the simple case.
UpdateQueryBuilder<DB & Record<A, Nullable<R>>, UT, TB | A, O>
UpdateQueryBuilder<DB & ShallowRecord<A, Nullable<R>>, UT, TB | A, O>

type LeftJoinedDB<DB, A extends keyof any, R> = {
type LeftJoinedDB<DB, A extends keyof any, R> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? Nullable<R>
: C extends keyof DB
? DB[C]
: never
}
}>

export type UpdateQueryBuilderWithRightJoin<
DB,
@@ -1062,15 +1064,20 @@ type RightJoinedBuilder<
R
> = UpdateQueryBuilder<RightJoinedDB<DB, TB, A, R>, UT, TB | A, O>

type RightJoinedDB<DB, TB extends keyof DB, A extends keyof any, R> = {
type RightJoinedDB<
DB,
TB extends keyof DB,
A extends keyof any,
R
> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? R
: C extends TB
? Nullable<DB[C]>
: C extends keyof DB
? DB[C]
: never
}
}>

export type UpdateQueryBuilderWithFullJoin<
DB,
@@ -1099,12 +1106,17 @@ type OuterJoinedBuilder<
R
> = UpdateQueryBuilder<OuterJoinedBuilderDB<DB, TB, A, R>, UT, TB | A, O>

type OuterJoinedBuilderDB<DB, TB extends keyof DB, A extends keyof any, R> = {
type OuterJoinedBuilderDB<
DB,
TB extends keyof DB,
A extends keyof any,
R
> = DrainOuterGeneric<{
[C in keyof DB | A]: C extends A
? Nullable<R>
: C extends TB
? Nullable<DB[C]>
: C extends keyof DB
? DB[C]
: never
}
}>
7 changes: 5 additions & 2 deletions src/schema/create-index-builder.ts
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ import {
} from '../parser/binary-operation-parser.js'
import { QueryNode } from '../operation-node/query-node.js'
import { ExpressionBuilder } from '../expression/expression-builder.js'
import { SqlBool } from '../util/type-utils.js'
import { ShallowRecord, SqlBool } from '../util/type-utils.js'
import { ImmediateValueTransformer } from '../plugin/immediate-value/immediate-value-transformer.js'

export class CreateIndexBuilder<C = never>
@@ -224,7 +224,10 @@ export class CreateIndexBuilder<C = never>

where(
factory: (
qb: ExpressionBuilder<Record<string, Record<C & string, any>>, string>
qb: ExpressionBuilder<
ShallowRecord<string, ShallowRecord<C & string, any>>,
string
>
) => Expression<SqlBool>
): CreateIndexBuilder<C>

14 changes: 9 additions & 5 deletions src/util/column-type.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DrainOuterGeneric } from './type-utils.js'

/**
* This type can be used to specify a different type for
* select, insert and update operations.
@@ -107,9 +109,11 @@ export type NonNullableInsertKeys<R> = {
/**
* Keys of `R` whose `SelectType` values are not `never`
*/
type NonNeverSelectKeys<R> = {
[K in keyof R]: IfNotNever<SelectType<R[K]>, K>
}[keyof R]
type NonNeverSelectKeys<R> = DrainOuterGeneric<
{
[K in keyof R]: IfNotNever<SelectType<R[K]>, K>
}[keyof R]
>

/**
* Keys of `R` whose `UpdateType` values are not `never`
@@ -139,9 +143,9 @@ export type UpdateKeys<R> = {
* // }
* ```
*/
export type Selectable<R> = {
export type Selectable<R> = DrainOuterGeneric<{
[K in NonNeverSelectKeys<R>]: SelectType<R[K]>
}
}>

/**
* Given a table interface, extracts the insert type from all
4 changes: 3 additions & 1 deletion src/util/object-utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ShallowRecord } from './type-utils.js'

export function isEmpty(obj: ArrayLike<unknown> | string | object): boolean {
if (Array.isArray(obj) || isString(obj) || isBuffer(obj)) {
return obj.length === 0
@@ -46,7 +48,7 @@ export function isFunction(obj: unknown): obj is Function {
return typeof obj === 'function'
}

export function isObject(obj: unknown): obj is Record<string, unknown> {
export function isObject(obj: unknown): obj is ShallowRecord<string, unknown> {
return typeof obj === 'object' && obj !== null
}

53 changes: 45 additions & 8 deletions src/util/type-utils.ts
Original file line number Diff line number Diff line change
@@ -35,10 +35,12 @@ import { KyselyTypeError } from './type-error.js'
* // Columns == 'id' | 'name' | 'species'
* ```
*/
export type AnyColumn<DB, TB extends keyof DB> = {
[T in TB]: keyof DB[T]
}[TB] &
string
export type AnyColumn<DB, TB extends keyof DB> = DrainOuterGeneric<
{
[T in TB]: keyof DB[T]
}[TB] &
string
>

/**
* Extracts a column type.
@@ -134,7 +136,7 @@ export type SimplifyResult<O> = O extends InsertResult
? O
: Simplify<O>

export type Simplify<T> = { [K in keyof T]: T[K] } & {}
export type Simplify<T> = DrainOuterGeneric<{ [K in keyof T]: T[K] } & {}>

/**
* Represents a database row whose column names and their types are unknown.
@@ -166,7 +168,9 @@ export type Nullable<T> = { [P in keyof T]: T[P] | null }
*
* // { name: string, age: number, species?: 'cat' | 'dog' }
*/
export type MergePartial<T1, T2> = T1 & Partial<Omit<T2, keyof T1>>
export type MergePartial<T1, T2> = DrainOuterGeneric<
T1 & Partial<Omit<T2, keyof T1>>
>

/**
* Evaluates to `true` if `T` is `never`.
@@ -187,12 +191,45 @@ export type Equals<T, U> = (<G>() => G extends T ? 1 : 2) extends <
? true
: false

export type NarrowPartial<S, T> = {
export type NarrowPartial<S, T> = DrainOuterGeneric<{
[K in keyof S & string]: K extends keyof T
? T[K] extends S[K]
? T[K]
: KyselyTypeError<`$narrowType() call failed: passed type does not exist in '${K}'s type union`>
: S[K]
}
}>

export type SqlBool = boolean | 0 | 1

/**
* Utility to reduce depth of TypeScript's internal type instantiation stack.
*
* Example:
*
* ```ts
* type A<T> = { item: T }
*
* type Test<T> = A<
* A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<T>>>>>>>>>>>>>>>>>>>>>>>>
* >
*
* type Error = Test<number> // Type instantiation is excessively deep and possibly infinite.ts (2589)
* ```
*
* To fix this, we can use `DrainOuterGeneric`:
*
* ```ts
* type A<T> = DrainOuterGeneric<{ item: T }>
*
* type Test<T> = A<
* A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<A<T>>>>>>>>>>>>>>>>>>>>>>>>
* >
*
* type Ok = Test<number> // Ok
* ```
*/
export type DrainOuterGeneric<T> = [T] extends [unknown] ? T : never

export type ShallowRecord<K extends keyof any, T> = DrainOuterGeneric<{
[P in K]: T
}>
35 changes: 5 additions & 30 deletions test/typings/test-d/with.test-d.ts
Original file line number Diff line number Diff line change
@@ -80,36 +80,11 @@ async function testWith(db: Kysely<Database>) {

async function testManyWith(db: Kysely<Database>) {
const res = await db
.with('w1', (eb) =>
eb
.selectFrom('person')
.select('first_name as fn1')
.$assertType<{ fn1: string }>()
)
.with('w2', (eb) =>
eb
.selectFrom('person')
.select('first_name as fn2')
.$assertType<{ fn2: string }>()
)
.with('w3', (eb) =>
eb
.selectFrom('person')
.select('first_name as fn3')
.$assertType<{ fn3: string }>()
)
.with('w4', (eb) =>
eb
.selectFrom('person')
.select('first_name as fn4')
.$assertType<{ fn4: string }>()
)
.with('w5', (eb) =>
eb
.selectFrom('person')
.select('first_name as fn5')
.$assertType<{ fn5: string }>()
)
.with('w1', (eb) => eb.selectFrom('person').select('first_name as fn1'))
.with('w2', (eb) => eb.selectFrom('person').select('first_name as fn2'))
.with('w3', (eb) => eb.selectFrom('person').select('first_name as fn3'))
.with('w4', (eb) => eb.selectFrom('person').select('first_name as fn4'))
.with('w5', (eb) => eb.selectFrom('person').select('first_name as fn5'))
.with('w6', (eb) => eb.selectFrom('person').select('first_name as fn6'))
.with('w7', (qb) => qb.selectFrom('person').select('first_name as fn7'))
.with('w8', (qb) => qb.selectFrom('person').select('first_name as fn8'))