chrevanthreddy commented on code in PR #20107:
URL: https://github.com/apache/hudi/pull/20107#discussion_r4384295539


##########
hudi-common/src/main/java/org/apache/hudi/common/index/vector/RaBitQEncoder.java:
##########
@@ -0,0 +1,588 @@
+/*
+ * 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.hudi.common.index.vector;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * RaBitQ (Randomized Binary Quantization) encoder.
+ *
+ * <p>Encodes float vectors into 1-bit-per-dimension binary codes using a 
random
+ * orthogonal rotation matrix R. The rotation is deterministic given (seed, 
dimension),
+ * so R is stored as a seed — it never needs retraining.
+ *
+ * <p><b>Encoding (write path):</b>
+ * <ol>
+ *   <li>Normalize: v̂ = v / ||v||</li>
+ *   <li>Rotate:    v_rot = R @ v̂</li>
+ *   <li>Binarize:  code = pack(sign(v_rot))  — D bits → ceil(D/8) bytes</li>
+ *   <li>Scalar:    s = ||v||  (stored alongside code; 1.0 if 
assume_normalized)</li>
+ * </ol>
+ *
+ * <p><b>Query scan (read path):</b>
+ * <ol>
+ *   <li>Normalize + rotate the query vector (same R)</li>
+ *   <li>Binarize to get q_bin</li>
+ *   <li>For each database code b: Hamming(q_bin, b) → estimated cosine</li>
+ *   <li>Re-rank top-R candidates with exact distance</li>
+ * </ol>
+ *
+ * <p>Memory: the rotation matrix is D×D floats = 4·D² bytes (~2.3 MB at 
D=768).
+ * It is built lazily on first use and reused across encode calls.
+ *
+ * <p>Thread-safe after the first call to {@link #encode} or {@link 
#encodeQuery}
+ * (the lazy init is synchronized).
+ */
+public final class RaBitQEncoder implements Serializable {
+  private static final long serialVersionUID = 1L;
+  private static final Map<RotationKey, float[][]> ROTATION_MATRIX_CACHE = new 
ConcurrentHashMap<>();
+  /** Neutral-factor thresholds for the current posting-block format (RFC-109 
§3). */
+  private static final RaBitQFactorConfig FACTOR_CONFIG = 
RaBitQFactorConfig.defaults();
+  private final int dimension;
+  private final int bits;
+  private final long seed;
+  private final boolean assumeNormalized;
+
+  /** Rotation matrix, row-major. Populated lazily. */
+  private transient volatile float[][] rotMat;
+  public RaBitQEncoder(int dimension, long seed, boolean assumeNormalized) {
+    this(dimension, 1, seed, assumeNormalized);
+  }
+
+  public RaBitQEncoder(int dimension, int bits, long seed, boolean 
assumeNormalized) {
+    if (dimension <= 0) {
+      throw new IllegalArgumentException("Dimension must be positive, got: " + 
dimension);
+    }
+    if (bits <= 0 || bits > 8) {
+      throw new IllegalArgumentException("RaBitQ bits must be in [1, 8], got: 
" + bits);
+    }
+    this.dimension = dimension;
+    this.bits = bits;
+    this.seed = seed;
+    this.assumeNormalized = assumeNormalized;
+  }
+
+  /** Convenience constructor using default seed. */
+  public RaBitQEncoder(int dimension) {
+    this(dimension, 1, 42L, false);
+  }
+
+  // ---- encoding ----------------------------------------------------------
+
+  public QuantizedVector encode(float[] vector) {
+    validateVector(vector, "vector");
+    float norm = norm(vector);
+    float scalar = assumeNormalized ? 1.0f : norm;
+    float[] normalized = (norm == 0f || assumeNormalized)
+        ? vector
+        : normalize(vector, norm);
+    float[] rotated = rotate(normalized);
+    return new QuantizedVector(binarize(rotated), scalar);
+  }
+
+  /**
+   * Metric-neutral residual encoding used by MDT postings.
+   *
+   * <p>The optional {@code center} is the IVF centroid in the <b>original</b> 
vector space.
+   * When null, the vector is encoded relative to the origin.
+   */
+  public QuantizedVector encodeResidual(float[] vector, float[] center) {

Review Comment:
   **Fixed** `03218b0f6f`. `fromNeutralFactors` names the only difference 
between bit widths (total rescale).



##########
hudi-common/src/main/java/org/apache/hudi/common/index/vector/VectorIndexMdtSearchUtils.java:
##########
@@ -0,0 +1,1310 @@
+/*
+ * 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.hudi.common.index.vector;
+
+import org.apache.hudi.avro.model.HoodieVectorIndexPostingBlock;
+import org.apache.hudi.common.data.HoodieData;
+import org.apache.hudi.common.data.HoodieListData;
+import org.apache.hudi.common.data.HoodiePairData;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.hudi.common.model.HoodieRecordGlobalLocation;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.common.util.collection.Pair;
+import org.apache.hudi.metadata.HoodieMetadataPayload;
+import org.apache.hudi.metadata.HoodieTableMetadata;
+import org.apache.hudi.metadata.RawKey;
+import org.apache.hudi.metadata.VectorIndexMetadataKey;
+import org.apache.hudi.metadata.VectorPostingPrefixRawKey;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+/**
+ * Shared helper for MDT-native vector posting lookup and approximate 
candidate reduction.
+ *
+ * <p>This provides the missing second-stage building block after coarse IVF 
cluster pruning:
+ * exact HFile lookups of cluster metadata, prefix scans over {@code 
P|gen|cluster|shard|},
+ * approximate RaBitQ scoring, and direct file-slice targeting from posting 
payload metadata.
+ */
+public final class VectorIndexMdtSearchUtils {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(VectorIndexMdtSearchUtils.class);
+  private static final int TOP_K_REDUCER_KEY = 0;
+  private static final Comparator<ScoredVectorPostingMatch> BEST_FIRST = 
Comparator
+      .comparingDouble(ScoredVectorPostingMatch::getApproxDistance)
+      .thenComparing(ScoredVectorPostingMatch::getRecordKey);
+
+  private VectorIndexMdtSearchUtils() {
+  }
+
+  public static Map<Integer, Integer> 
readClusterShardCounts(HoodieTableMetadata metadataTable,
+                                                             String 
indexPartition,
+                                                             int generationId,
+                                                             
Collection<Integer> clusterIds) {
+    if (clusterIds == null || clusterIds.isEmpty()) {
+      return Collections.emptyMap();
+    }
+
+    Map<Integer, Integer> shardCounts = new HashMap<>();
+    for (Integer clusterId : clusterIds) {
+      shardCounts.put(clusterId, 1);
+    }
+    return shardCounts;
+  }
+
+  public static Map<Integer, Set<String>> 
readClusterFileGroups(HoodieTableMetadata metadataTable,

Review Comment:
   **Fixed** `c8b06883b1`. `readClusterShardCounts` / `readClusterFileGroups` 
had no callers; removed.



##########
hudi-common/src/main/java/org/apache/hudi/common/index/vector/VectorIndexMdtSearchUtils.java:
##########
@@ -0,0 +1,1310 @@
+/*
+ * 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.hudi.common.index.vector;
+
+import org.apache.hudi.avro.model.HoodieVectorIndexPostingBlock;
+import org.apache.hudi.common.data.HoodieData;
+import org.apache.hudi.common.data.HoodieListData;
+import org.apache.hudi.common.data.HoodiePairData;
+import org.apache.hudi.common.model.HoodieRecord;
+import org.apache.hudi.common.model.HoodieRecordGlobalLocation;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.common.util.collection.Pair;
+import org.apache.hudi.metadata.HoodieMetadataPayload;
+import org.apache.hudi.metadata.HoodieTableMetadata;
+import org.apache.hudi.metadata.RawKey;
+import org.apache.hudi.metadata.VectorIndexMetadataKey;
+import org.apache.hudi.metadata.VectorPostingPrefixRawKey;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+/**
+ * Shared helper for MDT-native vector posting lookup and approximate 
candidate reduction.
+ *
+ * <p>This provides the missing second-stage building block after coarse IVF 
cluster pruning:
+ * exact HFile lookups of cluster metadata, prefix scans over {@code 
P|gen|cluster|shard|},
+ * approximate RaBitQ scoring, and direct file-slice targeting from posting 
payload metadata.
+ */
+public final class VectorIndexMdtSearchUtils {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(VectorIndexMdtSearchUtils.class);
+  private static final int TOP_K_REDUCER_KEY = 0;
+  private static final Comparator<ScoredVectorPostingMatch> BEST_FIRST = 
Comparator
+      .comparingDouble(ScoredVectorPostingMatch::getApproxDistance)
+      .thenComparing(ScoredVectorPostingMatch::getRecordKey);
+
+  private VectorIndexMdtSearchUtils() {
+  }
+
+  public static Map<Integer, Integer> 
readClusterShardCounts(HoodieTableMetadata metadataTable,
+                                                             String 
indexPartition,
+                                                             int generationId,
+                                                             
Collection<Integer> clusterIds) {
+    if (clusterIds == null || clusterIds.isEmpty()) {
+      return Collections.emptyMap();
+    }
+
+    Map<Integer, Integer> shardCounts = new HashMap<>();
+    for (Integer clusterId : clusterIds) {
+      shardCounts.put(clusterId, 1);
+    }
+    return shardCounts;
+  }
+
+  public static Map<Integer, Set<String>> 
readClusterFileGroups(HoodieTableMetadata metadataTable,
+                                                                 String 
indexPartition,
+                                                                 int 
generationId,
+                                                                 
Collection<Integer> clusterIds,
+                                                                 
Collection<String> partitionPaths,
+                                                                 boolean 
shouldLoadInMemory) {
+    if (clusterIds == null || clusterIds.isEmpty()) {
+      return Collections.emptyMap();
+    }
+
+    return Collections.emptyMap();
+  }
+
+  public static List<VectorPostingPrefixRawKey> buildPostingPrefixes(int 
generationId,
+                                                                     
Map<Integer, Integer> clusterShardCounts) {
+    if (clusterShardCounts == null || clusterShardCounts.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    List<Map.Entry<Integer, Integer>> clusters = new 
ArrayList<>(clusterShardCounts.entrySet());
+    clusters.sort(Map.Entry.comparingByKey());
+
+    List<VectorPostingPrefixRawKey> prefixes = new ArrayList<>();
+    for (Map.Entry<Integer, Integer> entry : clusters) {
+      int clusterId = entry.getKey();
+      int shardCount = Math.max(1, entry.getValue());
+      for (int shardId = 0; shardId < shardCount; shardId++) {
+        prefixes.add(new VectorPostingPrefixRawKey(generationId, clusterId, 
shardId));
+      }
+    }
+    return prefixes;
+  }
+
+  public static HoodieData<VectorPostingMatch> 
readPostingMatches(HoodieTableMetadata metadataTable,
+                                                            String 
indexPartition,
+                                                            int generationId,
+                                                            Map<Integer, 
Integer> clusterShardCounts,
+                                                            boolean 
shouldLoadInMemory) {
+    List<VectorPostingPrefixRawKey> postingPrefixes = 
buildPostingPrefixes(generationId, clusterShardCounts);
+    if (postingPrefixes.isEmpty()) {
+      return HoodieListData.eager(Collections.emptyList());
+    }
+
+    List<RawKey> rawKeys = new ArrayList<>(postingPrefixes);
+    HoodieData<VectorPostingMatch> matches = 
metadataTable.getRecordsByKeyPrefixes(HoodieListData.eager(rawKeys), 
indexPartition, shouldLoadInMemory)
+        .flatMap(record -> {
+          Option<Object> infoOpt = getVectorInfo(record);
+          if (!infoOpt.isPresent()) {
+            return Collections.<VectorPostingMatch>emptyIterator();
+          }
+
+          if (record.getData().isDeleted()) {
+            String deletedRecordKey = 
VectorIndexMetadataKey.postingRecordKey(record.getRecordKey());
+            return deletedRecordKey == null
+                ? Collections.<VectorPostingMatch>emptyIterator()
+                : 
Collections.singletonList(VectorPostingMatch.tombstone(deletedRecordKey, 
VectorPostingSerde.parsePostingKey(record.getRecordKey()))).iterator();
+          }
+
+          Object info = infoOpt.get();
+          int[] keyComponents = 
VectorPostingSerde.parsePostingKey(record.getRecordKey());
+          if (VectorPostingSerde.isPostingTombstoneInfo(info)) {
+            String deletedRecordKey = 
VectorIndexMetadataKey.postingRecordKey(record.getRecordKey());
+            return deletedRecordKey == null
+                ? Collections.<VectorPostingMatch>emptyIterator()
+                : 
Collections.singletonList(VectorPostingMatch.tombstone(deletedRecordKey, 
keyComponents)).iterator();
+          }
+          if (VectorPostingSerde.isPostingBlockInfo(info)) {
+            PostingBlockView view = new PostingBlockView(asPostingBlock(info));
+            List<VectorPostingMatch> blockMatches = new 
ArrayList<>(view.numVectors());
+            for (int vectorIndex = 0; vectorIndex < view.numVectors(); 
vectorIndex++) {
+              PostingBlockView.RowLocator rowLocator = 
view.rowLocator(vectorIndex);
+              blockMatches.add(new VectorPostingMatch(

Review Comment:
   **Fixed** `c8b06883b1`. `VectorPostingMatch.fromBlockRow`, used by both call 
sites.



##########
hudi-common/src/main/java/org/apache/hudi/common/index/vector/VectorIndexOptions.java:
##########
@@ -0,0 +1,321 @@
+/*
+ * 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.hudi.common.index.vector;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Options accepted by {@code CREATE INDEX ... USING VECTOR}.
+ *
+ * <p>The indexed column's Hudi {@code VECTOR(D[, elementType])} schema is 
authoritative for
+ * dimension and element type. Index options configure only the acceleration 
structure. DDL
+ * implementations must call {@link #validateAndNormalize(Map)} before 
persisting an index
+ * definition; individual parsing helpers are intentionally private so 
aggregate validation cannot
+ * be bypassed.
+ */
+public final class VectorIndexOptions {
+
+  public static final String METRIC = "vector.metric";
+  public static final String QUANTIZER = "vector.quantizer";
+  public static final String NUM_CLUSTERS = "vector.num_clusters";
+  public static final String MAX_ITER = "vector.max_iter";
+  public static final String RABITQ_BITS = "vector.rabitq.bits";
+  public static final String RABITQ_SEED = "vector.rabitq.seed";
+  public static final String RABITQ_ASSUME_NORMALIZED = 
"vector.rabitq.assume_normalized";
+  public static final String QUERY_NUM_PROBES = "vector.query.nprobes";
+  public static final String QUERY_REFINE_FACTOR = 
"vector.query.refine_factor";
+  public static final String QUERY_MODE = "vector.query.mode";
+  public static final String FRESHNESS_POLICY = "vector.freshness.policy";
+  public static final String STALE_LOCATOR_POLICY = 
"vector.stale.locator.policy";
+  public static final String FETCH_VERIFY_KEYS = "vector.fetch.verify.keys";
+
+  public static final VectorDistanceMetric DEFAULT_METRIC = 
VectorDistanceMetric.COSINE;
+  public static final VectorQuantizer DEFAULT_QUANTIZER = 
VectorQuantizer.IVF_RABITQ;
+  public static final int DEFAULT_NUM_CLUSTERS = 256;
+  public static final int DEFAULT_MAX_ITER = 20;
+  public static final int DEFAULT_RABITQ_BITS = 4;
+  public static final long DEFAULT_RABITQ_SEED = 42L;
+  public static final int DEFAULT_NUM_PROBES = 32;
+  public static final int DEFAULT_REFINE_FACTOR = 50;
+  public static final VectorQueryMode DEFAULT_QUERY_MODE = 
VectorQueryMode.EXACT_RERANK;
+  public static final VectorStalePolicy DEFAULT_EXACT_FRESHNESS_POLICY = 
VectorStalePolicy.FAIL;
+  public static final VectorStalePolicy DEFAULT_APPROXIMATE_FRESHNESS_POLICY = 
VectorStalePolicy.WARN;
+  public static final VectorStalePolicy DEFAULT_STALE_LOCATOR_POLICY = 
VectorStalePolicy.FALLBACK;
+  public static final boolean DEFAULT_FETCH_VERIFY_KEYS = true;
+
+  /** Parsed immutable view used by bootstrap and maintenance after aggregate 
validation. */
+  public static final class ResolvedOptions {
+    public final VectorDistanceMetric metric;
+    public final VectorQuantizer quantizer;
+    public final int numClusters;
+    public final int maxIterations;
+    public final int rabitqBits;
+    public final long rabitqSeed;
+    public final boolean assumeNormalized;
+
+    private ResolvedOptions(Map<String, String> options) {
+      this.metric = getMetric(options);
+      this.quantizer = getQuantizer(options);
+      this.numClusters = getNumClusters(options);
+      this.maxIterations = getMaxIter(options);
+      this.rabitqBits = getRaBitQBits(options);
+      this.rabitqSeed = getRaBitQSeed(options);
+      this.assumeNormalized = shouldAssumeNormalizedVectors(options);
+    }
+  }
+
+  /** Validates the complete map before exposing parsed writer-side settings. 
*/
+  public static ResolvedOptions resolve(Map<String, String> options) {
+    return new ResolvedOptions(validateAndNormalize(options));
+  }
+
+  private static final Set<String> SUPPORTED_OPTIONS = 
Collections.unmodifiableSet(
+      new HashSet<>(Arrays.asList(
+          METRIC,
+          QUANTIZER,
+          NUM_CLUSTERS,
+          MAX_ITER,
+          RABITQ_BITS,
+          RABITQ_SEED,
+          RABITQ_ASSUME_NORMALIZED,
+          QUERY_NUM_PROBES,
+          QUERY_REFINE_FACTOR,
+          QUERY_MODE,
+          FRESHNESS_POLICY,
+          STALE_LOCATOR_POLICY,
+          FETCH_VERIFY_KEYS)));
+
+  private VectorIndexOptions() {
+  }
+
+  /**
+   * Validates the complete option map and returns canonical values for 
persistence.
+   *
+   * <p>The returned map contains every supported option, including explicit 
defaults. Unknown,
+   * retired, misspelled, and invalid options are rejected instead of being 
silently ignored.
+   */
+  public static Map<String, String> validateAndNormalize(Map<String, String> 
options) {
+    Set<String> unknownOptions = new HashSet<>(options.keySet());
+    unknownOptions.removeAll(SUPPORTED_OPTIONS);
+    if (!unknownOptions.isEmpty()) {
+      throw new IllegalArgumentException("Unsupported vector index options: " 
+ unknownOptions);
+    }
+
+    VectorDistanceMetric metric = getMetric(options);
+    VectorQuantizer quantizer = getQuantizer(options);
+    int numClusters = getNumClusters(options);
+    int maxIter = getMaxIter(options);
+    int bits = getRaBitQBits(options);
+    long seed = getRaBitQSeed(options);
+    boolean assumeNormalized = shouldAssumeNormalizedVectors(options);
+    int numProbes = getNumProbes(options);
+    int refineFactor = getRefineFactor(options);
+    VectorQueryMode queryMode = getQueryMode(options);
+    VectorStalePolicy freshnessPolicy = getFreshnessPolicy(options);
+    VectorStalePolicy staleLocatorPolicy = getStaleLocatorPolicy(options);
+    boolean verifyFetchKeys = shouldVerifyFetchKeys(options);
+
+    if (numProbes > numClusters) {
+      throw new IllegalArgumentException(
+          "Option '" + QUERY_NUM_PROBES + "' must not exceed '" + NUM_CLUSTERS 
+ "': "
+              + numProbes + " > " + numClusters);
+    }
+
+    Map<String, String> normalized = new LinkedHashMap<>();
+    normalized.put(METRIC, metric.name().toLowerCase(Locale.ROOT));
+    normalized.put(QUANTIZER, quantizer.name());
+    normalized.put(NUM_CLUSTERS, String.valueOf(numClusters));
+    normalized.put(MAX_ITER, String.valueOf(maxIter));
+    normalized.put(RABITQ_BITS, String.valueOf(bits));
+    normalized.put(RABITQ_SEED, String.valueOf(seed));
+    normalized.put(RABITQ_ASSUME_NORMALIZED, String.valueOf(assumeNormalized));
+    normalized.put(QUERY_NUM_PROBES, String.valueOf(numProbes));
+    normalized.put(QUERY_REFINE_FACTOR, String.valueOf(refineFactor));
+    normalized.put(QUERY_MODE, queryMode.name().toLowerCase(Locale.ROOT));
+    normalized.put(FRESHNESS_POLICY, 
freshnessPolicy.name().toLowerCase(Locale.ROOT));
+    normalized.put(STALE_LOCATOR_POLICY, 
staleLocatorPolicy.name().toLowerCase(Locale.ROOT));
+    normalized.put(FETCH_VERIFY_KEYS, String.valueOf(verifyFetchKeys));
+    return Collections.unmodifiableMap(normalized);
+  }
+
+  public static VectorDistanceMetric getMetric(Map<String, String> options) {
+    String value = getOption(options, METRIC, DEFAULT_METRIC.name());
+    try {
+      return VectorDistanceMetric.fromString(value);
+    } catch (IllegalArgumentException e) {
+      throw unsupportedValue(METRIC, value, e);
+    }
+  }
+
+  private static VectorQuantizer getQuantizer(Map<String, String> options) {
+    String value = getOption(options, QUANTIZER, DEFAULT_QUANTIZER.name());
+    try {
+      return VectorQuantizer.fromString(value);
+    } catch (IllegalArgumentException e) {
+      throw unsupportedValue(QUANTIZER, value, e);
+    }
+  }
+
+  private static int getNumClusters(Map<String, String> options) {
+    return getPositiveInt(options, NUM_CLUSTERS, DEFAULT_NUM_CLUSTERS);
+  }
+
+  private static int getMaxIter(Map<String, String> options) {
+    return getPositiveInt(options, MAX_ITER, DEFAULT_MAX_ITER);
+  }
+
+  private static int getRaBitQBits(Map<String, String> options) {
+    int bits = getInt(options, RABITQ_BITS, DEFAULT_RABITQ_BITS);
+    if (bits < 1 || bits > 8) {
+      throw new IllegalArgumentException(
+          "Option '" + RABITQ_BITS + "' must be between 1 and 8: " + bits);
+    }
+    return bits;
+  }
+
+  private static long getRaBitQSeed(Map<String, String> options) {
+    String value = getOption(options, RABITQ_SEED, 
String.valueOf(DEFAULT_RABITQ_SEED));
+    try {
+      return Long.parseLong(value);
+    } catch (NumberFormatException e) {
+      throw invalidNumber(RABITQ_SEED, value, e);
+    }
+  }
+
+  private static boolean shouldAssumeNormalizedVectors(Map<String, String> 
options) {
+    return getBoolean(options, RABITQ_ASSUME_NORMALIZED, false);
+  }
+
+  public static int getNumProbes(Map<String, String> options) {
+    return getPositiveInt(options, QUERY_NUM_PROBES, DEFAULT_NUM_PROBES);
+  }
+
+  public static int getRefineFactor(Map<String, String> options) {
+    return getPositiveInt(options, QUERY_REFINE_FACTOR, DEFAULT_REFINE_FACTOR);
+  }
+
+  private static VectorQueryMode getQueryMode(Map<String, String> options) {
+    String value = getOption(options, QUERY_MODE, DEFAULT_QUERY_MODE.name());
+    try {
+      return VectorQueryMode.fromString(value);
+    } catch (IllegalArgumentException e) {
+      throw unsupportedValue(QUERY_MODE, value, e);
+    }
+  }
+
+  public static VectorStalePolicy getFreshnessPolicy(Map<String, String> 
options) {
+    VectorStalePolicy defaultPolicy = getQueryMode(options) == 
VectorQueryMode.APPROXIMATE
+        ? DEFAULT_APPROXIMATE_FRESHNESS_POLICY : 
DEFAULT_EXACT_FRESHNESS_POLICY;
+    return getStalePolicy(options, FRESHNESS_POLICY, defaultPolicy);
+  }
+
+  public static VectorStalePolicy getStaleLocatorPolicy(Map<String, String> 
options) {
+    return getStalePolicy(options, STALE_LOCATOR_POLICY, 
DEFAULT_STALE_LOCATOR_POLICY);
+  }
+
+  private static VectorStalePolicy getStalePolicy(
+      Map<String, String> options, String key, VectorStalePolicy 
defaultPolicy) {
+    String value = getOption(options, key, defaultPolicy.name());
+    try {
+      return VectorStalePolicy.fromString(value);
+    } catch (IllegalArgumentException e) {
+      throw unsupportedValue(key, value, e);
+    }
+  }
+
+  public static boolean isApproximateSearchMode(Map<String, String> options) {
+    return getQueryMode(options) == VectorQueryMode.APPROXIMATE;
+  }
+
+  /** The current posting format uses symmetric scoring. */
+  public static boolean isRaBitQAsymmetricScoring(Map<String, String> options) 
{
+    return false;

Review Comment:
   **Fixed** `3cde5283ea`. Replaced with constants / removed.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to