@@ -1944,6 +1944,44 @@ public void testPushDownConjunctionsToKnnPrefilter() {
19441944 assertEquals (expectedQuery .toString (), queryExec .query ().toString ());
19451945 }
19461946
1947+
1948+ public void testPushDownNegatedConjunctionsToKnnPrefilter () {
1949+ assumeTrue ("knn must be enabled" , EsqlCapabilities .Cap .KNN_FUNCTION_V3 .isEnabled ());
1950+
1951+ String query = """
1952+ from test
1953+ | where knn(dense_vector, [0, 1, 2], 10) and NOT integer > 10
1954+ """ ;
1955+ var plan = plannerOptimizer .plan (query , IS_SV_STATS , makeAnalyzer ("mapping-all-types.json" ));
1956+
1957+ var limit = as (plan , LimitExec .class );
1958+ var exchange = as (limit .child (), ExchangeExec .class );
1959+ var project = as (exchange .child (), ProjectExec .class );
1960+ var field = as (project .child (), FieldExtractExec .class );
1961+ var queryExec = as (field .child (), EsQueryExec .class );
1962+
1963+ // The filter condition should be pushed down to both the KNN query and the main query
1964+ QueryBuilder expectedFilterQueryBuilder = wrapWithSingleQuery (
1965+ query ,
1966+ unscore (boolQuery ().mustNot (unscore (rangeQuery ("integer" ).gt (10 )))),
1967+ "integer" ,
1968+ new Source (2 , 45 , "NOT integer > 10" )
1969+ );
1970+
1971+ KnnVectorQueryBuilder expectedKnnQueryBuilder = new KnnVectorQueryBuilder (
1972+ "dense_vector" ,
1973+ new float [] { 0 , 1 , 2 },
1974+ 10 ,
1975+ null ,
1976+ null ,
1977+ null
1978+ ).addFilterQuery (expectedFilterQueryBuilder );
1979+
1980+ var expectedQuery = boolQuery ().must (expectedKnnQueryBuilder ).must (expectedFilterQueryBuilder );
1981+
1982+ assertEquals (expectedQuery .toString (), queryExec .query ().toString ());
1983+ }
1984+
19471985 public void testNotPushDownDisjunctionsToKnnPrefilter () {
19481986 assumeTrue ("knn must be enabled" , EsqlCapabilities .Cap .KNN_FUNCTION_V3 .isEnabled ());
19491987
@@ -2007,6 +2045,57 @@ public void testNotPushDownKnnWithNonPushablePrefilters() {
20072045 assertEquals (integerGtQuery .toString (), queryExec .query ().toString ());
20082046 }
20092047
2048+ public void testPushDownComplexNegationsToKnnPrefilter () {
2049+ assumeTrue ("knn must be enabled" , EsqlCapabilities .Cap .KNN_FUNCTION_V3 .isEnabled ());
2050+
2051+ String query = """
2052+ from test
2053+ | where ((knn(dense_vector, [0, 1, 2], 10) or NOT integer > 10) and NOT ((keyword == "test") or knn(dense_vector, [4, 5, 6], 10)))
2054+ """ ;
2055+ var plan = plannerOptimizer .plan (query , IS_SV_STATS , makeAnalyzer ("mapping-all-types.json" ));
2056+
2057+ var limit = as (plan , LimitExec .class );
2058+ var exchange = as (limit .child (), ExchangeExec .class );
2059+ var project = as (exchange .child (), ProjectExec .class );
2060+ var fieldExtract = as (project .child (), FieldExtractExec .class );
2061+ var queryExec = as (fieldExtract .child (), EsQueryExec .class );
2062+
2063+ QueryBuilder notKeywordFilter = wrapWithSingleQuery (
2064+ query ,
2065+ unscore (boolQuery ().mustNot (unscore (termQuery ("keyword" , "test" )))),
2066+ "keyword" ,
2067+ new Source (2 , 74 , "keyword == \" test\" " )
2068+ );
2069+
2070+ QueryBuilder notIntegerGt10 = wrapWithSingleQuery (
2071+ query ,
2072+ unscore (boolQuery ().mustNot (unscore (rangeQuery ("integer" ).gt (10 )))),
2073+ "integer" ,
2074+ new Source (2 , 46 , "NOT integer > 10" )
2075+ );
2076+
2077+ KnnVectorQueryBuilder firstKnn = new KnnVectorQueryBuilder ("dense_vector" , new float [] { 0 , 1 , 2 }, 10 , null , null , null );
2078+ KnnVectorQueryBuilder firstKnnFilter = new KnnVectorQueryBuilder ("dense_vector" , new float [] { 0 , 1 , 2 }, 10 , null , null , null );
2079+ KnnVectorQueryBuilder secondKnn = new KnnVectorQueryBuilder ("dense_vector" , new float [] { 4 , 5 , 6 }, 10 , null , null , null );
2080+ KnnVectorQueryBuilder secondKnnFilter = new KnnVectorQueryBuilder ("dense_vector" , new float [] { 4 , 5 , 6 }, 10 , null , null , null );
2081+
2082+ firstKnn .addFilterQuery (boolQuery ()
2083+ .must (notKeywordFilter )
2084+ .must (unscore (boolQuery ().mustNot (secondKnnFilter ))));
2085+
2086+ secondKnn .addFilterQuery (boolQuery ()
2087+ .should (firstKnnFilter )
2088+ .should (notIntegerGt10 ));
2089+
2090+ // Build the main boolean query structure
2091+ BoolQueryBuilder expectedQuery = boolQuery ()
2092+ .must (notKeywordFilter ) // NOT (keyword == "test")
2093+ .must (unscore (boolQuery ().mustNot (secondKnn )))
2094+ .must (boolQuery ().should (firstKnn ).should (notIntegerGt10 ));
2095+
2096+ assertEquals (expectedQuery .toString (), queryExec .query ().toString ());
2097+ }
2098+
20102099 public void testMultipleKnnQueriesInPrefilters () {
20112100 assumeTrue ("knn must be enabled" , EsqlCapabilities .Cap .KNN_FUNCTION_V3 .isEnabled ());
20122101
0 commit comments