edgedb-rust icon indicating copy to clipboard operation
edgedb-rust copied to clipboard

Potential ergonomic improvements for `Queryable`

Open dustypomerleau opened this issue 8 months ago • 3 comments

It's quite exciting to see all of the progress being made on gel-rust. One feature that I'd like to use more often is the Queryable derive macro, but I'm running into certain limitations.

Here is an example of code I'd like to write:

module default {
    scalar type Position extending int32 {
        constraint min_value(0);
        constraint max_value(100);
    }

    type Widget {
         required size: int32 { constraint min_value(0); }
         required position: Position;
    }
}
pub struct Ready;

#[derive(Queryable)]
pub struct Position(i32);

#[derive(Queryable)]
pub struct Widget<State = Ready> {
    size: u32,
    position: Position,
    state: PhantomData<State>,
}

All 3 of the fields in the Rust Widget will error when deriving Queryable:

  1. DecodeScalar is not implemented for u32
  2. No newtypes: only named fields are supported
  3. DecodeScalar is not implemented for PhantomData<State>

My impression is that the main options in that situation are:

  1. Query as <json> and use serde_json::from_str(), so that my struct now looks more like:
#[derive(Deserialize, Serialize)]
pub struct Widget<State = Ready> {
    size: u32,
    position: Position,
    #[serde(skip)]
    state: PhantomData<State>,
  1. Create a dedicated struct for the query and do some conversion:
#[derive(Queryable)]
pub struct QueryWidget {
    size: i32,
    position: i32,
}

impl TryFrom<QueryWidget> for Widget<Ready> {...}

I don't know what the long term view is on the Queryable derive macro, but I would love to be able to annotate some of these fields so that I could use my original Widget with Queryable, without having to pass through JSON. I'm picturing serde-like field attributes that would look something like:

#[derive(Queryable)]
pub struct Widget<State = Ready> {
    #[gel(cast)]
    size: u32,
    #[gel(transparent)]
    position: Position,
    #[gel(skip)]
    state: PhantomData<State>,
}

I realize that things like integer casts could fail if the DB constraints are not correct, but since the query methods on the gel-tokio Client already return Result, I think the user could be expected to handle such cases.

All of this assumes that there isn't a much easier path, such as implementing DecodeScalar for the types in question, and doing the cast/skip under the hood somehow.

Does any of this sound feasible/reasonable? As always, I appreciate your time.

dustypomerleau avatar May 10 '25 09:05 dustypomerleau

I was thinking if we can replace custom Queryable macro with serde::Deserialize and just implement Deserializer for gel protocol.

0x241F31 avatar May 11 '25 20:05 0x241F31

Also, you can avoid manually writing Queryable types by using https://github.com/ifiokjr/edgedb_codegen

0x241F31 avatar May 11 '25 20:05 0x241F31

Using serde sounds like a great idea -- it shouldn't be too tough, but I'd have to make sure we can make the API work ergonomically.

I think we can probably expose some more powerful APIs to make something like edgedb_codegen easier to write as well.

I've recently created a new implementation of our wire protocol, and I'm going to work on 1.0/2.0 codecs next which should help unblock some of this work.

mmastrac avatar May 12 '25 15:05 mmastrac