-
-
Notifications
You must be signed in to change notification settings - Fork 3
RFC: *.edgeql workflow #21
Description
With the JS/TS RFC for this exact thing, I strongly feel like we should have the same work flow for the dotnet library
DX: How will it work?
The tool the dotnet library will use to generate the .cs files will be Source Generators, this allows for a real-time generation where the user doesn't need to run a command to update their generated code1.
Generation structure
The source generator will pick up any .edgeql files within the project tree and build a corresponding .g.cs file for them, take this 'GetUser.edgeql' file for example:
select User {
name, email
} filter .id = <uuid>$user_id
The source generator will get the typedescriptor info2 for this query and build a .cs file that will look like the following:
namespace EdgeDB.Generated;
[EdgeDBType]
public class User
{
[EdgeDBProperty("name")]
public string? Name { get; set; }
[EdgeDBProperty("email")]
public string? Email { get; set; }
}
public static class GetUser
{
public const string Query = @"select User {
name, email
} filter .id = <uuid>$user_id"
public static async Task<User> ExecuteAsync(IEdgeDBQueryable client, Guid userId, CancellationToken token = default)
=> client.QuerySingleAsync<User>(Query, new Dictionary<string, object?>() { { "user_id", userId}}, token: token);
}On top of this class being generated, we can also add another class for extension methods3 for clients. This would look something like this:
using EdgeDB.Generated;
namespace EdgeDB;
public static class Queries
{
public static Task<User> GetUserAsync(this IEdgeDBQueryable edgedb, Guid userId, CancellationToken token = default)
=> GetUser.ExecuteAsync(edgedb, userId, token);
}This allows for the generated methods to be directly called on a client like so:
var user = await client.GetUserAsync(userId);as well as using the Queries class:
var user = await Queries.GetUserAsync(client, userId);Conflicting types
There are two ways I see that we can deal with type conflictions
- Throw an error and don't generate the edgeql file
- Merge the two types into one class.
I really don't like option 1 here, so lets talk about option 2:
Each object type can be put into one .g.cs file we can call ObjectTypes which will look like the following...:
namespace EdgeDB.Generated;
[EdgeDBType]
public class User
{
[EdgeDBProperty("name")]
public string? Name { get; set; }
[EdgeDBProperty("email")]
public string? Email { get; set; }
}... where each new object type we find within the edgeql files can be put in this one, when we get a conflicting type (ex 2 files share the same resulting type) we can do one of two things:
- If they are the same type with the same shape, we can just leave the already generated one there.
- If they have different shapes, we can "merge" the two together therefor resolving the conflict. The downside to this approach is that if the two shapes differ, you're going to have empty properties for some queries, I don't see any obvious workaround for this besides putting each type in its own namespace.
Summary
This generator will add a great dotnet workflow for working with .edgeql files, and will allow users to work directly with edgeql without trying to turn them into one giant string :D. Users will also not need to rely on the query builder to preform complex queries.
Footnotes
-
Source generators work at build-time, see the source generator overview. ↩
-
The source generator requires a way to connect to the database instance where this query would be executed to get the Command Data Description packet for the given query. ↩