|
@@ -30,6 +30,8 @@ import org.apache.lucene.index.SegmentReadState;
|
|
|
import org.apache.lucene.index.SegmentWriteState;
|
|
|
import org.apache.lucene.index.VectorEncoding;
|
|
|
import org.apache.lucene.index.VectorSimilarityFunction;
|
|
|
+import org.apache.lucene.search.BooleanClause;
|
|
|
+import org.apache.lucene.search.BooleanQuery;
|
|
|
import org.apache.lucene.search.FieldExistsQuery;
|
|
|
import org.apache.lucene.search.Query;
|
|
|
import org.apache.lucene.search.join.BitSetProducer;
|
|
@@ -77,6 +79,7 @@ import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
|
|
|
import org.elasticsearch.search.lookup.Source;
|
|
|
import org.elasticsearch.search.vectors.DenseVectorQuery;
|
|
|
import org.elasticsearch.search.vectors.DiversifyingChildrenIVFKnnFloatVectorQuery;
|
|
|
+import org.elasticsearch.search.vectors.DiversifyingParentBlockQuery;
|
|
|
import org.elasticsearch.search.vectors.ESDiversifyingChildrenByteKnnVectorQuery;
|
|
|
import org.elasticsearch.search.vectors.ESDiversifyingChildrenFloatKnnVectorQuery;
|
|
|
import org.elasticsearch.search.vectors.ESKnnByteVectorQuery;
|
|
@@ -1391,6 +1394,18 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
public final int hashCode() {
|
|
|
return Objects.hash(type, doHashCode());
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Indicates whether the underlying vector search is performed using a flat (exhaustive) approach.
|
|
|
+ * <p>
|
|
|
+ * When {@code true}, it means the search does not use any approximate nearest neighbor (ANN)
|
|
|
+ * acceleration structures such as HNSW or IVF. Instead, it performs a brute-force comparison
|
|
|
+ * against all candidate vectors. This information can be used by higher-level components
|
|
|
+ * to decide whether additional acceleration or optimization is necessary.
|
|
|
+ *
|
|
|
+ * @return {@code true} if the vector search is flat (exhaustive), {@code false} if it uses ANN structures
|
|
|
+ */
|
|
|
+ abstract boolean isFlat();
|
|
|
}
|
|
|
|
|
|
abstract static class QuantizedIndexOptions extends DenseVectorIndexOptions {
|
|
@@ -1762,6 +1777,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return Objects.hash(confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ boolean isFlat() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public boolean updatableTo(DenseVectorIndexOptions update) {
|
|
|
return update.type.equals(this.type)
|
|
@@ -1810,6 +1830,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
public int doHashCode() {
|
|
|
return Objects.hash(type);
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ boolean isFlat() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public static class Int4HnswIndexOptions extends QuantizedIndexOptions {
|
|
@@ -1860,6 +1885,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return Objects.hash(m, efConstruction, confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ boolean isFlat() {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public String toString() {
|
|
|
return "{type="
|
|
@@ -1931,6 +1961,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return Objects.hash(confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ boolean isFlat() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public String toString() {
|
|
|
return "{type=" + type + ", confidence_interval=" + confidenceInterval + ", rescore_vector=" + rescoreVector + "}";
|
|
@@ -1999,6 +2034,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return Objects.hash(m, efConstruction, confidenceInterval, rescoreVector);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ boolean isFlat() {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public String toString() {
|
|
|
return "{type="
|
|
@@ -2088,6 +2128,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return Objects.hash(m, efConstruction);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ boolean isFlat() {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public String toString() {
|
|
|
return "{type=" + type + ", m=" + m + ", ef_construction=" + efConstruction + "}";
|
|
@@ -2126,6 +2171,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return Objects.hash(m, efConstruction, rescoreVector);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ boolean isFlat() {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
|
builder.startObject();
|
|
@@ -2179,6 +2229,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return CLASS_NAME_HASH;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ boolean isFlat() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
|
builder.startObject();
|
|
@@ -2237,6 +2292,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return Objects.hash(clusterSize, defaultNProbe, rescoreVector);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ boolean isFlat() {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
|
builder.startObject();
|
|
@@ -2485,9 +2545,21 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
KnnSearchStrategy searchStrategy
|
|
|
) {
|
|
|
elementType.checkDimensions(dims, queryVector.length);
|
|
|
- Query knnQuery = parentFilter != null
|
|
|
- ? new ESDiversifyingChildrenByteKnnVectorQuery(name(), queryVector, filter, k, numCands, parentFilter, searchStrategy)
|
|
|
- : new ESKnnByteVectorQuery(name(), queryVector, k, numCands, filter, searchStrategy);
|
|
|
+ Query knnQuery;
|
|
|
+ if (indexOptions != null && indexOptions.isFlat()) {
|
|
|
+ var exactKnnQuery = parentFilter != null
|
|
|
+ ? new DiversifyingParentBlockQuery(parentFilter, createExactKnnBitQuery(queryVector))
|
|
|
+ : createExactKnnBitQuery(queryVector);
|
|
|
+ knnQuery = filter == null
|
|
|
+ ? exactKnnQuery
|
|
|
+ : new BooleanQuery.Builder().add(exactKnnQuery, BooleanClause.Occur.SHOULD)
|
|
|
+ .add(filter, BooleanClause.Occur.FILTER)
|
|
|
+ .build();
|
|
|
+ } else {
|
|
|
+ knnQuery = parentFilter != null
|
|
|
+ ? new ESDiversifyingChildrenByteKnnVectorQuery(name(), queryVector, filter, k, numCands, parentFilter, searchStrategy)
|
|
|
+ : new ESKnnByteVectorQuery(name(), queryVector, k, numCands, filter, searchStrategy);
|
|
|
+ }
|
|
|
if (similarityThreshold != null) {
|
|
|
knnQuery = new VectorSimilarityQuery(
|
|
|
knnQuery,
|
|
@@ -2513,9 +2585,22 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
float squaredMagnitude = VectorUtil.dotProduct(queryVector, queryVector);
|
|
|
elementType.checkVectorMagnitude(similarity, ElementType.errorByteElementsAppender(queryVector), squaredMagnitude);
|
|
|
}
|
|
|
- Query knnQuery = parentFilter != null
|
|
|
- ? new ESDiversifyingChildrenByteKnnVectorQuery(name(), queryVector, filter, k, numCands, parentFilter, searchStrategy)
|
|
|
- : new ESKnnByteVectorQuery(name(), queryVector, k, numCands, filter, searchStrategy);
|
|
|
+
|
|
|
+ Query knnQuery;
|
|
|
+ if (indexOptions != null && indexOptions.isFlat()) {
|
|
|
+ var exactKnnQuery = parentFilter != null
|
|
|
+ ? new DiversifyingParentBlockQuery(parentFilter, createExactKnnByteQuery(queryVector))
|
|
|
+ : createExactKnnByteQuery(queryVector);
|
|
|
+ knnQuery = filter == null
|
|
|
+ ? exactKnnQuery
|
|
|
+ : new BooleanQuery.Builder().add(exactKnnQuery, BooleanClause.Occur.SHOULD)
|
|
|
+ .add(filter, BooleanClause.Occur.FILTER)
|
|
|
+ .build();
|
|
|
+ } else {
|
|
|
+ knnQuery = parentFilter != null
|
|
|
+ ? new ESDiversifyingChildrenByteKnnVectorQuery(name(), queryVector, filter, k, numCands, parentFilter, searchStrategy)
|
|
|
+ : new ESKnnByteVectorQuery(name(), queryVector, k, numCands, filter, searchStrategy);
|
|
|
+ }
|
|
|
if (similarityThreshold != null) {
|
|
|
knnQuery = new VectorSimilarityQuery(
|
|
|
knnQuery,
|
|
@@ -2568,7 +2653,16 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
numCands = Math.max(adjustedK, numCands);
|
|
|
}
|
|
|
Query knnQuery;
|
|
|
- if (indexOptions instanceof BBQIVFIndexOptions bbqIndexOptions) {
|
|
|
+ if (indexOptions != null && indexOptions.isFlat()) {
|
|
|
+ var exactKnnQuery = parentFilter != null
|
|
|
+ ? new DiversifyingParentBlockQuery(parentFilter, createExactKnnFloatQuery(queryVector))
|
|
|
+ : createExactKnnFloatQuery(queryVector);
|
|
|
+ knnQuery = filter == null
|
|
|
+ ? exactKnnQuery
|
|
|
+ : new BooleanQuery.Builder().add(exactKnnQuery, BooleanClause.Occur.SHOULD)
|
|
|
+ .add(filter, BooleanClause.Occur.FILTER)
|
|
|
+ .build();
|
|
|
+ } else if (indexOptions instanceof BBQIVFIndexOptions bbqIndexOptions) {
|
|
|
knnQuery = parentFilter != null
|
|
|
? new DiversifyingChildrenIVFKnnFloatVectorQuery(
|
|
|
name(),
|
|
@@ -2594,11 +2688,12 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
: new ESKnnFloatVectorQuery(name(), queryVector, adjustedK, numCands, filter, knnSearchStrategy);
|
|
|
}
|
|
|
if (rescore) {
|
|
|
- knnQuery = new RescoreKnnVectorQuery(
|
|
|
+ knnQuery = RescoreKnnVectorQuery.fromInnerQuery(
|
|
|
name(),
|
|
|
queryVector,
|
|
|
similarity.vectorSimilarityFunction(indexVersionCreated, ElementType.FLOAT),
|
|
|
k,
|
|
|
+ adjustedK,
|
|
|
knnQuery
|
|
|
);
|
|
|
}
|
|
@@ -2624,7 +2719,7 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return elementType;
|
|
|
}
|
|
|
|
|
|
- public IndexOptions getIndexOptions() {
|
|
|
+ public DenseVectorIndexOptions getIndexOptions() {
|
|
|
return indexOptions;
|
|
|
}
|
|
|
|