Open Oyelowo opened 1 month ago
After weighing the cost and benefits of this, I am not yet convinced that it is worth it, considering that the current APIs cover probably 99% of use-cases.
Pick
and/or omit
proc macro(s) might actually prove more useful for reusing and maintaining a valid reference to a subset of original fields of used structs in queries (Mostly SELECT
).
serde's flatten
already solves most of these and encourages more composition over inherittance, but this might not be feasible, at least by default, if we dont want an embedded object or foreign table fields but want each of them to be top level fields instead.Query "SELECT *, name, age, address.coordinates AS coordinates FROM person WHERE ->knows->person->(knows WHERE influencer = $influencer) TIMEOUT 5s "
PROS
We can validate/check the return type field names against what was selected in the query using field name or field alias (when field name has complex characters, uses functions or accesses foreign tables).
We can default all field types to serde_json::Value and allow the user to override the type
defaulting every field type to serde_json::Value might provide quick escape for adhoc queries. Can be especially useful if you dont have to share the return type struct to different service or client, so you're mainly just concerned about the serialized data. Rebut: We can also achieve this just just returning a top-level serde_json::Value from the select method.
CONS
Ergonomics: defaulting every field type to serde_json::Value might provide quick escape for adhoc queries, but it does not provide much utility/ good developer experience to know what type to expect especially if sharing same struct across multiple queries/services/clients.
Additional mental overhead: One more thing to learn for the developer. But this can be eased up a little by providing a macro to generate the return type struct.
With the new pick macro implemented, we can use this to infer nested fields. and prompt user to provide the trait for each nested level field.
The nice thing about this pattern is that, if the user provides the wrong picker trait, it wont compile. So, we still get compile-time check.
Also, we will have to implement a pattern matching engine for all the surreal
functions and casting to mapping the wrapped data into their respective types.
This will generate the type at the outer level.
struct Info {
age: u8,
class: String
}
struct User {
name: String,
info: Info,
}
select name, info.age
type nameField = pick!(User as UserPicker, [name])
type InfoField = pick!(User as UserPicker, [info]);
type ageField = pick!(InfoField as <Prompt the user to import the trait for this and must be `InfoPicker`>, [age]);
returnType {
name: nameField,
age: ageField
}
2. We can also implement an inline type inference that uses generics for all fields and then uses a builder pattern for each field to infer their return type. The downside of this is that, we cant use the type declaration outside the function and even though, it might be able to infer the return type, it wont generate proper type that can be used outside:
// original
struct User {
name: String,
age: u8
}
// duplicate:
struct User<T : Into<serde_json::Value>, U: Into<serde_json::Value> {
name: T,
age: U
For reference/link fields, i'll create double/triple types for each. One for the link container or id, and the other for the wrapped foreign object which will provide the foreign fields. I'll come up with a convention for it, maybe some prefixes e.g friends: LinkMany<Friend> or Vec<SurrealSimpleId<Friend>> and __link_many___friends: Friend
The API might look like this (Generated by a procedural macro):