This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new df43423696 [core] Add composite BTree key storage and point lookups
(#10338)
df43423696 is described below
commit df434236960c30f727bad467682f9ad7ddd9c0e0
Author: Jingsong Lee <[email protected]>
AuthorDate: Fri Oct 2 18:57:21 2026 +0800
[core] Add composite BTree key storage and point lookups (#10338)
---
.../paimon/globalindex/CompositeKeySerializer.java | 117 +++++++++
.../globalindex/GlobalIndexKeyExtractor.java | 2 +-
.../paimon/globalindex/GlobalIndexReader.java | 6 +
.../apache/paimon/globalindex/GlobalIndexer.java | 11 +-
.../paimon/globalindex/GlobalIndexerFactory.java | 24 +-
.../globalindex/GlobalIndexerFactoryUtils.java | 7 +-
.../paimon/globalindex/SortedGlobalIndexer.java | 5 +-
.../bitmap/BitmapGlobalIndexerFactory.java | 14 +-
.../bitmap/MultiValueGlobalIndexerFactory.java | 9 +-
.../globalindex/btree/BTreeGlobalIndexer.java | 22 +-
.../btree/BTreeGlobalIndexerFactory.java | 15 +-
.../paimon/globalindex/btree/BTreeIndexWriter.java | 15 +-
.../globalindex/btree/LazyFilteredBTreeReader.java | 20 ++
.../fmindex/FMGlobalIndexerFactory.java | 9 +-
.../globalindex/SortedIndexFileMetaTest.java | 8 +-
.../globalindex/btree/AbstractIndexReaderTest.java | 5 +-
.../globalindex/btree/BTreeBloomFilterTest.java | 3 +-
.../btree/BTreeIndexReaderCloseTest.java | 5 +-
.../globalindex/btree/BTreeThreadSafetyTest.java | 3 +-
.../globalindex/btree/CompositeBTreeIndexTest.java | 287 +++++++++++++++++++++
.../btree/LazyFilteredBTreeIndexReaderTest.java | 19 +-
.../globalindex/fmindex/FMGlobalIndexTest.java | 2 +-
.../TestFullTextGlobalIndexerFactory.java | 23 +-
.../TestMultiFieldVectorGlobalIndexer.java | 4 +-
.../testvector/TestVectorGlobalIndexerFactory.java | 15 +-
.../DataEvolutionGlobalIndexScanner.java | 7 +-
.../globalindex/GlobalIndexBuilderUtils.java | 16 +-
.../paimon/globalindex/GlobalIndexQuery.java | 12 +-
.../sorted/SortedGlobalIndexWriter.java | 7 +-
.../index/pkfulltext/PkFullTextIndexFile.java | 8 +-
.../index/pksorted/PkSortedIndexBuilder.java | 4 +-
.../paimon/index/pksorted/PkSortedIndexFile.java | 5 +-
.../index/pkvector/PkVectorAnnSegmentFile.java | 4 +-
.../index/pkvector/PkVectorAnnSegmentSearcher.java | 6 +-
.../org/apache/paimon/schema/SchemaValidation.java | 8 +-
.../source/AbstractDataEvolutionVectorRead.java | 7 +-
.../table/source/DataEvolutionFullTextRead.java | 8 +-
.../table/source/PrimaryKeyFullTextRead.java | 4 +-
.../table/source/PrimaryKeySortedIndexScan.java | 2 +-
.../paimon/table/source/RawFullTextReadImpl.java | 3 +-
.../paimon/globalindex/GlobalIndexQueryTest.java | 17 +-
.../index/pksorted/PkSortedIndexBuilderTest.java | 4 +-
.../paimon/table/BitmapGlobalIndexTableTest.java | 5 +-
.../apache/paimon/table/IndexQuerySplitTest.java | 5 +-
.../table/source/FullTextSearchBuilderTest.java | 22 +-
.../table/source/VectorSearchBuilderTest.java | 20 +-
.../source/VectorSearchRowFilterExactnessTest.java | 12 +-
.../eslib/index/ESIndexGlobalIndexerFactory.java | 18 +-
.../flink/globalindex/GenericIndexTopoBuilder.java | 2 +-
.../flink/globalindex/SortedIndexTopoBuilder.java | 4 +-
.../procedure/CreateGlobalIndexProcedure.java | 9 +-
.../procedure/VectorSearchProcedureITCase.java | 5 +-
.../index/NativeFullTextGlobalIndexerFactory.java | 8 +-
.../index/JavaPyNativeFullTextE2ETest.java | 2 +-
.../NativeFullTextGlobalIndexerFactoryTest.java | 4 +-
.../index/NativeFullTextRowFilterTest.java | 4 +-
.../index/NativePrimaryKeyFullTextIndexTest.java | 6 +-
.../index/LuminaVectorGlobalIndexerFactory.java | 9 +-
.../paimon/lumina/index/JavaPyLuminaE2ETest.java | 9 +-
.../globalindex/DefaultGlobalIndexBuilder.java | 2 +-
.../globalindex/sorted/SortedIndexTopoBuilder.java | 3 +-
.../procedure/CreateGlobalIndexProcedure.java | 6 +-
.../spark/read/SparkDataEvolutionVectorRead.java | 5 +-
.../index/NativeVectorGlobalIndexerFactory.java | 8 +-
.../test/java/org/apache/paimon/JavaPyE2ETest.java | 4 +-
65 files changed, 722 insertions(+), 222 deletions(-)
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/CompositeKeySerializer.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/CompositeKeySerializer.java
new file mode 100644
index 0000000000..0a5f6a2944
--- /dev/null
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/CompositeKeySerializer.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.paimon.globalindex;
+
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.data.InternalRow;
+import org.apache.paimon.memory.MemorySlice;
+import org.apache.paimon.memory.MemorySliceInput;
+import org.apache.paimon.memory.MemorySliceOutput;
+import org.apache.paimon.types.RowType;
+
+import java.util.Comparator;
+
+/** Length-delimited tuple keys, compared by their typed components with nulls
first. */
+public class CompositeKeySerializer implements KeySerializer {
+
+ private final RowType rowType;
+
+ private final KeySerializer[] serializers;
+ private final InternalRow.FieldGetter[] getters;
+ private final Comparator<Object>[] comparators;
+
+ @SuppressWarnings("unchecked")
+ public CompositeKeySerializer(RowType type) {
+ this.rowType = type;
+ int count = type.getFieldCount();
+ serializers = new KeySerializer[count];
+ getters = new InternalRow.FieldGetter[count];
+ comparators = new Comparator[count];
+ for (int i = 0; i < count; i++) {
+ serializers[i] = KeySerializer.create(type.getTypeAt(i));
+ getters[i] = InternalRow.createFieldGetter(type.getTypeAt(i), i);
+ comparators[i] = serializers[i].createComparator();
+ }
+ }
+
+ public RowType rowType() {
+ return rowType;
+ }
+
+ @Override
+ public byte[] serialize(Object key) {
+ InternalRow row = (InternalRow) key;
+ if (row.getFieldCount() != serializers.length) {
+ throw new IllegalArgumentException(
+ "Expected "
+ + serializers.length
+ + " composite key fields, but got "
+ + row.getFieldCount());
+ }
+ MemorySliceOutput output = new MemorySliceOutput(32);
+ for (int i = 0; i < serializers.length; i++) {
+ Object value = getters[i].getFieldOrNull(row);
+ if (value == null) {
+ output.writeInt(-1);
+ } else {
+ byte[] bytes = serializers[i].serialize(value);
+ output.writeInt(bytes.length);
+ output.writeBytes(bytes);
+ }
+ }
+ return output.toSlice().copyBytes();
+ }
+
+ @Override
+ public Object deserialize(MemorySlice data) {
+ MemorySliceInput input = data.toInput();
+ GenericRow row = new GenericRow(serializers.length);
+ for (int i = 0; i < serializers.length; i++) {
+ int length = input.readInt();
+ if (length >= 0) {
+ row.setField(i,
serializers[i].deserialize(input.readSlice(length)));
+ } else if (length != -1) {
+ throw new IllegalArgumentException(
+ "Invalid composite key component length: " + length);
+ }
+ }
+ if (input.available() != 0) {
+ throw new IllegalArgumentException("Trailing bytes in composite
key");
+ }
+ return row;
+ }
+
+ @Override
+ public Comparator<Object> createComparator() {
+ return (left, right) -> {
+ for (int i = 0; i < serializers.length; i++) {
+ Object a = getters[i].getFieldOrNull((InternalRow) left);
+ Object b = getters[i].getFieldOrNull((InternalRow) right);
+ int comparison =
+ a == null
+ ? (b == null ? 0 : -1)
+ : b == null ? 1 : comparators[i].compare(a, b);
+ if (comparison != 0) {
+ return comparison;
+ }
+ }
+ return 0;
+ };
+ }
+}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexKeyExtractor.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexKeyExtractor.java
index 9a0881fa13..d2d036e20d 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexKeyExtractor.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexKeyExtractor.java
@@ -25,7 +25,7 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Serializable;
-/** Extracts zero or more normalized index keys from one source-column value.
*/
+/** Extracts zero or more normalized index keys from one source value or
projected tuple. */
public interface GlobalIndexKeyExtractor extends Serializable {
/** Type of the normalized keys emitted by {@link #extract(Object,
KeyConsumer)}. */
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexReader.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexReader.java
index f5d6ab9fad..4506713b91 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexReader.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexReader.java
@@ -36,6 +36,12 @@ import java.util.concurrent.CompletableFuture;
public interface GlobalIndexReader
extends
FunctionVisitor<CompletableFuture<Optional<GlobalIndexResult>>>, Closeable {
+ /** Point lookup of a full composite key, with literals in index column
order. */
+ default CompletableFuture<Optional<GlobalIndexResult>> visitCompositeEqual(
+ List<Object> literals) {
+ return CompletableFuture.completedFuture(Optional.empty());
+ }
+
@Override
default CompletableFuture<Optional<GlobalIndexResult>> visitIsNaN(FieldRef
fieldRef) {
return CompletableFuture.completedFuture(Optional.empty());
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexer.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexer.java
index 45a4724bb1..9792602393 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexer.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexer.java
@@ -49,14 +49,7 @@ public interface GlobalIndexer {
@Nullable List<Range> rowRanges,
ExecutorService executor);
- static GlobalIndexer create(String type, DataField indexField, Options
options) {
- GlobalIndexerFactory globalIndexerFactory =
GlobalIndexerFactoryUtils.load(type);
- return globalIndexerFactory.create(indexField, options);
- }
-
- static GlobalIndexer create(
- String type, DataField indexField, List<DataField> extraFields,
Options options) {
- GlobalIndexerFactory globalIndexerFactory =
GlobalIndexerFactoryUtils.load(type);
- return globalIndexerFactory.create(indexField, extraFields, options);
+ static GlobalIndexer create(String type, List<DataField> indexFields,
Options options) {
+ return GlobalIndexerFactoryUtils.load(type).create(indexFields,
options);
}
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexerFactory.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexerFactory.java
index 6170515e6d..2e2d9166e6 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexerFactory.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexerFactory.java
@@ -39,28 +39,10 @@ public interface GlobalIndexerFactory {
* match; the default keeps all files when no safe pruning is available.
*/
default List<GlobalIndexIOMeta> selectFiles(
- DataField indexField,
- List<DataField> extraFields,
- Predicate predicate,
- List<GlobalIndexIOMeta> files) {
+ List<DataField> indexFields, Predicate predicate,
List<GlobalIndexIOMeta> files) {
return files;
}
- GlobalIndexer create(DataField indexField, Options options);
-
- /**
- * Creates an indexer over a primary column plus optional extra columns.
{@code indexField} is
- * the primary column; {@code extraFields} holds the remaining columns and
is empty for a
- * single-column index.
- */
- default GlobalIndexer create(
- DataField indexField, List<DataField> extraFields, Options
options) {
- if (extraFields != null && !extraFields.isEmpty()) {
- throw new UnsupportedOperationException(
- String.format(
- "Index type '%s' does not support multi-column
index, got extra columns: %s",
- identifier(), extraFields));
- }
- return create(indexField, options);
- }
+ /** Creates an indexer over a non-empty list of columns in index order. */
+ GlobalIndexer create(List<DataField> indexFields, Options options);
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexerFactoryUtils.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexerFactoryUtils.java
index a793090d15..6e045ec79c 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexerFactoryUtils.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexerFactoryUtils.java
@@ -61,13 +61,10 @@ public class GlobalIndexerFactoryUtils {
/** Keep unrecognized index types for the reader to resolve at execution
time. */
public static List<GlobalIndexIOMeta> selectFiles(
String type,
- DataField indexField,
- List<DataField> extraFields,
+ List<DataField> indexFields,
Predicate predicate,
List<GlobalIndexIOMeta> files) {
GlobalIndexerFactory factory = factories.get(type);
- return factory == null
- ? files
- : factory.selectFiles(indexField, extraFields, predicate,
files);
+ return factory == null ? files : factory.selectFiles(indexFields,
predicate, files);
}
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedGlobalIndexer.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedGlobalIndexer.java
index 1f3bc4e603..f706060a09 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedGlobalIndexer.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedGlobalIndexer.java
@@ -21,6 +21,9 @@ package org.apache.paimon.globalindex;
/** A global indexer whose normalized keys must be sorted before they are
written. */
public interface SortedGlobalIndexer extends GlobalIndexer {
- /** Defines how source-column values are normalized into the keys consumed
by the writer. */
+ /**
+ * Defines how source values or projected tuples are normalized into the
keys consumed by the
+ * writer.
+ */
GlobalIndexKeyExtractor keyExtractor();
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/BitmapGlobalIndexerFactory.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/BitmapGlobalIndexerFactory.java
index e573850bb6..c7ab53bd3e 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/BitmapGlobalIndexerFactory.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/BitmapGlobalIndexerFactory.java
@@ -41,16 +41,18 @@ public class BitmapGlobalIndexerFactory implements
GlobalIndexerFactory {
@Override
public List<GlobalIndexIOMeta> selectFiles(
- DataField indexField,
- List<DataField> extraFields,
- Predicate predicate,
- List<GlobalIndexIOMeta> files) {
+ List<DataField> indexFields, Predicate predicate,
List<GlobalIndexIOMeta> files) {
return SortedFileMetaSelector.selectFiles(
- predicate, files, KeySerializer.create(indexField.type()));
+ predicate, files,
KeySerializer.create(indexFields.get(0).type()));
}
@Override
- public GlobalIndexer create(DataField dataField, Options options) {
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ if (indexFields.size() != 1) {
+ throw new UnsupportedOperationException(
+ "Index type '" + identifier() + "' requires exactly one
index field.");
+ }
+ DataField dataField = indexFields.get(0);
return new BitmapGlobalIndexer(dataField, options);
}
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/MultiValueGlobalIndexerFactory.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/MultiValueGlobalIndexerFactory.java
index dba47d9d3c..f3cde2e9c9 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/MultiValueGlobalIndexerFactory.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/MultiValueGlobalIndexerFactory.java
@@ -23,6 +23,8 @@ import org.apache.paimon.globalindex.GlobalIndexerFactory;
import org.apache.paimon.options.Options;
import org.apache.paimon.types.DataField;
+import java.util.List;
+
/** Factory for bitmap-backed multivalue indexes on array columns. */
public class MultiValueGlobalIndexerFactory implements GlobalIndexerFactory {
@@ -34,7 +36,12 @@ public class MultiValueGlobalIndexerFactory implements
GlobalIndexerFactory {
}
@Override
- public GlobalIndexer create(DataField indexField, Options options) {
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ if (indexFields.size() != 1) {
+ throw new UnsupportedOperationException(
+ "Index type '" + identifier() + "' requires exactly one
index field.");
+ }
+ DataField indexField = indexFields.get(0);
return new MultiValueGlobalIndexer(indexField, options);
}
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexer.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexer.java
index 774b22bf1f..451b292f4c 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexer.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexer.java
@@ -20,6 +20,7 @@ package org.apache.paimon.globalindex.btree;
import org.apache.paimon.compression.BlockCompressionFactory;
import org.apache.paimon.compression.CompressOptions;
+import org.apache.paimon.globalindex.CompositeKeySerializer;
import org.apache.paimon.globalindex.GlobalIndexIOMeta;
import org.apache.paimon.globalindex.GlobalIndexKeyExtractor;
import org.apache.paimon.globalindex.GlobalIndexReader;
@@ -31,6 +32,8 @@ import org.apache.paimon.globalindex.io.GlobalIndexFileWriter;
import org.apache.paimon.io.cache.CacheManager;
import org.apache.paimon.options.Options;
import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.types.RowType;
import org.apache.paimon.utils.BloomFilter;
import org.apache.paimon.utils.LazyField;
import org.apache.paimon.utils.Range;
@@ -41,6 +44,8 @@ import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
+import static org.apache.paimon.utils.Preconditions.checkArgument;
+
/**
* The {@link GlobalIndexer} for btree index. We do not build a B-tree
directly in memory, instead,
* we form a logical B-tree via multi-level metadata over SST files that store
the actual data, as
@@ -74,9 +79,20 @@ public class BTreeGlobalIndexer implements
SortedGlobalIndexer {
private final long fallbackScanMaxSize;
private final LazyField<CacheManager> cacheManager;
- public BTreeGlobalIndexer(DataField dataField, Options options) {
- this.keySerializer = KeySerializer.create(dataField.type());
- this.keyExtractor = GlobalIndexKeyExtractor.identity(dataField.type());
+ public BTreeGlobalIndexer(List<DataField> fields, Options options) {
+ checkArgument(!fields.isEmpty(), "BTree index requires at least one
field.");
+ for (DataField field : fields) {
+ if (field.type() instanceof RowType) {
+ throw new UnsupportedOperationException(
+ "BTree index columns must have scalar types: " +
field.name());
+ }
+ }
+ DataType keyType = fields.size() == 1 ? fields.get(0).type() : new
RowType(fields);
+ this.keySerializer =
+ fields.size() == 1
+ ? KeySerializer.create(keyType)
+ : new CompositeKeySerializer((RowType) keyType);
+ this.keyExtractor = GlobalIndexKeyExtractor.identity(keyType);
this.options = options;
this.fallbackScanMaxSize =
options.get(BTreeIndexOptions.BTREE_INDEX_FALLBACK_SCAN_MAX_SIZE).getBytes();
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexerFactory.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexerFactory.java
index 98d5c36241..d0b1140fba 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexerFactory.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexerFactory.java
@@ -41,16 +41,17 @@ public class BTreeGlobalIndexerFactory implements
GlobalIndexerFactory {
@Override
public List<GlobalIndexIOMeta> selectFiles(
- DataField indexField,
- List<DataField> extraFields,
- Predicate predicate,
- List<GlobalIndexIOMeta> files) {
+ List<DataField> indexFields, Predicate predicate,
List<GlobalIndexIOMeta> files) {
+ if (indexFields.size() > 1) {
+ // Scalar predicates cannot safely prune tuple metadata.
+ return files;
+ }
return SortedFileMetaSelector.selectFiles(
- predicate, files, KeySerializer.create(indexField.type()));
+ predicate, files,
KeySerializer.create(indexFields.get(0).type()));
}
@Override
- public GlobalIndexer create(DataField dataField, Options options) {
- return new BTreeGlobalIndexer(dataField, options);
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ return new BTreeGlobalIndexer(indexFields, options);
}
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeIndexWriter.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeIndexWriter.java
index fecf600f56..238e70df41 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeIndexWriter.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeIndexWriter.java
@@ -20,6 +20,7 @@ package org.apache.paimon.globalindex.btree;
import org.apache.paimon.compression.BlockCompressionFactory;
import org.apache.paimon.fs.PositionOutputStream;
+import org.apache.paimon.globalindex.CompositeKeySerializer;
import org.apache.paimon.globalindex.GlobalIndexSingleColumnWriter;
import org.apache.paimon.globalindex.KeySerializer;
import org.apache.paimon.globalindex.ResultEntry;
@@ -148,19 +149,27 @@ public class BTreeIndexWriter implements
GlobalIndexSingleColumnWriter, Closeabl
return;
}
- if (lastKey != null && comparator.compare(key, lastKey) != 0) {
+ boolean differentKey = lastKey == null || comparator.compare(key,
lastKey) != 0;
+ if (lastKey != null && differentKey) {
try {
flush();
} catch (IOException e) {
throw new RuntimeException("Error in writing btree index
files.", e);
}
}
- lastKey = key;
+ if (differentKey || !(keySerializer instanceof
CompositeKeySerializer)) {
+ // Sorted engine iterators can reuse the row backing a tuple key.
+ lastKey =
+ keySerializer instanceof CompositeKeySerializer
+ ? keySerializer.deserialize(
+
MemorySlice.wrap(keySerializer.serialize(key)))
+ : key;
+ }
currentRowIds.add(rowId);
// update stats
if (firstKey == null) {
- firstKey = key;
+ firstKey = lastKey;
}
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/LazyFilteredBTreeReader.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/LazyFilteredBTreeReader.java
index 03bbb8a68d..baadcc3c71 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/LazyFilteredBTreeReader.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/LazyFilteredBTreeReader.java
@@ -18,6 +18,8 @@
package org.apache.paimon.globalindex.btree;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.globalindex.CompositeKeySerializer;
import org.apache.paimon.globalindex.GlobalIndexIOMeta;
import org.apache.paimon.globalindex.GlobalIndexResult;
import org.apache.paimon.globalindex.KeySerializer;
@@ -37,6 +39,7 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -109,6 +112,23 @@ public class LazyFilteredBTreeReader extends
SortedFileGlobalIndexReader<BTreeIn
return remaining == 0 ? Pair.of(min, max) : null;
}
+ @Override
+ public CompletableFuture<Optional<GlobalIndexResult>> visitCompositeEqual(
+ List<Object> literals) {
+ if (!(keySerializer instanceof CompositeKeySerializer)) {
+ return CompletableFuture.completedFuture(Optional.empty());
+ }
+ int fieldCount = ((CompositeKeySerializer)
keySerializer).rowType().getFieldCount();
+ if (literals.size() != fieldCount) {
+ throw new IllegalArgumentException(
+ "Expected " + fieldCount + " composite key fields, but got
" + literals.size());
+ }
+ if (literals.stream().anyMatch(Objects::isNull)) {
+ return
CompletableFuture.completedFuture(Optional.of(GlobalIndexResult.createEmpty()));
+ }
+ return visitEqual((FieldRef) null, GenericRow.of(literals.toArray()));
+ }
+
@Override
public CompletableFuture<Optional<GlobalIndexResult>> visitEqual(
FieldRef fieldRef, Object literal) {
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/fmindex/FMGlobalIndexerFactory.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/fmindex/FMGlobalIndexerFactory.java
index 4b5e37c056..3ac045fc3d 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/fmindex/FMGlobalIndexerFactory.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/fmindex/FMGlobalIndexerFactory.java
@@ -24,6 +24,8 @@ import org.apache.paimon.options.Options;
import org.apache.paimon.types.DataField;
import org.apache.paimon.types.DataTypeFamily;
+import java.util.List;
+
import static org.apache.paimon.utils.Preconditions.checkArgument;
/** Factory for the exact partitioned FM contains index. */
@@ -37,7 +39,12 @@ public class FMGlobalIndexerFactory implements
GlobalIndexerFactory {
}
@Override
- public GlobalIndexer create(DataField dataField, Options options) {
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ if (indexFields.size() != 1) {
+ throw new UnsupportedOperationException(
+ "Index type '" + identifier() + "' requires exactly one
index field.");
+ }
+ DataField dataField = indexFields.get(0);
checkArgument(
dataField.type().is(DataTypeFamily.CHARACTER_STRING),
"FM index requires a character string column, but field '%s'
is %s.",
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/SortedIndexFileMetaTest.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/SortedIndexFileMetaTest.java
index 537d672402..960b98bcf4 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/SortedIndexFileMetaTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/SortedIndexFileMetaTest.java
@@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@@ -157,8 +158,11 @@ class SortedIndexFileMetaTest {
};
GlobalIndexSingleColumnWriter indexWriter =
new BTreeGlobalIndexer(
- new DataField(
- 1, "testField", new
VarCharType(VarCharType.MAX_LENGTH)),
+ Collections.singletonList(
+ new DataField(
+ 1,
+ "testField",
+ new
VarCharType(VarCharType.MAX_LENGTH))),
new Options())
.createWriter(fileWriter);
for (int index = 0; index < keys.length; index++) {
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/AbstractIndexReaderTest.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/AbstractIndexReaderTest.java
index c00f26bff5..c46472305c 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/AbstractIndexReaderTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/AbstractIndexReaderTest.java
@@ -143,7 +143,10 @@ public abstract class AbstractIndexReaderTest {
new Path(new Path(tempPath.toUri()),
meta.filePath()));
options = new Options();
options.set(BTreeIndexOptions.BTREE_INDEX_CACHE_SIZE,
MemorySize.ofMebiBytes(8));
- globalIndexer = new BTreeGlobalIndexer(new DataField(1, "testField",
dataType), options);
+ globalIndexer =
+ new BTreeGlobalIndexer(
+ Collections.singletonList(new DataField(1,
"testField", dataType)),
+ options);
keySerializer = KeySerializer.create(dataType);
comparator = keySerializer.createComparator();
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeBloomFilterTest.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeBloomFilterTest.java
index 67f2985b58..1db4a2c222 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeBloomFilterTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeBloomFilterTest.java
@@ -115,7 +115,8 @@ class BTreeBloomFilterTest {
private Fixture writeIndex(Options options) throws IOException {
ByteArrayGlobalIndexFileWriter fileWriter = new
ByteArrayGlobalIndexFileWriter();
BTreeGlobalIndexer indexer =
- new BTreeGlobalIndexer(new DataField(0, "k", new IntType()),
options);
+ new BTreeGlobalIndexer(
+ Collections.singletonList(new DataField(0, "k", new
IntType())), options);
GlobalIndexSingleColumnWriter writer =
indexer.createWriter(fileWriter);
for (int i = 0; i < ENTRY_COUNT; i++) {
writer.write(i * 2, i);
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeIndexReaderCloseTest.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeIndexReaderCloseTest.java
index 90db80263a..11a389bd2f 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeIndexReaderCloseTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeIndexReaderCloseTest.java
@@ -43,6 +43,7 @@ import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.io.RandomAccessFile;
+import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@@ -82,7 +83,9 @@ public class BTreeIndexReaderCloseTest {
};
BTreeGlobalIndexer indexer =
- new BTreeGlobalIndexer(new DataField(1, "testField",
dataType), new Options());
+ new BTreeGlobalIndexer(
+ Collections.singletonList(new DataField(1,
"testField", dataType)),
+ new Options());
GlobalIndexSingleColumnWriter writer =
indexer.createWriter(fileWriter);
for (int i = 0; i < RECORD_NUM; i++) {
writer.write(i, (long) i);
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeThreadSafetyTest.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeThreadSafetyTest.java
index 83961dc636..9e6edfbd66 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeThreadSafetyTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/BTreeThreadSafetyTest.java
@@ -46,6 +46,7 @@ import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
@@ -108,7 +109,7 @@ public class BTreeThreadSafetyTest {
options.set(BTreeIndexOptions.BTREE_INDEX_CACHE_SIZE,
MemorySize.ofMebiBytes(8));
options.set(BTreeIndexOptions.BTREE_INDEX_BLOOM_FILTER_ENABLED, true);
DataField dataField = new DataField(1, "id", new IntType());
- globalIndexer = new BTreeGlobalIndexer(dataField, options);
+ globalIndexer = new
BTreeGlobalIndexer(Collections.singletonList(dataField), options);
keySerializer = KeySerializer.create(new IntType());
comparator = keySerializer.createComparator();
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/CompositeBTreeIndexTest.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/CompositeBTreeIndexTest.java
new file mode 100644
index 0000000000..7a75de7382
--- /dev/null
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/CompositeBTreeIndexTest.java
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.paimon.globalindex.btree;
+
+import org.apache.paimon.data.BinaryString;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.fs.PositionOutputStream;
+import org.apache.paimon.fs.local.LocalFileIO;
+import org.apache.paimon.globalindex.CompositeKeySerializer;
+import org.apache.paimon.globalindex.GlobalIndexIOMeta;
+import org.apache.paimon.globalindex.GlobalIndexReader;
+import org.apache.paimon.globalindex.GlobalIndexSingleColumnWriter;
+import org.apache.paimon.globalindex.GlobalIndexer;
+import org.apache.paimon.globalindex.KeySerializer;
+import org.apache.paimon.globalindex.ResultEntry;
+import org.apache.paimon.globalindex.SortedGlobalIndexer;
+import org.apache.paimon.globalindex.io.GlobalIndexFileWriter;
+import org.apache.paimon.memory.MemorySlice;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.predicate.PredicateBuilder;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.RowType;
+import org.apache.paimon.utils.Range;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+
+import static
org.apache.paimon.shade.guava30.com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests for typed composite BTree keys. */
+class CompositeBTreeIndexTest {
+
+ @TempDir java.nio.file.Path tempPath;
+
+ @ParameterizedTest
+ @ValueSource(ints = {1, 2})
+ void testMutableTupleKeysPostingListsAndLocalRanges(int version) throws
Exception {
+ RowType type =
+ new RowType(
+ Arrays.asList(
+ new DataField(10, "category",
DataTypes.STRING()),
+ new DataField(20, "item_number",
DataTypes.INT()),
+ new DataField(30, "tag", DataTypes.STRING())));
+ Options options = new Options();
+ options.set(BTreeIndexOptions.BTREE_INDEX_FILE_VERSION, version);
+ options.set(BTreeIndexOptions.BTREE_INDEX_BLOOM_FILTER_ENABLED, true);
+ options.set(BTreeIndexOptions.BTREE_INDEX_COMPRESSION, "lz4");
+ GlobalIndexer indexer = GlobalIndexer.create("btree",
type.getFields(), options);
+ LocalFileIO io = LocalFileIO.create();
+ Path directory = new Path(tempPath.toUri());
+ GlobalIndexFileWriter files =
+ new GlobalIndexFileWriter() {
+ @Override
+ public String newFileName(String prefix) {
+ return prefix + UUID.randomUUID();
+ }
+
+ @Override
+ public PositionOutputStream newOutputStream(String name)
+ throws java.io.IOException {
+ return io.newOutputStream(new Path(directory, name),
false);
+ }
+ };
+ GenericRow reused = row("category-a", -1, "");
+ GlobalIndexSingleColumnWriter writer =
+ (GlobalIndexSingleColumnWriter) indexer.createWriter(files);
+ writer.write(reused, 0);
+ reused.setField(1, 7);
+ writer.write(reused, 1);
+ reused.setField(2, BinaryString.fromString("tag"));
+ writer.write(reused, 2);
+ writer.write(reused, 3);
+ reused.setField(0, BinaryString.fromString("category-b"));
+ writer.write(reused, 4);
+ ResultEntry result = writer.finish().get(0);
+ Path path = new Path(directory, result.fileName());
+ GlobalIndexIOMeta meta =
+ new GlobalIndexIOMeta(path, io.getFileSize(path),
result.rowCount(), result.meta());
+ assertThat(
+ new BTreeGlobalIndexerFactory()
+ .selectFiles(
+ type.getFields(),
+ new PredicateBuilder(type)
+ .equal(0,
BinaryString.fromString("category-a")),
+ Collections.singletonList(meta)))
+ .containsExactly(meta);
+ ExecutorService executor = newDirectExecutorService();
+ try (GlobalIndexReader reader =
+ indexer.createReader(
+ file -> io.newInputStream(file.filePath()),
+ Collections.singletonList(meta),
+ 5,
+ null,
+ executor)) {
+ for (List<Object> invalid :
+ Arrays.asList(
+
Arrays.<Object>asList(BinaryString.fromString("category-a"), 7),
+ Arrays.<Object>asList(
+ BinaryString.fromString("category-a"),
+ 7,
+ BinaryString.fromString("tag"),
+ BinaryString.fromString("extra")),
+ Arrays.<Object>asList(null, 7,
BinaryString.fromString("tag"), null))) {
+ assertThatThrownBy(() -> reader.visitCompositeEqual(invalid))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Expected 3 composite key
fields");
+ }
+ assertThat(
+ reader.visitCompositeEqual(
+ Arrays.asList(null, 7,
BinaryString.fromString("tag")))
+ .get()
+ .get()
+ .results()
+ .toRangeList())
+ .isEmpty();
+ assertThat(
+ reader.visitCompositeEqual(
+ Arrays.asList(
+
BinaryString.fromString("category-a"),
+ -1,
+
BinaryString.fromString("")))
+ .get()
+ .get()
+ .results()
+ .toRangeList())
+ .containsExactly(new Range(0, 0));
+ assertThat(
+ reader.visitCompositeEqual(
+ Arrays.asList(
+
BinaryString.fromString("category-a"),
+ 7,
+
BinaryString.fromString("tag")))
+ .get()
+ .get()
+ .results()
+ .toRangeList())
+ .containsExactly(new Range(2, 3));
+ assertThat(
+ reader.visitCompositeEqual(
+ Arrays.asList(
+
BinaryString.fromString("category-a"),
+ 8,
+
BinaryString.fromString("tag")))
+ .get()
+ .get()
+ .results()
+ .isEmpty())
+ .isTrue();
+ }
+ try (GlobalIndexReader reader =
+ indexer.createReader(
+ file -> io.newInputStream(file.filePath()),
+ Collections.singletonList(meta),
+ 5,
+ Collections.singletonList(new Range(3, 3)),
+ executor)) {
+ assertThat(
+ reader.visitCompositeEqual(
+ Arrays.asList(
+
BinaryString.fromString("category-a"),
+ 7,
+
BinaryString.fromString("tag")))
+ .get()
+ .get()
+ .results()
+ .toRangeList())
+ .containsExactly(new Range(3, 3));
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ @Test
+ void testCompositeKeysPreserveTypesBoundariesAndNulls() {
+ RowType type =
+ new RowType(
+ Arrays.asList(
+ new DataField(10, "category",
DataTypes.STRING()),
+ new DataField(20, "item_number",
DataTypes.INT()),
+ new DataField(30, "tag", DataTypes.STRING())));
+ SortedGlobalIndexer indexer =
+ (SortedGlobalIndexer)
+ GlobalIndexer.create("btree", type.getFields(), new
Options());
+ KeySerializer serializer =
+ new CompositeKeySerializer((RowType)
indexer.keyExtractor().keyType());
+ Comparator<Object> comparator = serializer.createComparator();
+ for (GenericRow invalid :
+ Arrays.asList(
+ GenericRow.of(BinaryString.fromString("category-a"),
7),
+ GenericRow.of(
+ BinaryString.fromString("category-a"),
+ 7,
+ BinaryString.fromString("tag"),
+ BinaryString.fromString("extra")))) {
+ assertThatThrownBy(() -> serializer.serialize(invalid))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Expected 3 composite key fields");
+ }
+ GenericRow first = row("category-a", -1, "a\u0000b");
+ GenericRow second = row("category-a", 107, "");
+ GenericRow nullable = row(null, 107, null);
+ for (GenericRow key : Arrays.asList(first, second, nullable)) {
+ Object restored =
serializer.deserialize(MemorySlice.wrap(serializer.serialize(key)));
+ assertThat(restored).isEqualTo(key);
+ assertThat(comparator.compare(restored, key)).isZero();
+ }
+ assertThat(comparator.compare(first, second)).isNegative();
+ assertThat(comparator.compare(nullable, first)).isNegative();
+ assertThat(serializer.serialize(row("a", 1, "bc")))
+ .isNotEqualTo(serializer.serialize(row("ab", 1, "c")));
+ SortedGlobalIndexer scalarIndexer =
+ (SortedGlobalIndexer)
+ GlobalIndexer.create(
+ "btree",
+
Collections.singletonList(type.getFields().get(0)),
+ new Options());
+
assertThat(scalarIndexer.keyExtractor().keyType()).isEqualTo(DataTypes.STRING());
+ assertThat(indexer.keyExtractor().keyType()).isEqualTo(type);
+ }
+
+ @Test
+ void testFactoryRejectsUnsupportedFieldLists() {
+ assertThatThrownBy(
+ () -> GlobalIndexer.create("btree",
Collections.emptyList(), new Options()))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("at least one field");
+ List<DataField> fields =
+ Arrays.asList(
+ new DataField(10, "category", DataTypes.STRING()),
+ new DataField(20, "item_number", DataTypes.INT()));
+ for (String indexType : Arrays.asList("bitmap", "multivalue", "fm")) {
+ assertThatThrownBy(() -> GlobalIndexer.create(indexType, fields,
new Options()))
+ .isInstanceOf(UnsupportedOperationException.class)
+ .hasMessageContaining("exactly one index field");
+ }
+ }
+
+ @Test
+ void testCompositeSupportDoesNotEnablePhysicalRowIndexes() {
+ DataField field = new DataField(40, "nested",
RowType.of(DataTypes.INT()));
+ for (String indexType : Arrays.asList("btree", "bitmap")) {
+ assertThatThrownBy(
+ () ->
+ GlobalIndexer.create(
+ indexType,
+ Collections.singletonList(field),
+ new Options()))
+ .isInstanceOf(UnsupportedOperationException.class);
+ }
+ }
+
+ private GenericRow row(String category, int itemNumber, String tag) {
+ return GenericRow.of(
+ category == null ? null : BinaryString.fromString(category),
+ itemNumber,
+ tag == null ? null : BinaryString.fromString(tag));
+ }
+}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/LazyFilteredBTreeIndexReaderTest.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/LazyFilteredBTreeIndexReaderTest.java
index 83942e37b5..658d74cafb 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/LazyFilteredBTreeIndexReaderTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/btree/LazyFilteredBTreeIndexReaderTest.java
@@ -232,7 +232,10 @@ public class LazyFilteredBTreeIndexReaderTest extends
AbstractIndexReaderTest {
@TestTemplate
public void testFallbackScanDisabledByBudget() throws Exception {
options.set(BTreeIndexOptions.BTREE_INDEX_FALLBACK_SCAN_MAX_SIZE,
MemorySize.ofBytes(1));
- globalIndexer = new BTreeGlobalIndexer(new DataField(1, "testField",
dataType), options);
+ globalIndexer =
+ new BTreeGlobalIndexer(
+ Collections.singletonList(new DataField(1,
"testField", dataType)),
+ options);
List<GlobalIndexIOMeta> written = writeData();
FieldRef ref = new FieldRef(1, "testField", dataType);
@@ -264,7 +267,10 @@ public class LazyFilteredBTreeIndexReaderTest extends
AbstractIndexReaderTest {
options.set(
BTreeIndexOptions.BTREE_INDEX_FALLBACK_SCAN_MAX_SIZE,
MemorySize.ofBytes(written.get(1).fileSize()));
- globalIndexer = new BTreeGlobalIndexer(new DataField(1, "testField",
dataType), options);
+ globalIndexer =
+ new BTreeGlobalIndexer(
+ Collections.singletonList(new DataField(1,
"testField", dataType)),
+ options);
FieldRef ref = new FieldRef(1, "testField", dataType);
Object min = data.get(0).getKey();
@@ -336,7 +342,10 @@ public class LazyFilteredBTreeIndexReaderTest extends
AbstractIndexReaderTest {
@TestTemplate
public void testAllMatchRangeDoesNotConsumeScanBudget() throws Exception {
options.set(BTreeIndexOptions.BTREE_INDEX_FALLBACK_SCAN_MAX_SIZE,
MemorySize.ofBytes(1));
- globalIndexer = new BTreeGlobalIndexer(new DataField(1, "testField",
dataType), options);
+ globalIndexer =
+ new BTreeGlobalIndexer(
+ Collections.singletonList(new DataField(1,
"testField", dataType)),
+ options);
List<GlobalIndexIOMeta> written = writeData();
Object min = data.get(0).getKey();
Object max = data.get(dataNum - 1).getKey();
@@ -522,7 +531,9 @@ public class LazyFilteredBTreeIndexReaderTest extends
AbstractIndexReaderTest {
stressOptions.set(BTreeIndexOptions.BTREE_INDEX_CACHE_SIZE,
MemorySize.ofKibiBytes(64));
stressOptions.set(BTreeIndexOptions.BTREE_INDEX_HIGH_PRIORITY_POOL_RATIO, 0.1);
BTreeGlobalIndexer stressIndexer =
- new BTreeGlobalIndexer(new DataField(1, "testField",
dataType), stressOptions);
+ new BTreeGlobalIndexer(
+ Collections.singletonList(new DataField(1,
"testField", dataType)),
+ stressOptions);
// Inject null values at the tail to test isNull/isNotNull under
concurrency
for (int i = dataNum - 1; i >= dataNum * 0.9; i--) {
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/fmindex/FMGlobalIndexTest.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/fmindex/FMGlobalIndexTest.java
index 49eba6f1e0..8e7befeb4e 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/fmindex/FMGlobalIndexTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/fmindex/FMGlobalIndexTest.java
@@ -295,7 +295,7 @@ public class FMGlobalIndexTest {
try (GlobalIndexReader reader = createReader(Collections.emptyList(),
0)) {
assertRows(reader.visitContains(fieldRef, str("")).join());
}
- assertThat(GlobalIndexer.create("fm", dataField, new Options()))
+ assertThat(GlobalIndexer.create("fm",
Collections.singletonList(dataField), new Options()))
.isInstanceOf(FMGlobalIndexer.class);
List<GlobalIndexIOMeta> shifted =
writeData(Collections.singletonList(str("value")), 1);
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/testfulltext/TestFullTextGlobalIndexerFactory.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/testfulltext/TestFullTextGlobalIndexerFactory.java
index e8894affbe..c70267c785 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/testfulltext/TestFullTextGlobalIndexerFactory.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/testfulltext/TestFullTextGlobalIndexerFactory.java
@@ -45,22 +45,13 @@ public class TestFullTextGlobalIndexerFactory implements
GlobalIndexerFactory {
}
@Override
- public GlobalIndexer create(DataField field, Options options) {
- return new TestFullTextGlobalIndexer(field.type(), options);
- }
-
- @Override
- public GlobalIndexer create(
- DataField indexField, List<DataField> extraFields, Options
options) {
- // Multi-column support: this brute-force backend indexes a single
text column, which may be
- // the primary field or an extra field. Pick the VARCHAR/STRING column
among them.
- DataField textField = indexField;
- if (!(textField.type() instanceof VarCharType) && extraFields != null)
{
- for (DataField extra : extraFields) {
- if (extra.type() instanceof VarCharType) {
- textField = extra;
- break;
- }
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ // This brute-force backend indexes the first VARCHAR/STRING column.
+ DataField textField = indexFields.get(0);
+ for (DataField field : indexFields) {
+ if (field.type() instanceof VarCharType) {
+ textField = field;
+ break;
}
}
return new TestFullTextGlobalIndexer(textField.type(), options);
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/testvector/TestMultiFieldVectorGlobalIndexer.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/testvector/TestMultiFieldVectorGlobalIndexer.java
index ff18c4af1e..3edda5577a 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/testvector/TestMultiFieldVectorGlobalIndexer.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/testvector/TestMultiFieldVectorGlobalIndexer.java
@@ -42,6 +42,7 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -66,7 +67,8 @@ class TestMultiFieldVectorGlobalIndexer implements
VectorGlobalIndexer {
this.vectorField = vectorField;
this.scalarField = extraFields.get(0);
this.vectorIndexer = new TestVectorGlobalIndexer(vectorField.type(),
options);
- this.scalarIndexer = new BTreeGlobalIndexer(scalarField, options);
+ this.scalarIndexer =
+ new BTreeGlobalIndexer(Collections.singletonList(scalarField),
options);
}
@Override
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/testvector/TestVectorGlobalIndexerFactory.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/testvector/TestVectorGlobalIndexerFactory.java
index 04543f7919..e87a8eec32 100644
---
a/paimon-common/src/test/java/org/apache/paimon/globalindex/testvector/TestVectorGlobalIndexerFactory.java
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/testvector/TestVectorGlobalIndexerFactory.java
@@ -39,16 +39,11 @@ public class TestVectorGlobalIndexerFactory implements
GlobalIndexerFactory {
}
@Override
- public GlobalIndexer create(DataField field, Options options) {
- return new TestVectorGlobalIndexer(field.type(), options);
- }
-
- @Override
- public GlobalIndexer create(
- DataField indexField, List<DataField> extraFields, Options
options) {
- if (extraFields == null || extraFields.isEmpty()) {
- return create(indexField, options);
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ if (indexFields.size() == 1) {
+ return new TestVectorGlobalIndexer(indexFields.get(0).type(),
options);
}
- return new TestMultiFieldVectorGlobalIndexer(indexField, extraFields,
options);
+ return new TestMultiFieldVectorGlobalIndexer(
+ indexFields.get(0), indexFields.subList(1,
indexFields.size()), options);
}
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionGlobalIndexScanner.java
b/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionGlobalIndexScanner.java
index 5b000ad754..4e65b3be0e 100644
---
a/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionGlobalIndexScanner.java
+++
b/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionGlobalIndexScanner.java
@@ -419,16 +419,15 @@ public class DataEvolutionGlobalIndexScanner implements
Closeable {
private Collection<GlobalIndexReader> createReaders(
GlobalIndexFileReader indexFileReadWrite, IndexMetaFileGroup
group, RowType rowType) {
- DataField indexField = group.indexField(rowType);
- List<DataField> extraFields = group.extraFields(rowType);
+ List<DataField> indexFields =
+
group.fieldIds.stream().map(rowType::getField).collect(Collectors.toList());
Set<GlobalIndexReader> readers = new HashSet<>();
for (Map.Entry<String, Map<Range, List<IndexFileMeta>>> entry :
group.metas.entrySet()) {
String indexType = entry.getKey();
Map<Range, List<IndexFileMeta>> metas = entry.getValue();
GlobalIndexerFactory globalIndexerFactory =
GlobalIndexerFactoryUtils.load(indexType);
- GlobalIndexer globalIndexer =
- globalIndexerFactory.create(indexField, extraFields,
options);
+ GlobalIndexer globalIndexer =
globalIndexerFactory.create(indexFields, options);
List<CompletableFuture<GlobalIndexReader>> futures = new
ArrayList<>(metas.size());
for (Map.Entry<Range, List<IndexFileMeta>> rangeMetas :
metas.entrySet()) {
diff --git
a/paimon-core/src/main/java/org/apache/paimon/globalindex/GlobalIndexBuilderUtils.java
b/paimon-core/src/main/java/org/apache/paimon/globalindex/GlobalIndexBuilderUtils.java
index aea1efee5d..f3e6e6fdfb 100644
---
a/paimon-core/src/main/java/org/apache/paimon/globalindex/GlobalIndexBuilderUtils.java
+++
b/paimon-core/src/main/java/org/apache/paimon/globalindex/GlobalIndexBuilderUtils.java
@@ -607,21 +607,9 @@ public class GlobalIndexBuilderUtils {
}
public static GlobalIndexWriter createIndexWriter(
- FileStoreTable table, String indexType, DataField indexField,
Options options)
+ FileStoreTable table, String indexType, List<DataField>
indexFields, Options options)
throws IOException {
- GlobalIndexer globalIndexer = GlobalIndexer.create(indexType,
indexField, options);
- return
globalIndexer.createWriter(createGlobalIndexFileReadWrite(table));
- }
-
- public static GlobalIndexWriter createIndexWriter(
- FileStoreTable table,
- String indexType,
- DataField indexField,
- List<DataField> extraFields,
- Options options)
- throws IOException {
- GlobalIndexer globalIndexer =
- GlobalIndexer.create(indexType, indexField, extraFields,
options);
+ GlobalIndexer globalIndexer = GlobalIndexer.create(indexType,
indexFields, options);
return
globalIndexer.createWriter(createGlobalIndexFileReadWrite(table));
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/globalindex/GlobalIndexQuery.java
b/paimon-core/src/main/java/org/apache/paimon/globalindex/GlobalIndexQuery.java
index 7745f8ba21..7446ce5ed1 100644
---
a/paimon-core/src/main/java/org/apache/paimon/globalindex/GlobalIndexQuery.java
+++
b/paimon-core/src/main/java/org/apache/paimon/globalindex/GlobalIndexQuery.java
@@ -190,7 +190,7 @@ class GlobalIndexQuery {
for (IndexGroup group : fieldGroups) {
List<GlobalIndexIOMeta> selectedFiles =
GlobalIndexerFactoryUtils.selectFiles(
- group.type, group.field, group.extraFields,
predicate, group.files);
+ group.type, group.indexFields(), predicate,
group.files);
if (!selectedFiles.isEmpty()) {
selectedGroups.add(
selectedFiles == group.files
@@ -280,8 +280,7 @@ class GlobalIndexQuery {
continue;
}
GlobalIndexer indexer =
- GlobalIndexerFactoryUtils.load(group.type)
- .create(group.field, group.extraFields, options);
+
GlobalIndexerFactoryUtils.load(group.type).create(group.indexFields(), options);
try (GlobalIndexReader reader =
indexer.createReader(
meta -> fileIO.newInputStream(meta.filePath()),
@@ -478,6 +477,13 @@ class GlobalIndexQuery {
this.files = new ArrayList<>(files);
}
+ private List<DataField> indexFields() {
+ List<DataField> fields = new ArrayList<>(1 + extraFields.size());
+ fields.add(field);
+ fields.addAll(extraFields);
+ return fields;
+ }
+
private static List<IndexGroup> fromMetadata(
IndexMetaFileGroup group, RowType rowType, IndexPathFactory
pathFactory) {
DataField field = group.indexField(rowType);
diff --git
a/paimon-core/src/main/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexWriter.java
b/paimon-core/src/main/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexWriter.java
index b0bd408d87..52f3384010 100644
---
a/paimon-core/src/main/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexWriter.java
+++
b/paimon-core/src/main/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexWriter.java
@@ -86,7 +86,9 @@ public class SortedGlobalIndexWriter implements Serializable {
indexField,
table.fullName());
this.indexField = rowType.getField(indexField);
- GlobalIndexer indexer = GlobalIndexer.create(indexType,
this.indexField, options);
+ GlobalIndexer indexer =
+ GlobalIndexer.create(
+ indexType, Collections.singletonList(this.indexField),
options);
checkArgument(
indexer instanceof SortedGlobalIndexer,
"Index algorithm %s does not expose sorted index keys.",
@@ -131,7 +133,8 @@ public class SortedGlobalIndexWriter implements
Serializable {
}
public GlobalIndexSingleColumnWriter createWriter() throws IOException {
- GlobalIndexWriter indexWriter = createIndexWriter(table, indexType,
indexField, options);
+ GlobalIndexWriter indexWriter =
+ createIndexWriter(table, indexType,
Collections.singletonList(indexField), options);
if (!(indexWriter instanceof GlobalIndexSingleColumnWriter)) {
throw new RuntimeException(
"Unexpected implementation, the index writer of "
diff --git
a/paimon-core/src/main/java/org/apache/paimon/index/pkfulltext/PkFullTextIndexFile.java
b/paimon-core/src/main/java/org/apache/paimon/index/pkfulltext/PkFullTextIndexFile.java
index 1b2f22fddd..4a6e02e062 100644
---
a/paimon-core/src/main/java/org/apache/paimon/index/pkfulltext/PkFullTextIndexFile.java
+++
b/paimon-core/src/main/java/org/apache/paimon/index/pkfulltext/PkFullTextIndexFile.java
@@ -63,7 +63,9 @@ public class PkFullTextIndexFile extends IndexFile {
throws IOException {
GlobalIndexer indexer;
try {
- indexer = GlobalIndexer.create(INDEX_TYPE, textField,
indexOptions);
+ indexer =
+ GlobalIndexer.create(
+ INDEX_TYPE, Collections.singletonList(textField),
indexOptions);
} catch (RuntimeException | Error failure) {
IOUtils.closeQuietly(textReader);
throw failure;
@@ -76,7 +78,9 @@ public class PkFullTextIndexFile extends IndexFile {
throws IOException {
GlobalIndexer indexer;
try {
- indexer = GlobalIndexer.create(INDEX_TYPE, textField,
indexOptions);
+ indexer =
+ GlobalIndexer.create(
+ INDEX_TYPE, Collections.singletonList(textField),
indexOptions);
} catch (RuntimeException | Error failure) {
closeSources(sources);
throw failure;
diff --git
a/paimon-core/src/main/java/org/apache/paimon/index/pksorted/PkSortedIndexBuilder.java
b/paimon-core/src/main/java/org/apache/paimon/index/pksorted/PkSortedIndexBuilder.java
index b5412ad490..2b26739a48 100644
---
a/paimon-core/src/main/java/org/apache/paimon/index/pksorted/PkSortedIndexBuilder.java
+++
b/paimon-core/src/main/java/org/apache/paimon/index/pksorted/PkSortedIndexBuilder.java
@@ -42,6 +42,7 @@ import javax.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@@ -104,7 +105,8 @@ public class PkSortedIndexBuilder {
sourceFiles.add(
new PrimaryKeyIndexSourceFile(dataFile.fileName(),
dataFile.rowCount()));
}
- GlobalIndexer indexer = GlobalIndexer.create(indexType, indexField,
options);
+ GlobalIndexer indexer =
+ GlobalIndexer.create(indexType,
Collections.singletonList(indexField), options);
checkArgument(
indexer instanceof SortedGlobalIndexer,
"Index algorithm %s does not expose sorted index keys.",
diff --git
a/paimon-core/src/main/java/org/apache/paimon/index/pksorted/PkSortedIndexFile.java
b/paimon-core/src/main/java/org/apache/paimon/index/pksorted/PkSortedIndexFile.java
index fb05e43ba3..456f7878f4 100644
---
a/paimon-core/src/main/java/org/apache/paimon/index/pksorted/PkSortedIndexFile.java
+++
b/paimon-core/src/main/java/org/apache/paimon/index/pksorted/PkSortedIndexFile.java
@@ -39,6 +39,7 @@ import org.apache.paimon.utils.IOUtils;
import javax.annotation.Nullable;
import java.io.IOException;
+import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
@@ -130,7 +131,9 @@ public class PkSortedIndexFile extends IndexFile {
Options indexOptions,
GlobalIndexFileWriter fileWriter)
throws IOException {
- GlobalIndexer indexer = GlobalIndexer.create(indexType, indexField,
indexOptions);
+ GlobalIndexer indexer =
+ GlobalIndexer.create(
+ indexType, Collections.singletonList(indexField),
indexOptions);
GlobalIndexWriter writer = indexer.createWriter(fileWriter);
checkArgument(
writer instanceof GlobalIndexSingleColumnWriter,
diff --git
a/paimon-core/src/main/java/org/apache/paimon/index/pkvector/PkVectorAnnSegmentFile.java
b/paimon-core/src/main/java/org/apache/paimon/index/pkvector/PkVectorAnnSegmentFile.java
index e337db2802..93ac807aff 100644
---
a/paimon-core/src/main/java/org/apache/paimon/index/pkvector/PkVectorAnnSegmentFile.java
+++
b/paimon-core/src/main/java/org/apache/paimon/index/pkvector/PkVectorAnnSegmentFile.java
@@ -82,7 +82,9 @@ public class PkVectorAnnSegmentFile extends IndexFile {
}
checkArgument(totalRowCount > 0, "An ANN segment must reference at
least one source row.");
- GlobalIndexer indexer = GlobalIndexer.create(indexType, vectorField,
indexOptions);
+ GlobalIndexer indexer =
+ GlobalIndexer.create(
+ indexType, Collections.singletonList(vectorField),
indexOptions);
checkArgument(
indexer instanceof VectorGlobalIndexer,
"Index algorithm %s does not implement VectorGlobalIndexer.",
diff --git
a/paimon-core/src/main/java/org/apache/paimon/index/pkvector/PkVectorAnnSegmentSearcher.java
b/paimon-core/src/main/java/org/apache/paimon/index/pkvector/PkVectorAnnSegmentSearcher.java
index 30f5024554..14b8ab3334 100644
---
a/paimon-core/src/main/java/org/apache/paimon/index/pkvector/PkVectorAnnSegmentSearcher.java
+++
b/paimon-core/src/main/java/org/apache/paimon/index/pkvector/PkVectorAnnSegmentSearcher.java
@@ -181,7 +181,8 @@ public class PkVectorAnnSegmentSearcher {
return CompletableFuture.completedFuture(Collections.emptyList());
}
GlobalIndexer indexer =
- GlobalIndexer.create(segment.indexType(), vectorField,
indexOptions);
+ GlobalIndexer.create(
+ segment.indexType(),
Collections.singletonList(vectorField), indexOptions);
checkArgument(
indexer instanceof VectorGlobalIndexer,
"Index algorithm %s does not implement VectorGlobalIndexer.",
@@ -279,7 +280,8 @@ public class PkVectorAnnSegmentSearcher {
return
CompletableFuture.completedFuture(Collections.unmodifiableList(results));
}
GlobalIndexer indexer =
- GlobalIndexer.create(segment.indexType(), vectorField,
indexOptions);
+ GlobalIndexer.create(
+ segment.indexType(),
Collections.singletonList(vectorField), indexOptions);
checkArgument(
indexer instanceof VectorGlobalIndexer,
"Index algorithm %s does not implement VectorGlobalIndexer.",
diff --git
a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
index ed316a7223..2da251e5ea 100644
--- a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
+++ b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
@@ -1466,25 +1466,25 @@ public class SchemaValidation {
for (String column : options.primaryKeyBTreeIndexColumns()) {
GlobalIndexer.create(
BTreeGlobalIndexerFactory.IDENTIFIER,
- fields.get(column),
+ Collections.singletonList(fields.get(column)),
options.primaryKeyBTreeIndexOptions(column));
}
for (String column : options.primaryKeyBitmapIndexColumns()) {
GlobalIndexer.create(
BitmapGlobalIndexerFactory.IDENTIFIER,
- fields.get(column),
+ Collections.singletonList(fields.get(column)),
options.primaryKeyBitmapIndexOptions(column));
}
for (String column : options.primaryKeyMultiValueIndexColumns()) {
GlobalIndexer.create(
MultiValueGlobalIndexerFactory.IDENTIFIER,
- fields.get(column),
+ Collections.singletonList(fields.get(column)),
options.primaryKeyMultiValueIndexOptions(column));
}
for (String column : options.primaryKeyFMIndexColumns()) {
GlobalIndexer.create(
FMGlobalIndexerFactory.IDENTIFIER,
- fields.get(column),
+ Collections.singletonList(fields.get(column)),
options.primaryKeyFMIndexOptions(column));
}
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/AbstractDataEvolutionVectorRead.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/AbstractDataEvolutionVectorRead.java
index 9acef5d9ac..c638a400a3 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/AbstractDataEvolutionVectorRead.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/AbstractDataEvolutionVectorRead.java
@@ -130,7 +130,9 @@ public abstract class AbstractDataEvolutionVectorRead
implements Serializable {
protected GlobalIndexer createGlobalIndexer(String indexType) {
return GlobalIndexerFactoryUtils.load(indexType)
- .create(vectorColumn, table.coreOptions().toConfiguration());
+ .create(
+ Collections.singletonList(vectorColumn),
+ table.coreOptions().toConfiguration());
}
private GlobalIndexer createGlobalIndexer(String indexType,
GlobalIndexMeta meta) {
@@ -139,8 +141,7 @@ public abstract class AbstractDataEvolutionVectorRead
implements Serializable {
}
return GlobalIndexerFactoryUtils.load(indexType)
.create(
- meta.getIndexField(table.rowType()),
- meta.getExtraFields(table.rowType()),
+ meta.getIndexedFields(table.rowType()),
table.coreOptions().toConfiguration());
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/DataEvolutionFullTextRead.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/DataEvolutionFullTextRead.java
index 33cf8f6c35..05d16d8852 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/DataEvolutionFullTextRead.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/DataEvolutionFullTextRead.java
@@ -51,6 +51,7 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -386,12 +387,13 @@ public class DataEvolutionFullTextRead implements
FullTextRead {
if (meta.extraFieldIds() != null) {
return GlobalIndexerFactoryUtils.load(indexType)
.create(
- meta.getIndexField(table.rowType()),
- meta.getExtraFields(table.rowType()),
+ meta.getIndexedFields(table.rowType()),
table.coreOptions().toConfiguration());
}
return GlobalIndexerFactoryUtils.load(indexType)
- .create(textColumn, table.coreOptions().toConfiguration());
+ .create(
+ Collections.singletonList(textColumn),
+ table.coreOptions().toConfiguration());
}
private CompletableFuture<Optional<ScoredGlobalIndexResult>> eval(
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeyFullTextRead.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeyFullTextRead.java
index 18e7f05c24..80d85b5e89 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeyFullTextRead.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeyFullTextRead.java
@@ -176,7 +176,9 @@ public class PrimaryKeyFullTextRead implements FullTextRead
{
table.coreOptions().toConfiguration().get(GLOBAL_INDEX_THREAD_NUM));
this.indexer =
GlobalIndexer.create(
- PkFullTextIndexFile.INDEX_TYPE, textField,
definition.options());
+ PkFullTextIndexFile.INDEX_TYPE,
+ Collections.singletonList(textField),
+ definition.options());
this.archiveReader = meta ->
fileIO.newInputStream(meta.filePath());
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScan.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScan.java
index dd28bd49bf..31b35e0a5f 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScan.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/PrimaryKeySortedIndexScan.java
@@ -114,7 +114,7 @@ public final class PrimaryKeySortedIndexScan {
GlobalIndexer indexer =
GlobalIndexer.create(
definition.indexType(),
- rowType.getField(definition.fieldId()),
+
Collections.singletonList(rowType.getField(definition.fieldId())),
definition.options());
return indexer.createReader(fileReader, ioMetas, totalRowCount,
null, executor);
};
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/source/RawFullTextReadImpl.java
b/paimon-core/src/main/java/org/apache/paimon/table/source/RawFullTextReadImpl.java
index 44a7e63a36..58cb5a59e9 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/source/RawFullTextReadImpl.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/source/RawFullTextReadImpl.java
@@ -211,7 +211,8 @@ class RawFullTextReadImpl {
indexType = checkNotNull(fallbackIndexType);
}
GlobalIndexer globalIndexer =
- GlobalIndexerFactoryUtils.load(indexType).create(textColumn,
rawSearchOptions());
+ GlobalIndexerFactoryUtils.load(indexType)
+ .create(Collections.singletonList(textColumn),
rawSearchOptions());
try {
RawFullTextIndexFileWriter fileWriter = new
RawFullTextIndexFileWriter(column);
GlobalIndexWriter indexWriter =
globalIndexer.createWriter(fileWriter);
diff --git
a/paimon-core/src/test/java/org/apache/paimon/globalindex/GlobalIndexQueryTest.java
b/paimon-core/src/test/java/org/apache/paimon/globalindex/GlobalIndexQueryTest.java
index d2821933f7..7534931a31 100644
---
a/paimon-core/src/test/java/org/apache/paimon/globalindex/GlobalIndexQueryTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/globalindex/GlobalIndexQueryTest.java
@@ -39,7 +39,6 @@ import org.apache.paimon.predicate.PredicateBuilder;
import org.apache.paimon.stats.SimpleStats;
import org.apache.paimon.table.source.DataSplit;
import org.apache.paimon.table.source.SplitSerializer;
-import org.apache.paimon.types.DataField;
import org.apache.paimon.types.DataType;
import org.apache.paimon.types.DataTypes;
import org.apache.paimon.types.RowType;
@@ -171,8 +170,7 @@ class GlobalIndexQueryTest {
GlobalIndexer indexer = mock(GlobalIndexer.class);
when(indexer.createReader(any(), anyList(), eq(100L), anyList(),
any())).thenReturn(reader);
GlobalIndexerFactory factory = mock(GlobalIndexerFactory.class);
- when(factory.create(any(DataField.class), anyList(),
any(Options.class)))
- .thenReturn(indexer);
+ when(factory.create(anyList(),
any(Options.class))).thenReturn(indexer);
try (MockedStatic<GlobalIndexerFactoryUtils> factories =
mockStatic(GlobalIndexerFactoryUtils.class)) {
factories.when(() ->
GlobalIndexerFactoryUtils.load("btree")).thenReturn(factory);
@@ -224,7 +222,9 @@ class GlobalIndexQueryTest {
GlobalIndexSingleColumnWriter writer =
(GlobalIndexSingleColumnWriter)
GlobalIndexer.create(
- indexType,
rowType.getFields().get(0), new Options())
+ indexType,
+
Collections.singletonList(rowType.getFields().get(0)),
+ new Options())
.createWriter(io);
if (nulls) {
writer.write(null, 0);
@@ -361,8 +361,7 @@ class GlobalIndexQueryTest {
when(indexer.createReader(any(), anyList(), anyLong(), anyList(),
any()))
.thenReturn(reader);
GlobalIndexerFactory factory = mock(GlobalIndexerFactory.class);
- when(factory.create(any(DataField.class), anyList(),
any(Options.class)))
- .thenReturn(indexer);
+ when(factory.create(anyList(),
any(Options.class))).thenReturn(indexer);
try (MockedStatic<GlobalIndexerFactoryUtils> factories =
mockStatic(GlobalIndexerFactoryUtils.class)) {
factories.when(() ->
GlobalIndexerFactoryUtils.load(indexType)).thenReturn(factory);
@@ -432,8 +431,7 @@ class GlobalIndexQueryTest {
when(indexer.createReader(any(), anyList(), anyLong(), anyList(),
any()))
.thenReturn(reader);
GlobalIndexerFactory factory = mock(GlobalIndexerFactory.class);
- when(factory.create(any(DataField.class), anyList(),
any(Options.class)))
- .thenReturn(indexer);
+ when(factory.create(anyList(),
any(Options.class))).thenReturn(indexer);
RowType rowType = RowType.of(DataTypes.INT());
GlobalIndexQuery plan =
@@ -508,8 +506,7 @@ class GlobalIndexQueryTest {
GlobalIndexer indexer = mock(GlobalIndexer.class);
when(indexer.createReader(any(), anyList(), eq(100L), anyList(),
any())).thenReturn(reader);
GlobalIndexerFactory factory = mock(GlobalIndexerFactory.class);
- when(factory.create(any(DataField.class), anyList(),
any(Options.class)))
- .thenReturn(indexer);
+ when(factory.create(anyList(),
any(Options.class))).thenReturn(indexer);
FileIO fileIO = mock(FileIO.class);
List<Range> first = Collections.singletonList(new Range(110, 119));
List<Range> second = Collections.singletonList(new Range(130, 139));
diff --git
a/paimon-core/src/test/java/org/apache/paimon/index/pksorted/PkSortedIndexBuilderTest.java
b/paimon-core/src/test/java/org/apache/paimon/index/pksorted/PkSortedIndexBuilderTest.java
index 79d2155149..5af2f8b00f 100644
---
a/paimon-core/src/test/java/org/apache/paimon/index/pksorted/PkSortedIndexBuilderTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/index/pksorted/PkSortedIndexBuilderTest.java
@@ -142,7 +142,7 @@ class PkSortedIndexBuilderTest {
payload.globalIndexMeta().indexMeta()));
ExecutorService executor = newDirectExecutorService();
try (GlobalIndexReader reader =
- GlobalIndexer.create("multivalue", tags, options)
+ GlobalIndexer.create("multivalue",
Collections.singletonList(tags), options)
.createReader(
new GlobalIndexFileReadWrite(fileIO,
pathFactory),
ioMetas,
@@ -391,7 +391,7 @@ class PkSortedIndexBuilderTest {
payload.globalIndexMeta().indexMeta()));
ExecutorService executor = newDirectExecutorService();
try (GlobalIndexReader reader =
- GlobalIndexer.create(indexType, field(), options)
+ GlobalIndexer.create(indexType,
Collections.singletonList(field()), options)
.createReader(
new GlobalIndexFileReadWrite(fileIO,
pathFactory),
ioMetas,
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/BitmapGlobalIndexTableTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/BitmapGlobalIndexTableTest.java
index fb8f980dcc..a50eb2d037 100644
---
a/paimon-core/src/test/java/org/apache/paimon/table/BitmapGlobalIndexTableTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/table/BitmapGlobalIndexTableTest.java
@@ -214,7 +214,10 @@ public class BitmapGlobalIndexTableTest extends
DataEvolutionTestBase {
Range rowRange = rowRange(split);
GlobalIndexWriter indexWriter =
GlobalIndexBuilderUtils.createIndexWriter(
- table, INDEX_TYPE, indexField,
table.coreOptions().toConfiguration());
+ table,
+ INDEX_TYPE,
+ Collections.singletonList(indexField),
+ table.coreOptions().toConfiguration());
GlobalIndexSingleColumnWriter writer = (GlobalIndexSingleColumnWriter)
indexWriter;
InternalRow.FieldGetter fieldGetter =
InternalRow.createFieldGetter(
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/IndexQuerySplitTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/IndexQuerySplitTest.java
index 90e48f61aa..0dff419709 100644
--- a/paimon-core/src/test/java/org/apache/paimon/table/IndexQuerySplitTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/table/IndexQuerySplitTest.java
@@ -834,7 +834,10 @@ public class IndexQuerySplitTest extends
DataEvolutionTestBase {
indexOptions.set(FMGlobalIndexOptions.SA_SAMPLE_RATE, 1);
GlobalIndexSingleColumnWriter writer =
(GlobalIndexSingleColumnWriter)
- GlobalIndexer.create("fm",
table.rowType().getField("f1"), indexOptions)
+ GlobalIndexer.create(
+ "fm",
+
Collections.singletonList(table.rowType().getField("f1")),
+ indexOptions)
.createWriter(io);
for (int i = 0; i < 100; i++) {
writer.write(str("a" + i), i);
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/source/FullTextSearchBuilderTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/source/FullTextSearchBuilderTest.java
index 281f3ce32c..574d4d5888 100644
---
a/paimon-core/src/test/java/org/apache/paimon/table/source/FullTextSearchBuilderTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/table/source/FullTextSearchBuilderTest.java
@@ -1616,7 +1616,7 @@ public class FullTextSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestFullTextGlobalIndexerFactory.IDENTIFIER,
- textField,
+ Collections.singletonList(textField),
options);
for (int i = 0; i < documents.length; i++) {
writer.write(documents[i], i);
@@ -1657,7 +1657,7 @@ public class FullTextSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestFullTextGlobalIndexerFactory.IDENTIFIER,
- textField,
+ Collections.singletonList(textField),
options);
for (int i = 0; i < documents.length; i++) {
writer.write(documents[i], i);
@@ -1724,7 +1724,7 @@ public class FullTextSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestFullTextGlobalIndexerFactory.IDENTIFIER,
- textField,
+ Collections.singletonList(textField),
options);
// Doc ids are file-local (0-based); the global row offset is carried
by rowRange.from,
// which
@@ -1816,7 +1816,7 @@ public class FullTextSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestFullTextGlobalIndexerFactory.IDENTIFIER,
- textField,
+ Collections.singletonList(textField),
options);
for (int i = 0; i < documents.length; i++) {
writer.write(documents[i], i);
@@ -1856,7 +1856,10 @@ public class FullTextSearchBuilderTest extends
TableTestBase {
GlobalIndexSingleColumnWriter writer =
(GlobalIndexSingleColumnWriter)
GlobalIndexBuilderUtils.createIndexWriter(
- table, BTreeGlobalIndexerFactory.IDENTIFIER,
idField, options);
+ table,
+ BTreeGlobalIndexerFactory.IDENTIFIER,
+ Collections.singletonList(idField),
+ options);
for (long rowId = rowRange.from; rowId <= rowRange.to; rowId++) {
writer.write((int) rowId, rowId - rowRange.from);
}
@@ -1892,7 +1895,10 @@ public class FullTextSearchBuilderTest extends
TableTestBase {
GlobalIndexSingleColumnWriter writer =
(GlobalIndexSingleColumnWriter)
GlobalIndexBuilderUtils.createIndexWriter(
- table, BTreeGlobalIndexerFactory.IDENTIFIER,
textField, options);
+ table,
+ BTreeGlobalIndexerFactory.IDENTIFIER,
+ Collections.singletonList(textField),
+ options);
for (int i = 0; i < documents.length; i++) {
writer.write(BinaryString.fromString(documents[i]), i);
}
@@ -1944,7 +1950,7 @@ public class FullTextSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestFullTextGlobalIndexerFactory.IDENTIFIER,
- textField,
+ Collections.singletonList(textField),
options);
for (int i = 0; i < mid; i++) {
writer1.write(documents[i], i);
@@ -1967,7 +1973,7 @@ public class FullTextSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestFullTextGlobalIndexerFactory.IDENTIFIER,
- textField,
+ Collections.singletonList(textField),
options);
for (int i = mid; i < documents.length; i++) {
writer2.write(documents[i], i - mid);
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/source/VectorSearchBuilderTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/source/VectorSearchBuilderTest.java
index e9c972d978..13a1fe39a4 100644
---
a/paimon-core/src/test/java/org/apache/paimon/table/source/VectorSearchBuilderTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/table/source/VectorSearchBuilderTest.java
@@ -658,7 +658,7 @@ public class VectorSearchBuilderTest extends TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
MultiValueGlobalIndexerFactory.IDENTIFIER,
- tagsField,
+ Collections.singletonList(tagsField),
options);
for (int row = 0; row < rowCount; row++) {
writer.write(BinaryString.fromString("t" + row), row);
@@ -1906,7 +1906,7 @@ public class VectorSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestVectorGlobalIndexerFactory.IDENTIFIER,
- vectorField,
+ Collections.singletonList(vectorField),
options);
for (int i = 0; i < vectors.length; i++) {
writer.write(vectors[i], i);
@@ -1949,7 +1949,7 @@ public class VectorSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestVectorGlobalIndexerFactory.IDENTIFIER,
- vectorField,
+ Collections.singletonList(vectorField),
options);
for (int i = 0; i < mid; i++) {
writer1.write(vectors[i], i);
@@ -1972,7 +1972,7 @@ public class VectorSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestVectorGlobalIndexerFactory.IDENTIFIER,
- vectorField,
+ Collections.singletonList(vectorField),
options);
for (int i = mid; i < vectors.length; i++) {
writer2.write(vectors[i], i - mid);
@@ -2128,8 +2128,7 @@ public class VectorSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestVectorGlobalIndexerFactory.IDENTIFIER,
- vectorField,
- indexFields.subList(1, indexFields.size()),
+ indexFields,
options);
for (int i = 0; i < vectors.length; i++) {
writer.write(
@@ -2144,7 +2143,7 @@ public class VectorSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestVectorGlobalIndexerFactory.IDENTIFIER,
- vectorField,
+ Collections.singletonList(vectorField),
options);
for (int i = 0; i < vectors.length; i++) {
writer.write(vectors[i], i);
@@ -2184,7 +2183,10 @@ public class VectorSearchBuilderTest extends
TableTestBase {
GlobalIndexSingleColumnWriter writer =
(GlobalIndexSingleColumnWriter)
GlobalIndexBuilderUtils.createIndexWriter(
- table, BTreeGlobalIndexerFactory.IDENTIFIER,
idField, options);
+ table,
+ BTreeGlobalIndexerFactory.IDENTIFIER,
+ Collections.singletonList(idField),
+ options);
for (int id : ids) {
long relativeRowId = id - rowRange.from;
writer.write(id, relativeRowId);
@@ -2225,7 +2227,7 @@ public class VectorSearchBuilderTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestVectorGlobalIndexerFactory.IDENTIFIER,
- vectorField,
+ Collections.singletonList(vectorField),
options);
for (int i = 0; i < vectors.length; i++) {
writer.write(vectors[i], i);
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/source/VectorSearchRowFilterExactnessTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/source/VectorSearchRowFilterExactnessTest.java
index e203229c30..acd97870d8 100644
---
a/paimon-core/src/test/java/org/apache/paimon/table/source/VectorSearchRowFilterExactnessTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/table/source/VectorSearchRowFilterExactnessTest.java
@@ -467,7 +467,7 @@ public class VectorSearchRowFilterExactnessTest extends
TableTestBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestVectorGlobalIndexerFactory.IDENTIFIER,
- vectorField,
+ Collections.singletonList(vectorField),
options);
for (long rowId = rowRange.from; rowId <= rowRange.to; rowId++) {
writer.write(vectors[(int) rowId], rowId - rowRange.from);
@@ -491,7 +491,10 @@ public class VectorSearchRowFilterExactnessTest extends
TableTestBase {
GlobalIndexSingleColumnWriter writer =
(GlobalIndexSingleColumnWriter)
GlobalIndexBuilderUtils.createIndexWriter(
- table, BTreeGlobalIndexerFactory.IDENTIFIER,
nameField, options);
+ table,
+ BTreeGlobalIndexerFactory.IDENTIFIER,
+ Collections.singletonList(nameField),
+ options);
// The btree writer needs sorted keys.
Integer[] order = new Integer[names.length];
for (int i = 0; i < names.length; i++) {
@@ -519,7 +522,10 @@ public class VectorSearchRowFilterExactnessTest extends
TableTestBase {
GlobalIndexSingleColumnWriter writer =
(GlobalIndexSingleColumnWriter)
GlobalIndexBuilderUtils.createIndexWriter(
- table, BTreeGlobalIndexerFactory.IDENTIFIER,
idField, options);
+ table,
+ BTreeGlobalIndexerFactory.IDENTIFIER,
+ Collections.singletonList(idField),
+ options);
for (int i = 0; i < rowCount; i++) {
writer.write(i, i);
}
diff --git
a/paimon-eslib/src/main/java/org/apache/paimon/eslib/index/ESIndexGlobalIndexerFactory.java
b/paimon-eslib/src/main/java/org/apache/paimon/eslib/index/ESIndexGlobalIndexerFactory.java
index 5e520b7e14..98cc1d8dee 100644
---
a/paimon-eslib/src/main/java/org/apache/paimon/eslib/index/ESIndexGlobalIndexerFactory.java
+++
b/paimon-eslib/src/main/java/org/apache/paimon/eslib/index/ESIndexGlobalIndexerFactory.java
@@ -44,21 +44,7 @@ public class ESIndexGlobalIndexerFactory implements
GlobalIndexerFactory {
}
@Override
- public GlobalIndexer create(DataField field, Options options) {
- return new
ESIndexGlobalIndexer(java.util.Collections.singletonList(field), options);
- }
-
- @Override
- public GlobalIndexer create(
- DataField indexField, List<DataField> extraFields, Options
options) {
- List<DataField> fields;
- if (extraFields == null || extraFields.isEmpty()) {
- fields = java.util.Collections.singletonList(indexField);
- } else {
- fields = new java.util.ArrayList<>(extraFields.size() + 1);
- fields.add(indexField);
- fields.addAll(extraFields);
- }
- return new ESIndexGlobalIndexer(fields, options);
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ return new ESIndexGlobalIndexer(indexFields, options);
}
}
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/GenericIndexTopoBuilder.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/GenericIndexTopoBuilder.java
index bb14cefa43..55e005d0a2 100644
---
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/GenericIndexTopoBuilder.java
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/GenericIndexTopoBuilder.java
@@ -441,7 +441,7 @@ public class GenericIndexTopoBuilder {
long startTime = System.currentTimeMillis();
GlobalIndexWriter indexWriter =
- createIndexWriter(table, indexType, indexField,
extraFields, mergedOptions);
+ createIndexWriter(table, indexType, indexedFields,
mergedOptions);
try {
long rowsSeen = 0;
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilder.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilder.java
index 67a73da986..c4d86def52 100644
---
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilder.java
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilder.java
@@ -183,7 +183,9 @@ public class SortedIndexTopoBuilder {
String buildTaskIdField = buildTaskIdFieldName(dataReadType);
GlobalIndexer indexer =
GlobalIndexer.create(
- indexType, table.rowType().getField(indexColumn),
userOptions);
+ indexType,
+
Collections.singletonList(table.rowType().getField(indexColumn)),
+ userOptions);
if (!(indexer instanceof SortedGlobalIndexer)) {
throw new IllegalArgumentException(
"Index algorithm " + indexType + " does not expose
sorted index keys.");
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedure.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedure.java
index f93d19a0dc..c0a0ea90d5 100644
---
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedure.java
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedure.java
@@ -119,13 +119,10 @@ public class CreateGlobalIndexProcedure extends
ProcedureBase {
if (indexColumns.size() > 1) {
// Fail fast before submitting the job: index types that do not
support multi-column
// throw from GlobalIndexerFactory#create, which happens before
any indexer side effect.
- DataField indexField = rowType.getField(indexColumns.get(0));
- List<DataField> extraFields =
- indexColumns.subList(1, indexColumns.size()).stream()
- .map(rowType::getField)
- .collect(Collectors.toList());
+ List<DataField> indexFields =
+
indexColumns.stream().map(rowType::getField).collect(Collectors.toList());
try {
- GlobalIndexer.create(indexType, indexField, extraFields,
userOptions);
+ GlobalIndexer.create(indexType, indexFields, userOptions);
} catch (UnsupportedOperationException e) {
throw new IllegalArgumentException(
String.format(
diff --git
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/VectorSearchProcedureITCase.java
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/VectorSearchProcedureITCase.java
index 31324f2ec7..1cf45a2fa5 100644
---
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/VectorSearchProcedureITCase.java
+++
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/VectorSearchProcedureITCase.java
@@ -1057,7 +1057,7 @@ public class VectorSearchProcedureITCase extends
CatalogITCaseBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestVectorGlobalIndexerFactory.IDENTIFIER,
- vectorField,
+ Collections.singletonList(vectorField),
options);
for (int i = 0; i < vectors.length; i++) {
writer.write(vectors[i], i);
@@ -1098,8 +1098,7 @@ public class VectorSearchProcedureITCase extends
CatalogITCaseBase {
GlobalIndexBuilderUtils.createIndexWriter(
table,
TestVectorGlobalIndexerFactory.IDENTIFIER,
- vectorField,
- Collections.singletonList(idField),
+ Arrays.asList(vectorField, idField),
options);
for (int i = 0; i < vectors.length; i++) {
writer.write(i, GenericRow.of(new GenericArray(vectors[i]), (int)
(rowRange.from + i)));
diff --git
a/paimon-full-text/src/main/java/org/apache/paimon/fulltext/index/NativeFullTextGlobalIndexerFactory.java
b/paimon-full-text/src/main/java/org/apache/paimon/fulltext/index/NativeFullTextGlobalIndexerFactory.java
index 1f1aed8c45..7263257346 100644
---
a/paimon-full-text/src/main/java/org/apache/paimon/fulltext/index/NativeFullTextGlobalIndexerFactory.java
+++
b/paimon-full-text/src/main/java/org/apache/paimon/fulltext/index/NativeFullTextGlobalIndexerFactory.java
@@ -23,6 +23,8 @@ import org.apache.paimon.globalindex.GlobalIndexerFactory;
import org.apache.paimon.options.Options;
import org.apache.paimon.types.DataField;
+import java.util.List;
+
/** Factory for creating native full-text index. */
public class NativeFullTextGlobalIndexerFactory implements
GlobalIndexerFactory {
@@ -39,7 +41,11 @@ public class NativeFullTextGlobalIndexerFactory implements
GlobalIndexerFactory
}
@Override
- public GlobalIndexer create(DataField field, Options options) {
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ if (indexFields.size() != 1) {
+ throw new UnsupportedOperationException(
+ "Index type '" + identifier() + "' requires exactly one
index field.");
+ }
return new NativeFullTextGlobalIndexer(
new NativeFullTextIndexOptions(
options.removePrefix(NativeFullTextIndexOptions.FULL_TEXT_PREFIX).toMap()));
diff --git
a/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/JavaPyNativeFullTextE2ETest.java
b/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/JavaPyNativeFullTextE2ETest.java
index 7883f0a584..3ac0c4a3b5 100644
---
a/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/JavaPyNativeFullTextE2ETest.java
+++
b/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/JavaPyNativeFullTextE2ETest.java
@@ -190,7 +190,7 @@ public class JavaPyNativeFullTextE2ETest {
GlobalIndexBuilderUtils.createIndexWriter(
table,
NativeFullTextGlobalIndexerFactory.IDENTIFIER,
- contentField,
+ Collections.singletonList(contentField),
indexOptions);
// Write the same text data to the index.
diff --git
a/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativeFullTextGlobalIndexerFactoryTest.java
b/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativeFullTextGlobalIndexerFactoryTest.java
index b96c228c20..8d78bfe286 100644
---
a/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativeFullTextGlobalIndexerFactoryTest.java
+++
b/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativeFullTextGlobalIndexerFactoryTest.java
@@ -25,6 +25,8 @@ import org.apache.paimon.types.DataTypes;
import org.junit.jupiter.api.Test;
+import java.util.Collections;
+
import static org.assertj.core.api.Assertions.assertThat;
/** Tests for {@link NativeFullTextGlobalIndexerFactory}. */
@@ -37,7 +39,7 @@ public class NativeFullTextGlobalIndexerFactoryTest {
Options options = new Options();
options.set("full-text.searcher-pool.max-size", "0");
- GlobalIndexer indexer = factory.create(field, options);
+ GlobalIndexer indexer =
factory.create(Collections.singletonList(field), options);
assertThat(indexer).isInstanceOf(NativeFullTextGlobalIndexer.class);
}
diff --git
a/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativeFullTextRowFilterTest.java
b/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativeFullTextRowFilterTest.java
index e2898bdaee..a659b1860a 100644
---
a/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativeFullTextRowFilterTest.java
+++
b/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativeFullTextRowFilterTest.java
@@ -450,7 +450,7 @@ public class NativeFullTextRowFilterTest {
GlobalIndexBuilderUtils.createIndexWriter(
table,
NativeFullTextGlobalIndexerFactory.IDENTIFIER,
- contentField,
+ Collections.singletonList(contentField),
table.coreOptions().toConfiguration());
for (int i = 0; i < fullTextIndexedRows; i++) {
textWriter.write(BinaryString.fromString(contents.get(i)), i);
@@ -472,7 +472,7 @@ public class NativeFullTextRowFilterTest {
GlobalIndexBuilderUtils.createIndexWriter(
table,
BTreeGlobalIndexerFactory.IDENTIFIER,
- categoryField,
+ Collections.singletonList(categoryField),
table.coreOptions().toConfiguration());
// The btree writer is an SST writer: keys must arrive in sorted order.
List<Integer> rowIdsByCategory = new ArrayList<>(rowCount);
diff --git
a/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativePrimaryKeyFullTextIndexTest.java
b/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativePrimaryKeyFullTextIndexTest.java
index a8da5ddebb..db0c26b7cc 100644
---
a/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativePrimaryKeyFullTextIndexTest.java
+++
b/paimon-full-text/src/test/java/org/apache/paimon/fulltext/index/NativePrimaryKeyFullTextIndexTest.java
@@ -128,7 +128,8 @@ class NativePrimaryKeyFullTextIndexTest {
deletionVector.delete(2);
Map<String, DeletionVector> deletionVectors =
Collections.singletonMap(dataFile.fileName(), deletionVector);
- GlobalIndexer indexer = GlobalIndexer.create("full-text", TEXT_FIELD,
options);
+ GlobalIndexer indexer =
+ GlobalIndexer.create("full-text",
Collections.singletonList(TEXT_FIELD), options);
PrimaryKeyFullTextBucketSearch search =
new PrimaryKeyFullTextBucketSearch(
(payload, totalRowCount) ->
@@ -169,7 +170,8 @@ class NativePrimaryKeyFullTextIndexTest {
}
private IndexFileMeta buildArchive(List<BinaryString> texts, Options
options) throws Exception {
- GlobalIndexer indexer = GlobalIndexer.create("full-text", TEXT_FIELD,
options);
+ GlobalIndexer indexer =
+ GlobalIndexer.create("full-text",
Collections.singletonList(TEXT_FIELD), options);
GlobalIndexSingleColumnWriter writer =
(GlobalIndexSingleColumnWriter)
indexer.createWriter(fileWriter());
for (int i = 0; i < texts.size(); i++) {
diff --git
a/paimon-lumina/src/main/java/org/apache/paimon/lumina/index/LuminaVectorGlobalIndexerFactory.java
b/paimon-lumina/src/main/java/org/apache/paimon/lumina/index/LuminaVectorGlobalIndexerFactory.java
index 7d9c062feb..5adc143a1a 100644
---
a/paimon-lumina/src/main/java/org/apache/paimon/lumina/index/LuminaVectorGlobalIndexerFactory.java
+++
b/paimon-lumina/src/main/java/org/apache/paimon/lumina/index/LuminaVectorGlobalIndexerFactory.java
@@ -23,6 +23,8 @@ import org.apache.paimon.globalindex.GlobalIndexerFactory;
import org.apache.paimon.options.Options;
import org.apache.paimon.types.DataField;
+import java.util.List;
+
/** Factory for creating Lumina vector index. */
public class LuminaVectorGlobalIndexerFactory implements GlobalIndexerFactory {
@@ -34,7 +36,12 @@ public class LuminaVectorGlobalIndexerFactory implements
GlobalIndexerFactory {
}
@Override
- public GlobalIndexer create(DataField field, Options options) {
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ if (indexFields.size() != 1) {
+ throw new UnsupportedOperationException(
+ "Index type '" + identifier() + "' requires exactly one
index field.");
+ }
+ DataField field = indexFields.get(0);
Options fieldOptions =
LuminaVectorIndexOptions.resolveFieldOptions(field.name(), options);
return new LuminaVectorGlobalIndexer(field.type(), fieldOptions);
}
diff --git
a/paimon-lumina/src/test/java/org/apache/paimon/lumina/index/JavaPyLuminaE2ETest.java
b/paimon-lumina/src/test/java/org/apache/paimon/lumina/index/JavaPyLuminaE2ETest.java
index 3a311b5a58..1f14493b28 100644
---
a/paimon-lumina/src/test/java/org/apache/paimon/lumina/index/JavaPyLuminaE2ETest.java
+++
b/paimon-lumina/src/test/java/org/apache/paimon/lumina/index/JavaPyLuminaE2ETest.java
@@ -174,7 +174,7 @@ public class JavaPyLuminaE2ETest {
GlobalIndexBuilderUtils.createIndexWriter(
table,
LuminaVectorGlobalIndexerFactory.IDENTIFIER,
- embeddingField,
+ Collections.singletonList(embeddingField),
indexOptions);
for (int i = 0; i < vectors.length; i++) {
@@ -284,7 +284,7 @@ public class JavaPyLuminaE2ETest {
GlobalIndexBuilderUtils.createIndexWriter(
table,
LuminaVectorGlobalIndexerFactory.IDENTIFIER,
- embeddingField,
+ Collections.singletonList(embeddingField),
indexOptions);
for (int i = 0; i < vectors.length; i++) {
vectorWriter.write(vectors[i], i);
@@ -305,7 +305,10 @@ public class JavaPyLuminaE2ETest {
GlobalIndexSingleColumnWriter idWriter =
(GlobalIndexSingleColumnWriter)
GlobalIndexBuilderUtils.createIndexWriter(
- table, BTreeGlobalIndexerFactory.IDENTIFIER,
idField, indexOptions);
+ table,
+ BTreeGlobalIndexerFactory.IDENTIFIER,
+ Collections.singletonList(idField),
+ indexOptions);
for (int i = 0; i < vectors.length; i++) {
idWriter.write(i, i);
}
diff --git
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/DefaultGlobalIndexBuilder.java
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/DefaultGlobalIndexBuilder.java
index 5734cf84e3..a78ad1d7e1 100644
---
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/DefaultGlobalIndexBuilder.java
+++
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/DefaultGlobalIndexBuilder.java
@@ -168,7 +168,7 @@ public class DefaultGlobalIndexBuilder implements
Serializable {
private List<ResultEntry> writePaimonRows(
CloseableIterator<InternalRow> rows, LongCounter rowCounter)
throws IOException {
GlobalIndexWriter indexWriter =
- createIndexWriter(table, indexType, indexField, extraFields,
options);
+ createIndexWriter(table, indexType, indexedFields(), options);
boolean multiColumn = !extraFields.isEmpty();
try {
diff --git
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/sorted/SortedIndexTopoBuilder.java
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/sorted/SortedIndexTopoBuilder.java
index 7431d062f8..3b9a125eb1 100644
---
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/sorted/SortedIndexTopoBuilder.java
+++
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/sorted/SortedIndexTopoBuilder.java
@@ -131,7 +131,8 @@ public class SortedIndexTopoBuilder implements
GlobalIndexTopologyBuilder {
int maxParallelism =
options.get(SortedIndexOptions.SORTED_INDEX_BUILD_MAX_PARALLELISM);
List<CommitMessage> allMessages = new ArrayList<>();
- GlobalIndexer indexer = GlobalIndexer.create(indexType, indexField,
options);
+ GlobalIndexer indexer =
+ GlobalIndexer.create(indexType,
Collections.singletonList(indexField), options);
if (!(indexer instanceof SortedGlobalIndexer)) {
throw new IllegalArgumentException(
"Index algorithm " + indexType + " does not expose sorted
index keys.");
diff --git
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedure.java
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedure.java
index cff2fece60..ed349c6534 100644
---
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedure.java
+++
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedure.java
@@ -176,11 +176,7 @@ public class CreateGlobalIndexProcedure extends
BaseProcedure {
// multi-column throw from
GlobalIndexerFactory#create, which happens
// before any indexer side effect.
try {
- GlobalIndexer.create(
- indexType,
- indexFields.get(0),
- indexFields.subList(1,
indexFields.size()),
- userOptions);
+ GlobalIndexer.create(indexType, indexFields,
userOptions);
} catch (UnsupportedOperationException e) {
throw new IllegalArgumentException(
String.format(
diff --git
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/read/SparkDataEvolutionVectorRead.java
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/read/SparkDataEvolutionVectorRead.java
index 47e2a1114b..5b0896a50c 100644
---
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/read/SparkDataEvolutionVectorRead.java
+++
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/read/SparkDataEvolutionVectorRead.java
@@ -42,6 +42,7 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -126,7 +127,9 @@ public class SparkDataEvolutionVectorRead extends
DataEvolutionVectorRead {
group -> {
GlobalIndexer taskGlobalIndexer =
GlobalIndexerFactoryUtils.load(indexType)
- .create(vectorColumn,
table.coreOptions().toConfiguration());
+ .create(
+
Collections.singletonList(vectorColumn),
+
table.coreOptions().toConfiguration());
IndexPathFactory indexPathFactory =
table.store().pathFactory().globalIndexFileFactory();
diff --git
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactory.java
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactory.java
index ad11f4bf16..01b3a71a1c 100644
---
a/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactory.java
+++
b/paimon-vector/src/main/java/org/apache/paimon/vector/index/NativeVectorGlobalIndexerFactory.java
@@ -26,6 +26,7 @@ import org.apache.paimon.types.DataType;
import org.apache.paimon.types.VectorType;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
/** Factory for creating vector indexes backed by paimon-vector-index-java. */
@@ -36,7 +37,12 @@ public abstract class NativeVectorGlobalIndexerFactory
implements GlobalIndexerF
static final double DEFAULT_TRAIN_SAMPLE_RATIO = 1.0;
@Override
- public GlobalIndexer create(DataField field, Options options) {
+ public GlobalIndexer create(List<DataField> indexFields, Options options) {
+ if (indexFields.size() != 1) {
+ throw new UnsupportedOperationException(
+ "Index type '" + identifier() + "' requires exactly one
index field.");
+ }
+ DataField field = indexFields.get(0);
String identifier = identifier();
return new NativeVectorGlobalIndexer(
field.type(),
diff --git a/paimon-vector/src/test/java/org/apache/paimon/JavaPyE2ETest.java
b/paimon-vector/src/test/java/org/apache/paimon/JavaPyE2ETest.java
index 8b87bd2bb5..7975fc76fb 100644
--- a/paimon-vector/src/test/java/org/apache/paimon/JavaPyE2ETest.java
+++ b/paimon-vector/src/test/java/org/apache/paimon/JavaPyE2ETest.java
@@ -157,7 +157,7 @@ public class JavaPyE2ETest {
GlobalIndexBuilderUtils.createIndexWriter(
table,
IvfFlatVectorGlobalIndexerFactory.IDENTIFIER,
- embeddingField,
+ Collections.singletonList(embeddingField),
indexOptions);
for (int i = 0; i < vectors.length; i++) {
@@ -267,7 +267,7 @@ public class JavaPyE2ETest {
GlobalIndexBuilderUtils.createIndexWriter(
table,
IvfFlatVectorGlobalIndexerFactory.IDENTIFIER,
- embeddingField,
+ Collections.singletonList(embeddingField),
indexOptions);
for (int i = 0; i < indexedVectors.length; i++) {