Skip to content

Commit

Permalink
Merge branch 'master' into controlled-transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
igalklebanov authored Jul 18, 2024
2 parents 972d0f4 + 9b4d6dd commit aff3f7a
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 41 deletions.
62 changes: 40 additions & 22 deletions site/docs/recipes/0001-relations.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,12 @@ Simple right 😅. Yeah, not so much. But it does provide full control over the
Fortunately we can improve and simplify this a lot using Kysely. First let's define a couple of helpers:

```ts
function jsonArrayFrom<O>(
expr: Expression<O>
): RawBuilder<Simplify<O>[]> {
return sql`(select coalesce(json_agg(agg), '[]') from ${expr} as agg)`
function jsonArrayFrom<O>(expr: Expression<O>) {
return sql<Simplify<O>[]>`(select coalesce(json_agg(agg), '[]') from ${expr} as agg)`
}

export function jsonObjectFrom<O>(
expr: Expression<O>
): RawBuilder<Simplify<O>> {
return sql`(select to_json(obj) from ${expr} as obj)`
function jsonObjectFrom<O>(expr: Expression<O>) {
return sql<Simplify<O>>`(select to_json(obj) from ${expr} as obj)`
}
```

Expand Down Expand Up @@ -132,27 +128,27 @@ const persons = await db
.execute()

console.log(persons[0].pets[0].name)
console.log(persons[0].mother.first_name)
console.log(persons[0].mother?.first_name)
```

That's better right? If you need to do this over and over in your codebase, you can create some helpers like these:

```ts
function withPets(eb: ExpressionBuilder<DB, 'person'>) {
function pets(ownerId: Expression<string>) {
return jsonArrayFrom(
eb.selectFrom('pet')
db.selectFrom('pet')
.select(['pet.id', 'pet.name'])
.whereRef('pet.owner_id', '=', 'person.id')
.where('pet.owner_id', '=', ownerId)
.orderBy('pet.name')
).as('pets')
)
}

function withMom(eb: ExpressionBuilder<DB, 'person'>) {
function mother(motherId: Expression<string>) {
return jsonObjectFrom(
eb.selectFrom('person as mother')
db.selectFrom('person as mother')
.select(['mother.id', 'mother.first_name'])
.whereRef('mother.id', '=', 'person.mother_id')
).as('mother')
.where('mother.id', '=', motherId)
)
}
```

Expand All @@ -162,9 +158,27 @@ And now you get this:
const persons = await db
.selectFrom('person')
.selectAll('person')
.select((eb) => [
withPets(eb),
withMom(eb)
.select(({ ref }) => [
pets(ref('person.id')).as('pets'),
mother(ref('person.mother_id')).as('mother')
])
.execute()

console.log(persons[0].pets[0].name)
console.log(persons[0].mother?.first_name)
```

In some cases Kysely marks your selections as nullable if it's not able to know the related object
always exists. If you have that information, you can mark the relation non-null using the
`$notNull()` helper like this:

```ts
const persons = await db
.selectFrom('person')
.selectAll('person')
.select(({ ref }) => [
pets(ref('person.id')).as('pets'),
mother(ref('person.mother_id')).$notNull().as('mother')
])
.execute()

Expand All @@ -178,7 +192,11 @@ If you need to select relations conditionally, `$if` is your friend:
const persons = await db
.selectFrom('person')
.selectAll('person')
.$if(includePets, (qb) => qb.select(withPets))
.$if(includeMom, (qb) => qb.select(withMom))
.$if(includePets, (qb) => qb.select(
(eb) => pets(eb.ref('person.id')).as('pets')
))
.$if(includeMom, (qb) => qb.select(
(eb) => mother(eb.ref('person.mother_id')).as('mother')
))
.execute()
```
52 changes: 33 additions & 19 deletions site/docs/recipes/0006-expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const person = await db
.whereRef('pet.owner_id', '=', 'person.id')
.limit(1)
.as('pet_name'),
// Select a boolean expression..

// Select a boolean expression
eb('first_name', '=', 'Jennifer').as('is_jennifer')
])
// You can also destructure the expression builder like this
Expand Down Expand Up @@ -64,7 +64,7 @@ select
) as "pet_name",

