Skip to content

Commit

Permalink
Add tuple support
Browse files Browse the repository at this point in the history
  • Loading branch information
koskimas committed Jul 23, 2023
1 parent 135dd4f commit 14fdc8d
Show file tree
Hide file tree
Showing 13 changed files with 547 additions and 35 deletions.
205 changes: 205 additions & 0 deletions src/expression/expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,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 @@ -444,6 +455,184 @@ export interface ExpressionBuilder<DB, TB extends keyof DB> {
value: VE
): ExpressionWrapper<DB, TB, ExtractTypeFromValueExpressionOrList<VE>>

/**
* Creates a tuple expression.
*
* This creates a tuple using column references by default. See {@link valTuple}
* if you need to create value tuples.
*
* ### Examples
*
* ```ts
* db.selectFrom('person')
* .selectAll('person')
* .where(({ eb, tuple, valTuple }) => eb(
* tuple('first_name', 'last_name'),
* 'in',
* [
* valTuple('Jennifer', 'Aniston'),
* valTuple('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, tuple, selectFrom }) => eb(
* tuple('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
* )
* ```
*/
tuple<
R1 extends ReferenceExpression<DB, TB>,
R2 extends ReferenceExpression<DB, TB>
>(
value1: R1,
value2: R2
): ExpressionWrapper<DB, TB, RefTuple2<DB, TB, R1, R2>>

tuple<
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>>

tuple<
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>>

tuple<
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 tuple} if you need to create
* tuples using column references.
*
* ### Examples
*
* ```ts
* db.selectFrom('person')
* .selectAll('person')
* .where(({ eb, tuple, valTuple }) => eb(
* tuple('first_name', 'last_name'),
* 'in',
* [
* valTuple('Jennifer', 'Aniston'),
* valTuple('Sylvester', 'Stallone')
* ]
* ))
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* select
* "person".*
* from
* "person"
* where
* ("first_name", "last_name")
* in
* (
* ($1, $2),
* ($3, $4)
* )
* ```
*/
valTuple<V1, V2>(
value1: V1,
value2: V2
): ExpressionWrapper<DB, TB, ValTuple2<V1, V2>>

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

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

valTuple<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 @@ -920,6 +1109,22 @@ export function createExpressionBuilder<DB, TB extends keyof DB>(
return new ExpressionWrapper(parseValueExpressionOrList(value))
},

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

valTuple(
...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),
})
},
})
73 changes: 73 additions & 0 deletions src/parser/tuple-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ExtractTypeFromReferenceExpression } from './reference-parser.js'
import { ExtractTypeFromValueExpression } from './value-parser.js'

export type RefTuple2<DB, TB extends keyof DB, R1, R2> = [R1] extends [unknown]
? [
ExtractTypeFromReferenceExpression<DB, TB, R1>,
ExtractTypeFromReferenceExpression<DB, TB, R2>
]
: never

export type RefTuple3<DB, TB extends keyof DB, R1, R2, R3> = [R1] extends [
unknown
]
? [
ExtractTypeFromReferenceExpression<DB, TB, R1>,
ExtractTypeFromReferenceExpression<DB, TB, R2>,
ExtractTypeFromReferenceExpression<DB, TB, R3>
]
: never

export type RefTuple4<DB, TB extends keyof DB, R1, R2, R3, R4> = [R1] extends [
unknown
]
? [
ExtractTypeFromReferenceExpression<DB, TB, R1>,
ExtractTypeFromReferenceExpression<DB, TB, R2>,
ExtractTypeFromReferenceExpression<DB, TB, R3>,
ExtractTypeFromReferenceExpression<DB, TB, R4>
]
: never

export type RefTuple5<DB, TB extends keyof DB, R1, R2, R3, R4, R5> = [
R1
] extends [unknown]
? [
ExtractTypeFromReferenceExpression<DB, TB, R1>,
ExtractTypeFromReferenceExpression<DB, TB, R2>,
ExtractTypeFromReferenceExpression<DB, TB, R3>,
ExtractTypeFromReferenceExpression<DB, TB, R4>,
ExtractTypeFromReferenceExpression<DB, TB, R5>
]
: never

export type ValTuple2<V1, V2> = [V1] extends [unknown]
? [ExtractTypeFromValueExpression<V1>, ExtractTypeFromValueExpression<V2>]
: never

export type ValTuple3<V1, V2, V3> = V1 extends any
? [
ExtractTypeFromValueExpression<V1>,
ExtractTypeFromValueExpression<V2>,
ExtractTypeFromValueExpression<V3>
]
: never

export type ValTuple4<V1, V2, V3, V4> = [V1] extends [unknown]
? [
ExtractTypeFromValueExpression<V1>,
ExtractTypeFromValueExpression<V2>,
ExtractTypeFromValueExpression<V3>,
ExtractTypeFromValueExpression<V4>
]
: never

export type ValTuple5<V1, V2, V3, V4, V5> = [V1] extends [unknown]
? [
ExtractTypeFromValueExpression<V1>,
ExtractTypeFromValueExpression<V2>,
ExtractTypeFromValueExpression<V3>,
ExtractTypeFromValueExpression<V4>,
ExtractTypeFromValueExpression<V5>
]
: never
Loading

0 comments on commit 14fdc8d

Please sign in to comment.