+ * For generic types, this method returns the raw type without any type arguments
+ * (e.g., {@code List} becomes {@code List}). For non-generic types, the type
+ * is returned as-is. If the type belongs to the same package as {@code currentPackage},
+ * the package prefix is omitted from the generated type name.
+ *
+ * This is particularly useful when generating code that needs to reference the
+ * {@code .class} literal of a generic type, as class literals must use raw types.
+ *
+ * @param typeMirror the type mirror to convert
+ * @param currentPackage the current package name, used to determine whether to omit
+ * package prefixes for types in the same package
+ * @return a TypeName representing the raw type (without type parameters) if the type
+ * is generic, or the original type name if not generic
+ */
+ @Nonnull
+ private static TypeName getRawTypeName(@Nonnull TypeMirror typeMirror, @Nonnull String currentPackage) {
+ if (typeMirror.getKind() == TypeKind.DECLARED) {
+ final var declaredType = (DeclaredType) typeMirror;
+ final var typeElement = (TypeElement) declaredType.asElement();
+ final boolean isGeneric = !typeElement.getTypeParameters().isEmpty();
+
+ if (isGeneric) {
+ final ClassName className = ClassName.get(typeElement);
+ return removePackagePrefix(className, currentPackage);
+ }
+ }
+
+ // return as-is, remove the package if it is the same as the currentPackage.
+ final TypeName typeName = TypeName.get(typeMirror);
+ if (typeName instanceof ClassName) {
+ return removePackagePrefix((ClassName) typeName, currentPackage);
+ }
+
+ return typeName;
+ }
+
+ /**
+ * Converts a type mirror to a TypeName with wildcard type arguments for generic types.
+ *
+ * For generic types, this method creates a parameterized type with wildcard bounds
+ * (e.g., {@code List} becomes {@code List>}). For non-generic types, the type
+ * is returned as-is. If the type belongs to the same package as {@code currentPackage},
+ * the package prefix is omitted from the generated type name.
+ *
+ * @param typeMirror the type mirror to convert
+ * @param currentPackage the current package name, used to determine whether to omit
+ * package prefixes for types in the same package
+ * @return a TypeName representing the type with wildcard type arguments if the type
+ * is generic, or the original type name if not generic
+ */
+ @Nonnull
+ private static TypeName getWildcardTypeName(@Nonnull final TypeMirror typeMirror, @Nonnull final String currentPackage) {
+ if (typeMirror.getKind() == TypeKind.DECLARED) {
+ final var declaredType = (DeclaredType) typeMirror;
+ final var typeElement = (TypeElement) declaredType.asElement();
+ final boolean isGeneric = !typeElement.getTypeParameters().isEmpty();
+
+ if (isGeneric) {
+ ClassName rawType = ClassName.get(typeElement);
+ rawType = removePackagePrefix(rawType, currentPackage);
+
+ final WildcardTypeName[] wildcards = new WildcardTypeName[typeElement.getTypeParameters().size()];
+ Arrays.fill(wildcards, WildcardTypeName.subtypeOf(Object.class));
+ return ParameterizedTypeName.get(rawType, wildcards);
+ }
+ }
+
+ // return as-is, remove the package if it is the same as the currentPackage.
+ final TypeName typeName = TypeName.get(typeMirror);
+ if (typeName instanceof ClassName) {
+ return removePackagePrefix((ClassName) typeName, currentPackage);
+ }
+
+ return typeName;
+ }
+
+ /**
+ * Removes the package prefix from a ClassName if it belongs to the same package as currentPackage.
+ *
+ * This is useful when generating code references to types that are in the same package,
+ * as the package prefix can be omitted for brevity.
+ *
+ * @param className the ClassName to potentially strip the package prefix from
+ * @param currentPackage the current package name to compare against
+ * @return a ClassName without the package prefix if it's in the same package,
+ * otherwise returns the original ClassName unchanged
+ */
+ @Nonnull
+ private static ClassName removePackagePrefix(@Nonnull final ClassName className, @Nonnull final String currentPackage) {
+ if (className.packageName().equals(currentPackage)) {
+ return ClassName.get("", className.topLevelClassName().simpleName(),
+ className.simpleNames().subList(1, className.simpleNames().size()).toArray(new String[0]));
+ }
+ return className;
+ }
+
private static void generateImplementationWithDefaults(@Nonnull final Types typeUtils,
@Nonnull final Filer filer,
@Nonnull final GenerateVisitor generateVisitor,
@@ -240,7 +345,7 @@ private static void generateImplementationWithDefaults(@Nonnull final Types type
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
.addAnnotation(Nonnull.class)
.addAnnotation(Override.class)
- .addParameter(ParameterSpec.builder(TypeName.get(typeMirror), parameterName).addAnnotation(Nonnull.class).build())
+ .addParameter(ParameterSpec.builder(getWildcardTypeName(typeMirror, packageElement.getQualifiedName().toString()), parameterName).addAnnotation(Nonnull.class).build())
.returns(typeVariableName)
.addCode(CodeBlock.builder()
.addStatement("return " + defaultMethodName + "(" + parameterName + ")")
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/IndexTypes.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/IndexTypes.java
index 1d19171093..8d10f26d9e 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/IndexTypes.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/metadata/IndexTypes.java
@@ -164,6 +164,11 @@ public class IndexTypes {
*/
public static final String MULTIDIMENSIONAL = "multidimensional";
+ /**
+ * An index using an HNSW structure.
+ */
+ public static final String VECTOR = "vector";
+
private IndexTypes() {
}
}
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/IndexKeyValueToPartialRecord.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/IndexKeyValueToPartialRecord.java
index c5a8ddee35..edc50f8830 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/IndexKeyValueToPartialRecord.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/IndexKeyValueToPartialRecord.java
@@ -463,6 +463,9 @@ public boolean copy(@Nonnull Descriptors.Descriptor recordDescriptor, @Nonnull M
return !fieldDescriptor.isRequired();
}
switch (fieldDescriptor.getType()) {
+ case INT32:
+ value = ((Number)value).intValue();
+ break;
case MESSAGE:
value = TupleFieldsHelper.toProto(value, fieldDescriptor.getMessageType());
break;
diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Value.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Value.java
index 0b5da02fef..14126baf64 100644
--- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Value.java
+++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/Value.java
@@ -21,6 +21,7 @@
package com.apple.foundationdb.record.query.plan.cascades.values;
import com.apple.foundationdb.annotation.API;
+import com.apple.foundationdb.annotation.GenerateVisitor;
import com.apple.foundationdb.record.EvaluationContext;
import com.apple.foundationdb.record.PlanHashable;
import com.apple.foundationdb.record.PlanSerializable;
@@ -91,6 +92,7 @@
* A scalar value type.
*/
@API(API.Status.EXPERIMENTAL)
+@GenerateVisitor
public interface Value extends Correlated, TreeLike, UsesValueEquivalence, PlanHashable, Typed, Narrowable, PlanSerializable {
@Nonnull
diff --git a/fdb-relational-core/src/jmh/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalBenchmark.java b/fdb-relational-core/src/jmh/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalBenchmark.java
index 706f0ccc1b..367205cbdf 100644
--- a/fdb-relational-core/src/jmh/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalBenchmark.java
+++ b/fdb-relational-core/src/jmh/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalBenchmark.java
@@ -63,8 +63,8 @@ public abstract class EmbeddedRelationalBenchmark {
"CREATE TABLE \"RestaurantRecord\" (\"rest_no\" bigint, \"name\" string, \"location\" \"Location\", \"reviews\" \"RestaurantReview\" ARRAY, \"tags\" \"RestaurantTag\" ARRAY, \"customer\" string ARRAY, PRIMARY KEY(\"rest_no\")) " +
"CREATE TABLE \"RestaurantReviewer\" (\"id\" bigint, \"name\" string, \"email\" string, \"stats\" \"ReviewerStats\", PRIMARY KEY(\"id\")) " +
- "CREATE INDEX \"record_name_idx\" as select \"name\" from \"RestaurantRecord\" " +
- "CREATE INDEX \"reviewer_name_idx\" as select \"name\" from \"RestaurantReviewer\" ";
+ "CREATE INDEX \"record_name_idx\" ON \"RestaurantRecord\"(\"name\") " +
+ "CREATE INDEX \"reviewer_name_idx\" ON \"RestaurantReviewer\"(\"name\") ";
static final String restaurantRecordTable = "RestaurantRecord";
diff --git a/fdb-relational-core/src/main/antlr/RelationalLexer.g4 b/fdb-relational-core/src/main/antlr/RelationalLexer.g4
index 77a34e26f3..bcbbc340c7 100644
--- a/fdb-relational-core/src/main/antlr/RelationalLexer.g4
+++ b/fdb-relational-core/src/main/antlr/RelationalLexer.g4
@@ -999,6 +999,15 @@ GREATEST: 'GREATEST';
GTID_SUBSET: 'GTID_SUBSET';
GTID_SUBTRACT: 'GTID_SUBTRACT';
HEX: 'HEX';
+HNSW_EF_CONSTRUCTION: 'HNSW_EF_CONSTRUCTION';
+HNSW_M_MAX: 'HNSW_M_MAX';
+HNSW_M: 'HNSW_M';
+HNSW_MAINTAIN_STATS_PROBABILITY: 'HNSW_MAINTAIN_STATS_PROBABILITY';
+HNSW_METRIC: 'HNSW_METRIC';
+HNSW_RABITQ_NUM_EX_BITS: 'HNSW_RABITQ_NUM_EX_BITS';
+HNSW_SAMPLE_VECTOR_STATS_PROBABILITY:'HNSW_SAMPLE_VECTOR_STATS_PROBABILITY';
+HNSW_STATS_THRESHOLD: 'HNSW_STATS_THRESHOLD';
+HNSW_USE_RABITQ: 'HNSW_USE_RABITQ';
IFNULL: 'IFNULL';
INET6_ATON: 'INET6_ATON';
INET6_NTOA: 'INET6_NTOA';
diff --git a/fdb-relational-core/src/main/antlr/RelationalParser.g4 b/fdb-relational-core/src/main/antlr/RelationalParser.g4
index 3bbbd614b5..2b84c79e4a 100644
--- a/fdb-relational-core/src/main/antlr/RelationalParser.g4
+++ b/fdb-relational-core/src/main/antlr/RelationalParser.g4
@@ -168,7 +168,49 @@ enumDefinition
;
indexDefinition
- : (UNIQUE)? INDEX indexName=uid AS queryTerm indexAttributes?
+ : (UNIQUE)? INDEX indexName=uid AS queryTerm indexAttributes? #indexAsSelectDefinition
+ | (UNIQUE)? INDEX indexName=uid ON source=fullId indexColumnList includeClause? indexOptions? #indexOnSourceDefinition
+ | VECTOR INDEX indexName=uid ON source=fullId indexColumnList partitionClause? vectorIndexOptions? #vectorIndexDefinition
+ ;
+
+indexColumnList
+ : '(' indexColumnSpec (',' indexColumnSpec)* ')'
+ ;
+
+indexColumnSpec
+ : columnName=uid orderClause?
+ ;
+
+includeClause
+ : INCLUDE '(' uidList ')'
+ ;
+
+indexType
+ : UNIQUE | VECTOR
+ ;
+
+indexOptions
+ : OPTIONS '(' indexOption (COMMA indexOption)* ')'
+ ;
+
+indexOption
+ : LEGACY_EXTREMUM_EVER
+ ;
+
+vectorIndexOptions
+ : OPTIONS '(' vectorIndexOption (COMMA vectorIndexOptions)* ')'
+ ;
+
+vectorIndexOption
+ : HNSW_EF_CONSTRUCTION '=' mValue=DECIMAL_LITERAL
+ | HNSW_M '=' mValue=DECIMAL_LITERAL
+ | HNSW_M_MAX '=' mValue=DECIMAL_LITERAL
+ | HNSW_MAINTAIN_STATS_PROBABILITY '=' mValue=DECIMAL_LITERAL
+ | HNSW_METRIC '=' mValue=DECIMAL_LITERAL // change
+ | HNSW_RABITQ_NUM_EX_BITS '=' mValue=DECIMAL_LITERAL
+ | HNSW_SAMPLE_VECTOR_STATS_PROBABILITY '=' mValue=DECIMAL_LITERAL
+ | HNSW_STATS_THRESHOLD '=' mValue=DECIMAL_LITERAL
+ | HNSW_USE_RABITQ '=' mValue=DECIMAL_LITERAL // change
;
indexAttributes
@@ -413,7 +455,12 @@ orderByClause
;
orderByExpression
- : expression order=(ASC | DESC)? (NULLS nulls=(FIRST | LAST))?
+ : expression orderClause?
+ ;
+
+orderClause
+ : order=(ASC | DESC) (NULLS nulls=(FIRST | LAST))?
+ | NULLS nulls=(FIRST | LAST)
;
tableSources // done
@@ -1104,10 +1151,11 @@ frameRange
| expression (PRECEDING | FOLLOWING)
;
+*/
+
partitionClause
- : PARTITION BY expression (',' expression)*
+ : PARTITION BY uid (',' uid)*
;
-*/
scalarFunctionName
: functionNameBase
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/AbstractEmbeddedStatement.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/AbstractEmbeddedStatement.java
index 140a86496f..bb203cbcb2 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/AbstractEmbeddedStatement.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/AbstractEmbeddedStatement.java
@@ -215,10 +215,15 @@ private int countUpdates(@Nonnull ResultSet resultSet) throws SQLException {
}
return count;
} catch (SQLException | RuntimeException ex) {
- if (conn.canCommit()) {
- conn.rollbackInternal();
+ SQLException finalException = ExceptionUtil.toRelationalException(ex).toSqlException();
+ try {
+ if (conn.canCommit()) {
+ conn.rollbackInternal();
+ }
+ } catch (SQLException | RuntimeException rollbackError) {
+ finalException.addSuppressed(rollbackError);
}
- throw ExceptionUtil.toRelationalException(ex).toSqlException();
+ throw finalException;
}
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java
index 00095bc203..cfa48a62ae 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerIndex.java
@@ -200,6 +200,15 @@ public Builder setOptions(@Nonnull final Map options) {
return this;
}
+ @Nonnull
+ public Builder addAllOptions(@Nonnull final Map options) {
+ if (optionsBuilder == null) {
+ optionsBuilder = ImmutableMap.builder();
+ }
+ optionsBuilder.putAll(options);
+ return this;
+ }
+
@Nonnull
public Builder setOption(@Nonnull final String optionKey, @Nonnull final String optionValue) {
if (optionsBuilder == null) {
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ParseHelpers.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ParseHelpers.java
index 53b8454405..88fa8466a4 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ParseHelpers.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ParseHelpers.java
@@ -40,6 +40,7 @@
import org.antlr.v4.runtime.tree.ParseTree;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.util.Base64;
import java.util.Locale;
import java.util.function.Supplier;
@@ -163,13 +164,18 @@ public static byte[] parseBytes(String text) {
}
}
- public static boolean isDescending(@Nonnull RelationalParser.OrderByExpressionContext orderByExpressionContext) {
- return (orderByExpressionContext.ASC() == null) && (orderByExpressionContext.DESC() != null);
+ public static boolean isNullsLast(@Nullable RelationalParser.OrderClauseContext orderClause, boolean isDescending) {
+ if (orderClause == null || orderClause.nulls == null) {
+ return isDescending; // Default behavior: ASC NULLS FIRST, DESC NULLS LAST
+ }
+ return orderClause.LAST() != null;
}
- public static boolean isNullsLast(@Nonnull RelationalParser.OrderByExpressionContext orderByExpressionContext, boolean isDescending) {
- return orderByExpressionContext.nulls == null ? isDescending :
- (orderByExpressionContext.FIRST() == null) && (orderByExpressionContext.LAST() != null);
+ public static boolean isDescending(@Nullable RelationalParser.OrderClauseContext orderClause) {
+ if (orderClause == null) {
+ return false; // Default is ASC
+ }
+ return orderClause.DESC() != null;
}
public static class ParseTreeLikeAdapter implements TreeLike {
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/ColumnSort.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/ColumnSort.java
new file mode 100644
index 0000000000..0614faa94a
--- /dev/null
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/ColumnSort.java
@@ -0,0 +1,60 @@
+/*
+ * ColumnSort.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.relational.recordlayer.query.ddl;
+
+import javax.annotation.Nonnull;
+
+public enum ColumnSort {
+
+ Undefined("undefined"),
+ AscendingNullsFirst("order_asc_nulls_first"),
+ AscendingNullsLast("order_asc_nulls_last"),
+ DescendingNullsFirst("order_desc_nulls_first"),
+ DescendingNullsLast("order_desc_nulls_last");
+
+ @Nonnull
+ private final String keyExpressionFunctionName;
+
+ ColumnSort(@Nonnull final String keyExpressionFunctionName) {
+ this.keyExpressionFunctionName = keyExpressionFunctionName;
+ }
+
+ @Nonnull
+ public String getKeyExpressionFunctionName() {
+ return keyExpressionFunctionName;
+ }
+
+ @Nonnull
+ public static ColumnSort of(boolean isDescending, boolean isNullsLast) {
+ if (isDescending) {
+ if (isNullsLast) {
+ return DescendingNullsLast;
+ } else {
+ return DescendingNullsFirst;
+ }
+ } else
+ if (isNullsLast) {
+ return AscendingNullsLast;
+ } else {
+ return AscendingNullsFirst;
+ }
+ }
+}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/IndexGenerator.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/IndexGenerator.java
new file mode 100644
index 0000000000..33b2a2bf71
--- /dev/null
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/IndexGenerator.java
@@ -0,0 +1,294 @@
+/*
+ * IndexBuilder.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.relational.recordlayer.query.ddl;
+
+import com.apple.foundationdb.record.EvaluationContext;
+import com.apple.foundationdb.record.RecordCoreException;
+import com.apple.foundationdb.record.RecordMetaDataProto;
+import com.apple.foundationdb.record.metadata.IndexPredicate;
+import com.apple.foundationdb.record.metadata.IndexTypes;
+import com.apple.foundationdb.record.query.combinatorics.TopologicalSort;
+import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
+import com.apple.foundationdb.record.query.plan.cascades.Reference;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
+import com.apple.foundationdb.record.query.plan.cascades.values.RecordConstructorValue;
+import com.apple.foundationdb.relational.api.exceptions.ErrorCode;
+import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerIndex;
+import com.apple.foundationdb.relational.recordlayer.query.Expression;
+import com.apple.foundationdb.relational.recordlayer.query.Expressions;
+import com.apple.foundationdb.relational.recordlayer.query.Identifier;
+import com.apple.foundationdb.relational.recordlayer.query.SemanticAnalyzer;
+import com.apple.foundationdb.relational.util.Assert;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Streams;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.apple.foundationdb.record.query.plan.cascades.properties.ReferencesAndDependenciesProperty.referencesAndDependencies;
+import static java.util.stream.Collectors.toList;
+
+public final class IndexGenerator {
+
+ @Nonnull
+ private final Identifier indexName;
+
+ @Nonnull
+ private final List keyColumns;
+
+ @Nonnull
+ private final List valueColumns;
+
+ @Nonnull
+ private final String indexType;
+
+ @Nonnull
+ private final Map indexOptions;
+
+ @Nonnull
+ private final RelationalExpression source;
+
+ @Nonnull
+ private final SemanticAnalyzer semanticAnalyzer;
+
+ private final boolean isUnique;
+
+ private final boolean useLegacyExtremum;
+
+ public IndexGenerator(@Nonnull final Identifier indexName, @Nonnull final RelationalExpression source, @Nonnull final SemanticAnalyzer semanticAnalyzer,
+ @Nonnull final List keyColumns, @Nonnull final List valueColumns,
+ @Nonnull final String indexType, @Nonnull final Map indexOptions, final boolean isUnique,
+ final boolean useLegacyExtremum) {
+ this.indexName = indexName;
+ this.source = source;
+ this.semanticAnalyzer = semanticAnalyzer;
+ this.keyColumns = ImmutableList.copyOf(keyColumns);
+ this.valueColumns = ImmutableList.copyOf(valueColumns);
+ this.indexType = indexType;
+ this.indexOptions = ImmutableMap.copyOf(indexOptions);
+ this.isUnique = isUnique;
+ this.useLegacyExtremum = useLegacyExtremum;
+ }
+
+ @Nonnull
+ public RecordLayerIndex generate() {
+ final var recordLayerIndexBuilder = buildKeyExpression();
+ return recordLayerIndexBuilder.setName(indexName.getName())
+ .setUnique(isUnique)
+ .setIndexType(indexType)
+ .addAllOptions(indexOptions)
+ .build();
+ }
+
+ @Nonnull
+ private RecordLayerIndex.Builder buildKeyExpression() {
+ final var derivedSourceValue = new ValueLineageVisitor().visit(source).simplify(EvaluationContext.empty(), AliasMap.emptyMap(), Set.of());
+ final var derivedSourceExpression = Expressions.of(Assert.castUnchecked(derivedSourceValue, RecordConstructorValue.class)
+ .getColumns().stream().map(Expression::fromColumn).collect(ImmutableList.toImmutableList()));
+
+ final var keyExpressionDecoratorBuilder = KeyExpressionBuilder.KeyExpressionDecorator.newBuilder()
+ .setUseLegacyExtremum(useLegacyExtremum);
+ final var indexedExpressions = Streams.concat(
+ keyColumns.stream().map(keyColumn -> {
+ final var value = Assert.optionalUnchecked(semanticAnalyzer.lookupAlias(keyColumn.identifier, derivedSourceExpression),
+ ErrorCode.UNDEFINED_COLUMN, () -> "Attempting to index non existing column '" + keyColumn.identifier + "'");
+ if (keyColumn.columnSort != ColumnSort.Undefined) {
+ keyExpressionDecoratorBuilder.addOrderKeyExpression(value.getUnderlying(), keyColumn.columnSort);
+ }
+ return value;
+ }),
+ valueColumns.stream().map(includeUid ->
+ Assert.optionalUnchecked(semanticAnalyzer.lookupAlias(includeUid.identifier, derivedSourceExpression),
+ ErrorCode.UNDEFINED_COLUMN, () -> "Attempting to index non existing column '" + includeUid.identifier + "'")))
+ .collect(ImmutableList.toImmutableList());
+ final var indexExpr = Expressions.of(indexedExpressions);
+ final var rcv = RecordConstructorValue.ofColumns(indexExpr.underlyingAsColumns());
+ if (!valueColumns.isEmpty()) {
+ keyExpressionDecoratorBuilder.addKeyValueExpression(rcv, keyColumns.size());
+ }
+
+ final var keyExpressionBuilder = KeyExpressionBuilder.buildKeyExpression(rcv, keyExpressionDecoratorBuilder.build());
+ final var indexBuilder = RecordLayerIndex.newBuilder();
+
+ indexBuilder.setKeyExpression(keyExpressionBuilder.getKeyExpression())
+ .setIndexType(keyExpressionBuilder.getIndexType())
+ .setTableName(keyExpressionBuilder.getBaseTypeName());
+
+ @Nullable var indexPredicate = getIndexPredicate();
+ if (indexPredicate != null) {
+ indexBuilder.setPredicate(indexPredicate);
+ }
+
+ return indexBuilder;
+ }
+
+ @Nullable
+ private RecordMetaDataProto.Predicate getIndexPredicate() {
+ final var partialOrder = referencesAndDependencies().evaluate(Reference.initialOf(source));
+ final var expressionRefs =
+ TopologicalSort.anyTopologicalOrderPermutation(partialOrder)
+ .orElseThrow(() -> new RecordCoreException("graph has cycles")).stream().map(Reference::get).collect(toList());
+ final var predicate = LegacyIndexGenerator.getTopLevelPredicate(Lists.reverse(expressionRefs));
+ if (predicate == null) {
+ return null;
+ }
+ return IndexPredicate.fromQueryPredicate(predicate).toProto();
+ }
+
+ public static final class IndexedColumn {
+
+ @Nonnull
+ private final Identifier identifier;
+
+ @Nonnull
+ private final ColumnSort columnSort;
+
+ private IndexedColumn(@Nonnull final Identifier identifier,
+ @Nonnull final ColumnSort sortCriteria) {
+ this.identifier = identifier;
+ this.columnSort = sortCriteria;
+ }
+
+ @Nonnull
+ public static IndexedColumn of(@Nonnull final Identifier identifier) {
+ return new IndexedColumn(identifier, ColumnSort.Undefined);
+ }
+
+ @Nonnull
+ public static IndexedColumn of(@Nonnull final Identifier identifier, ColumnSort sort) {
+ return new IndexedColumn(identifier, sort);
+ }
+
+ @Nonnull
+ public static IndexedColumn of(@Nonnull final Identifier identifier, boolean isDescending, boolean isNullsLast) {
+ return new IndexedColumn(identifier, ColumnSort.of(isDescending, isNullsLast));
+ }
+ }
+
+ @Nonnull
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+
+ private Identifier indexName;
+
+ private RelationalExpression indexSource;
+
+ private SemanticAnalyzer semanticAnalyzer;
+
+ @Nonnull
+ private final List keyColumns;
+
+ @Nonnull
+ private final List valueColumns;
+
+ @Nonnull
+ private final Map indexOptions;
+
+ private String indexType;
+
+ private boolean isUnique;
+
+ private boolean useLegacyExtremum;
+
+ private Builder() {
+ this.keyColumns = new ArrayList<>();
+ this.valueColumns = new ArrayList<>();
+ this.indexOptions = new LinkedHashMap<>();
+ }
+
+ @Nonnull
+ public Builder setIndexName(@Nonnull final Identifier indexName) {
+ this.indexName = indexName;
+ return this;
+ }
+
+ @Nonnull
+ public Builder setIndexSource(@Nonnull final RelationalExpression relationalExpression) {
+ indexSource = relationalExpression;
+ return this;
+ }
+
+ @Nonnull
+ public Builder setSemanticAnalyzer(@Nonnull final SemanticAnalyzer semanticAnalyzer) {
+ this.semanticAnalyzer = semanticAnalyzer;
+ return this;
+ }
+
+ @Nonnull
+ public Builder addKeyColumn(@Nonnull final IndexedColumn keyColumn) {
+ keyColumns.add(keyColumn);
+ return this;
+ }
+
+ @Nonnull
+ public Builder addValueColumn(@Nonnull final IndexedColumn keyColumn) {
+ valueColumns.add(keyColumn);
+ return this;
+ }
+
+ @Nonnull
+ public Builder addIndexOption(@Nonnull final String key, @Nonnull final String value) {
+ indexOptions.put(key, value);
+ return this;
+ }
+
+ @Nonnull
+ public Builder setIndexType(@Nonnull final String indexType) {
+ this.indexType = indexType;
+ return this;
+ }
+
+ @Nonnull
+ public Builder setUnique(boolean isUnique) {
+ this.isUnique = isUnique;
+ return this;
+ }
+
+ @Nonnull
+ public Builder setUseLegacyExtremum(boolean useLegacyExtremum) {
+ this.useLegacyExtremum = useLegacyExtremum;
+ return this;
+ }
+
+ @Nonnull
+ public IndexGenerator build() {
+ Assert.notNullUnchecked(indexName);
+ Assert.notNullUnchecked(indexSource);
+ Assert.notNullUnchecked(semanticAnalyzer);
+ Assert.thatUnchecked(!keyColumns.isEmpty());
+ if (indexType == null) {
+ indexType = IndexTypes.VALUE;
+ }
+ return new IndexGenerator(indexName, indexSource, semanticAnalyzer, keyColumns, valueColumns, indexType,
+ indexOptions, isUnique, useLegacyExtremum);
+ }
+ }
+}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/KeyExpressionBuilder.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/KeyExpressionBuilder.java
new file mode 100644
index 0000000000..8bb95cc935
--- /dev/null
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/KeyExpressionBuilder.java
@@ -0,0 +1,446 @@
+/*
+ * KeyExpressionBuilder.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.relational.recordlayer.query.ddl;
+
+import com.apple.foundationdb.record.RecordCoreException;
+import com.apple.foundationdb.record.metadata.IndexTypes;
+import com.apple.foundationdb.record.metadata.Key;
+import com.apple.foundationdb.record.metadata.expressions.EmptyKeyExpression;
+import com.apple.foundationdb.record.metadata.expressions.FieldKeyExpression;
+import com.apple.foundationdb.record.metadata.expressions.GroupingKeyExpression;
+import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
+import com.apple.foundationdb.record.metadata.expressions.ThenKeyExpression;
+import com.apple.foundationdb.record.query.plan.cascades.LinkedIdentityMap;
+import com.apple.foundationdb.record.query.plan.cascades.LinkedIdentitySet;
+import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
+import com.apple.foundationdb.record.query.plan.cascades.values.CountValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.IndexOnlyAggregateValue;
+import com.apple.foundationdb.record.query.plan.cascades.values.NumericAggregationValue;
+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.ValueVisitorWithDefaults;
+import com.apple.foundationdb.relational.util.Assert;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static com.apple.foundationdb.record.metadata.Key.Expressions.field;
+
+public final class KeyExpressionBuilder {
+
+ @Nonnull
+ private final ValueToKeyExpressionVisitor.KeyExpressionConstruction keyExpressionConstruction;
+
+ private KeyExpressionBuilder(@Nonnull final Value value, @Nonnull final KeyExpressionDecorator keyExpressionDecorator) {
+ final var visitor = new ValueToKeyExpressionVisitor(keyExpressionDecorator);
+ keyExpressionConstruction = visitor.visit(value);
+ }
+
+ @Nonnull
+ public KeyExpression getKeyExpression() {
+ return Assert.notNullUnchecked(keyExpressionConstruction.getKeyExpression());
+ }
+
+ @Nonnull
+ public String getIndexType() {
+ @Nullable final var indexType = keyExpressionConstruction.getIndexType();
+ return indexType == null ? IndexTypes.VALUE : indexType;
+ }
+
+ @Nonnull
+ public Type getBaseType() {
+ @Nullable final var baseType = keyExpressionConstruction.getBaseType();
+ return baseType == null ? Type.any() : baseType;
+ }
+
+ @Nonnull
+ public String getBaseTypeName() {
+ final var type = getBaseType();
+ Assert.thatUnchecked(type.isRecord());
+ final var relationType = Assert.castUnchecked(type, Type.Record.class);
+ return Assert.notNullUnchecked(relationType.getName());
+ }
+
+ private static final class ValueToKeyExpressionVisitor implements ValueVisitorWithDefaults {
+
+ @Nonnull
+ private final KeyExpressionDecorator keyExpressionDecorator;
+
+ ValueToKeyExpressionVisitor(@Nonnull final KeyExpressionDecorator keyExpressionDecorator) {
+ this.keyExpressionDecorator = keyExpressionDecorator;
+ }
+
+ @Nonnull
+ private KeyExpressionConstruction decorate(@Nonnull final Value value, @Nonnull final KeyExpressionConstruction keyExpressionConstruction) {
+ final var keyExpression = keyExpressionConstruction.keyExpression;
+ if (keyExpression == null) {
+ return keyExpressionConstruction;
+ }
+ return keyExpressionConstruction.replaceKeyExpression(Objects.requireNonNull(keyExpressionDecorator.decorate(value, keyExpression)));
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpressionConstruction visitRecordConstructorValue(@Nonnull final RecordConstructorValue recordConstructorValue) {
+ final var fieldExpressionsBuilder = ImmutableList.builder();
+ for (final var fieldValue : recordConstructorValue.getChildren()) {
+ fieldExpressionsBuilder.add(visit(fieldValue));
+ }
+ final var fieldExpressions = fieldExpressionsBuilder.build();
+ return decorate(recordConstructorValue, KeyExpressionConstruction.combine(fieldExpressions));
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpressionConstruction visitDefault(@Nonnull final Value element) {
+ throw new RecordCoreException("generating key expression from " + element + " is not supported");
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpressionConstruction visitSum(@Nonnull final NumericAggregationValue.Sum element) {
+ final var child = visit(element.getChild());
+ return decorate(element, child.withIndexType(IndexTypes.SUM));
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpressionConstruction visitCountValue(@Nonnull final CountValue element) {
+ final var child = visit(Iterables.getOnlyElement(element.getChildren()));
+ return decorate(element, child.withIndexType(IndexTypes.COUNT));
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpressionConstruction visitMaxEverValue(@Nonnull final IndexOnlyAggregateValue.MaxEverValue element) {
+ final var child = visit(element.getChild());
+ if (keyExpressionDecorator.useLegacyExtremum) {
+ Verify.verify(element.getChild().getResultType().isNumeric(), "only numeric types allowed in " +
+ IndexTypes.MAX_EVER_LONG + " aggregation operation");
+ return decorate(element, child.withIndexType(IndexTypes.MAX_EVER_LONG));
+ }
+ return decorate(element, child.withIndexType(IndexTypes.MAX_EVER_TUPLE));
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpressionConstruction visitMinEverValue(@Nonnull final IndexOnlyAggregateValue.MinEverValue element) {
+ final var child = visit(element.getChild());
+ if (keyExpressionDecorator.useLegacyExtremum) {
+ Verify.verify(element.getChild().getResultType().isNumeric(), "only numeric types allowed in " +
+ IndexTypes.MIN_EVER_LONG + " aggregation operation");
+ return decorate(element, child.withIndexType(IndexTypes.MIN_EVER_LONG));
+ }
+ return decorate(element, child.withIndexType(IndexTypes.MIN_EVER_TUPLE));
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpressionConstruction visitFieldValue(@Nonnull final FieldValue fieldValue) {
+ final var keyExpression = toKeyExpression(fieldValue);
+ final var keyExpressionConstruction = visit(fieldValue.getChild());
+ return decorate(fieldValue, keyExpressionConstruction.withKeyExpression(keyExpression));
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpressionConstruction visitQuantifiedObjectValue(@Nonnull final QuantifiedObjectValue quantifiedObjectValue) {
+ return decorate(quantifiedObjectValue, KeyExpressionConstruction.ofBaseType(quantifiedObjectValue.getResultType()));
+ }
+
+ @Nonnull
+ @Override
+ public KeyExpressionConstruction visitQueriedValue(@Nonnull final QueriedValue queriedValue) {
+ return decorate(queriedValue, KeyExpressionConstruction.ofBaseType(queriedValue.getResultType()));
+ }
+
+ @Nonnull
+ private static KeyExpression toKeyExpression(@Nonnull final FieldValue fieldValue) {
+ return toKeyExpression(fieldValue, 0);
+ }
+
+ @Nonnull
+ private static KeyExpression toKeyExpression(@Nonnull final FieldValue fieldValue, int index) {
+ final var field = fieldValue.getFieldPath().getFieldAccessors().get(index);
+ final var keyExpression = toKeyExpression(Assert.notNullUnchecked(field.getName()), field.getType());
+ if (index + 1 < fieldValue.getFieldPath().getFieldAccessors().size()) {
+ return keyExpression.nest(toKeyExpression(fieldValue, index + 1));
+ }
+ return keyExpression;
+ }
+
+ @Nonnull
+ public static FieldKeyExpression toKeyExpression(@Nonnull final String name, @Nonnull final Type type) {
+ Assert.notNullUnchecked(name);
+ final var fanType = type.getTypeCode() == Type.TypeCode.ARRAY ?
+ KeyExpression.FanType.FanOut :
+ KeyExpression.FanType.None;
+ return field(name, fanType);
+ }
+
+ public static final class KeyExpressionConstruction {
+ @Nullable
+ private final KeyExpression keyExpression;
+
+ @Nullable
+ private final Type baseType;
+
+ @Nullable
+ private final String indexType;
+
+ private KeyExpressionConstruction(@Nullable final KeyExpression keyExpression, @Nullable final Type baseType, @Nullable String indexType) {
+ this.keyExpression = keyExpression;
+ this.baseType = baseType;
+ this.indexType = indexType;
+ }
+
+ @Nonnull
+ public KeyExpressionConstruction withKeyExpression(@Nonnull final KeyExpression keyExpression) {
+ Assert.isNullUnchecked(this.keyExpression);
+ return new KeyExpressionConstruction(keyExpression, baseType, indexType);
+ }
+
+ @Nonnull
+ KeyExpressionConstruction replaceKeyExpression(@Nonnull final KeyExpression keyExpression) {
+ return new KeyExpressionConstruction(keyExpression, baseType, indexType);
+ }
+
+ @Nonnull
+ public KeyExpressionConstruction withBaseType(@Nonnull final Type type) {
+ if (baseType == null) {
+ return new KeyExpressionConstruction(keyExpression, type, indexType);
+ }
+ Assert.thatUnchecked(baseType.equals(type), "defining key expression on multiple types is not supported");
+ return this;
+ }
+
+ @Nonnull
+ public KeyExpressionConstruction withIndexType(@Nonnull final String indexType) {
+ if (this.indexType == null) {
+ return new KeyExpressionConstruction(keyExpression, baseType, indexType);
+ }
+ Assert.thatUnchecked(this.indexType.equals(indexType), "defining a key expression with multiple index types is not supported");
+ return this;
+ }
+
+ @Nullable
+ public Type getBaseType() {
+ return baseType;
+ }
+
+ @Nullable
+ public String getIndexType() {
+ return indexType;
+ }
+
+ @Nullable
+ public KeyExpression getKeyExpression() {
+ return keyExpression;
+ }
+
+ private boolean isAggregate() {
+ return IndexTypes.COUNT.equals(indexType) ||
+ IndexTypes.COUNT_NOT_NULL.equals(indexType) ||
+ IndexTypes.SUM.equals(indexType) ||
+ IndexTypes.MAX_EVER_LONG.equals(indexType) ||
+ IndexTypes.PERMUTED_MAX.equals(indexType) ||
+ IndexTypes.MIN_EVER_LONG.equals(indexType) ||
+ IndexTypes.MIN_EVER_TUPLE.equals(indexType) ||
+ IndexTypes.BITMAP_VALUE.equals(indexType);
+ }
+
+ @Nonnull
+ static KeyExpressionConstruction ofBaseType(@Nonnull final Type baseType) {
+ return new KeyExpressionConstruction(null, baseType, null);
+ }
+
+ @Nonnull
+ static KeyExpressionConstruction ofExpression(@Nonnull final KeyExpression expression) {
+ return new KeyExpressionConstruction(expression, null, null);
+ }
+
+ @Nonnull
+ static KeyExpressionConstruction ofIndexType(@Nonnull final String indexType) {
+ return new KeyExpressionConstruction(null, null, indexType);
+ }
+
+ @Nonnull
+ static KeyExpressionConstruction combine(@Nonnull final List fields) {
+ Assert.thatUnchecked(!fields.isEmpty());
+ if (fields.size() == 1) {
+ return fields.get(0);
+ }
+ final var fieldTypes = fields.stream().map(KeyExpressionConstruction::getBaseType).flatMap(Stream::ofNullable).collect(ImmutableSet.toImmutableSet());
+ Assert.thatUnchecked(fieldTypes.size() == 1, "defining key expression on multiple base types is not supported");
+ final var indexTypes = fields.stream().map(KeyExpressionConstruction::getIndexType).flatMap(Stream::ofNullable).collect(ImmutableSet.toImmutableSet());
+ Assert.thatUnchecked(indexTypes.size() <= 1, "defining key expression on multiple index types is not supported");
+ final var fieldKeyExpressions = fields.stream().map(KeyExpressionConstruction::getKeyExpression).flatMap(Stream::ofNullable).collect(ImmutableList.toImmutableList());
+ final var containsAggregates = fields.stream().anyMatch(KeyExpressionConstruction::isAggregate);
+ if (!containsAggregates) {
+ return new KeyExpressionConstruction(Key.Expressions.concat(fieldKeyExpressions), Assert.optionalUnchecked(fieldTypes.stream().findFirst()), null);
+ }
+ final var aggregateKeyExpression = generateAggregateIndexKeyExpression(fields);
+ return aggregateKeyExpression.withBaseType(Assert.optionalUnchecked(fieldTypes.stream().findFirst()));
+ }
+
+ @Nonnull
+ private static KeyExpressionConstruction generateAggregateIndexKeyExpression(@Nonnull List fields) {
+ final var remainingFieldsBuilder = ImmutableList.builder();
+ KeyExpressionConstruction aggregateField = null;
+ for (final var field : fields) {
+ if (field.isAggregate()) {
+ Assert.isNullUnchecked(aggregateField, "defining key expression with multiple aggregations is not supported");
+ aggregateField = field;
+ } else {
+ remainingFieldsBuilder.add(field);
+ }
+ }
+ Assert.notNullUnchecked(aggregateField);
+ final var remainingFields = remainingFieldsBuilder.build();
+ var indexTypeName = Assert.notNullUnchecked(aggregateField.indexType);
+ final KeyExpression groupedValue;
+ final GroupingKeyExpression keyExpression;
+ // COUNT(*) is a special case.
+ if (IndexTypes.COUNT.equals(indexTypeName)) {
+ if (!remainingFields.isEmpty()) {
+ keyExpression = new GroupingKeyExpression(Assert.notNullUnchecked(combine(remainingFields).keyExpression), 0);
+ } else {
+ keyExpression = new GroupingKeyExpression(EmptyKeyExpression.EMPTY, 0);
+ }
+// } 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());
+// // 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");
+// final FunctionKeyExpression functionGroupedValue = (FunctionKeyExpression) groupedValue;
+// Assert.thatUnchecked(BITMAP_BIT_POSITION.equals(functionGroupedValue.getName()), "Unsupported index definition, expecting a bitmap_bit_position function in bitmap_construct_agg function");
+// final var groupedColumnValue = ((ThenKeyExpression) ((FunctionKeyExpression) groupedValue).getArguments()).getChildren().get(0);
+//
+// if (maybeGroupingExpression.isPresent()) {
+// final var afterRemove = removeBitmapBucketOffset(maybeGroupingExpression.get());
+// if (afterRemove == null) {
+// keyExpression = ((FieldKeyExpression) groupedColumnValue).ungrouped();
+// } else {
+// keyExpression = ((FieldKeyExpression) groupedColumnValue).groupBy(afterRemove);
+// }
+// } else {
+// throw Assert.failUnchecked("Unsupported index definition, unexpected grouping expression " + groupedValue);
+// }
+ } else {
+ groupedValue = aggregateField.keyExpression;
+ Assert.thatUnchecked(groupedValue instanceof FieldKeyExpression || groupedValue instanceof ThenKeyExpression);
+ if (!remainingFields.isEmpty()) {
+ keyExpression = (groupedValue instanceof FieldKeyExpression) ?
+ ((FieldKeyExpression)groupedValue).groupBy(Assert.notNullUnchecked(combine(remainingFields).keyExpression)) :
+ ((ThenKeyExpression)groupedValue).groupBy(Assert.notNullUnchecked(combine(remainingFields).keyExpression));
+ } else {
+ keyExpression = (groupedValue instanceof FieldKeyExpression) ?
+ ((FieldKeyExpression)groupedValue).ungrouped() :
+ ((ThenKeyExpression)groupedValue).ungrouped();
+ }
+ }
+ return KeyExpressionConstruction.ofExpression(keyExpression).withIndexType(aggregateField.getIndexType());
+ }
+ }
+ }
+
+ public static final class KeyExpressionDecorator {
+ @Nonnull
+ private final SetMultimap> expressionDecorationMap;
+
+ private final boolean useLegacyExtremum;
+
+ private KeyExpressionDecorator(@Nonnull final SetMultimap> expressionDecorationMap,
+ final boolean useLegacyExtremum) {
+ this.expressionDecorationMap = expressionDecorationMap;
+ this.useLegacyExtremum = useLegacyExtremum;
+ }
+
+ @Nullable
+ KeyExpression decorate(@Nonnull final Value value, @Nullable final KeyExpression keyExpression) {
+ if (keyExpression == null) {
+ return null;
+ }
+ return expressionDecorationMap.get(value).stream()
+ .reduce(keyExpression, (acc, function) -> Objects.requireNonNull(function).apply(acc), (a, b) -> b);
+ }
+
+ @Nonnull
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+
+ @Nonnull
+ private final SetMultimap> expressionDecorationMap;
+
+ private boolean useLegacyExtremum;
+
+ Builder() {
+ expressionDecorationMap = Multimaps.newSetMultimap(new LinkedIdentityMap<>(), LinkedIdentitySet::new);
+ }
+
+ @Nonnull
+ public Builder addOrderKeyExpression(@Nonnull final Value value, @Nonnull final ColumnSort columnSort) {
+ expressionDecorationMap.put(value, keyExpression -> Key.Expressions.function(columnSort.getKeyExpressionFunctionName(), keyExpression));
+ return this;
+ }
+
+ @Nonnull
+ public Builder addKeyValueExpression(@Nonnull final Value value, int keySize) {
+ expressionDecorationMap.put(value, keyExpression -> Key.Expressions.keyWithValue(keyExpression, keySize));
+ return this;
+ }
+
+ @Nonnull
+ public Builder setUseLegacyExtremum(boolean useLegacyExtremum) {
+ this.useLegacyExtremum = useLegacyExtremum;
+ return this;
+ }
+
+ @Nonnull
+ public KeyExpressionDecorator build() {
+ return new KeyExpressionDecorator(expressionDecorationMap, useLegacyExtremum);
+ }
+ }
+ }
+
+ @Nonnull
+ public static KeyExpressionBuilder buildKeyExpression(@Nonnull final Value value, @Nonnull final KeyExpressionDecorator keyExpressionDecorator) {
+ return new KeyExpressionBuilder(value, keyExpressionDecorator);
+ }
+}
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/ddl/LegacyIndexGenerator.java
similarity index 97%
rename from fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/IndexGenerator.java
rename to fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/LegacyIndexGenerator.java
index 14c10f53c5..66b798a6b1 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/ddl/LegacyIndexGenerator.java
@@ -3,7 +3,7 @@
*
* This source file is part of the FoundationDB open source project
*
- * Copyright 2021-2025 Apple Inc. and the FoundationDB project authors
+ * 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.
@@ -18,7 +18,7 @@
* limitations under the License.
*/
-package com.apple.foundationdb.relational.recordlayer.query;
+package com.apple.foundationdb.relational.recordlayer.query.ddl;
import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.EvaluationContext;
@@ -71,6 +71,7 @@
import com.apple.foundationdb.relational.api.exceptions.ErrorCode;
import com.apple.foundationdb.relational.api.exceptions.RelationalException;
import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerIndex;
+import com.apple.foundationdb.relational.recordlayer.query.FieldValueTrieNode;
import com.apple.foundationdb.relational.util.Assert;
import com.apple.foundationdb.relational.util.NullableArrayUtils;
import com.google.common.base.Verify;
@@ -110,7 +111,7 @@
*/
@SuppressWarnings({"PMD.TooManyStaticImports", "OptionalUsedAsFieldOrParameterType"})
@API(API.Status.EXPERIMENTAL)
-public final class IndexGenerator {
+public final class LegacyIndexGenerator {
private static final String BITMAP_BIT_POSITION = "bitmap_bit_position";
private static final String BITMAP_BUCKET_OFFSET = "bitmap_bucket_offset";
@@ -129,7 +130,7 @@ public final class IndexGenerator {
private final boolean useLegacyBasedExtremumEver;
- private IndexGenerator(@Nonnull RelationalExpression relationalExpression, boolean useLegacyBasedExtremumEver) {
+ private LegacyIndexGenerator(@Nonnull RelationalExpression relationalExpression, boolean useLegacyBasedExtremumEver) {
collectQuantifiers(relationalExpression);
final var partialOrder = referencesAndDependencies().evaluate(Reference.initialOf(relationalExpression));
relationalExpressions =
@@ -604,7 +605,7 @@ private void checkValidity(@Nonnull List extends RelationalExpression> express
}
@Nullable
- private static QueryPredicate getTopLevelPredicate(@Nonnull List extends RelationalExpression> expressions) {
+ public static QueryPredicate getTopLevelPredicate(@Nonnull List extends RelationalExpression> expressions) {
if (expressions.isEmpty()) {
return null;
}
@@ -629,7 +630,11 @@ private static QueryPredicate getTopLevelPredicate(@Nonnull List extends Relat
Assert.thatUnchecked(innerSelect.getPredicates().isEmpty(), ErrorCode.UNSUPPORTED_OPERATION, "Unsupported index definition, found predicate in inner-select");
}
}
- final var predicates = ((SelectExpression) expressions.get(currentExpression)).getPredicates().stream().map(QueryPredicate::toResidualPredicate).collect(toList());
+ final var expr = expressions.get(currentExpression);
+ if (!(expr instanceof SelectExpression)) {
+ return null;
+ }
+ final var predicates = ((SelectExpression) expr).getPredicates().stream().map(QueryPredicate::toResidualPredicate).collect(toList());
// todo (yhatem) make sure we through if the generated DNF does not meet the deserialization requirements.
if (predicates.isEmpty()) {
return null;
@@ -776,7 +781,7 @@ private static FieldKeyExpression toKeyExpression(@Nonnull String name, @Nonnull
}
@Nonnull
- public static IndexGenerator from(@Nonnull RelationalExpression relationalExpression, boolean useLongBasedExtremumEver) {
- return new IndexGenerator(relationalExpression, useLongBasedExtremumEver);
+ public static LegacyIndexGenerator from(@Nonnull RelationalExpression relationalExpression, boolean useLongBasedExtremumEver) {
+ return new LegacyIndexGenerator(relationalExpression, useLongBasedExtremumEver);
}
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/ValueLineageVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/ValueLineageVisitor.java
new file mode 100644
index 0000000000..937da0fda2
--- /dev/null
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/ValueLineageVisitor.java
@@ -0,0 +1,81 @@
+/*
+ * ValueLineageVisitor.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.relational.recordlayer.query.ddl;
+
+import com.apple.foundationdb.record.query.plan.cascades.Column;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.ExplodeExpression;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalTypeFilterExpression;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression;
+import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpressionVisitorWithDefaults;
+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.QuantifiedValue;
+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.TranslationMap;
+import com.apple.foundationdb.relational.util.Assert;
+import com.google.common.collect.ImmutableList;
+
+import javax.annotation.Nonnull;
+
+public final class ValueLineageVisitor implements RelationalExpressionVisitorWithDefaults {
+
+ @Nonnull
+ @Override
+ public Value visitDefault(@Nonnull final RelationalExpression element) {
+ var translationMapBuilder = TranslationMap.regularBuilder();
+ final var quantifiers = element.getQuantifiers();
+ for (final var quantifier : quantifiers) {
+ translationMapBuilder
+ .when(quantifier.getAlias())
+ .then(((sourceAlias, leafValue) -> visit(quantifier.getRangesOver().get())));
+ }
+ return element.getResultValue().translateCorrelations(translationMapBuilder.build());
+ }
+
+
+ @Nonnull
+ @Override
+ public Value visitLogicalTypeFilterExpression(@Nonnull final LogicalTypeFilterExpression element) {
+ return projectFields(Assert.castUnchecked(element.getResultValue(), QuantifiedValue.class));
+ }
+
+ @Nonnull
+ @Override
+ public Value visitExplodeExpression(@Nonnull final ExplodeExpression element) {
+ return element.getCollectionValue();
+ }
+
+ @Nonnull
+ private static Value projectFields(@Nonnull final QuantifiedValue value) {
+ final var type = value.getResultType();
+ if (!type.isRecord()) {
+ return value;
+ }
+ final var recordType = Assert.castUnchecked(type, Type.Record.class);
+ final var columns = ImmutableList.>builder();
+ for (final var field : recordType.getFields()) {
+ final var fieldName = field.getFieldName();
+ columns.add(Column.of(field, FieldValue.ofFieldName(value, fieldName)));
+ }
+ return RecordConstructorValue.ofColumns(columns.build());
+ }
+}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/package-info.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/package-info.java
new file mode 100644
index 0000000000..4542e54c51
--- /dev/null
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/ddl/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * package-info.java
+ *
+ * This source file is part of the FoundationDB open source project
+ *
+ * Copyright 2021-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.
+ */
+
+/**
+ * This package contains code responsible for creating index definitions from SQL.
+ */
+
+package com.apple.foundationdb.relational.recordlayer.query.ddl;
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java
index c318a443b3..9a1a306b20 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java
@@ -398,8 +398,32 @@ public DataType.Named visitEnumDefinition(@Nonnull RelationalParser.EnumDefiniti
@Nonnull
@Override
- public RecordLayerIndex visitIndexDefinition(@Nonnull RelationalParser.IndexDefinitionContext ctx) {
- return ddlVisitor.visitIndexDefinition(ctx);
+ public RecordLayerIndex visitIndexAsSelectDefinition(@Nonnull RelationalParser.IndexAsSelectDefinitionContext ctx) {
+ return ddlVisitor.visitIndexAsSelectDefinition(ctx);
+ }
+
+ @Nonnull
+ @Override
+ public RecordLayerIndex visitIndexOnSourceDefinition(@Nonnull RelationalParser.IndexOnSourceDefinitionContext ctx) {
+ return ddlVisitor.visitIndexOnSourceDefinition(ctx);
+ }
+
+ @Nonnull
+ @Override
+ public Object visitIndexColumnList(@Nonnull RelationalParser.IndexColumnListContext ctx) {
+ return ddlVisitor.visitIndexColumnList(ctx);
+ }
+
+ @Nonnull
+ @Override
+ public Object visitIndexColumnSpec(@Nonnull RelationalParser.IndexColumnSpecContext ctx) {
+ return ddlVisitor.visitIndexColumnSpec(ctx);
+ }
+
+ @Nonnull
+ @Override
+ public Object visitIncludeClause(@Nonnull RelationalParser.IncludeClauseContext ctx) {
+ return ddlVisitor.visitIncludeClause(ctx);
}
@Override
@@ -1692,4 +1716,9 @@ public DdlQueryFactory getDdlQueryFactory() {
public URI getDbUri() {
return dbUri;
}
+
+ @Override
+ public Object visitOrderClause(@Nonnull RelationalParser.OrderClauseContext ctx) {
+ return visitChildren(ctx);
+ }
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java
index 48ebf89a8c..56d27e38ec 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DdlVisitor.java
@@ -21,6 +21,7 @@
package com.apple.foundationdb.relational.recordlayer.query.visitors;
import com.apple.foundationdb.annotation.API;
+import com.apple.foundationdb.record.metadata.IndexTypes;
import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier;
import com.apple.foundationdb.record.query.plan.cascades.RawSqlFunction;
import com.apple.foundationdb.record.query.plan.cascades.UserDefinedFunction;
@@ -45,7 +46,8 @@
import com.apple.foundationdb.relational.recordlayer.query.Expression;
import com.apple.foundationdb.relational.recordlayer.query.Expressions;
import com.apple.foundationdb.relational.recordlayer.query.Identifier;
-import com.apple.foundationdb.relational.recordlayer.query.IndexGenerator;
+import com.apple.foundationdb.relational.recordlayer.query.ddl.IndexGenerator;
+import com.apple.foundationdb.relational.recordlayer.query.ddl.LegacyIndexGenerator;
import com.apple.foundationdb.relational.recordlayer.query.LogicalOperator;
import com.apple.foundationdb.relational.recordlayer.query.PreparedParams;
import com.apple.foundationdb.relational.recordlayer.query.ProceduralPlan;
@@ -58,11 +60,13 @@
import org.antlr.v4.runtime.ParserRuleContext;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -193,23 +197,78 @@ public RecordLayerTable visitStructDefinition(@Nonnull RelationalParser.StructDe
@Nonnull
@Override
- public RecordLayerIndex visitIndexDefinition(@Nonnull RelationalParser.IndexDefinitionContext ctx) {
- final var indexId = visitUid(ctx.indexName);
+ public RecordLayerIndex visitIndexAsSelectDefinition(@Nonnull RelationalParser.IndexAsSelectDefinitionContext indexDefinitionContext) {
+ final var indexId = visitUid(indexDefinitionContext.indexName);
final var ddlCatalog = metadataBuilder.build();
// parse the index SQL query using the newly constructed metadata.
getDelegate().replaceSchemaTemplate(ddlCatalog);
final var viewPlan = getDelegate().getPlanGenerationContext().withDisabledLiteralProcessing(() ->
- Assert.castUnchecked(ctx.queryTerm().accept(this), LogicalOperator.class).getQuantifier().getRangesOver().get());
+ Assert.castUnchecked(indexDefinitionContext.queryTerm().accept(this), LogicalOperator.class).getQuantifier().getRangesOver().get());
- final var useLegacyBasedExtremumEver = ctx.indexAttributes() != null && ctx.indexAttributes().indexAttribute().stream().anyMatch(attribute -> attribute.LEGACY_EXTREMUM_EVER() != null);
- final var isUnique = ctx.UNIQUE() != null;
- final var generator = IndexGenerator.from(viewPlan, useLegacyBasedExtremumEver);
+ final var useLegacyBasedExtremumEver = indexDefinitionContext.indexAttributes() != null && indexDefinitionContext.indexAttributes().indexAttribute().stream().anyMatch(attribute -> attribute.LEGACY_EXTREMUM_EVER() != null);
+ final var isUnique = indexDefinitionContext.UNIQUE() != null;
+ final var generator = LegacyIndexGenerator.from(viewPlan, useLegacyBasedExtremumEver);
final var table = metadataBuilder.findTable(generator.getRecordTypeName());
Assert.thatUnchecked(viewPlan instanceof LogicalSortExpression, ErrorCode.INVALID_COLUMN_REFERENCE, "Cannot create index and order by an expression that is not present in the projection list");
return generator.generate(indexId.getName(), isUnique, table.getType(), containsNullableArray);
}
+ @Nonnull
+ @Override
+ public RecordLayerIndex visitIndexOnSourceDefinition(@Nonnull RelationalParser.IndexOnSourceDefinitionContext indexDefinitionContext) {
+ final var ddlCatalog = metadataBuilder.build();
+ // parse the index SQL query using the newly constructed metadata.
+ getDelegate().replaceSchemaTemplate(ddlCatalog);
+ getDelegate().pushPlanFragment();
+ final var tableIdentifier = visitFullId(indexDefinitionContext.source);
+ final var logicalOperator = getDelegate().getPlanGenerationContext().withDisabledLiteralProcessing(() ->
+ LogicalOperator.generateAccess(tableIdentifier, Optional.empty(), Set.of(),
+ getDelegate().getSemanticAnalyzer(), getDelegate().getCurrentPlanFragment(),
+ getDelegate().getLogicalOperatorCatalog()));
+ getDelegate().getCurrentPlanFragment().setOperator(logicalOperator);
+
+ final Identifier indexId = visitUid(indexDefinitionContext.indexName);
+ final var isUnique = indexDefinitionContext.UNIQUE() != null;
+
+ @Nullable final var indexOptions = indexDefinitionContext.indexOptions();
+ final var useLegacyExtremum = indexOptions != null && indexOptions.indexOption().stream().anyMatch(option -> option.LEGACY_EXTREMUM_EVER() != null);
+
+ final var indexGeneratorBuilder = IndexGenerator.newBuilder()
+ .setIndexName(indexId)
+ .setIndexSource(getDelegate().getCurrentPlanFragment().getLogicalOperators().first().getQuantifier().getRangesOver().get())
+ .setSemanticAnalyzer(getDelegate().getSemanticAnalyzer())
+ .setIndexType(IndexTypes.VALUE)
+ .setUnique(isUnique);
+
+ indexDefinitionContext.indexColumnList().indexColumnSpec().forEach(columnSpec -> {
+ final var columnId = visitUid(columnSpec.columnName);
+ final var orderContext = columnSpec.orderClause();
+ if (orderContext != null) {
+ final boolean isDesc = orderContext.DESC() != null;
+ final boolean nullsLast;
+ if (orderContext.nulls == null) {
+ nullsLast = isDesc;
+ } else {
+ nullsLast = orderContext.LAST() != null;
+ }
+ indexGeneratorBuilder.addKeyColumn(IndexGenerator.IndexedColumn.of(columnId, isDesc, nullsLast));
+ } else {
+ indexGeneratorBuilder.addKeyColumn(IndexGenerator.IndexedColumn.of(columnId));
+ }
+ });
+
+ if (indexDefinitionContext.includeClause() != null) {
+ indexDefinitionContext.includeClause().uidList().uid().forEach(uid -> {
+ final var columnId = visitUid(uid);
+ indexGeneratorBuilder.addValueColumn(IndexGenerator.IndexedColumn.of(columnId));
+ });
+ }
+
+ getDelegate().popPlanFragment();
+ return indexGeneratorBuilder.build().generate();
+ }
+
@Nonnull
@Override
public DataType.Named visitEnumDefinition(@Nonnull RelationalParser.EnumDefinitionContext ctx) {
@@ -266,7 +325,6 @@ public ProceduralPlan visitCreateSchemaTemplateStatement(@Nonnull RelationalPars
}
structClauses.build().stream().map(this::visitStructDefinition).map(RecordLayerTable::getDatatype).forEach(metadataBuilder::addAuxiliaryType);
tableClauses.build().stream().map(this::visitTableDefinition).forEach(metadataBuilder::addTable);
- final var indexes = indexClauses.build().stream().map(this::visitIndexDefinition).collect(ImmutableList.toImmutableList());
// TODO: this is currently relying on the lexical order of the function to resolve function dependencies which
// is limited.
sqlInvokedFunctionClauses.build().forEach(functionClause -> {
@@ -277,6 +335,7 @@ public ProceduralPlan visitCreateSchemaTemplateStatement(@Nonnull RelationalPars
final var view = getViewMetadata(viewClause, metadataBuilder.build());
metadataBuilder.addView(view);
});
+ final var indexes = indexClauses.build().stream().map(clause -> Assert.castUnchecked(visit(clause), RecordLayerIndex.class)).collect(ImmutableList.toImmutableList());
for (final var index : indexes) {
final var table = metadataBuilder.extractTable(index.getTableName());
final var tableWithIndex = RecordLayerTable.Builder.from(table).addIndex(index).build();
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java
index 4273b248ac..74d751aef1 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java
@@ -242,10 +242,29 @@ public DataType.Named visitEnumDefinition(@Nonnull RelationalParser.EnumDefiniti
return getDelegate().visitEnumDefinition(ctx);
}
- @Nonnull
@Override
- public RecordLayerIndex visitIndexDefinition(@Nonnull RelationalParser.IndexDefinitionContext ctx) {
- return getDelegate().visitIndexDefinition(ctx);
+ public Object visitIndexType(final RelationalParser.IndexTypeContext ctx) {
+ return getDelegate().visitIndexType(ctx);
+ }
+
+ @Override
+ public Object visitIndexOptions(final RelationalParser.IndexOptionsContext ctx) {
+ return getDelegate().visitIndexOptions(ctx);
+ }
+
+ @Override
+ public Object visitIndexOption(final RelationalParser.IndexOptionContext ctx) {
+ return getDelegate().visitIndexOption(ctx);
+ }
+
+ @Override
+ public Object visitVectorIndexOptions(final RelationalParser.VectorIndexOptionsContext ctx) {
+ return getDelegate().visitVectorIndexOptions(ctx);
+ }
+
+ @Override
+ public Object visitVectorIndexOption(final RelationalParser.VectorIndexOptionContext ctx) {
+ return getDelegate().visitVectorIndexOption(ctx);
}
@Nonnull
@@ -542,6 +561,41 @@ public OrderByExpression visitOrderByExpression(@Nonnull RelationalParser.OrderB
return getDelegate().visitOrderByExpression(ctx);
}
+ @Nonnull
+ @Override
+ public RecordLayerIndex visitIndexAsSelectDefinition(@Nonnull RelationalParser.IndexAsSelectDefinitionContext ctx) {
+ return getDelegate().visitIndexAsSelectDefinition(ctx);
+ }
+
+ @Nonnull
+ @Override
+ public RecordLayerIndex visitIndexOnSourceDefinition(@Nonnull RelationalParser.IndexOnSourceDefinitionContext ctx) {
+ return getDelegate().visitIndexOnSourceDefinition(ctx);
+ }
+
+ @Override
+ public Object visitVectorIndexDefinition(final RelationalParser.VectorIndexDefinitionContext ctx) {
+ return getDelegate().visitVectorIndexDefinition(ctx);
+ }
+
+ @Nonnull
+ @Override
+ public Object visitIndexColumnList(@Nonnull RelationalParser.IndexColumnListContext ctx) {
+ return getDelegate().visitIndexColumnList(ctx);
+ }
+
+ @Nonnull
+ @Override
+ public Object visitIndexColumnSpec(@Nonnull RelationalParser.IndexColumnSpecContext ctx) {
+ return getDelegate().visitIndexColumnSpec(ctx);
+ }
+
+ @Nonnull
+ @Override
+ public Object visitIncludeClause(@Nonnull RelationalParser.IncludeClauseContext ctx) {
+ return getDelegate().visitIncludeClause(ctx);
+ }
+
@Override
@Nullable
public Void visitTableSources(@Nonnull RelationalParser.TableSourcesContext ctx) {
@@ -1370,6 +1424,11 @@ public Object visitWindowName(@Nonnull RelationalParser.WindowNameContext ctx) {
return getDelegate().visitWindowName(ctx);
}
+ @Override
+ public Object visitPartitionClause(final RelationalParser.PartitionClauseContext ctx) {
+ return null;
+ }
+
@Nonnull
@Override
public Object visitScalarFunctionName(@Nonnull RelationalParser.ScalarFunctionNameContext ctx) {
@@ -1578,6 +1637,11 @@ public Object visitChildren(RuleNode node) {
return getDelegate().visitChildren(node);
}
+ @Override
+ public Object visitOrderClause(@Nonnull RelationalParser.OrderClauseContext ctx) {
+ return getDelegate().visitOrderClause(ctx);
+ }
+
@Override
public Object visitTerminal(TerminalNode node) {
return getDelegate().visitTerminal(node);
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java
index 32bc2e7ff7..0be9baadec 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java
@@ -199,8 +199,8 @@ public List visitOrderByClause(@Nonnull RelationalParser.Orde
@Override
public OrderByExpression visitOrderByExpression(@Nonnull RelationalParser.OrderByExpressionContext orderByExpressionContext) {
final var expression = Assert.castUnchecked(orderByExpressionContext.expression().accept(this), Expression.class);
- final var descending = ParseHelpers.isDescending(orderByExpressionContext);
- final var nullsLast = ParseHelpers.isNullsLast(orderByExpressionContext, descending);
+ final var descending = ParseHelpers.isDescending(orderByExpressionContext.orderClause());
+ final var nullsLast = ParseHelpers.isNullsLast(orderByExpressionContext.orderClause(), descending);
return OrderByExpression.of(expression, descending, nullsLast);
}
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java
index 0ff0587b33..dd5b75c288 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/QueryVisitor.java
@@ -595,8 +595,8 @@ public List visitOrderByClauseForSelect(@Nonnull RelationalPa
final var matchingExpressionMaybe = isAliasMaybe.flatMap(alias -> semanticAnalyzer.lookupAlias(Identifier.toProtobufCompliant(visitFullId(alias)), validSelectAliases));
matchingExpressionMaybe.ifPresentOrElse(
matchingExpression -> {
- final var descending = ParseHelpers.isDescending(orderByExpression);
- final var nullsLast = ParseHelpers.isNullsLast(orderByExpression, descending);
+ final var descending = ParseHelpers.isDescending(orderByExpression.orderClause());
+ final var nullsLast = ParseHelpers.isNullsLast(orderByExpression.orderClause(), descending);
orderBysBuilder.add(OrderByExpression.of(matchingExpression, descending, nullsLast));
},
() -> orderBysBuilder.add(visitOrderByExpression(orderByExpression))
diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java
index 92a0ca8db9..b31f15c611 100644
--- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java
+++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java
@@ -165,7 +165,23 @@ public interface TypedVisitor extends RelationalParserVisitor