From c58cd3b3af7028fae1b05b57125e361bb3cdaa73 Mon Sep 17 00:00:00 2001 From: Adam Quigley Date: Mon, 1 Jun 2026 14:25:09 -0400 Subject: [PATCH 1/8] SOLR-18267: Add flat vector index with no HNSW --- .../apache/solr/core/SchemaCodecFactory.java | 3 +- .../solr/core/SolrFlatVectorFormat.java | 65 +++++++++ .../BinaryQuantizedDenseVectorField.java | 13 ++ .../apache/solr/schema/DenseVectorField.java | 12 ++ .../ScalarQuantizedDenseVectorField.java | 6 + .../apache/solr/search/vector/KnnQParser.java | 8 + .../org.apache.lucene.codecs.KnnVectorsFormat | 16 ++ .../conf/bad-schema-densevector-flat-bq.xml | 29 ++++ .../conf/bad-schema-densevector-flat-sq.xml | 29 ++++ .../conf/schema-densevector-flat.xml | 31 ++++ .../BinaryQuantizedDenseVectorFieldTest.java | 8 + .../solr/schema/DenseVectorFieldTest.java | 137 ++++++++++++++++++ .../ScalarQuantizedDenseVectorFieldTest.java | 12 +- .../pages/dense-vector-search.adoc | 16 +- 14 files changed, 378 insertions(+), 7 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java create mode 100644 solr/core/src/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat create mode 100644 solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-bq.xml create mode 100644 solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-sq.xml create mode 100644 solr/core/src/test-files/solr/collection1/conf/schema-densevector-flat.xml diff --git a/solr/core/src/java/org/apache/solr/core/SchemaCodecFactory.java b/solr/core/src/java/org/apache/solr/core/SchemaCodecFactory.java index 1746c0ce3f63..4d4815fbfd73 100644 --- a/solr/core/src/java/org/apache/solr/core/SchemaCodecFactory.java +++ b/solr/core/src/java/org/apache/solr/core/SchemaCodecFactory.java @@ -128,7 +128,8 @@ public KnnVectorsFormat getKnnVectorsFormatForField(String field) { FieldType fieldType = (schemaField == null ? null : schemaField.getType()); if (fieldType instanceof DenseVectorField vectorField) { final String knnAlgorithm = vectorField.getKnnAlgorithm(); - if (!DenseVectorField.HNSW_ALGORITHM.equals(knnAlgorithm)) { + if (!DenseVectorField.HNSW_ALGORITHM.equals(knnAlgorithm) + && !DenseVectorField.FLAT_ALGORITHM.equals(knnAlgorithm)) { throw new SolrException( ErrorCode.SERVER_ERROR, knnAlgorithm + " KNN algorithm is not supported"); } diff --git a/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java b/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java new file mode 100644 index 000000000000..eac67794c973 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java @@ -0,0 +1,65 @@ +/* + * 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.solr.core; + +import java.io.IOException; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.KnnVectorsReader; +import org.apache.lucene.codecs.KnnVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatVectorScorerUtil; +import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.index.SegmentWriteState; + +/** + * Flat (non-HNSW) vector format for brute-force KNN search. + * + * @lucene.spi {@value #NAME} + * @since 10.1 + */ +public final class SolrFlatVectorFormat extends KnnVectorsFormat { + + static final String NAME = "SolrFlatVectorFormat"; + + private final Lucene99FlatVectorsFormat delegate; + + public SolrFlatVectorFormat() { + super(NAME); + this.delegate = + new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()); + } + + @Override + public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException { + return delegate.fieldsWriter(state); + } + + @Override + public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException { + return delegate.fieldsReader(state); + } + + @Override + public int getMaxDimensions(String fieldName) { + return delegate.getMaxDimensions(fieldName); + } + + @Override + public String toString() { + return NAME; + } +} diff --git a/solr/core/src/java/org/apache/solr/schema/BinaryQuantizedDenseVectorField.java b/solr/core/src/java/org/apache/solr/schema/BinaryQuantizedDenseVectorField.java index d8e15f16435e..28da4f678da9 100644 --- a/solr/core/src/java/org/apache/solr/schema/BinaryQuantizedDenseVectorField.java +++ b/solr/core/src/java/org/apache/solr/schema/BinaryQuantizedDenseVectorField.java @@ -16,12 +16,25 @@ */ package org.apache.solr.schema; +import java.util.Map; import org.apache.lucene.codecs.KnnVectorsFormat; import org.apache.lucene.codecs.lucene104.Lucene104HnswScalarQuantizedVectorsFormat; import org.apache.lucene.codecs.lucene104.Lucene104ScalarQuantizedVectorsFormat.ScalarEncoding; +import org.apache.solr.common.SolrException; public class BinaryQuantizedDenseVectorField extends DenseVectorField { + @Override + public void init(IndexSchema schema, Map args) { + super.init(schema, args); + + if (FLAT_ALGORITHM.equals(getKnnAlgorithm())) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "knnAlgorithm 'flat' is not supported for BinaryQuantizedDenseVectorField"); + } + } + @Override public KnnVectorsFormat buildKnnVectorsFormat() { return new Lucene104HnswScalarQuantizedVectorsFormat( diff --git a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java index f29714d5b1ee..55a1077fac3c 100644 --- a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java +++ b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java @@ -27,6 +27,7 @@ import java.util.Map; import org.apache.lucene.codecs.KnnVectorsFormat; import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat; +import org.apache.solr.core.SolrFlatVectorFormat; import org.apache.lucene.document.FieldType; import org.apache.lucene.document.KnnByteVectorField; import org.apache.lucene.document.KnnFloatVectorField; @@ -69,6 +70,7 @@ public class DenseVectorField extends FloatPointField { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); public static final String HNSW_ALGORITHM = "hnsw"; + public static final String FLAT_ALGORITHM = "flat"; public static final String CAGRA_HNSW_ALGORITHM = "cagra_hnsw"; public static final String DEFAULT_KNN_ALGORITHM = HNSW_ALGORITHM; static final String KNN_VECTOR_DIMENSION = "vectorDimension"; @@ -471,6 +473,9 @@ public DenseVectorParser getVectorBuilder( } public KnnVectorsFormat buildKnnVectorsFormat() { + if (FLAT_ALGORITHM.equals(knnAlgorithm)) { + return new SolrFlatVectorFormat(); + } return new Lucene99HnswVectorsFormat(hnswM, hnswEfConstruction); } @@ -503,6 +508,13 @@ public Query getKnnVectorQuery( EarlyTerminationParams earlyTermination, Integer filteredSearchThreshold) { + if (FLAT_ALGORITHM.equals(knnAlgorithm)) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "KNN vector queries are not supported for fields using knnAlgorithm=\"flat\". " + + "Use vectorSimilarity() function queries instead."); + } + DenseVectorParser vectorBuilder = getVectorBuilder(vectorToSearch, DenseVectorParser.BuilderPhase.QUERY); diff --git a/solr/core/src/java/org/apache/solr/schema/ScalarQuantizedDenseVectorField.java b/solr/core/src/java/org/apache/solr/schema/ScalarQuantizedDenseVectorField.java index 4d91d1eafe2b..26a33be48a24 100644 --- a/solr/core/src/java/org/apache/solr/schema/ScalarQuantizedDenseVectorField.java +++ b/solr/core/src/java/org/apache/solr/schema/ScalarQuantizedDenseVectorField.java @@ -122,6 +122,12 @@ public void init(IndexSchema schema, Map args) { } super.init(schema, args); + + if (FLAT_ALGORITHM.equals(getKnnAlgorithm())) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "knnAlgorithm 'flat' is not supported for ScalarQuantizedDenseVectorField"); + } } @Override diff --git a/solr/core/src/java/org/apache/solr/search/vector/KnnQParser.java b/solr/core/src/java/org/apache/solr/search/vector/KnnQParser.java index 2170f56f19b8..d376bfa5a40f 100644 --- a/solr/core/src/java/org/apache/solr/search/vector/KnnQParser.java +++ b/solr/core/src/java/org/apache/solr/search/vector/KnnQParser.java @@ -119,6 +119,14 @@ public Query parse() throws SyntaxError { final String vectorField = getFieldName(); final SchemaField schemaField = req.getCore().getLatestSchema().getField(getFieldName()); final DenseVectorField denseVectorType = getCheckedFieldType(schemaField); + + if (DenseVectorField.FLAT_ALGORITHM.equals(denseVectorType.getKnnAlgorithm())) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "The {!knn} query parser is not supported for fields using knnAlgorithm=\"flat\". " + + "Use vectorSimilarity() function queries instead."); + } + final String vectorToSearch = getVectorToSearch(); final int topK = localParams.getInt(TOP_K, DEFAULT_TOP_K); diff --git a/solr/core/src/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat b/solr/core/src/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat new file mode 100644 index 000000000000..a32f9ce26689 --- /dev/null +++ b/solr/core/src/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat @@ -0,0 +1,16 @@ +# 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. + +org.apache.solr.core.SolrFlatVectorFormat diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-bq.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-bq.xml new file mode 100644 index 000000000000..f4e6d392f9e4 --- /dev/null +++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-bq.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + id + \ No newline at end of file diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-sq.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-sq.xml new file mode 100644 index 000000000000..365bddd03e77 --- /dev/null +++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-sq.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + id + diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-densevector-flat.xml b/solr/core/src/test-files/solr/collection1/conf/schema-densevector-flat.xml new file mode 100644 index 000000000000..a1e66ee3aa27 --- /dev/null +++ b/solr/core/src/test-files/solr/collection1/conf/schema-densevector-flat.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + id + diff --git a/solr/core/src/test/org/apache/solr/schema/BinaryQuantizedDenseVectorFieldTest.java b/solr/core/src/test/org/apache/solr/schema/BinaryQuantizedDenseVectorFieldTest.java index de08d3f7ed22..74b20c175af2 100644 --- a/solr/core/src/test/org/apache/solr/schema/BinaryQuantizedDenseVectorFieldTest.java +++ b/solr/core/src/test/org/apache/solr/schema/BinaryQuantizedDenseVectorFieldTest.java @@ -38,6 +38,14 @@ public void fieldDefinition_correctConfiguration_shouldLoadSchemaField() throws } } + @Test + public void fieldDefinition_flatAlgorithm_shouldThrowException() throws Exception { + assertConfigs( + "solrconfig-basic.xml", + "bad-schema-densevector-flat-bq.xml", + "knnAlgorithm 'flat' is not supported for BinaryQuantizedDenseVectorField"); + } + // there are no major interface differences between BinaryBitQuantizedDenseVectorField and // DenseVectorField // so we can rely on those tests for validation cases diff --git a/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java b/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java index f84eaf40ef0b..6c388a09d447 100644 --- a/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java +++ b/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java @@ -1136,4 +1136,141 @@ public void testFilteredSearchThreshold_seededByteThresholdInInput_shouldSetCust deleteCore(); } } + + @Test + public void fieldDefinition_flatAlgorithm_shouldLoadSchemaField() throws Exception { + try { + initCore("solrconfig_codec.xml", "schema-densevector-flat.xml"); + IndexSchema schema = h.getCore().getLatestSchema(); + + SchemaField vector = schema.getField("vector_flat"); + assertNotNull(vector); + + DenseVectorField type = (DenseVectorField) vector.getType(); + assertThat(type.getKnnAlgorithm(), is("flat")); + assertThat(type.getDimension(), is(4)); + assertThat(type.getSimilarityFunction(), is(VectorSimilarityFunction.COSINE)); + + assertTrue(vector.indexed()); + assertTrue(vector.stored()); + } finally { + deleteCore(); + } + } + + @Test + public void flatAlgorithm_knnQuery_shouldThrowException() throws Exception { + try { + initCore("solrconfig_codec.xml", "schema-densevector-flat.xml"); + + assertQEx( + "Running {!knn} on a flat vector field should raise an Exception", + "knnAlgorithm=\"flat\"", + req("q", "{!knn f=vector_flat topK=2}[1, 2, 3, 4]", "fl", "id"), + SolrException.ErrorCode.BAD_REQUEST); + } finally { + deleteCore(); + } + } + + @Test + public void flatAlgorithm_vectorSimilarity_shouldReturnResults() throws Exception { + try { + initCore("solrconfig_codec.xml", "schema-densevector-flat.xml"); + + SolrInputDocument doc1 = new SolrInputDocument(); + doc1.addField("id", "0"); + doc1.addField("vector_flat", Arrays.asList(1, 2, 3, 4)); + assertU(adoc(doc1)); + + SolrInputDocument doc2 = new SolrInputDocument(); + doc2.addField("id", "1"); + doc2.addField("vector_flat", Arrays.asList(2, 3, 4, 5)); + assertU(adoc(doc2)); + + SolrInputDocument doc3 = new SolrInputDocument(); + doc3.addField("id", "2"); + doc3.addField("vector_flat", Arrays.asList(100, 200, 50, 25)); + assertU(adoc(doc3)); + + assertU(commit()); + + assertJQ( + req( + "q", "*:*", + "fl", "id,sim:vectorSimilarity(vector_flat,[1, 2, 3, 4])", + "sort", "vectorSimilarity(vector_flat,[1, 2, 3, 4]) desc"), + "/response/numFound==3", + "/response/docs/[0]/id=='0'"); + } finally { + deleteCore(); + } + } + + @Test + public void flatAlgorithm_storedField_shouldBeReturnedInResults() throws Exception { + try { + initCore("solrconfig_codec.xml", "schema-densevector-flat.xml"); + + SolrInputDocument doc1 = new SolrInputDocument(); + doc1.addField("id", "0"); + doc1.addField("vector_flat", Arrays.asList(1.1f, 2.2f, 3.3f, 4.4f)); + assertU(adoc(doc1)); + assertU(commit()); + + assertJQ( + req("q", "id:0", "fl", "vector_flat"), + "/response/docs/[0]=={'vector_flat':[1.1,2.2,3.3,4.4]}"); + } finally { + deleteCore(); + } + } + + @Test + public void flatAlgorithm_byteEncoding_shouldWork() throws Exception { + try { + initCore("solrconfig_codec.xml", "schema-densevector-flat.xml"); + + SolrInputDocument doc1 = new SolrInputDocument(); + doc1.addField("id", "0"); + doc1.addField("vector_flat_byte", Arrays.asList(1, 2, 3, 4)); + assertU(adoc(doc1)); + + SolrInputDocument doc2 = new SolrInputDocument(); + doc2.addField("id", "1"); + doc2.addField("vector_flat_byte", Arrays.asList(5, 6, 7, 8)); + assertU(adoc(doc2)); + + assertU(commit()); + + assertJQ( + req( + "q", "*:*", + "fl", "id,sim:vectorSimilarity(vector_flat_byte,[1, 2, 3, 4])", + "sort", "vectorSimilarity(vector_flat_byte,[1, 2, 3, 4]) desc"), + "/response/numFound==2", + "/response/docs/[0]/id=='0'"); + } finally { + deleteCore(); + } + } + + @Test + public void flatAlgorithm_getKnnVectorQuery_shouldThrowException() throws Exception { + try { + initCore("solrconfig_codec.xml", "schema-densevector-flat.xml"); + IndexSchema schema = h.getCore().getLatestSchema(); + SchemaField vectorField = schema.getField("vector_flat"); + assertNotNull(vectorField); + DenseVectorField type = (DenseVectorField) vectorField.getType(); + + SolrException ex = + expectThrows( + SolrException.class, + () -> type.getKnnVectorQuery("vector_flat", "[1, 2, 3, 4]", 3, 3, null, null, null, null)); + assertTrue(ex.getMessage().contains("knnAlgorithm=\"flat\"")); + } finally { + deleteCore(); + } + } } diff --git a/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java b/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java index 8168288c044f..e0d473570046 100644 --- a/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java +++ b/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java @@ -20,13 +20,9 @@ import org.apache.lucene.index.VectorSimilarityFunction; import org.apache.solr.core.AbstractBadConfigTestBase; -import org.junit.Before; import org.junit.Test; public class ScalarQuantizedDenseVectorFieldTest extends AbstractBadConfigTestBase { - @Before - public void init() {} - @Test public void fieldTypeDefinition_invalidBitSize_shouldThrowException() throws Exception { assertConfigs( @@ -132,4 +128,12 @@ public void fieldDefinition_dynamicConfidenceInterval_shouldLoadSchemaField() th deleteCore(); } } + + @Test + public void fieldDefinition_flatAlgorithm_shouldThrowException() throws Exception { + assertConfigs( + "solrconfig-basic.xml", + "bad-schema-densevector-flat-sq.xml", + "knnAlgorithm 'flat' is not supported for ScalarQuantizedDenseVectorField"); + } } diff --git a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc index 2681cc598409..859c10261643 100644 --- a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc +++ b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc @@ -126,8 +126,10 @@ Here's how `DenseVectorField` can be configured with the advanced hyperparameter + (advanced) Specifies the underlying knn algorithm to use + -Accepted values: `hnsw`, `cagra_hnsw` (requires GPU acceleration setup). - +Accepted values: `hnsw`, `flat`, `cagra_hnsw` (requires GPU acceleration setup). ++ +The `flat` algorithm stores vectors without building an HNSW graph. This avoids the indexing overhead of graph construction, but does not support the `{!knn}` query parser. Use `vectorSimilarity()` function queries to score and rank documents by vector similarity. See <> for details. ++ Please note that the `knnAlgorithm` accepted values may change in future releases. `vectorEncoding`:: @@ -837,6 +839,16 @@ Details about using the ReRank Query Parser can be found in the xref:query-guide ==== +== Flat Vector Index + +Setting `knnAlgorithm="flat"` stores vectors without building an HNSW graph, avoiding the indexing cost of graph construction. + +Flat fields do not support the `{!knn}` query parser. Use `vectorSimilarity()` function queries to score and rank by similarity: + +[source] +fl=id,sim:vectorSimilarity(vector,[1.0, 2.0, 3.0, 4.0])&sort=vectorSimilarity(vector,[1.0, 2.0, 3.0, 4.0]) desc + + == Indexing Multi-Vectors for Late Interaction For Late Interaction usecases, Solr provides a `StrFloatLateInteractionVectorField` field type, which supports indexing a variable length "Multi-Vector" of Float vectors, serialized as as a single String value. From 80f894b0f11a921f1d8ea13d7e13d7ae425ce5ea Mon Sep 17 00:00:00 2001 From: Adam Quigley Date: Mon, 1 Jun 2026 15:17:08 -0400 Subject: [PATCH 2/8] Add changelog entry --- changelog/unreleased/SOLR-18267-add-flat-vector-index.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog/unreleased/SOLR-18267-add-flat-vector-index.yml diff --git a/changelog/unreleased/SOLR-18267-add-flat-vector-index.yml b/changelog/unreleased/SOLR-18267-add-flat-vector-index.yml new file mode 100644 index 000000000000..94bb9c5b7f24 --- /dev/null +++ b/changelog/unreleased/SOLR-18267-add-flat-vector-index.yml @@ -0,0 +1,7 @@ +title: Add knnAlgorithm="flat" option to DenseVectorField to skip HNSW graph construction for exact vector search +type: added +authors: +- name: Adam Quigley +links: +- name: SOLR-18267 + url: https://issues.apache.org/jira/browse/SOLR-18267 \ No newline at end of file From f46d94458cf15824c19a7490104f471bd96889d3 Mon Sep 17 00:00:00 2001 From: Adam Quigley Date: Wed, 3 Jun 2026 10:17:15 -0400 Subject: [PATCH 3/8] Address PR comments --- .../core/src/java/org/apache/solr/schema/DenseVectorField.java | 3 ++- ...-bq.xml => bad-schema-densevector-flat-binaryQuantized.xml} | 0 ...-sq.xml => bad-schema-densevector-flat-scalarQuantized.xml} | 0 .../solr/schema/BinaryQuantizedDenseVectorFieldTest.java | 2 +- .../solr/schema/ScalarQuantizedDenseVectorFieldTest.java | 2 +- 5 files changed, 4 insertions(+), 3 deletions(-) rename solr/core/src/test-files/solr/collection1/conf/{bad-schema-densevector-flat-bq.xml => bad-schema-densevector-flat-binaryQuantized.xml} (100%) rename solr/core/src/test-files/solr/collection1/conf/{bad-schema-densevector-flat-sq.xml => bad-schema-densevector-flat-scalarQuantized.xml} (100%) diff --git a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java index 55a1077fac3c..0e11de4bcb82 100644 --- a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java +++ b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java @@ -475,8 +475,9 @@ public DenseVectorParser getVectorBuilder( public KnnVectorsFormat buildKnnVectorsFormat() { if (FLAT_ALGORITHM.equals(knnAlgorithm)) { return new SolrFlatVectorFormat(); + } else { + return new Lucene99HnswVectorsFormat(hnswM, hnswEfConstruction); } - return new Lucene99HnswVectorsFormat(hnswM, hnswEfConstruction); } @Override diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-bq.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-binaryQuantized.xml similarity index 100% rename from solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-bq.xml rename to solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-binaryQuantized.xml diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-sq.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-scalarQuantized.xml similarity index 100% rename from solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-sq.xml rename to solr/core/src/test-files/solr/collection1/conf/bad-schema-densevector-flat-scalarQuantized.xml diff --git a/solr/core/src/test/org/apache/solr/schema/BinaryQuantizedDenseVectorFieldTest.java b/solr/core/src/test/org/apache/solr/schema/BinaryQuantizedDenseVectorFieldTest.java index 74b20c175af2..7e0e798bbf2b 100644 --- a/solr/core/src/test/org/apache/solr/schema/BinaryQuantizedDenseVectorFieldTest.java +++ b/solr/core/src/test/org/apache/solr/schema/BinaryQuantizedDenseVectorFieldTest.java @@ -42,7 +42,7 @@ public void fieldDefinition_correctConfiguration_shouldLoadSchemaField() throws public void fieldDefinition_flatAlgorithm_shouldThrowException() throws Exception { assertConfigs( "solrconfig-basic.xml", - "bad-schema-densevector-flat-bq.xml", + "bad-schema-densevector-flat-binaryQuantized.xml", "knnAlgorithm 'flat' is not supported for BinaryQuantizedDenseVectorField"); } diff --git a/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java b/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java index e0d473570046..94ebc63003be 100644 --- a/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java +++ b/solr/core/src/test/org/apache/solr/schema/ScalarQuantizedDenseVectorFieldTest.java @@ -133,7 +133,7 @@ public void fieldDefinition_dynamicConfidenceInterval_shouldLoadSchemaField() th public void fieldDefinition_flatAlgorithm_shouldThrowException() throws Exception { assertConfigs( "solrconfig-basic.xml", - "bad-schema-densevector-flat-sq.xml", + "bad-schema-densevector-flat-scalarQuantized.xml", "knnAlgorithm 'flat' is not supported for ScalarQuantizedDenseVectorField"); } } From bf63de6cc3f210311b254020c3f84639ce929214 Mon Sep 17 00:00:00 2001 From: Adam Quigley Date: Wed, 3 Jun 2026 10:31:15 -0400 Subject: [PATCH 4/8] Improve comment about Lucene99FlatVectorsFormat SPI limitation --- .../src/java/org/apache/solr/core/SolrFlatVectorFormat.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java b/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java index eac67794c973..cb6ca2c457d7 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java +++ b/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java @@ -26,7 +26,8 @@ import org.apache.lucene.index.SegmentWriteState; /** - * Flat (non-HNSW) vector format for brute-force KNN search. + * SPI-registered wrapper for {@link Lucene99FlatVectorsFormat}, which Lucene does not register as + * a {@link KnnVectorsFormat} in SPI. * * @lucene.spi {@value #NAME} * @since 10.1 @@ -39,8 +40,7 @@ public final class SolrFlatVectorFormat extends KnnVectorsFormat { public SolrFlatVectorFormat() { super(NAME); - this.delegate = - new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()); + this.delegate = new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()); } @Override From be37b1c37d170375f965086d89b4664775f0a6b0 Mon Sep 17 00:00:00 2001 From: Adam Quigley Date: Wed, 3 Jun 2026 10:56:53 -0400 Subject: [PATCH 5/8] Format with gradlew tidy --- .../java/org/apache/solr/core/SolrFlatVectorFormat.java | 7 ++++--- .../src/java/org/apache/solr/schema/DenseVectorField.java | 2 +- .../test/org/apache/solr/schema/DenseVectorFieldTest.java | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java b/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java index cb6ca2c457d7..59055e6d6732 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java +++ b/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java @@ -26,8 +26,8 @@ import org.apache.lucene.index.SegmentWriteState; /** - * SPI-registered wrapper for {@link Lucene99FlatVectorsFormat}, which Lucene does not register as - * a {@link KnnVectorsFormat} in SPI. + * SPI-registered wrapper for {@link Lucene99FlatVectorsFormat}, which Lucene does not register as a + * {@link KnnVectorsFormat} in SPI. * * @lucene.spi {@value #NAME} * @since 10.1 @@ -40,7 +40,8 @@ public final class SolrFlatVectorFormat extends KnnVectorsFormat { public SolrFlatVectorFormat() { super(NAME); - this.delegate = new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()); + this.delegate = + new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()); } @Override diff --git a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java index 0e11de4bcb82..474da640f6d5 100644 --- a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java +++ b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java @@ -27,7 +27,6 @@ import java.util.Map; import org.apache.lucene.codecs.KnnVectorsFormat; import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat; -import org.apache.solr.core.SolrFlatVectorFormat; import org.apache.lucene.document.FieldType; import org.apache.lucene.document.KnnByteVectorField; import org.apache.lucene.document.KnnFloatVectorField; @@ -46,6 +45,7 @@ import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.hnsw.HnswGraph; import org.apache.solr.common.SolrException; +import org.apache.solr.core.SolrFlatVectorFormat; import org.apache.solr.search.QParser; import org.apache.solr.search.vector.KnnQParser.EarlyTerminationParams; import org.apache.solr.search.vector.SolrKnnByteVectorQuery; diff --git a/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java b/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java index 6c388a09d447..04068f567efd 100644 --- a/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java +++ b/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java @@ -1267,7 +1267,9 @@ public void flatAlgorithm_getKnnVectorQuery_shouldThrowException() throws Except SolrException ex = expectThrows( SolrException.class, - () -> type.getKnnVectorQuery("vector_flat", "[1, 2, 3, 4]", 3, 3, null, null, null, null)); + () -> + type.getKnnVectorQuery( + "vector_flat", "[1, 2, 3, 4]", 3, 3, null, null, null, null)); assertTrue(ex.getMessage().contains("knnAlgorithm=\"flat\"")); } finally { deleteCore(); From 5e285b2f3fcfa67f4b166f1119f9c0fee30442da Mon Sep 17 00:00:00 2001 From: Adam Quigley Date: Wed, 3 Jun 2026 16:22:39 -0400 Subject: [PATCH 6/8] Update test and docs --- .../vector/VectorSimilarityQParser.java | 8 +++++ .../solr/schema/DenseVectorFieldTest.java | 35 ++++++++++++++----- .../pages/dense-vector-search.adoc | 5 +-- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/search/vector/VectorSimilarityQParser.java b/solr/core/src/java/org/apache/solr/search/vector/VectorSimilarityQParser.java index f2d9be3738ad..a88d1dfb3062 100644 --- a/solr/core/src/java/org/apache/solr/search/vector/VectorSimilarityQParser.java +++ b/solr/core/src/java/org/apache/solr/search/vector/VectorSimilarityQParser.java @@ -46,6 +46,14 @@ public Query parse() throws SyntaxError { final String fieldName = getFieldName(); final SchemaField schemaField = req.getCore().getLatestSchema().getField(fieldName); final DenseVectorField denseVectorType = getCheckedFieldType(schemaField); + + if (DenseVectorField.FLAT_ALGORITHM.equals(denseVectorType.getKnnAlgorithm())) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, + "The {!vectorSimilarity} query parser is not supported for fields using knnAlgorithm=\"flat\". " + + "Use vectorSimilarity() function queries instead."); + } + final String vectorToSearch = getVectorToSearch(); final float minTraverse = localParams.getFloat(MIN_TRAVERSE, DEFAULT_MIN_TRAVERSE); final Float minReturn = localParams.getFloat(MIN_RETURN); diff --git a/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java b/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java index 04068f567efd..79a341422e02 100644 --- a/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java +++ b/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java @@ -1174,7 +1174,7 @@ public void flatAlgorithm_knnQuery_shouldThrowException() throws Exception { } @Test - public void flatAlgorithm_vectorSimilarity_shouldReturnResults() throws Exception { + public void flatAlgorithm_vectorSimilarityFunction_shouldReturnResults() throws Exception { try { initCore("solrconfig_codec.xml", "schema-densevector-flat.xml"); @@ -1197,11 +1197,11 @@ public void flatAlgorithm_vectorSimilarity_shouldReturnResults() throws Exceptio assertJQ( req( - "q", "*:*", - "fl", "id,sim:vectorSimilarity(vector_flat,[1, 2, 3, 4])", - "sort", "vectorSimilarity(vector_flat,[1, 2, 3, 4]) desc"), + "q", "{!func}vectorSimilarity(vector_flat,[1, 2, 3, 4])", + "fl", "id,score"), "/response/numFound==3", - "/response/docs/[0]/id=='0'"); + "/response/docs/[0]/id=='0'", + "/response/docs/[0]/score==1.0"); } finally { deleteCore(); } @@ -1245,11 +1245,28 @@ public void flatAlgorithm_byteEncoding_shouldWork() throws Exception { assertJQ( req( - "q", "*:*", - "fl", "id,sim:vectorSimilarity(vector_flat_byte,[1, 2, 3, 4])", - "sort", "vectorSimilarity(vector_flat_byte,[1, 2, 3, 4]) desc"), + "q", "{!func}vectorSimilarity(vector_flat_byte,[1, 2, 3, 4])", + "fl", "id,score"), "/response/numFound==2", - "/response/docs/[0]/id=='0'"); + "/response/docs/[0]/id=='0'", + "/response/docs/[0]/score==1.0"); + } finally { + deleteCore(); + } + } + + @Test + public void flatAlgorithm_vectorSimilarityQParser_shouldThrowException() throws Exception { + try { + initCore("solrconfig_codec.xml", "schema-densevector-flat.xml"); + + assertQEx( + "Running {!vectorSimilarity} on a flat vector field should raise an Exception", + "knnAlgorithm=\"flat\"", + req( + "q", "{!vectorSimilarity f=vector_flat minReturn=0.99}[1, 2, 3, 4]", + "fl", "id"), + SolrException.ErrorCode.BAD_REQUEST); } finally { deleteCore(); } diff --git a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc index 859c10261643..a9e965f0ae61 100644 --- a/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc +++ b/solr/solr-ref-guide/modules/query-guide/pages/dense-vector-search.adoc @@ -843,10 +843,11 @@ Details about using the ReRank Query Parser can be found in the xref:query-guide Setting `knnAlgorithm="flat"` stores vectors without building an HNSW graph, avoiding the indexing cost of graph construction. -Flat fields do not support the `{!knn}` query parser. Use `vectorSimilarity()` function queries to score and rank by similarity: +Flat fields do not support the `{!knn}`, `{!knn_text_to_vector}`, or `{!vectorSimilarity}` query parsers. +Use `vectorSimilarity()` function queries to score and rank by similarity: [source] -fl=id,sim:vectorSimilarity(vector,[1.0, 2.0, 3.0, 4.0])&sort=vectorSimilarity(vector,[1.0, 2.0, 3.0, 4.0]) desc +q={!func}vectorSimilarity(vector,[1.0, 2.0, 3.0, 4.0])&fl=id,score == Indexing Multi-Vectors for Late Interaction From ac2afb7776b7586ac140a308dbe45115b5009a9c Mon Sep 17 00:00:00 2001 From: Adam Quigley Date: Wed, 3 Jun 2026 16:36:45 -0400 Subject: [PATCH 7/8] Add fq case to vectorSimilarity test case --- .../org/apache/solr/schema/DenseVectorFieldTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java b/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java index 79a341422e02..a11e184e2e11 100644 --- a/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java +++ b/solr/core/src/test/org/apache/solr/schema/DenseVectorFieldTest.java @@ -1202,6 +1202,16 @@ public void flatAlgorithm_vectorSimilarityFunction_shouldReturnResults() throws "/response/numFound==3", "/response/docs/[0]/id=='0'", "/response/docs/[0]/score==1.0"); + + // Filtered test + assertJQ( + req( + "q", "{!func}vectorSimilarity(vector_flat,[1, 2, 3, 4])", + "fq", "id:(0 2)", + "fl", "id,score"), + "/response/numFound==2", + "/response/docs/[0]/id=='0'", + "/response/docs/[0]/score==1.0"); } finally { deleteCore(); } From d1cd307d001855373f7b641e6766d2329b6e85fe Mon Sep 17 00:00:00 2001 From: Adam Quigley Date: Fri, 5 Jun 2026 13:14:11 -0400 Subject: [PATCH 8/8] Rename wrapper class to Solr101FlatVectorFormat to include version name --- ...lrFlatVectorFormat.java => Solr101FlatVectorFormat.java} | 6 +++--- .../src/java/org/apache/solr/schema/DenseVectorField.java | 4 ++-- .../services/org.apache.lucene.codecs.KnnVectorsFormat | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename solr/core/src/java/org/apache/solr/core/{SolrFlatVectorFormat.java => Solr101FlatVectorFormat.java} (92%) diff --git a/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java b/solr/core/src/java/org/apache/solr/core/Solr101FlatVectorFormat.java similarity index 92% rename from solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java rename to solr/core/src/java/org/apache/solr/core/Solr101FlatVectorFormat.java index 59055e6d6732..d81119f0156d 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrFlatVectorFormat.java +++ b/solr/core/src/java/org/apache/solr/core/Solr101FlatVectorFormat.java @@ -32,13 +32,13 @@ * @lucene.spi {@value #NAME} * @since 10.1 */ -public final class SolrFlatVectorFormat extends KnnVectorsFormat { +public final class Solr101FlatVectorFormat extends KnnVectorsFormat { - static final String NAME = "SolrFlatVectorFormat"; + static final String NAME = "Solr101FlatVectorFormat"; private final Lucene99FlatVectorsFormat delegate; - public SolrFlatVectorFormat() { + public Solr101FlatVectorFormat() { super(NAME); this.delegate = new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()); diff --git a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java index 474da640f6d5..d1f1430bcee6 100644 --- a/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java +++ b/solr/core/src/java/org/apache/solr/schema/DenseVectorField.java @@ -45,7 +45,7 @@ import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.hnsw.HnswGraph; import org.apache.solr.common.SolrException; -import org.apache.solr.core.SolrFlatVectorFormat; +import org.apache.solr.core.Solr101FlatVectorFormat; import org.apache.solr.search.QParser; import org.apache.solr.search.vector.KnnQParser.EarlyTerminationParams; import org.apache.solr.search.vector.SolrKnnByteVectorQuery; @@ -474,7 +474,7 @@ public DenseVectorParser getVectorBuilder( public KnnVectorsFormat buildKnnVectorsFormat() { if (FLAT_ALGORITHM.equals(knnAlgorithm)) { - return new SolrFlatVectorFormat(); + return new Solr101FlatVectorFormat(); } else { return new Lucene99HnswVectorsFormat(hnswM, hnswEfConstruction); } diff --git a/solr/core/src/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat b/solr/core/src/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat index a32f9ce26689..2e6fda571dbd 100644 --- a/solr/core/src/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat +++ b/solr/core/src/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -org.apache.solr.core.SolrFlatVectorFormat +org.apache.solr.core.Solr101FlatVectorFormat