Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.apple.foundationdb.record.query.plan.cascades.KeyExpressionVisitor;
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
import com.apple.foundationdb.record.query.plan.cascades.expressions.ExplodeExpression;
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.apple.foundationdb.record.util.ProtoUtils;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.Descriptors;
Expand Down Expand Up @@ -274,19 +275,25 @@ public <S extends KeyExpressionVisitor.State, R> R expand(@Nonnull final KeyExpr
return visitor.visitExpression(this);
}

/**
* Creates a quantifier that unnests this field of {@code baseValue}.
*
* @param baseValue the value this field is a field of
* @param fieldNamePrefix the nesting chain of non-repeated fields leading to this field
*
* @return a quantifier ranging over an {@link ExplodeExpression} of this field
*/
@Nonnull
public Quantifier.ForEach explodeField(@Nonnull Quantifier.ForEach baseQuantifier, @Nonnull List<String> fieldNamePrefix) {
public Quantifier.ForEach explodeField(@Nonnull Value baseValue, @Nonnull List<String> fieldNamePrefix) {
final List<String> fieldNames = ImmutableList.<String>builder()
.addAll(fieldNamePrefix)
.add(ProtoUtils.toUserIdentifier(fieldName))
.build();
switch (fanType) {
case FanOut:
return Quantifier.forEach(Reference.initialOf(
ExplodeExpression.explodeField(baseQuantifier, fieldNames)));
default:
throw new RecordCoreException("unrecognized fan type");
if (fanType == FanType.FanOut) {
return Quantifier.forEach(Reference.initialOf(
ExplodeExpression.explodeField(baseValue, fieldNames)));
}
throw new RecordCoreException("unrecognized fan type");
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.apple.foundationdb.record.metadata.expressions.NestingKeyExpression;
import com.apple.foundationdb.record.metadata.expressions.ThenKeyExpression;
import com.apple.foundationdb.record.query.plan.cascades.KeyExpressionExpansionVisitor.VisitorState;
import com.apple.foundationdb.record.query.plan.cascades.expressions.ExplodeExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.SelectExpression;
import com.apple.foundationdb.record.query.plan.cascades.predicates.Placeholder;
import com.apple.foundationdb.record.query.plan.cascades.predicates.PredicateWithValueAndRanges;
Expand Down Expand Up @@ -131,7 +132,6 @@ public GraphExpansion visitExpression(@Nonnull FieldKeyExpression fieldKeyExpres
final KeyExpression.FanType fanType = fieldKeyExpression.getFanType();
final VisitorState state = getCurrentState();
final List<String> fieldNamePrefix = state.getFieldNamePrefix();
final Quantifier.ForEach baseQuantifier = state.getBaseQuantifier();
final List<String> fieldNames = ImmutableList.<String>builder()
.addAll(fieldNamePrefix)
.add(fieldName)
Expand All @@ -141,8 +141,8 @@ public GraphExpansion visitExpression(@Nonnull FieldKeyExpression fieldKeyExpres
switch (fanType) {
case FanOut:
// explode this field and prefixes of this field
final Quantifier.ForEach childBase = fieldKeyExpression.explodeField(baseQuantifier, fieldNamePrefix);
value = state.registerValue(childBase.getFlowedObjectValue());
final Quantifier.ForEach childBase = fieldKeyExpression.explodeField(state.getBaseValue(), fieldNamePrefix);
value = state.registerValue(ExplodeExpression.elementValueOf(childBase));
column = Column.unnamedOf(value);
final GraphExpansion childExpansion;
if (state.isKey() && !state.isInternalExpansion()) {
Expand All @@ -162,9 +162,11 @@ public GraphExpansion visitExpression(@Nonnull FieldKeyExpression fieldKeyExpres
.build();
case Concatenate:
case None:
// Note: `Concatenate` and `None` can use the graph expansion logic. Both just access the field directly
// via `FieldValue.ofFieldNames()`.
value = state.registerValue(FieldValue.ofFieldNames(baseQuantifier.getFlowedObjectValue(), fieldNames));
// Note: `Concatenate` and `None` can use the graph expansion logic. Both just access the field
// directly. The access is fused into the base, which is itself a field access when the base quantifier
// ranges over an explode: a field access whose child is another field access cannot be pulled up,
// since the pull-up rules relate a field path to the quantifier it is rooted in.
value = state.registerValue(FieldValue.ofFieldNamesAndFuseIfPossible(state.getBaseValue(), fieldNames));
if (state.isSelectStar()) {
if (state.isKey() && !state.isInternalExpansion()) {
return GraphExpansion.ofPlaceholder(value.asPlaceholder(newParameterAlias()));
Expand Down Expand Up @@ -258,7 +260,6 @@ public GraphExpansion visitExpression(@Nonnull final KeyWithValueExpression keyW
public GraphExpansion visitExpression(@Nonnull final NestingKeyExpression nestingKeyExpression) {
final VisitorState state = getCurrentState();
final List<String> fieldNamePrefix = state.getFieldNamePrefix();
final Quantifier.ForEach baseQuantifier = state.getBaseQuantifier();

final FieldKeyExpression parent = nestingKeyExpression.getParent();
final KeyExpression child = nestingKeyExpression.getChild();
Expand Down Expand Up @@ -304,7 +305,7 @@ public GraphExpansion visitExpression(@Nonnull final NestingKeyExpression nestin
}
case FanOut:
// explode the parent field(s) also depending on the prefix
final Quantifier.ForEach childBaseQuantifier = parent.explodeField(baseQuantifier, fieldNamePrefix);
final Quantifier.ForEach childBaseQuantifier = parent.explodeField(state.getBaseValue(), fieldNamePrefix);
// expand the children of the key expression and then unify them into an expansion of this expression
final GraphExpansion childExpansion =
pop(child.expand(push(state.withBaseQuantifier(childBaseQuantifier).withFieldNamePrefix(ImmutableList.of()))));
Expand Down Expand Up @@ -539,6 +540,20 @@ public Quantifier.ForEach getBaseQuantifier() {
return baseQuantifier;
}

/**
* The value {@link #baseQuantifier} contributes as the base of a field access. That is its flowed object value,
* except under an unnesting: an {@link ExplodeExpression} flows the array element wrapped in a struct, so the
* base is the element within that wrapper.
*
* @return the value field accesses of the current state are relative to
*/
@Nonnull
public Value getBaseValue() {
return baseQuantifier.getRangesOver().get() instanceof ExplodeExpression
? ExplodeExpression.elementValueOf(baseQuantifier)
: baseQuantifier.getFlowedObjectValue();
}

@Nonnull
public List<String> getFieldNamePrefix() {
return fieldNamePrefix;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraph;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue;
import com.apple.foundationdb.record.query.plan.cascades.values.QuantifiedObjectValue;
import com.apple.foundationdb.record.query.plan.cascades.values.QueriedValue;
import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue;
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.apple.foundationdb.record.query.plan.cascades.values.translation.PullUp;
import com.apple.foundationdb.record.query.plan.cascades.values.translation.RegularTranslationMap;
import com.apple.foundationdb.record.query.plan.cascades.values.translation.TranslationMap;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
Expand All @@ -65,6 +67,16 @@
*/
@API(API.Status.EXPERIMENTAL)
public class ExplodeExpression extends AbstractRelationalExpressionWithoutChildren implements InternalPlannerGraphRewritable {
/**
* The ordinal of the array element in the value an explode flows.
*/
public static final int ELEMENT_ORDINAL = 0;

/**
* The ordinal of the ordinality in the value an explode flows, for the {@code WITH ORDINALITY} variant.
*/
public static final int ORDINALITY_ORDINAL = 1;

@Nonnull
private final Value collectionValue;

Expand Down Expand Up @@ -110,7 +122,8 @@ public ExplodeExpression(@Nonnull final Value collectionValue, final boolean wit
}

public ExplodeExpression(@Nonnull final Value collectionValue, final boolean withOrdinality) {
this(collectionValue, withOrdinality, false);
// every explode flows a record constructor: of the element alone, or of the element and the ordinal
this(collectionValue, withOrdinality, true);
}

public ExplodeExpression(@Nonnull final Value collectionValue) {
Expand Down Expand Up @@ -308,8 +321,56 @@ public String toString() {
: collectionValue.toString();
}

/**
* Returns a {@link TranslationMap} that makes each given explode quantifier stand for the array element alone,
* rather than for the record the explode flows.
*
* <p>A rule that turns explode quantifiers into <em>bindings</em> -- an in-join or an in-union, which bind an
* element of the collection under {@code __corr_«alias»} and re-execute the inner plan per element -- has to apply
* this to the plans it puts underneath, because those plans reference the alias as {@code «alias»._0}. Wrapping
* what the alias stands for lets that access compose away to a plain reference to the binding, which is what the
* runtime actually provides.
*
* @param quantifierToExplodeMap what each explode quantifier ranges over
*
* @return a translation map wrapping each explode alias in the record its explode flows
*/
@Nonnull
public static TranslationMap elementBindingTranslationMap(@Nonnull final Map<? extends Quantifier, ExplodeExpression> quantifierToExplodeMap) {
final var translationMapBuilder = RegularTranslationMap.builder();
for (final var entry : quantifierToExplodeMap.entrySet()) {
final var explodeExpression = entry.getValue();
if (explodeExpression.isWithOrdinality()) {
// an ordinal cannot be carried by a binding, so such an explode is never turned into one; see the
// callers, which refuse to use it as a source
continue;
}
final var alias = entry.getKey().getAlias();
final var elementValue = QuantifiedObjectValue.of(alias, explodeExpression.getElementType());
translationMapBuilder.when(alias).then((sourceAlias, leafValue) ->
RecordConstructorValue.ofColumns(ImmutableList.of(Column.unnamedOf(elementValue)), true));
}
return translationMapBuilder.build();
}

/**
* Returns the value standing for the array element an explode quantifier ranges over, reached through the record
* the explode flows.
*/
@Nonnull
public static Value elementValueOf(@Nonnull final Quantifier explodeQuantifier) {
return FieldValue.ofOrdinalNumber(explodeQuantifier.getFlowedObjectValue(), ELEMENT_ORDINAL);
}

@Nonnull
public static ExplodeExpression explodeField(@Nonnull final Quantifier.ForEach baseQuantifier,
@Nonnull final List<String> fieldNames) {
return new ExplodeExpression(FieldValue.ofFieldNames(baseQuantifier.getFlowedObjectValue(), fieldNames));
return explodeField(baseQuantifier.getFlowedObjectValue(), fieldNames);
}

@Nonnull
public static ExplodeExpression explodeField(@Nonnull final Value baseValue,
@Nonnull final List<String> fieldNames) {
return new ExplodeExpression(FieldValue.ofFieldNamesAndFuseIfPossible(baseValue, fieldNames));
}
}
Loading
Loading