Select & Include
Using with/fetch for fetching relations is okay, but leaves something to be desired in terms of type safety. The select & include query builder functions & macros can help with this, providing an exact type for each field and relation you choose to fetch.
Select also provides the ability to only fetch specific fields, whereas include will fetch all scalar fields and any specified relations.
The examples use the following schema:
model Post {
id String @id @default(cuid())
title String
comments Comment[]
}
model Comment {
id String @id @default(cuid())
content String
post Post @relation(fields: [postID], references: [id])
postID String
}
Setup
select!
and include!
rely on recursion and call each other internally, so they have to be aware of the module path of the generated client.
By default this is crate::prisma
, which assumes your client is generated to a file named prisma.rs
and sits at the root of the crate it is in.
If you have configured your client to have a different name or be located somewhere else,
you will need to provide this location through the module_path
generator option.
module_path
is a Rust path relative to crate
that points to your generated client.
generator client {
provider = "cargo prisma"
output = "./src/generated/db.rs"
// `select` macros will now point to `crate::generated::db`
// instead of `crate::prisma`
module_path = "generated::db"
}
The Basics
select!
and include!
are very similar in syntax, with their only difference being what they do and don't fetch.
Every model module contains select!
and include!
macros,
the result of which can be provided to their respective query builder functions.
Fields to fetch can be specified as a space separated list inside {}
:
post::select!({
id // select can pick individual fields
title
})
// Above will generate
struct Data {
id: String,
title: String,
}
post::include!({
comments // include can only pick relations
})
// Above will generate
struct Data {
id: String,
title: String,
comments: Vec<comment::Data>
}
Nested Selections
select
and include
can also be applied while fetching a relation, to any depth in fact.
Simply add a :
, specify whether you want to select or include on the relation, and add your selection:
// Nested include inside select
post::select!({
comments: include {
post
}
})
// Above will generate
struct Data {
comments: comments::Data // refers to below module
}
// Module + data struct generated for nested selection
mod comments {
pub struct Data {
post: post::Data
}
}
// Nested select inside include
post::include!({
comments: select {
content
}
})
// Above will generate
struct Data {
id: String,
title: String,
comments: comments::Data // refers to below module
}
// Module + data struct generated for nested selection
mod comments {
pub struct Data {
content: String
}
}
Many Relation Options
When fetching many-relations, the fetching statement can act as an equivalent call to model::field::fetch
,
allowing for filtering, pagination and ordering to occur.
This works in select!
and include!
.
post::select!({
// Equivalent to post::comments::fetch(..) ..
comments(vec![comment::content::contains("prisma".to_string())])
.skip(5)
.take(10): select { // You can add on nested selections too!
id
content
}
})
Usage in Queries
Just pass the result of select!
or include!
to an equivalent query builder function:
// Type is anonymous and does not exist outside of the call to `include`
let posts: Vec<_> = client
.post()
.find_many(vec![])
.include(post::include!({
comments: select {
id
}
}))
.exec()
.await?;
// Generated type is equivalent to
struct Data {
id: String,
title: String,
comments: comments::Data
}
mod comments {
pub struct Data {
id: String
}
}
Using Types Outside Queries
In some cases it can be useful to access the type generated by select!
and include!
outside of the call to the query builder functions,
for example if you want to return the result of a query from a function.
This can be done by adding a name before the root selection.
This will cause a module to be generated with that name and will contain a Data
struct as well as either an include
or select
function, depending on what macro you use.
post::select!(post_only_title {
title
})
async fn do_query() -> Vec<post_only_title::Data> {
client
.post()
.find_many(vec![])
.select(post_only_title::select())
.exec()
.await
.unwrap()
}
// Generated type is equivalent to
pub mod post_only_title {
pub struct Data {
title: String
}
pub fn select() // return type is an internal detail
}
Passing Arguments
When performing a selection inline, outside values can be used as arguments just fine since they can be captured from outside the macro.
When a selection is declared outside of a query builder function, this context cannot be captured.
This is why the select
and include
functions aren't just structs,
they can be passed arguments defined within the macros using the following syntax:
post::include!((filters: Vec<comment::WhereParam>, skip: i64, take: i64) => post_with_comments {
comments(filters).skip(skip).take(take)
})
async fn do_query() -> Vec<post_only_title::Data> {
let filters = vec![comment::content::contains("prisma".to_string())];
let skip = 5;
let take = 5;
client
.post()
.find_many(vec![])
.select(post_only_title::select(filters, skip, take))
.exec()
.await
.unwrap()
}
// Generated type is equivalent to
pub mod post_with_comments {
pub struct Data {
id: String,
title: String,
comments: Vec<comment::Data>
}
pub fn select(filters: Vec<comment::WhereParam>, skip: i64, take: i64) // return type is an internal detail
}