"first_name" = $1 as "is_jennifer"
from
from
"person"
where (
(
Expand Down Expand Up @@ -114,7 +114,7 @@ qb = qb.where(eb.not(eb.exists(

## Creating reusable helpers

The expression builder can be used to create reusable helper functions.
The expression builder can be used to create reusable helper functions.
Let's say we have a complex `where` expression we want to reuse in multiple queries:

```ts
Expand Down Expand Up @@ -151,27 +151,40 @@ const bigFatFailure = await db
.execute()
```

We can write a more type-safe version of the helper like this:
It's better to not make assumptions about the calling context and pass in all dependencies
as arguments. In the following example we pass in the person's id as an expression. We also
changed the type of `name` from `string` to `Expression<string>`, which allows us to pass
in arbitrary expressions instead of just values.

```ts
function hasDogNamed(name: string) {
return (eb: ExpressionBuilder<DB, 'person'>) => {
return eb.exists(
eb.selectFrom('pet')
.select('pet.id')
.whereRef('pet.owner_id', '=', 'person.id')
.where('pet.species', '=', 'dog')
.where('pet.name', '=', name)
)
}
function hasDogNamed(name: Expression<string>, ownerId: Expression<number>) {
// Create an expression builder without any tables in the context.
// This way we make no assumptions about the calling context.
const eb = expressionBuilder<DB, never>()

return eb.exists(
eb.selectFrom('pet')
.select('pet.id')
.where('pet.owner_id', '=', ownerId)
.where('pet.species', '=', 'dog')
.where('pet.name', '=', name)
)
}
```

With this helper, you get a type error when trying to use it in contexts that don't include the "person" table.
Here's how you'd use our brand new helper:

```ts
const doggoPersons = await db
.selectFrom('person')
.selectAll('person')
.where((eb) => hasDogNamed(eb.val('Doggo'), eb.ref('person.id')))
.execute()
```

## Conditional expressions

In the following, we'll only cover `where` expressions. The same logic applies to `having`, `on`, `orderBy`, `groupBy` etc.
In the following, we'll only cover `where` expressions. The same logic applies to `having`, `on`, `orderBy`, `groupBy` etc.

> This section should not be confused with conditional selections in `select` clauses, which is a whole 'nother topic we discuss in [this recipe](https://www.kysely.dev/docs/recipes/conditional-selects).
Expand Down Expand Up @@ -203,7 +216,7 @@ const persons = await db
.selectFrom('person')
.selectAll('person')
.where((eb) => {
const filters: Expression<boolean>[] = []
const filters: Expression<SqlBool>[] = []

if (firstName) {
filters.push(eb('first_name', '=', firstName))
Expand All @@ -212,9 +225,10 @@ const persons = await db
if (lastName) {
filters.push(eb('last_name', '=', lastName))
}

return eb.and(filters)
})
.execute()
```

Using the latter design, you can build conditional expressions of any complexity.
5 changes: 5 additions & 0 deletions src/dialect/mysql/mysql-dialect-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export interface MysqlDialectConfig {
* Called once for each created connection.
*/
onCreateConnection?: (connection: DatabaseConnection) => Promise<void>

/**
* Called every time a connection is acquired from the connection pool.
*/
onReserveConnection?: (connection: DatabaseConnection) => Promise<void>
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/mysql/mysql-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export class MysqlDriver implements Driver {
}
}

if (this.#config?.onReserveConnection) {
await this.#config.onReserveConnection(connection)
}

return connection
}

Expand Down
5 changes: 5 additions & 0 deletions src/dialect/postgres/postgres-dialect-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export interface PostgresDialectConfig {
* Called once for each created connection.
*/
onCreateConnection?: (connection: DatabaseConnection) => Promise<void>

/**
* Called every time a connection is acquired from the pool.
*/
onReserveConnection?: (connection: DatabaseConnection) => Promise<void>
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/postgres/postgres-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export class PostgresDriver implements Driver {
}
}

if (this.#config.onReserveConnection) {
await this.#config.onReserveConnection(connection)
}

return connection
}

Expand Down

0 comments on commit aff3f7a

Please sign in to comment.