quinchs / EdgeDB.Net

C# edgedb client
Apache License 2.0
45 stars 3 forks source link

Autogenerated node resolution #22

Closed quinchs closed 1 year ago

quinchs commented 1 year ago

Summary

Since the query builder can generate extra nodes for given context, these nodes need to always track their reference. Take a look at the given query:

var query = QueryBuilder.For(people, person => QueryBuilder.Insert(person, true));

This query will generate an insert statement with an autogenerated select statement selecting out the default shape of Person. Heres a simple representation of the generated nodes:

FOR 
{
  SELECT { INSERT }
}

With the way autogenerated nodes behave, the user defined node (INSERT) will be globalized into a with statement and the autogenerated node (SELECT) will be the main body of the query. With the above example, the FOR node will only iterate over the autogenerated node (SELECT) and the insert statement will not be called with the FOR nodes iteration values. There are 2 ways to solve this:

Solution 1: Autogenerated nodes wrap their parents

Any autogenerated nodes will no longer control globalization of their parent, they will only be able to add on top of their parent like so:

AUTOGENERATED (USER_DEFINED)

given our example above, the select/insert query would look something like this:

FOR x IN {...} 
UNION (SELECT (INSERT Person {...}) { name, email})

Testing this approach, this seems like a valid take at the problem as this query compiles and runs fine. There may be other example of node combinations that may not work due to contextual attachment of types.

Solution 2: Autogenerated nodes wrap their target nodes parent tree

Autogenerated nodes would work up the parent tree of their parent node to wrap whatever is wrapping their parent node. With our example above, the query may look somthing like so:

WITH
  x := (FOR y IN {...} UNION (INSERT Person {...}))
SELECT x { name, email}

The autogenerated node (SELECT) will select out the parent of the insert (FOR in this case) with the autogenerated shape. With this approach there can be unexpected results as the parent node tree can contain any type of node; for example, how would a query like this work?:

QueryBuilder.Select(QueryBuilder.Insert(new Person {...}, true));

The insert node has a true parameter for autogenerating the shape, so lets express the nodes here:

SELECT 
{
  SELECT { INSERT }
}

Following our defined rule above, our query would look like this:

WITH 
  x := (SELECT (INSERT Person {...}) { name, email })
SELECT x { name, email }

A check can be issued for this duplication of nodes and we can remove duplicate query nodes compared by their context. With this approach, each autogenerated node is put into it's own scope to prevent pollution of type conflicts.