Replies: 1 comment 2 replies
-
First, this is great and I love that we can implement magic like this Some comments:
|
Beta Was this translation helpful? Give feedback.
-
First, this is great and I love that we can implement magic like this Some comments:
|
Beta Was this translation helpful? Give feedback.
-
Codegen in Rust
Instead of generating
.rs
files directly, it's more common to use procedural macros to generate code in Rust. Either by using expression macro:Or by generating the whole module:
We'll be discussing expression macro below, as it looks like more convenient.
Upsides of this codegen approach:
Downsides:
What Does it Generate?
Generated code for:
... looks similar to this (although, this piece is simplified for brevity):
Another possible option is to use
query_as!(..)
. In this case you can declare the structure manually:This is needed if you need to name a type or make it public and to implement more traits for it. The expansion of it is the same (except struct definition).
The magic of typechecking in the latter case is in the constructing object code:
Offline Mode
For offline compilation (i.e. when database is not used for compilation) we need type information for all the queries committed to git.
We can't generate that as part of the regular Rust build because:
Also we need
--all-features --all-targets
build to ensure that all queries are in the cache (which is not easily accessible sometimes).So we probably have to do what sqlx does:
cargo edgedb
oredgedb-generate
or eventually a part of edgedb command-line tooloffline
macro feature to read those filesOpen Questions
It's Less Powerful than Derive
When you use
derive(Queryable)
its possible to use types that are "compatible" to EdgeDB types but different ones. For example instead of usingedgedb::model::Datetime
you can usestd::time::SystemTime
, or instead ofVec<T>
you can useBTreeSet
:To make
query_as!
work withderive(Queryable)
rather than only with plain structs with limited set of types we need some creative approach.Short overview of non-solutions:
Into
orTryInto
conversion for each field after decoding: (a) it makes typechecking loose (i.e. there are conversions between types of integers) and (b) the types can be decoded areT: TryFrom<U>
whereU
is from fixed set of types, rather than anyT: Queryable
.struct
definition intoquery_*
macro (so procedural macro can parse it): (a) this is quite inconvenient and (b) type information is not available in programmatic macros, so it's not generic enough anyways.Const Type Descriptors
One possible solution is to make
Queryable
define an internal type as const property:Then expansion of
query_as!(Object, ...)
should work like this:Yet to be determined:
Inner Type
Another possible solution is to do the same but at a type level:
This has to be explored more.
Error Reporting on
query_as!
Here is how error looks like (this is adapted from sqlx):
This gives you zero understanding of which field has the wrong type.
Non-solutions:
Using Const Type Descriptors
One possible solution is to use
And then make const-evaluated assertion method:
The method uses
panic!()
to describe when type is wrong. This has some complexity due to limitations of theconst fn
code but to the best of my understanding should be doable.TBD
Beta Was this translation helpful? Give feedback.
All reactions