Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3a52030
Introduce json_queries key in JSON DSL, pass-through to req.getJSON()
Copilot Jun 30, 2026
d13108e
Introduce IntervalsQParserPlugin ({!intervals json_query=...}), retur…
Copilot Jun 30, 2026
de1cf82
Improve TestIntervalsQParserPlugin: add json_queries pass-through test
Copilot Jun 30, 2026
5e892d5
Implement IntervalsQParserPlugin term interval query from JSON DSL
Copilot Jun 30, 2026
643c95e
Implement OpenSearch-style intervals JSON rule parser
Copilot Jun 30, 2026
a402594
Fix intervals DSL parser warnings and finalize validation
Copilot Jun 30, 2026
e0841a3
Clarify fuzzy expansion constant in intervals parser
Copilot Jun 30, 2026
c76d287
Add interval rule support: term, phrase, regexp, range, max_width, ex…
Copilot Jul 1, 2026
1d9f1e8
Add Intervals Query Parser documentation page and references
Copilot Jul 1, 2026
363eb9e
Fix spelling: analyse -> analyze in intervals-query-parser.adoc
Copilot Jul 1, 2026
e2047c3
Add SOLR-13764 changelog fragment
Copilot Jul 1, 2026
655b511
fix changelog
mkhludnev Jul 1, 2026
53c8730
Support df local/query param as field for IntervalsQParser; update tests
Copilot Jul 1, 2026
d42e02c
Update intervals-query-parser.adoc to document both df and legacy que…
Copilot Jul 1, 2026
81b3464
Remove legacy format support from IntervalsQParserPlugin
Copilot Jul 1, 2026
92ec988
Rename SOLR-13764-intervals-query-parser-reference-guide.yml to SOLR-…
mkhludnev Jul 1, 2026
73e50a9
Change type to 'added' for intervals query parser
mkhludnev Jul 1, 2026
a45e9f4
Revise intervals query parser documentation
mkhludnev Jul 1, 2026
b9b9846
Add Lucene intervals package link to intervals-query-parser.adoc
Copilot Jul 1, 2026
4a7e58f
Fix spotless formatting in TestIntervalsQParserPlugin
Copilot Jul 1, 2026
168ff13
Fix the guide
mkhludnev Jul 1, 2026
d644ea9
Change source format from json to text in intervals query
mkhludnev Jul 1, 2026
a9a1007
Add intervals vs xmlparser nested query comparison test
Copilot Jul 1, 2026
6d7d239
Clarify JSON DSL reference in intervals query parser
mkhludnev Jul 1, 2026
5755f59
Fix typo in author URL field
mkhludnev Jul 1, 2026
5f29a8f
Fix tidy and checkSiteLinks CI failures
Copilot Jul 1, 2026
003d263
Add DistributedQParserTest: cloud-based distributed search test for l…
Copilot Jul 1, 2026
f8652de
Add testIntervalsQParser to DistributedQParserTest
Copilot Jul 1, 2026
88e4c3b
fix: reformat DistributedQParserTest.java per spotless rules
Copilot Jul 1, 2026
b7e85da
Merge pull request #3 from mkhludnev/copilot/fix-failing-github-actio…
mkhludnev Jul 1, 2026
223440d
Fix Spotless formatting violation in DistributedQParserTest.java
Copilot Jul 1, 2026
11ccd1e
Potential fix for pull request finding
mkhludnev Jul 1, 2026
ce87c95
Apply suggestions from code review
mkhludnev Jul 1, 2026
6f2f3ce
multiterm analyzer and QueryEqualityTest
mkhludnev Jul 1, 2026
002e8a4
Merge pull request #4 from mkhludnev/add-json-queries-key
mkhludnev Jul 1, 2026
a61b90f
fix tests
mkhludnev Jul 1, 2026
2f3141b
review
mkhludnev Jul 2, 2026
da96d7c
Delete solr/.vscode/settings.json
mkhludnev Jul 2, 2026
50130b5
fix test compile
mkhludnev Jul 2, 2026
abde6b2
check prefix len
mkhludnev Jul 2, 2026
e52214d
change syntax to {!intervals df=title}$foobar
mkhludnev Jul 2, 2026
dde6a64
strict field check
mkhludnev Jul 2, 2026
c1b4635
assertJQ
mkhludnev Jul 3, 2026
c9ccd8a
tiding manually
mkhludnev Jul 3, 2026
bac1035
tidyng spaces. dunno why
mkhludnev Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions changelog/unreleased/SOLR-13764-intervals-query-parser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
title: Add Intervals Query Parser
type: added
authors:
- name: Mikhail Khludnev
nick: mkhludnev
url: https://home.apache.org/phonebook.html?uid=mkhl
links:
- name: SOLR-13764
url: https://issues.apache.org/jira/browse/SOLR-13764
1 change: 1 addition & 0 deletions solr/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@

