diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java index ff4fbc1af1..af68b68706 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/FieldKeyExpression.java @@ -44,15 +44,26 @@ import java.util.List; /** - * Take keys from a record field. - * If fieldName is a repeated field, then FanType.Concatenate turns all the - * field values into a single Key.Evaluated. If FanType.FanOut, there is one (singleton) - * Key.Evaluated for each repeated value. If this is evaluated on the null record, then - * it will the same value as if it were evaluated on a record where the field is either unset (in the case of scalar - * fields) or empty (in the case of repeated fields). In particular, if FanType.None, then this returns - * a single Key.Evaluated containing null; if FanType.FanOut, then - * this returns no Key.Evaluateds; and if FanType.Concatenate, then this returns a single - * Key.Evaluated containing the empty list. + * Takes keys from a record field. + * + *

On a record where the field carries a value, the result depends on the + * {@link com.apple.foundationdb.record.metadata.expressions.KeyExpression.FanType FanType}: + *

+ * + *

On a {@code null} record (or a record where the field is absent), the result is driven by the + * {@link Key.Evaluated.NullStandin} of this expression: + *

*/ @API(API.Status.UNSTABLE) public class FieldKeyExpression extends BaseKeyExpression implements AtomKeyExpression, KeyExpressionWithoutChildren { @@ -174,13 +185,24 @@ public List evaluateMessage(@Nullable FDBReco } } + /** + * Evaluates the case where no value can be extracted for this field. This method is called from + * {@link #evaluateMessage} when either the input {@code message} is {@code null}, or the field is absent on the + * message and its {@link #nullStandin} is not {@code NOT_NULL} (i.e., the type-default substitution does not apply). + * + *

The result depends on the fan type and the null standin: {@code FanOut} emits no entries, {@code Concatenate} + * and {@code None} emit a single entry carrying either the {@code NullStandin} (indexable null) or, for + * {@code Concatenate} with a {@code NOT_NULL} standin, an empty list (the proto default for a repeated field). + */ private List getNullResult() { - // As opposed to default value, in order to get indexable NULL. switch (fanType) { case FanOut: return Collections.emptyList(); case Concatenate: - return Collections.singletonList(Key.Evaluated.scalar(Collections.emptyList())); + Key.Evaluated result = (nullStandin == Key.Evaluated.NullStandin.NOT_NULL) + ? Key.Evaluated.scalar(Collections.emptyList()) + : Key.Evaluated.scalar(nullStandin); + return Collections.singletonList(result); case None: return Collections.singletonList(Key.Evaluated.scalar(nullStandin)); default: diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/metadata/KeyExpressionTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/metadata/KeyExpressionTest.java index 1fe4c2dd5f..c6f1f50f9a 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/metadata/KeyExpressionTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/metadata/KeyExpressionTest.java @@ -334,9 +334,11 @@ void testConcatenateSingleRepeatedField() { assertFalse(expression.createsDuplicates()); assertEquals(Collections.singletonList(scalar(Arrays.asList("Boxes", "Bowls"))), evaluate(expression, plantsBoxesAndBowls)); + // `repeat_me` has 0 repetitions: Yields the empty list. assertEquals(Collections.singletonList(scalar(Collections.emptyList())), evaluate(expression, emptyScalar)); - assertEquals(Collections.singletonList(scalar(Collections.emptyList())), + // Null record: Propagates the field’s NullStandin (default NULL). + assertEquals(Collections.singletonList(scalar(NULL)), evaluate(expression, null)); } @@ -348,9 +350,11 @@ void testFieldThenConcatenateRepeated() { assertFalse(expression.createsDuplicates()); assertEquals(Collections.singletonList(Key.Evaluated.concatenate("Plants", Arrays.asList("Boxes", "Bowls"))), evaluate(expression, plantsBoxesAndBowls)); + // Both fields unset: The scalar `field` yields the NULL standin; concatenate yields the empty list. assertEquals(Collections.singletonList(Key.Evaluated.concatenate(NULL, Collections.emptyList())), evaluate(expression, emptyScalar)); - assertEquals(Collections.singletonList(Key.Evaluated.concatenate(NULL, Collections.emptyList())), + // Null record: Both parts propagate their null standin (default NULL) and yield NULL. + assertEquals(Collections.singletonList(Key.Evaluated.concatenate(NULL, NULL)), evaluate(expression, null)); } @@ -504,11 +508,14 @@ void testNestedThenRepeatsConcatenated() { assertFalse(expression.createsDuplicates()); assertEquals(Collections.singletonList(scalar(Arrays.asList("lily", "rose"))), evaluate(expression, matryoshkaDolls)); - assertEquals(Collections.singletonList(scalar(Collections.emptyList())), + // `nesty` absent: Concatenate propagates its NULL standin and yields NULL. + assertEquals(Collections.singletonList(scalar(NULL)), evaluate(expression, emptyNested)); + // `nesty` is present but `repeated_field` has 0 repetitions: Yields the empty-list result. assertEquals(Collections.singletonList(scalar(Collections.emptyList())), evaluate(expression, lonelyDoll)); - assertEquals(Collections.singletonList(scalar(Collections.emptyList())), + // Null record: Concatenate propagates its NULL standin and yields NULL. + assertEquals(Collections.singletonList(scalar(NULL)), evaluate(expression, null)); }