|
50 | 50 | import com.google.common.base.Verify; |
51 | 51 | import com.google.common.collect.ImmutableList; |
52 | 52 | import com.google.common.collect.Iterables; |
| 53 | +import com.google.common.collect.Multimap; |
53 | 54 | import com.google.common.collect.Streams; |
54 | 55 |
|
55 | 56 | import javax.annotation.Nonnull; |
@@ -231,24 +232,72 @@ public boolean isNamedArgument() { |
231 | 232 | return false; |
232 | 233 | } |
233 | 234 |
|
| 235 | + /** |
| 236 | + * Returns this expression rewritten in terms of the given value, which is simplified on the way. See |
| 237 | + * {@link #pullUpSimplified} for details. |
| 238 | + */ |
234 | 239 | @Nonnull |
235 | 240 | public Expression pullUp(@Nonnull Value value, @Nonnull CorrelationIdentifier correlationIdentifier, |
236 | 241 | @Nonnull Set<CorrelationIdentifier> constantAliases) { |
237 | | - final var aliasMap = AliasMap.identitiesFor(value.getCorrelatedTo()); |
238 | | - final var simplifiedValue = value.simplify(EvaluationContext.empty(), aliasMap, constantAliases); |
239 | | - final var underlying = getUnderlying(); |
240 | | - final var pulledUpUnderlying = Assert.notNullUnchecked(underlying.replace( |
| 242 | + final AliasMap aliasMap = AliasMap.identitiesFor(value.getCorrelatedTo()); |
| 243 | + final Value simplifiedValue = value.simplify(EvaluationContext.empty(), aliasMap, constantAliases); |
| 244 | + return withUnderlying(pullUpSimplified(getUnderlying(), simplifiedValue, aliasMap, correlationIdentifier, |
| 245 | + constantAliases)); |
| 246 | + } |
| 247 | + |
| 248 | + /** |
| 249 | + * Rewrites the given value in terms of a candidate value, by replacing every sub-value that can be expressed as a |
| 250 | + * reference into the candidate with such a reference. This is the matching step behind the {@code pullUp()} |
| 251 | + * methods, on values rather than on expressions, and the place where all of them are documented. |
| 252 | + * |
| 253 | + * <p>For example, given {@code SELECT g, COUNT(a) + 1 FROM T GROUP BY g}, the group-by operator computes |
| 254 | + * {@code (g, COUNT(a))}, and the projection then has to be expressed over that result rather than over {@code T}. |
| 255 | + * “Pulling up” the {@code COUNT(a) + 1} against it yields {@code _._1._0 + 1}, the group-by result keeping the |
| 256 | + * grouping columns and the aggregates in separate records. The {@code COUNT(a)} sub-value is matched and replaced |
| 257 | + * by a reference to the column that holds it, while the {@code + 1} is left alone because it has no counterpart on |
| 258 | + * the other side. |
| 259 | + * |
| 260 | + * <p>The candidate has to arrive simplified, under the same {@code aliasMap} and {@code constantAliases} that are |
| 261 | + * passed here. Simplification paves the way for the matching by performing certain canonicalization steps (such as |
| 262 | + * collapsing a record constructor that effectively reconstructs a whole record to just that record), and the |
| 263 | + * matching is structural, so a value that is canonicalized on one side but not on the other does not match at all. |
| 264 | + * |
| 265 | + * <p>Neither value is modified; the result is a new value, or the given one in case nothing matched. |
| 266 | + * |
| 267 | + * @param value the value to rewrite |
| 268 | + * @param simplifiedValue the candidate to express {@code value} in terms of, simplified |
| 269 | + * @param aliasMap the alias map of equalities to match under |
| 270 | + * @param correlationIdentifier the alias the resulting references are expressed over |
| 271 | + * @param constantAliases the aliases that are considered constant |
| 272 | + * @return {@code value}, rewritten in terms of {@code simplifiedValue} |
| 273 | + */ |
| 274 | + @Nonnull |
| 275 | + static Value pullUpSimplified(@Nonnull Value value, @Nonnull Value simplifiedValue, @Nonnull AliasMap aliasMap, |
| 276 | + @Nonnull CorrelationIdentifier correlationIdentifier, |
| 277 | + @Nonnull Set<CorrelationIdentifier> constantAliases) { |
| 278 | + // Walk the value, “offering” every sub-value for replacement in terms of the candidate. |
| 279 | + return Assert.notNullUnchecked(value.replace( |
241 | 280 | subExpression -> { |
242 | | - final var pulledUpExpressionMap = |
| 281 | + // Match this sub-value against the candidate. |
| 282 | + final Multimap<Value, Value> pulledUpExpressionMap = |
243 | 283 | simplifiedValue.pullUp(List.of(subExpression), EvaluationContext.empty(), aliasMap, |
244 | 284 | constantAliases, correlationIdentifier); |
245 | | - if (pulledUpExpressionMap.containsKey(subExpression)) { |
246 | | - return Iterables.getOnlyElement(pulledUpExpressionMap.get(subExpression)); |
| 285 | + final Collection<Value> references = pulledUpExpressionMap.get(subExpression); |
| 286 | + |
| 287 | + // If the candidate cannot express the sub-value, keep it. |
| 288 | + if (references.isEmpty()) { |
| 289 | + return subExpression; |
247 | 290 | } |
248 | | - return subExpression; |
| 291 | + |
| 292 | + // Reject an ambiguous match. If a candidate exposes the same value twice (e.g., `x AS a` and |
| 293 | + // `x AS b`), this can mean the query left a column ambiguous, and we can’t just take a guess here. |
| 294 | + Assert.thatUnchecked(references.size() == 1, |
| 295 | + ErrorCode.AMBIGUOUS_COLUMN, "Ambiguous columns for %s", subExpression); |
| 296 | + |
| 297 | + // Replace the sub-value with the reference the candidate came back with. |
| 298 | + return Iterables.getOnlyElement(references); |
249 | 299 | } |
250 | 300 | )); |
251 | | - return this.withUnderlying(pulledUpUnderlying); |
252 | 301 | } |
253 | 302 |
|
254 | 303 | public boolean canBeDerivedFrom(@Nonnull final Expression expression, |
|
0 commit comments