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 9abd194543..1a26d073f7 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
@@ -54,7 +54,7 @@
* Key.Evaluated
containing the empty list.
*/
@API(API.Status.UNSTABLE)
-public class FieldKeyExpression extends BaseKeyExpression implements AtomKeyExpression, KeyExpressionWithoutChildren {
+public class FieldKeyExpression extends BaseKeyExpression implements AtomKeyExpression, KeyExpressionWithoutChildren, GroupableKeyExpression {
private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Field-Key-Expression");
@Nonnull
@@ -290,16 +290,14 @@ public NestingKeyExpression nest(@Nonnull KeyExpression child) {
return new NestingKeyExpression(this, child);
}
- /**
- * Get this field as a group without any grouping keys.
- * @return this field without any grouping keys
- */
@Nonnull
+ @Override
public GroupingKeyExpression ungrouped() {
return new GroupingKeyExpression(this, 1);
}
@Nonnull
+ @Override
public GroupingKeyExpression groupBy(@Nonnull KeyExpression groupByFirst, @Nonnull KeyExpression... groupByRest) {
return GroupingKeyExpression.of(this, groupByFirst, groupByRest);
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/GroupableKeyExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/GroupableKeyExpression.java
new file mode 100644
index 0000000000..ad20d9ba07
--- /dev/null
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/GroupableKeyExpression.java
@@ -0,0 +1,42 @@
+/*
+ * GroupableKeyExpression.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.apple.foundationdb.record.metadata.expressions;
+
+import com.apple.foundationdb.annotation.API;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A {@link KeyExpression} that can be grouped by another {@link KeyExpression}.
+ */
+@API(API.Status.EXPERIMENTAL)
+public interface GroupableKeyExpression extends KeyExpression {
+
+ @Nonnull
+ GroupingKeyExpression groupBy(@Nonnull KeyExpression groupByFirst, @Nonnull KeyExpression... groupByRest);
+
+ /**
+ * Get {@code this} nesting as a group without any grouping keys.
+ *
+ * @return this nesting without any grouping keys
+ */
+ GroupingKeyExpression ungrouped();
+}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/NestingKeyExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/NestingKeyExpression.java
index 65f3d3da18..e7114076bc 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/NestingKeyExpression.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/NestingKeyExpression.java
@@ -46,7 +46,7 @@
* record, then it will evaluate the same as if the parent field is unset or empty (depending on the fan type).
*/
@API(API.Status.UNSTABLE)
-public class NestingKeyExpression extends BaseKeyExpression implements KeyExpressionWithChild, AtomKeyExpression {
+public class NestingKeyExpression extends BaseKeyExpression implements KeyExpressionWithChild, AtomKeyExpression, GroupableKeyExpression {
private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Nesting-Key-Expression");
@Nonnull
@@ -167,16 +167,14 @@ public KeyExpression getChild() {
return child;
}
- /**
- * Get this nesting as a group without any grouping keys.
- * @return this nesting without any grouping keys
- */
@Nonnull
+ @Override
public GroupingKeyExpression ungrouped() {
return new GroupingKeyExpression(this, getColumnSize());
}
@Nonnull
+ @Override
public GroupingKeyExpression groupBy(@Nonnull KeyExpression groupByFirst, @Nonnull KeyExpression... groupByRest) {
return GroupingKeyExpression.of(this, groupByFirst, groupByRest);
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/ThenKeyExpression.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/ThenKeyExpression.java
index c79ce6a5ab..3bd7c279f2 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/ThenKeyExpression.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/expressions/ThenKeyExpression.java
@@ -45,7 +45,7 @@
* against the null
record.
*/
@API(API.Status.UNSTABLE)
-public class ThenKeyExpression extends BaseKeyExpression implements KeyExpressionWithChildren {
+public class ThenKeyExpression extends BaseKeyExpression implements KeyExpressionWithChildren, GroupableKeyExpression {
private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Then-Key-Expression");
@Nonnull
@@ -146,16 +146,14 @@ public int getColumnSize() {
return columnSize;
}
- /**
- * Get this entire concatenation as a group without any grouping keys.
- * @return this concatenation without any grouping keys
- */
@Nonnull
+ @Override
public GroupingKeyExpression ungrouped() {
return new GroupingKeyExpression(this, getColumnSize());
}
@Nonnull
+ @Override
public GroupingKeyExpression groupBy(@Nonnull KeyExpression groupByFirst, @Nonnull KeyExpression... groupByRest) {
return GroupingKeyExpression.of(this, groupByFirst, groupByRest);
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/IndexGenerator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/IndexGenerator.java
index 14c10f53c5..7e3d78ca66 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/IndexGenerator.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/IndexGenerator.java
@@ -30,6 +30,7 @@
import com.apple.foundationdb.record.metadata.expressions.EmptyKeyExpression;
import com.apple.foundationdb.record.metadata.expressions.FieldKeyExpression;
import com.apple.foundationdb.record.metadata.expressions.FunctionKeyExpression;
+import com.apple.foundationdb.record.metadata.expressions.GroupableKeyExpression;
import com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression;
import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
import com.apple.foundationdb.record.metadata.expressions.ThenKeyExpression;
@@ -391,7 +392,6 @@ private NonnullPair generateAggregateIndexKeyExpression(@
final var indexableAggregateValue = (IndexableAggregateValue) aggregateValue;
final var child = Iterables.getOnlyElement(aggregateValue.getChildren());
var indexTypeName = indexableAggregateValue.getIndexTypeName();
- final KeyExpression groupedValue;
final GroupingKeyExpression keyExpression;
// COUNT(*) is a special case.
if (aggregateValue instanceof CountValue && IndexTypes.COUNT.equals(indexTypeName)) {
@@ -402,7 +402,7 @@ private NonnullPair generateAggregateIndexKeyExpression(@
}
} else if (aggregateValue instanceof NumericAggregationValue.BitmapConstructAgg && IndexTypes.BITMAP_VALUE.equals(indexTypeName)) {
Assert.thatUnchecked(child instanceof FieldValue || child instanceof ArithmeticValue, "Unsupported index definition, expecting a column argument in aggregation function");
- groupedValue = generate(List.of(child), Collections.emptyMap());
+ final var groupedValue = generate(List.of(child), Collections.emptyMap());
// only support bitmap_construct_agg(bitmap_bit_position(column))
// doesn't support bitmap_construct_agg(column)
Assert.thatUnchecked(groupedValue instanceof FunctionKeyExpression, "Unsupported index definition, expecting a bitmap_bit_position function in bitmap_construct_agg function");
@@ -422,16 +422,11 @@ private NonnullPair generateAggregateIndexKeyExpression(@
}
} else {
Assert.thatUnchecked(child instanceof FieldValue, "Unsupported index definition, expecting a column argument in aggregation function");
- groupedValue = generate(List.of(child), Collections.emptyMap());
- Assert.thatUnchecked(groupedValue instanceof FieldKeyExpression || groupedValue instanceof ThenKeyExpression);
+ final var groupedValue = Assert.castUnchecked(generate(List.of(child), Collections.emptyMap()), GroupableKeyExpression.class);
if (maybeGroupingExpression.isPresent()) {
- keyExpression = (groupedValue instanceof FieldKeyExpression) ?
- ((FieldKeyExpression) groupedValue).groupBy(maybeGroupingExpression.get()) :
- ((ThenKeyExpression) groupedValue).groupBy(maybeGroupingExpression.get());
+ keyExpression = groupedValue.groupBy(maybeGroupingExpression.get());
} else {
- keyExpression = (groupedValue instanceof FieldKeyExpression) ?
- ((FieldKeyExpression) groupedValue).ungrouped() :
- ((ThenKeyExpression) groupedValue).ungrouped();
+ keyExpression = groupedValue.ungrouped();
}
}
// special handling of min_ever and max_ever, depending on index attributes we either create the
diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java
index c61ada2fc4..52a9c02ca5 100644
--- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java
+++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java
@@ -378,6 +378,32 @@ void createAggregateIndexWithComplexGroupingExpressionCase1() throws Exception {
function("add", concat(field("B"), value(3))))), IndexTypes.PERMUTED_MAX);
}
+ @Test
+ void createAggregateIndexWithComplexGroupingExpressionCase2() throws Exception {
+ final String stmt = "CREATE SCHEMA TEMPLATE test_template " +
+ "CREATE TYPE AS STRUCT A(x BIGINT)" +
+ "CREATE TYPE AS STRUCT B(y A)" +
+ "CREATE TYPE AS STRUCT C(z B)" +
+ "CREATE TABLE T1(p1 bigint, a bigint, b bigint, c C, primary key(p1)) " +
+ "CREATE INDEX mv1 AS SELECT a & 2, b + 3, MAX(c.z.y.x) FROM T1 GROUP BY a & 2, b + 3";
+ indexIs(stmt, field("C").nest(field("Z").nest(field("Y").nest(field("X")))).groupBy(concat(function("bitand", concat(field("A"), value(2))),
+ function("add", concat(field("B"), value(3))))), IndexTypes.PERMUTED_MAX);
+ }
+
+ @Test
+ void createAggregateIndexWithComplexGroupingExpressionCase3() throws Exception {
+ final String stmt = "CREATE SCHEMA TEMPLATE test_template " +
+ "CREATE TYPE AS STRUCT A(x BIGINT)" +
+ "CREATE TYPE AS STRUCT B(y A)" +
+ "CREATE TYPE AS STRUCT C(z B)" +
+ "CREATE TABLE T1(p1 bigint, a bigint, b bigint, c C, primary key(p1)) " +
+ "CREATE INDEX mv1 AS SELECT a & 2, b + 3, c.z.y.x, MAX(b) FROM T1 GROUP BY a & 2, b + 3, c.z.y.x";
+ indexIs(stmt, field("B").groupBy(concat(
+ function("bitand", concat(field("A"), value(2))),
+ function("add", concat(field("B"), value(3))),
+ field("C").nest(field("Z").nest(field("Y").nest(field("X")))))), IndexTypes.PERMUTED_MAX);
+ }
+
@Test
void createSimpleValueIndex() throws Exception {
final String stmt = "CREATE SCHEMA TEMPLATE test_template " +
@@ -853,6 +879,19 @@ void createMaxEverLong() throws Exception {
);
}
+ @Test
+ void createMaxEvenNestedNonrepeatedField() throws Exception {
+ final String stmt = "CREATE SCHEMA TEMPLATE test_template " +
+ "CREATE TYPE AS STRUCT S(x bigint) " +
+ "CREATE TYPE AS STRUCT Q(y bigint)" +
+ "CREATE TABLE T1(col1 bigint, col2 S, col3 Q, primary key(col1)) " +
+ "CREATE INDEX mv1 AS SELECT MAX_EVER(col3.y) FROM T1 group by col2.x";
+ indexIs(stmt,
+ field("COL3").nest("Y").groupBy(field("COL2").nest("X")),
+ IndexTypes.MAX_EVER_TUPLE
+ );
+ }
+
@Test
void createMaxEverTupleIncorrectType() throws Exception {
final String stmt = "CREATE SCHEMA TEMPLATE test_template " +
@@ -875,6 +914,19 @@ void createMinEverTupleIncorrectType() throws Exception {
);
}
+ @Test
+ void createMinEvenNestedNonrepeatedField() throws Exception {
+ final String stmt = "CREATE SCHEMA TEMPLATE test_template " +
+ "CREATE TYPE AS STRUCT S(x bigint) " +
+ "CREATE TYPE AS STRUCT Q(y bigint)" +
+ "CREATE TABLE T1(col1 bigint, col2 S, col3 Q, primary key(col1)) " +
+ "CREATE INDEX mv1 AS SELECT MIN_EVER(col3.y) FROM T1 group by col2.x";
+ indexIs(stmt,
+ field("COL3").nest("Y").groupBy(field("COL2").nest("X")),
+ IndexTypes.MIN_EVER_TUPLE
+ );
+ }
+
@Test
void createMaxEverLongIncorrectType() throws Exception {
final String stmt = "CREATE SCHEMA TEMPLATE test_template " +