lib/
test-lib/
.vscode/
10 changes: 10 additions & 0 deletions solr/core/src/java/org/apache/solr/request/json/RequestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
import org.noggit.ObjectBuilder;

public class RequestUtil {
/**
* Top-level JSON key holding the map of named query definitions, referenced elsewhere (e.g. by
* {@link org.apache.solr.search.IntervalsQParserPlugin}) via a {@code $name} local param value.
*/
public static final String JSON_QUERIES_KEY = "json_queries";

/**
* Set default-ish params on a SolrQueryRequest as well as do standard macro processing and JSON
* request parsing.
Expand Down Expand Up @@ -254,6 +260,10 @@ public static void processParams(
SolrException.ErrorCode.BAD_REQUEST,
"Expected Map for 'queries', received " + queriesJsonObj);
}
} else if (JSON_QUERIES_KEY.equals(key)) {
// passed through as a parsed object for use by SearchComponent.prepare() at subordinate
// nodes; not processed here
continue;
} else if ("params".equals(key) || "facet".equals(key)) {
// handled elsewhere
continue;
Expand Down
699 changes: 699 additions & 0 deletions solr/core/src/java/org/apache/solr/search/IntervalsQParserPlugin.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public abstract class QParserPlugin implements NamedListInitializedPlugin {
map.put(VectorSimilarityQParserPlugin.NAME, new VectorSimilarityQParserPlugin());
map.put(FuzzyQParserPlugin.NAME, new FuzzyQParserPlugin());
map.put(NumericRangeQParserPlugin.NAME, new NumericRangeQParserPlugin());
map.put(IntervalsQParserPlugin.NAME, new IntervalsQParserPlugin());

standardPlugins = Collections.unmodifiableMap(map);
}
Expand Down
166 changes: 166 additions & 0 deletions solr/core/src/test/org/apache/solr/search/DistributedQParserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* 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.search;

import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.junit.BeforeClass;
import org.junit.Test;

/**
* Distributed search tests for the standard query parsers: {@code lucene}, {@code dismax}, {@code
* edismax}, and {@code intervals}.
*/
public class DistributedQParserTest extends SolrCloudTestCase {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have this... shouldn't this be only for intervals?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if the intent is testing distributed... well we have a whole test framework for that which is quite good: BaseDistributedSearchTestCase. Doesn't even require SolrCloud!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afiak SolrCloudTestCase is rather faster and convenient. It's a little bit redundant for local-only QP, but there's a https://github.com/apache/solr/pull/4582#issuecomment-4861727946 regarding transferring new JSON request entries. I've decided to double check.
We cad drop it if you wish.


private static final String COLLECTION = "distributed-qparser";

@BeforeClass
public static void setupCluster() throws Exception {
configureCluster(2).addConfig("conf", configset("cloud-dynamic")).configure();

CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 1)
.process(cluster.getSolrClient());
cluster.waitForActiveCollection(COLLECTION, 2, 2);

new UpdateRequest()
.add(sdoc("id", "1", "subject", "quick brown fox"))
.add(sdoc("id", "2", "subject", "lazy brown dog"))
.add(sdoc("id", "3", "subject", "quick red dog"))
.add(sdoc("id", "4", "subject", "slow green cat"))
.commit(cluster.getSolrClient(), COLLECTION);
}

@Test
public void testLuceneQParser() throws Exception {
QueryResponse response =
new QueryRequest(params("q", "subject:quick", "defType", "lucene", "fl", "id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(2, response.getResults().getNumFound());

response =
new QueryRequest(params("q", "subject:brown", "defType", "lucene", "fl", "id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(2, response.getResults().getNumFound());
}

@Test
public void testDismaxQParser() throws Exception {
QueryResponse response =
new QueryRequest(params("q", "quick", "defType", "dismax", "qf", "subject", "fl", "id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(2, response.getResults().getNumFound());

response =
new QueryRequest(params("q", "brown dog", "defType", "dismax", "qf", "subject", "fl", "id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(3, response.getResults().getNumFound());
}

@Test
public void testEdismaxQParser() throws Exception {
QueryResponse response =
new QueryRequest(params("q", "quick", "defType", "edismax", "qf", "subject", "fl", "id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(2, response.getResults().getNumFound());

response =
new QueryRequest(
params("q", "brown dog", "defType", "edismax", "qf", "subject", "fl", "id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(3, response.getResults().getNumFound());
}

@Test
public void testIntervalsQParser() throws Exception {
// match rule: "quick" appears in docs 1 ("quick brown fox") and 3 ("quick red dog")
QueryResponse response =
new QueryRequest(
params(
"q",
"{!intervals df=subject}$q1",
"json",
"{json_queries:{q1:{match:{query:quick}}"
+ (random().nextBoolean() ? ",ignore:{match:{query:lazy}}}}" : "}}"),
"fl",
"id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(2, response.getResults().getNumFound());

// a distinct match rule: "lazy" appears only in doc 2 ("lazy brown dog") — confirm the
// result differs from the "quick" query above
QueryResponse lazyResponse =
new QueryRequest(
params(
"q",
"{!intervals df=subject}$q1",
"json",
"{json_queries:{q1:{match:{query:lazy}}"
+ (random().nextBoolean() ? ",ignore:{match:{query:quick}}}}" : "}}"),
"fl",
"id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(1, lazyResponse.getResults().getNumFound());
assertNotEquals(response.getResults().getNumFound(), lazyResponse.getResults().getNumFound());

// all_of ordered: "quick" then "fox" — only doc 1 ("quick brown fox") matches
response =
new QueryRequest(
params(
"q",
"{!intervals df=subject}$q1",
"json",
"{json_queries:{q1:{all_of:{ordered:true,"
+ "intervals:[{match:{query:quick}},{match:{query:fox}}]}}}}",
"fl",
"id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(1, response.getResults().getNumFound());

// union of two top-level interval queries: "quick" (docs 1, 3) or "lazy" (doc 2) — three
// docs match. Note: the leading space before the first "{!" is required, otherwise the
// whole q string is parsed as a single set of local params rather than two clauses.
response =
new QueryRequest(
params(
"q",
" {!intervals df=subject}$q1 {!intervals df=subject}$q2",
"json",
"{json_queries:{q1:{match:{query:quick}},q2:{match:{query:lazy}}}}",
"fl",
"id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(3, response.getResults().getNumFound());

// intersection of two top-level interval queries (using "+" to require both clauses):
// "quick" (docs 1, 3) and "brown" (docs 1, 2) — only doc 1 has both terms
response =
new QueryRequest(
params(
"q",
" +{!intervals df=subject}$q1 +{!intervals df=subject}$q2",
"json",
"{json_queries:{q1:{match:{query:quick}},q2:{match:{query:brown}}}}",
"fl",
"id"))
.process(cluster.getSolrClient(), COLLECTION);
assertEquals(1, response.getResults().getNumFound());
}
}
11 changes: 11 additions & 0 deletions solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ public void testQueryFuzzy() throws Exception {
}
}

public void testQueryIntervals() throws Exception {
try (SolrQueryRequest req = req("myField", "foo_s")) {
req.setJSON(Map.of("json_queries", Map.of("q1", Map.of("term", Map.of("value", "asdf")))));
assertQueryEquals(
IntervalsQParserPlugin.NAME,
req,
"{!intervals df=$myField}$q1",
"{!intervals df=foo_s}$q1");
}
}

public void testQueryBoost() throws Exception {
SolrQueryRequest req = req("df", "foo_s", "myBoost", "sum(3,foo_i)");
try {
Expand Down
Loading
Loading