-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Specify array traversal using mappers #42
Comments
A few things I've discovered: Element access is maybe a bit confusingIn this original proposal then element access ( I therefore propose that element access should also be treated as an inner mapping as well. This means that:
There will be two different ways to trigger the other behavior:
The second one is probably the most sensible solution. It makes more sense to filter/slice/access before you map than after. Projection can't be a simple mapperIn this proposal we have the rule that "an array mapper after a simple mapper will work on the inner value". This makes sense in
This obviously is not the semantics we want. Go over the the mappers again I realize that there are more mappers than just "simple" and "array". It probably makes more sense to group them as:
(We also need to treat The general rule we want for array traversal is: **When a X-to-array mapper is next to an any/object-to-Y mapper" then it invokes an array traversal. The trickiness here is also that this array traversal is something that we want to be right-associative. I'm struggling a bit to represent it in CFG… |
I think this is pretty good, but I'm not sure it's easier to explain than previous efforts. Is it possible that we're overthinking it? The previous idea of saying that |
Alright, here's a current set of rules which I think works the way we want: Classify the mappers into three categories:
Next we classify the composite mappers into four different categories. (Similar to the modes in the previous proposal.)
The difference between these categories and the previous "modes" is that these are defined in a right-to-left order. This is needed to capture the associativity correctly. Another advantage of the right-to-left rules is that they are easier to parse with a recursive decent parser since the terminal is on the left-hand side in all of these rules. When combining multiple mappers there are three possible choices:
Example expression:
The rules below are designed to handle all of the following cases:
Here are the full matrix of rules:
|
We haven't really had a full proposal with this "array mode" so it's a bit hard to compare. I also don't quite see how it changes any of the complexity. If you know that a LHS is in array-mode, there are still multiple choices between what can happen. In addition, considering that it's right-associative I don't think there's a nice way of dealing with this going left-to-right. When parsing
Sure, and we can teach it like that. The specification only needs to be unambiguous. |
Introduction
Today we have two different array traversal semantics:
This is a third proposal which attempts to make
The main problem with the original proposal for improved array traversal
In v2 this will no longer work:
*[_type == "user"]._id
. This is because.id
accesses an attribute and arrays don't have attributes. Instead, you would have to write*[_type == "user"][]._id
. This is a highly breaking change. However, the following still works:*[_type == "user"]{foo{bar}}
. Why doesn't this have to be*[_type == "user"][]{foo{bar}}
? (The answer is because{…}
has special behavior.)This leads to a strange situation: We are introducing a highly breaking change in one area, but we're not achieving full consistency.
It should also be mentioned that
*[_type == "user"]._id
doesn't have any other sensible meaning other than "apply._id
to each element". It seems unfortunate that we're discarding a syntax which is unambiguous and clear.The new proposal
Goals
The goal of this proposal is to
Overview
The main complication of supporting
*[…]._id
is knowing why._id
should be mapped inside the array without depending on run time information. In*[_type == "user"]{"a": bar._id}
we want._id
to mean "access _id on the bar object" and never treat it as an array projection.The solution in this proposal is to treat traversal (
[]
), filtering ([_type == "user"]
) and slicing ([0..5]
) as array coercing markers. These will coerce the left-hand side to an array (meaning that if it's not an array it will be converted to null), and as such we know that any.foo
coming after it makes sense to treat as mapping inside the array.Details
The core idea behind this proposal is to introduce a concept of a mapper.
.foo
,[_type == "bar"]
and->name
are all mappers. A mapper is an operation which you can apply to a value, but are not valid expressions by themselves. We can group mappers into two categories: Simple mappers and array mappers, with the distinction being that array mappers work on arrays.We have the following simple mappers:
.foo
{foo}
(in*{foo}
, not as a standalone expression)->
and->foo
..(foo)
.This is a new invention which would allow more flexibility in the way you compose mappers. This allows e.g.
*[_type == "user"].(count(books))
which would return an array of numbers.And then we have the following array mappers:
[0..5]
[_type == "user"]
[]
.This is the same as a
[true]
or[0..-1]
.It acts merely as a way of marking that the value is an array.
Pipes (
|
) are supported in various places to handle backwards compatibility.Here's the grammar for composing mappers:
Explanation:
MapArrayMulti
andMapSimpleMulti
represents composed mappers. They are mappers which are built on top of other mappers.MapSimpleMulti
is quite simple: When applied to a value it will apply the simple mappers and theMapArrayMulti
in order.MapArrayMulti
is a bit more complicated:null
immediately.MapSimpleMulti
it will apply that mapper on each element of the arrry.*
is interpreted as an array expression. The only impact this has is that aMapSimpleMulti
applied on anArrExpr
will apply the mapping on each element instead of on the value itself. This casues*{foo}
to be interpreted as intended.Implications
*[_type == "user"].id
returns the ID of all documents.*[_type == "user"].slug.title
returnsslug.title
.*[_type == "user"].roles[].title
returns a nested array of role titles. If there's two users who have the roles (A,B) and (C), then this will return[["A", "B"], ["C"]]
.*[_type == "user"]{foo{bar}}
, thenfoo
must be an object. If it's an array then it will end up beingnull
.*[_type == "user"]{foo[]{bar}}
, thenfoo
must be an array.How do we teach this?
Here are some phrases which can be used for explaining the behavior:
.foo
to get thefoo
attribute of all of the documents/elements/objects."->
at the end."[]
."How to deal with flattening?
There's never any flattening happening here. I propose that we separately introduce a
flat
-function (that's what it is called in JavaScript):flat(*[_type == "user"].roles[].title)
will flatten it one level.The text was updated successfully, but these errors were encountered: