-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Motivation
Insert statements are usually look like:
db.fetchrow("""
INSERT User {
first_name := <str>$first_name,
last_name := <str>$last_name,
birth_date := <cal::local_date>$birth_date,
}
""", dict(
first_name=form['first_name'],
last_name=form['last_name'],
birth_date=form['birth_date'],
))This is repetitive and error-prone (adding a field must take place in multiple places).
If some fields are optional, it needs the whole query builder to deal with that correctly.
(spectrum thread)
New Syntax
So I propose to make a dedicated syntax for that:
db.fetchrow("INSERT User {..$user}", dict(first_name=....)This is much easier to write and doesn't need query builder for optional fields.
It uses .. operator that comes from Rust. We can use ** from python, or ... from Javascript.
Protocol
This requires the new protocol message to make queries, let's call it SmartExecute which consists of:
query: EdgeQL textparams: Data serialized by some self-descriptive serialization formatoutput_typedesc_id: Optional, plays same role as inOptimisticExecute
Notes:
- Generally server sends output type descriptor first, then data
- In case of
output_typedesc_idis specified and it matches the value, this behaves as optimistic execute
Client
Client might switch to this new protocol message if any of the argument is a dictionary. Otherwise, keep old protocol for compatibility. Later we can benchmark new protocol and make some other heuristics if the new protocol is faster (i.e. at least when there are no arguments).
Internals
- Server extracts list of keys from
paramsand sends it to the compiler alongside withquery. Compiler returns same thing as normal query, exceptargmapis a bit more complex (contains not just indexes for top-level arguments but also for keys of dictionaries). - Then server transcodes arguments from self-descriptive format to what is provided by input type descriptor returned by compiler.
Performance Notes
- This introduces re-encoding of
params, which take a little bit more CPU but I doubt it's too much. Transcoding arguments in Rust should be quite fast. - For small number of arguments it should be faster because it avoids Prepare/Execute and doesn't need client-side cache of descriptors (output typedesc may only be cached if it's large enough that it justifies traffic savings).
- If this is benchmarked as performance issue, we can recommend using expanded syntax for either (a) hot spots or (b) starting with certain amount of data (which should be at least tens of arguments or hundreds of kilobytes of data I think).
Open Questions and Alternatives
-
We use unpack/spread operator in proposal. We can use
<User>$xinstead, but:- Spread operator allows computed properties:
User {..$x, user_id=gen_user_id()}and links. - On the other hand, if we accept "user type" we can also allow
<SET OF User>$x(as of Make query arguments required by default; add syntax to make them optional gel#1340)
- Spread operator allows computed properties:
-
We may want to make type inference first (
INSERT { last_name :=$last_name }), but this is not something that is strictly prerequisite and is not huge ergonomic improvement by itself. -
If this is a spread operator, should it work in places other than inserts? Perhaps yes, as far as we can make type inference for fields. Error "can't infer types, use expanded syntax" is fine for me.
-
Should this work for links? In the first implementation, no.
User {..$fields, link := (SELECT ..)}is fine.