Skip to content

Commit

Permalink
add lateral joins
Browse files Browse the repository at this point in the history
  • Loading branch information
koskimas committed Jun 26, 2022
1 parent e6d86dd commit 4829c9f
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 68 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kysely",
"version": "0.19.4",
"version": "0.19.5",
"description": "Type safe SQL query builder",
"repository": {
"type": "git",
Expand Down
8 changes: 7 additions & 1 deletion src/operation-node/join-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { OperationNode } from './operation-node.js'
import { TableNode } from './table-node.js'

export type JoinTableNode = TableNode | AliasNode
export type JoinType = 'InnerJoin' | 'LeftJoin' | 'RightJoin' | 'FullJoin'
export type JoinType =
| 'InnerJoin'
| 'LeftJoin'
| 'RightJoin'
| 'FullJoin'
| 'LateralInnerJoin'
| 'LateralLeftJoin'

export interface JoinNode extends OperationNode {
readonly kind: 'JoinNode'
Expand Down
16 changes: 8 additions & 8 deletions src/parser/join-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { AnyColumn, AnyColumnWithTable } from '../util/type-utils.js'
import {
TableExpression,
parseTableExpression,
TableExpressionDatabase,
TableExpressionTables,
From,
FromTables,
} from './table-parser.js'
import { parseReferenceFilter } from './filter-parser.js'
import { JoinBuilder } from '../query-builder/join-builder.js'
Expand All @@ -16,19 +16,19 @@ export type JoinReferenceExpression<DB, TB extends keyof DB, TE> =

export type JoinCallbackExpression<DB, TB extends keyof DB, TE> = (
join: JoinBuilder<
TableExpressionDatabase<DB, TE>,
TableExpressionTables<DB, TB, TE>
From<DB, TE>,
FromTables<DB, TB, TE>
>
) => JoinBuilder<any, any>

type AnyJoinColumn<DB, TB extends keyof DB, TE> = AnyColumn<
TableExpressionDatabase<DB, TE>,
TableExpressionTables<DB, TB, TE>
From<DB, TE>,
FromTables<DB, TB, TE>
>

type AnyJoinColumnWithTable<DB, TB extends keyof DB, TE> = AnyColumnWithTable<
TableExpressionDatabase<DB, TE>,
TableExpressionTables<DB, TB, TE>
From<DB, TE>,
FromTables<DB, TB, TE>
>

export function parseJoin(joinType: JoinType, args: any[]): JoinNode {
Expand Down
25 changes: 13 additions & 12 deletions src/parser/table-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,24 @@ export type TableReference<DB> =
| AnyTable<DB>
| AnyAliasedRawBuilder

export type TableExpressionDatabase<
DB,
TE,
A extends keyof any = ExtractAliasFromTableExpression<DB, TE>
> = {
[C in keyof DB | A]: C extends A
export type From<DB, TE> = {
[C in
| keyof DB
| ExtractAliasFromTableExpression<
DB,
TE
>]: C extends ExtractAliasFromTableExpression<DB, TE>
? ExtractRowTypeFromTableExpression<DB, TE, C>
: C extends keyof DB
? DB[C]
: never
}

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

type ExtractAliasFromTableExpression<DB, TE> =
TE extends `${string} as ${infer TA}`
? TA
: TE extends keyof DB
Expand All @@ -51,11 +56,7 @@ export type ExtractAliasFromTableExpression<DB, TE> =
? RA
: never

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

export type ExtractRowTypeFromTableExpression<
type ExtractRowTypeFromTableExpression<
DB,
TE,
A extends keyof any
Expand Down
16 changes: 4 additions & 12 deletions src/query-builder/expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { SelectQueryNode } from '../operation-node/select-query-node.js'
import {
parseTableExpressionOrList,
TableExpression,
TableExpressionDatabase,
From,
TableExpressionOrList,
TableExpressionTables,
FromTables,
} from '../parser/table-parser.js'
import { WithSchemaPlugin } from '../plugin/with-schema/with-schema-plugin.js'
import { createQueryId } from '../util/query-id.js'
Expand Down Expand Up @@ -108,19 +108,11 @@ export class ExpressionBuilder<DB, TB extends keyof DB> {
*/
selectFrom<TE extends TableExpression<DB, TB>>(
from: TE[]
): SelectQueryBuilder<
TableExpressionDatabase<DB, TE>,
TableExpressionTables<DB, TB, TE>,
{}
>
): SelectQueryBuilder<From<DB, TE>, FromTables<DB, TB, TE>, {}>

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

selectFrom(table: TableExpressionOrList<DB, TB>): any {
return new SelectQueryBuilder({
Expand Down
48 changes: 48 additions & 0 deletions src/query-builder/select-query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,54 @@ export class SelectQueryBuilder<DB, TB extends keyof DB, O>
})
}

/**
* Just like {@link innerJoin} but adds a lateral join instead of an inner join.
*/
innerJoinLateral<
TE extends TableExpression<DB, TB>,
K1 extends JoinReferenceExpression<DB, TB, TE>,
K2 extends JoinReferenceExpression<DB, TB, TE>
>(table: TE, k1: K1, k2: K2): SelectQueryBuilderWithInnerJoin<DB, TB, O, TE>

innerJoinLateral<
TE extends TableExpression<DB, TB>,
FN extends JoinCallbackExpression<DB, TB, TE>
>(table: TE, callback: FN): SelectQueryBuilderWithInnerJoin<DB, TB, O, TE>

innerJoinLateral(...args: any): any {
return new SelectQueryBuilder({
...this.#props,
queryNode: QueryNode.cloneWithJoin(
this.#props.queryNode,
parseJoin('LateralInnerJoin', args)
),
})
}

/**
* Just like {@link innerJoin} but adds a lateral left join instead of an inner join.
*/
leftJoinLateral<
TE extends TableExpression<DB, TB>,
K1 extends JoinReferenceExpression<DB, TB, TE>,
K2 extends JoinReferenceExpression<DB, TB, TE>
>(table: TE, k1: K1, k2: K2): SelectQueryBuilderWithLeftJoin<DB, TB, O, TE>

leftJoinLateral<
TE extends TableExpression<DB, TB>,
FN extends JoinCallbackExpression<DB, TB, TE>
>(table: TE, callback: FN): SelectQueryBuilderWithLeftJoin<DB, TB, O, TE>

leftJoinLateral(...args: any): any {
return new SelectQueryBuilder({
...this.#props,
queryNode: QueryNode.cloneWithJoin(
this.#props.queryNode,
parseJoin('LateralLeftJoin', args)
),
})
}

/**
* Adds an `order by` clause to the query.
*
Expand Down
18 changes: 4 additions & 14 deletions src/query-builder/update-query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
} from '../parser/join-parser.js'
import {
TableExpression,
TableExpressionDatabase,
TableExpressionTables,
From,
FromTables,
parseTableExpressionOrList,
TableExpressionOrList,
} from '../parser/table-parser.js'
Expand Down Expand Up @@ -212,21 +212,11 @@ export class UpdateQueryBuilder<DB, UT extends keyof DB, TB extends keyof DB, O>
*/
from<TE extends TableExpression<DB, TB>>(
table: TE
): UpdateQueryBuilder<
TableExpressionDatabase<DB, TE>,
UT,
TableExpressionTables<DB, TB, TE>,
O
>
): UpdateQueryBuilder<From<DB, TE>, UT, FromTables<DB, TB, TE>, O>

from<TE extends TableExpression<DB, TB>>(
table: TE[]
): UpdateQueryBuilder<
TableExpressionDatabase<DB, TE>,
UT,
TableExpressionTables<DB, TB, TE>,
O
>
): UpdateQueryBuilder<From<DB, TE>, UT, FromTables<DB, TB, TE>, O>

from(from: TableExpressionOrList<any, any>): any {
return new UpdateQueryBuilder({
Expand Down
2 changes: 2 additions & 0 deletions src/query-compiler/default-query-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1147,4 +1147,6 @@ const JOIN_TYPE_SQL: Readonly<Record<JoinType, string>> = freeze({
LeftJoin: 'left join',
RightJoin: 'right join',
FullJoin: 'full join',
LateralInnerJoin: 'inner join lateral',
LateralLeftJoin: 'left join lateral',
})
28 changes: 8 additions & 20 deletions src/query-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
parseTableExpression,
parseTableExpressionOrList,
TableExpression,
TableExpressionDatabase,
From,
TableExpressionOrList,
TableExpressionTables,
FromTables,
TableReference,
} from './parser/table-parser.js'
import { QueryExecutor } from './query-executor/query-executor.js'
Expand Down Expand Up @@ -148,19 +148,11 @@ export class QueryCreator<DB> {
*/
selectFrom<TE extends TableExpression<DB, keyof DB>>(
from: TE[]
): SelectQueryBuilder<
TableExpressionDatabase<DB, TE>,
TableExpressionTables<DB, never, TE>,
{}
>
): SelectQueryBuilder<From<DB, TE>, FromTables<DB, never, TE>, {}>

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

selectFrom(from: TableExpressionOrList<any, any>): any {
return new SelectQueryBuilder({
Expand Down Expand Up @@ -245,11 +237,7 @@ export class QueryCreator<DB> {
*/
deleteFrom<TR extends TableReference<DB>>(
table: TR
): DeleteQueryBuilder<
TableExpressionDatabase<DB, TR>,
TableExpressionTables<DB, never, TR>,
DeleteResult
> {
): DeleteQueryBuilder<From<DB, TR>, FromTables<DB, never, TR>, DeleteResult> {
return new DeleteQueryBuilder({
queryId: createQueryId(),
executor: this.#props.executor,
Expand Down Expand Up @@ -286,9 +274,9 @@ export class QueryCreator<DB> {
updateTable<TR extends TableReference<DB>>(
table: TR
): UpdateQueryBuilder<
TableExpressionDatabase<DB, TR>,
TableExpressionTables<DB, never, TR>,
TableExpressionTables<DB, never, TR>,
From<DB, TR>,
FromTables<DB, never, TR>,
FromTables<DB, never, TR>,
UpdateResult
> {
return new UpdateQueryBuilder({
Expand Down
66 changes: 66 additions & 0 deletions test/node/src/join.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,72 @@ for (const dialect of BUILT_IN_DIALECTS) {
await query.execute()
})
})

describe('lateral join', () => {
it('should join an expression laterally', async () => {
const query = ctx.db
.selectFrom('person')
.innerJoinLateral(
(eb) =>
eb
.selectFrom('pet')
.select('name')
.whereRef('pet.owner_id', '=', 'person.id')
.as('p'),
(join) => join.on(sql`true`)
)
.select(['first_name', 'p.name'])
.orderBy('first_name')

testSql(query, dialect, {
postgres: {
sql: `select "first_name", "p"."name" from "person" inner join lateral (select "name" from "pet" where "pet"."owner_id" = "person"."id") as "p" on true order by "first_name"`,
parameters: [],
},
mysql: NOT_SUPPORTED,
sqlite: NOT_SUPPORTED,
})

const res = await query.execute()
expect(res).to.eql([
{ first_name: 'Arnold', name: 'Doggo' },
{ first_name: 'Jennifer', name: 'Catto' },
{ first_name: 'Sylvester', name: 'Hammo' },
])
})

it('should left join an expression laterally', async () => {
const query = ctx.db
.selectFrom('person')
.leftJoinLateral(
(eb) =>
eb
.selectFrom('pet')
.select('name')
.whereRef('pet.owner_id', '=', 'person.id')
.as('p'),
(join) => join.on(sql`true`)
)
.select(['first_name', 'p.name'])
.orderBy('first_name')

testSql(query, dialect, {
postgres: {
sql: `select "first_name", "p"."name" from "person" left join lateral (select "name" from "pet" where "pet"."owner_id" = "person"."id") as "p" on true order by "first_name"`,
parameters: [],
},
mysql: NOT_SUPPORTED,
sqlite: NOT_SUPPORTED,
})

const res = await query.execute()
expect(res).to.eql([
{ first_name: 'Arnold', name: 'Doggo' },
{ first_name: 'Jennifer', name: 'Catto' },
{ first_name: 'Sylvester', name: 'Hammo' },
])
})
})
}
})
}

0 comments on commit 4829c9f

Please sign in to comment.