Recently I rebuilt this website, using Rust as backend programming language and EdgeDB as database. EdgeDB is great, but its Rust support is still weak comparing to other languages. One very important, but missing, feature is to support named parameters in query. Fortunately, there is already a base for the support in the future. In this post, we will exploit that minimum base to let our work done.
Current state
As the time of this writing, if you look into edgedb-tokio, the official Rust client library, documentation and example, you will see that it only supports using positional (numbered) parameters:
let categories: = conn.query.await?;
That limitation is quite inconvenient, because in real application, which parameters to pass to the query often varies by external input. Like if you are building a page that list products in a store, the users need to be able filter the products by some criteria (brand, price, etc.) and hence, the query to database needs to be dynamic.
You may think about using an IndexMap to hold the to-be-used parameters then deduces the value and the positions from that map. But unfortunately, the arguments
parameter of the query()
function expects a tuple, and in Rust, tuple size must be fixed at compile time, not as in Python, so this solution is not applicable.
Solution
Fortunately, when surfing the reference documentation, checking the signature of query()
function:
pub async
I clicked to the link of QueryArgs
and saw this at the very last of the page:
Well, this must be a hint and open a chance for using named parameters.
Follow the Value
type and I found that, is has some variants which can be candidate for holding named parameters:
After some experiments (facing with the errors and debugging), I finally find out that Value::Object
can be used to hold named parameters and pass to query
. But it is quite verbose to build Value::Object
, due to its complex structure.
The most convenient way is to convert from a map to it. So let's define a function:
use Value as EValue;
use ;
use Cardinality;
We can then use the utility as:
use Cardinality as Cd;
// offset (i64), limit (i64) are data from external source
let pairs = indexmap!
let args = edge_object_from_pairs;
let categories: = conn.query.await?;
You can use any map type. Here I use IndexMap
just because I'm from Python and familiar with its dict
behaviour.
Some notes regarding to Cardinality
:
-
If your passed value can be
None
(not fixedlySome
), useCardinality::AtMostOne
, and the "type cast" expression must be<optional ...>
, e.g.<optional str>$content
. -
If your passed value is fixedly
None
, like when you want to clear the data of some object field in the database, the corresponding element inValue::Object.fields
vector must beNone
(rule for cardinality is the same as above). -
In contrast to what we may think, even if we are setting value for
multi
property, the cardinality for parameter is stillCardinality::One
, notCardinality::Many
, norCardinality::Cardinality
. For example, we have:type BlogPost { multi link categories: BlogCategory; }
and we want to update
BlogPost.categories
field with this query:SELECT BlogCategory FILTER .id IN array_unpack(<array<uuid>>$categories) )
we still use
Cardinality::One
for$categories
.
So I have given you a way to fully exploit EdgeDB. Hope your experience with EdgeDB will be good. The conversion function above, I don't extract and publish as a library, because I still hope that EdgeDB authors will provide a more elegant way to build Value::Object
(with proc macro, for example).