Skip to content

Commit

Permalink
Support tuples (#611)
Browse files Browse the repository at this point in the history
* further type speedup and simplification

* Add tuple support

* rename tuple to refTuple and valTuple to tuple

* improve  typings
  • Loading branch information
koskimas authored Jul 25, 2023
1 parent 735b084 commit 53d9125
Show file tree
Hide file tree
Showing 15 changed files with 618 additions and 64 deletions.
203 changes: 203 additions & 0 deletions src/expression/expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ import {
Selection,
parseSelectArg,
} from '../parser/select-parser.js'
import {
RefTuple2,
RefTuple3,
RefTuple4,
RefTuple5,
ValTuple2,
ValTuple3,
ValTuple4,
ValTuple5,
} from '../parser/tuple-parser.js'
import { TupleNode } from '../operation-node/tuple-node.js'

export interface ExpressionBuilder<DB, TB extends keyof DB> {
/**
Expand Down Expand Up @@ -474,6 +485,184 @@ export interface ExpressionBuilder<DB, TB extends keyof DB> {
value: VE
): ExpressionWrapper<DB, TB, ExtractTypeFromValueExpression<VE>>

/**
* Creates a tuple expression.
*
* This creates a tuple using column references by default. See {@link tuple}
* if you need to create value tuples.
*
* ### Examples
*
* ```ts
* db.selectFrom('person')
* .selectAll('person')
* .where(({ eb, refTuple, tuple }) => eb(
* refTuple('first_name', 'last_name'),
* 'in',
* [
* tuple('Jennifer', 'Aniston'),
* tuple('Sylvester', 'Stallone')
* ]
* ))
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* select
* "person".*
* from
* "person"
* where
* ("first_name", "last_name")
* in
* (
* ($1, $2),
* ($3, $4)
* )
* ```
*
* In the next example a reference tuple is compared to a subquery. Note that
* in this case you need to use the {@link @SelectQueryBuilder.$asTuple | $asTuple}
* function:
*
* ```ts
* db.selectFrom('person')
* .selectAll('person')
* .where(({ eb, refTuple, selectFrom }) => eb(
* refTuple('first_name', 'last_name'),
* 'in',
* selectFrom('pet')
* .select(['name', 'species'])
* .where('species', '!=', 'cat')
* .$asTuple('name', 'species')
* ))
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* select
* "person".*
* from
* "person"
* where
* ("first_name", "last_name")
* in
* (
* select "name", "species"
* from "pet"
* where "species" != $1
* )
* ```
*/
refTuple<
R1 extends ReferenceExpression<DB, TB>,
R2 extends ReferenceExpression<DB, TB>
>(
value1: R1,
value2: R2
): ExpressionWrapper<DB, TB, RefTuple2<DB, TB, R1, R2>>

refTuple<
R1 extends ReferenceExpression<DB, TB>,
R2 extends ReferenceExpression<DB, TB>,
R3 extends ReferenceExpression<DB, TB>
>(
value1: R1,
value2: R2,
value3: R3
): ExpressionWrapper<DB, TB, RefTuple3<DB, TB, R1, R2, R3>>

refTuple<
R1 extends ReferenceExpression<DB, TB>,
R2 extends ReferenceExpression<DB, TB>,
R3 extends ReferenceExpression<DB, TB>,
R4 extends ReferenceExpression<DB, TB>
>(
value1: R1,
value2: R2,
value3: R3,
value4: R4
): ExpressionWrapper<DB, TB, RefTuple4<DB, TB, R1, R2, R3, R4>>

refTuple<
R1 extends ReferenceExpression<DB, TB>,
R2 extends ReferenceExpression<DB, TB>,
R3 extends ReferenceExpression<DB, TB>,
R4 extends ReferenceExpression<DB, TB>,
R5 extends ReferenceExpression<DB, TB>
>(
value1: R1,
value2: R2,
value3: R3,
value4: R4,
value5: R5
): ExpressionWrapper<DB, TB, RefTuple5<DB, TB, R1, R2, R3, R4, R5>>

/**
* Creates a value tuple expression.
*
* This creates a tuple using values by default. See {@link refTuple} if you need to create
* tuples using column references.
*
* ### Examples
*
* ```ts
* db.selectFrom('person')
* .selectAll('person')
* .where(({ eb, refTuple, tuple }) => eb(
* refTuple('first_name', 'last_name'),
* 'in',
* [
* tuple('Jennifer', 'Aniston'),
* tuple('Sylvester', 'Stallone')
* ]
* ))
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* select
* "person".*
* from
* "person"
* where
* ("first_name", "last_name")
* in
* (
* ($1, $2),
* ($3, $4)
* )
* ```
*/
tuple<V1, V2>(
value1: V1,
value2: V2
): ExpressionWrapper<DB, TB, ValTuple2<V1, V2>>

tuple<V1, V2, V3>(
value1: V1,
value2: V2,
value3: V3
): ExpressionWrapper<DB, TB, ValTuple3<V1, V2, V3>>

tuple<V1, V2, V3, V4>(
value1: V1,
value2: V2,
value3: V3,
value4: V4
): ExpressionWrapper<DB, TB, ValTuple4<V1, V2, V3, V4>>

tuple<V1, V2, V3, V4, V5>(
value1: V1,
value2: V2,
value3: V3,
value4: V4,
value5: V5
): ExpressionWrapper<DB, TB, ValTuple5<V1, V2, V3, V4, V5>>

/**
* Returns a literal value expression.
*
Expand Down Expand Up @@ -950,6 +1139,20 @@ export function createExpressionBuilder<DB, TB extends keyof DB>(
return new ExpressionWrapper(parseValueExpressionOrList(value))
},

refTuple(
...values: ReadonlyArray<ReferenceExpression<any, any>>
): ExpressionWrapper<DB, TB, any> {
return new ExpressionWrapper(
TupleNode.create(values.map(parseReferenceExpression))
)
},

tuple(...values: ReadonlyArray<unknown>): ExpressionWrapper<DB, TB, any> {
return new ExpressionWrapper(
TupleNode.create(values.map(parseValueExpression))
)
},

lit<VE extends number | boolean | null>(
value: VE
): ExpressionWrapper<DB, TB, VE> {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export * from './operation-node/json-reference-node.js'
export * from './operation-node/json-path-leg-node.js'
export * from './operation-node/json-path-node.js'
export * from './operation-node/json-operator-chain-node.js'
export * from './operation-node/tuple-node.js'

export * from './util/column-type.js'
export * from './util/compilable.js'
Expand Down
9 changes: 9 additions & 0 deletions src/operation-node/operation-node-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import { JSONReferenceNode } from './json-reference-node.js'
import { JSONPathNode } from './json-path-node.js'
import { JSONPathLegNode } from './json-path-leg-node.js'
import { JSONOperatorChainNode } from './json-operator-chain-node.js'
import { TupleNode } from './tuple-node.js'

/**
* Transforms an operation node tree into another one.
Expand Down Expand Up @@ -206,6 +207,7 @@ export class OperationNodeTransformer {
JSONPathNode: this.transformJSONPath.bind(this),
JSONPathLegNode: this.transformJSONPathLeg.bind(this),
JSONOperatorChainNode: this.transformJSONOperatorChain.bind(this),
TupleNode: this.transformTuple.bind(this),
})

transformNode<T extends OperationNode | undefined>(node: T): T {
Expand Down Expand Up @@ -963,6 +965,13 @@ export class OperationNodeTransformer {
})
}

protected transformTuple(node: TupleNode): TupleNode {
return requireAllProps<TupleNode>({
kind: 'TupleNode',
values: this.transformNodeList(node.values),
})
}

protected transformDataType(node: DataTypeNode): DataTypeNode {
// An Object.freezed leaf node. No need to clone.
return node
Expand Down
3 changes: 3 additions & 0 deletions src/operation-node/operation-node-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import { JSONReferenceNode } from './json-reference-node.js'
import { JSONPathNode } from './json-path-node.js'
import { JSONPathLegNode } from './json-path-leg-node.js'
import { JSONOperatorChainNode } from './json-operator-chain-node.js'
import { TupleNode } from './tuple-node.js'

export abstract class OperationNodeVisitor {
protected readonly nodeStack: OperationNode[] = []
Expand Down Expand Up @@ -183,6 +184,7 @@ export abstract class OperationNodeVisitor {
JSONPathNode: this.visitJSONPath.bind(this),
JSONPathLegNode: this.visitJSONPathLeg.bind(this),
JSONOperatorChainNode: this.visitJSONOperatorChain.bind(this),
TupleNode: this.visitTuple.bind(this),
})

protected readonly visitNode = (node: OperationNode): void => {
Expand Down Expand Up @@ -286,4 +288,5 @@ export abstract class OperationNodeVisitor {
protected abstract visitJSONPath(node: JSONPathNode): void
protected abstract visitJSONPathLeg(node: JSONPathLegNode): void
protected abstract visitJSONOperatorChain(node: JSONOperatorChainNode): void
protected abstract visitTuple(node: TupleNode): void
}
1 change: 1 addition & 0 deletions src/operation-node/operation-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export type OperationNodeKind =
| 'JSONPathNode'
| 'JSONPathLegNode'
| 'JSONOperatorChainNode'
| 'TupleNode'

export interface OperationNode {
readonly kind: OperationNodeKind
Expand Down
23 changes: 23 additions & 0 deletions src/operation-node/tuple-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { freeze } from '../util/object-utils.js'
import { OperationNode } from './operation-node.js'

export interface TupleNode extends OperationNode {
readonly kind: 'TupleNode'
readonly values: ReadonlyArray<OperationNode>
}

/**
* @internal
*/
export const TupleNode = freeze({
is(node: OperationNode): node is TupleNode {
return node.kind === 'TupleNode'
},

create(values: ReadonlyArray<OperationNode>): TupleNode {
return freeze({
kind: 'TupleNode',
values: freeze(values),
})
},
})
24 changes: 14 additions & 10 deletions src/parser/select-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ export type SelectArg<
| ReadonlyArray<SE>
| ((eb: ExpressionBuilder<DB, TB>) => ReadonlyArray<SE>)

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

type ExtractAliasFromSelectExpression<SE> = SE extends string
? ExtractAliasFromStringSelectExpression<SE>
Expand Down Expand Up @@ -151,11 +153,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> = [DB] extends [unknown]
? Selectable<{
[C in AnyColumn<DB, TB>]: {
[T in TB]: C extends keyof DB[T] ? DB[T][C] : never
}[TB]
}>
: never

export function parseSelectArg(
selection: SelectArg<any, any, SelectExpression<any, any>>
Expand Down
Loading

1 comment on commit 53d9125

@vercel
Copy link

@vercel vercel bot commented on 53d9125 Jul 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

kysely – ./

kysely-kysely-team.vercel.app
kysely-git-master-kysely-team.vercel.app
www.kysely.dev
kysely.dev

Please sign in to comment.