Skip to content

Commit

Permalink
Improve expressions recipe (#1083)
Browse files Browse the repository at this point in the history
  • Loading branch information
koskimas authored Jul 17, 2024
1 parent 1374f73 commit 9b4d6dd
Showing 1 changed file with 33 additions and 19 deletions.
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.

0 comments on commit 9b4d6dd

Please sign in to comment.