Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve expressions recipe #1083

Merged
merged 1 commit into from
Jul 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.