Skip to content

Pre-RFC: Simpler inserts #6

@tailhook

Description

@tailhook

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:

  1. query: EdgeQL text
  2. params: Data serialized by some self-descriptive serialization format
  3. output_typedesc_id: Optional, plays same role as in OptimisticExecute

Notes:

  1. Generally server sends output type descriptor first, then data
  2. In case of output_typedesc_id is 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

  1. Server extracts list of keys from params and sends it to the compiler alongside with query. Compiler returns same thing as normal query, except argmap is a bit more complex (contains not just indexes for top-level arguments but also for keys of dictionaries).
  2. Then server transcodes arguments from self-descriptive format to what is provided by input type descriptor returned by compiler.

Performance Notes

  1. 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.
  2. 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).
  3. 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

  1. We use unpack/spread operator in proposal. We can use <User>$x instead, but:

  2. 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.

  3. 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.

  4. Should this work for links? In the first implementation, no. User {..$fields, link := (SELECT ..)} is fine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions