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 " +