ModeShape/modeshape — pull request #1180 by rhauch (2014-07-17 18:07:23+00:00)
Title: MODE-2178 Corrected processing of asymmetric UNION/INTERSECT/EXCEPT expressions

Set operations like `UNION`, `INTERSECT` and `EXCEPT` require that the result columns projected from each side of the set operation be structurally identical. This was already handled by the query engine. However, when the two sides of the set operation have different numbers of selectors (e.g., one side involves a join while the other does not), then the NodeSequence implementations that result from each side do not have the same widths.

For example, consider a simplistic `UNION` query like this:

SELECT a.[jcr:path] FROM a
UNION
SELECT c.[jcr:path] FROM b JOIN c ON ISCHILDNODE(b,c)

The NodeSequence on the left side (e.g., for "`a`") would have a width of 1, meaning each row in the sequence contains a single NodeKey (at index 0 in that row). The NodeSequence on the right side would have a width of 2, meaning each row in the sequence contains a NodeKey for `b` (at index 0) and a NodeKey for `c` (at index 1). But note that the result of the right side needs to be the path of the `c` node (again, at index 1).

In symbolic form, this is shown as:

[a] UNION [b,c]

The node sequence that implements the UNION thus sees asymmetric NodeSequences. What we really want is:

[a] UNION [c]

This commit corrects the logic by looking for the asymmetric case (where the width of the sequences on each is not the same), and if needed wrapping one or both of the sequences with a new NodeSequence implementation that "slices" the original node sequence to return only the required/desired selectors. This is pretty straightforward, and simply involves mapping the required/desired selector indexes (in the example, 0 for both the left and right sides) to the actually wrapped sequence.

Thus, in our example, the sequence on the left side is `[a]` and of size 1, and it does not need to be wrapped since all of its selectors are used in the results needed by the `UNION`. On the other hand, the sequence on the right side is `[b,c]` (of size 2), but only `c` is needed in the results. Note that `b` is at index 0 and `c` is at index 1, and the UNION needs `c` to be at index 0. Therefore, we wrap this sequence with a new slicing sequence that merely converts the '0' index used by the UNION to obtain the NodeKey into the '1' index that can be passed to the real underlying sequence to obtain the `c` node keys.

The implementation works for any widths.

All test cases @Ignored in previous commits have been re-enabled and all pass.