From e054398f908c49f1eac933d56e7e253f20e13c85 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:54:43 +0100 Subject: [PATCH 01/37] #25 - Added basic types [skip ci] --- .../ODataSearchToMongoSearchParser.java | 55 +++++++++++++++++++ .../search/SearchDocumentFactory.java | 9 +++ .../search/SearchOperatorResult.java | 5 ++ 3 files changed, 69 insertions(+) create mode 100644 core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParser.java create mode 100644 core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java create mode 100644 core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResult.java diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParser.java new file mode 100644 index 0000000..f65eb1a --- /dev/null +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParser.java @@ -0,0 +1,55 @@ +package com.github.starnowski.jamolingo.core.operators.search; + +import java.util.List; +import org.apache.olingo.server.api.uri.queryoption.SearchOption; +import org.bson.Document; +import org.bson.conversions.Bson; + +public class ODataSearchToMongoSearchParser { + + public SearchOperatorResult parse( + SearchOption searchOption, SearchDocumentFactory searchDocumentFactory) { + return new DefaultSearchOperatorResult( + new Document("$search", searchDocumentFactory.build(searchOption.getSearchExpression()))); + } + + private static class DefaultSearchOperatorResult implements SearchOperatorResult { + + private final List stageObjects; + + private DefaultSearchOperatorResult(Bson searchStage) { + this.stageObjects = List.of(searchStage); + } + + @Override + public List getStageObjects() { + return stageObjects; + } + + @Override + public List getUsedMongoDocumentProperties() { + return List.of(); + } + + @Override + public List getWrittenMongoDocumentProperties() { + return List.of(); + } + + @Override + public List getAddedMongoDocumentProperties() { + // TODO $meta field is going to be added + return List.of(); + } + + @Override + public List getRemovedMongoDocumentProperties() { + return List.of(); + } + + @Override + public boolean isDocumentShapeRedefined() { + return false; + } + } +} diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java new file mode 100644 index 0000000..cd7efd5 --- /dev/null +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java @@ -0,0 +1,9 @@ +package com.github.starnowski.jamolingo.core.operators.search; + +import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression; +import org.bson.conversions.Bson; + +public interface SearchDocumentFactory { + + Bson build(SearchExpression searchExpression); +} diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResult.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResult.java new file mode 100644 index 0000000..a89e879 --- /dev/null +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResult.java @@ -0,0 +1,5 @@ +package com.github.starnowski.jamolingo.core.operators.search; + +import com.github.starnowski.jamolingo.core.operators.OlingoOperatorResult; + +public interface SearchOperatorResult extends OlingoOperatorResult {} From c8d9ad78aa352088ac5e97a91021fd29c1eddd9a Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:16:40 +0100 Subject: [PATCH 02/37] #25 - Added unit tests [skip ci] --- .../SearchDocumentForQueryStringFactory.java | 48 ++++++++++++++ .../ODataSearchToMongoSearchParserTest.groovy | 66 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java create mode 100644 core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java new file mode 100644 index 0000000..04bae4c --- /dev/null +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java @@ -0,0 +1,48 @@ +package com.github.starnowski.jamolingo.core.operators.search; + +import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression; +import org.bson.conversions.Bson; + +public abstract class SearchDocumentForQueryStringFactory implements SearchDocumentFactory { + @Override + public Bson build(SearchExpression searchExpression) { + return build(searchExpression, pares(searchExpression)); + } + + private QueryStringParsingResult pares(SearchExpression searchExpression) { + try { + return new QueryStringParsingResult("", true, null); + } catch (Exception ex) { + return new QueryStringParsingResult(null, false, ex); + } + } + + public abstract Bson build( + SearchExpression searchExpression, QueryStringParsingResult queryStringParsingResult); + + public static class QueryStringParsingResult { + + public String getQuery() { + return query; + } + + public boolean isSuccess() { + return success; + } + + private final String query; + private final boolean success; + + public Exception getCause() { + return cause; + } + + private final Exception cause; + + public QueryStringParsingResult(String query, boolean success, Exception cause) { + this.query = query; + this.success = success; + this.cause = cause; + } + } +} diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy new file mode 100644 index 0000000..a3990db --- /dev/null +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy @@ -0,0 +1,66 @@ +package com.github.starnowski.jamolingo.core.operators.search + +import com.github.starnowski.jamolingo.core.AbstractSpecification +import com.mongodb.MongoClientSettings +import org.apache.olingo.commons.api.edm.Edm +import org.apache.olingo.server.api.OData +import org.apache.olingo.server.api.uri.UriInfo +import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression +import org.apache.olingo.server.core.uri.parser.Parser +import org.bson.Document +import org.bson.UuidRepresentation +import org.bson.codecs.DocumentCodec +import org.bson.codecs.UuidCodecProvider +import org.bson.codecs.configuration.CodecRegistries +import org.bson.codecs.configuration.CodecRegistry +import org.bson.conversions.Bson +import org.bson.json.JsonWriterSettings +import spock.lang.Unroll + +class ODataSearchToMongoSearchParserTest extends AbstractSpecification { + + + /** + * Verifies that the generated MongoDB $match stage matches the expected BSON document. + */ + @Unroll + def "should return expected stage bson objects"(){ + given: + System.out.println("Testing search: " + searchValue) + Bson expectedBson = Document.parse(expectedBsonJson) + Edm edm = loadEmdProvider("edm/edm6_filter_main.xml") + JsonWriterSettings settings = JsonWriterSettings.builder().build() + CodecRegistry registry = CodecRegistries.fromRegistries( + CodecRegistries.fromProviders(new UuidCodecProvider(UuidRepresentation.STANDARD)), + MongoClientSettings.getDefaultCodecRegistry() + ) + DocumentCodec codec = new DocumentCodec(registry) + + UriInfo uriInfo = new Parser(edm, OData.newInstance()) + .parseUri("examples2", + "\$search=" +searchValue + , null, null) + ODataSearchToMongoSearchParser tested = new ODataSearchToMongoSearchParser() + + when: + def result = tested.parse(uriInfo.getSearchOption(), new SearchDocumentForQueryStringFactory() { + @Override + Bson build(SearchExpression searchExpression, SearchDocumentForQueryStringFactory.QueryStringParsingResult queryStringParsingResult) { + return new Document().append("index", "default") + .append("queryString", new Document() + .append("path", Arrays.asList("name","description")) + .append("query", queryStringParsingResult.getQuery()) + ) + } + }) + + then: + [((Document)result.getStageObjects().get(0)).toJson(settings, codec)] == [expectedBson.toJson(settings, codec)] + + where: + searchValue || expectedBsonJson + """database AND search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND search", "path": ["name","description"] }}}""" + } + + +} From 9f75ab7661f4cdf48510df311bc1ef9411042d54 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:17:42 +0100 Subject: [PATCH 03/37] #25 - Added unit tests [skip ci] --- .../operators/search/ODataSearchToMongoSearchParserTest.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy index a3990db..6feff0e 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy @@ -60,6 +60,8 @@ class ODataSearchToMongoSearchParserTest extends AbstractSpecification { where: searchValue || expectedBsonJson """database AND search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND search", "path": ["name","description"] }}}""" + """database OR search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database OR search", "path": ["name","description"] }}}""" + """database NOT legacy""" || """{ "\$search": { "index": "default", "queryString": { "query": "database NOT legacy", "path": ["name","description"] }}}""" } From 643266356434c068a78ecef6212ec46c3c64d411 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:18:51 +0100 Subject: [PATCH 04/37] #25 - Added unit tests [skip ci] --- .../search/ODataSearchToMongoSearchParserTest.groovy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy index 6feff0e..9068f76 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy @@ -58,10 +58,11 @@ class ODataSearchToMongoSearchParserTest extends AbstractSpecification { [((Document)result.getStageObjects().get(0)).toJson(settings, codec)] == [expectedBson.toJson(settings, codec)] where: - searchValue || expectedBsonJson - """database AND search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND search", "path": ["name","description"] }}}""" - """database OR search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database OR search", "path": ["name","description"] }}}""" - """database NOT legacy""" || """{ "\$search": { "index": "default", "queryString": { "query": "database NOT legacy", "path": ["name","description"] }}}""" + searchValue || expectedBsonJson + """database AND search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND search", "path": ["name","description"] }}}""" + """database OR search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database OR search", "path": ["name","description"] }}}""" + """database NOT legacy""" || """{ "\$search": { "index": "default", "queryString": { "query": "database NOT legacy", "path": ["name","description"] }}}""" + """\"AND\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND\\"", "path": ["name","description"] }}}""" } From ea5ae8c069f473db3638ea606fd6fd6a08318bad Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:23:04 +0100 Subject: [PATCH 05/37] #25 - Added unit tests [skip ci] --- .../operators/search/ODataSearchToMongoSearchParserTest.groovy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy index 9068f76..6051fe1 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy @@ -63,6 +63,9 @@ class ODataSearchToMongoSearchParserTest extends AbstractSpecification { """database OR search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database OR search", "path": ["name","description"] }}}""" """database NOT legacy""" || """{ "\$search": { "index": "default", "queryString": { "query": "database NOT legacy", "path": ["name","description"] }}}""" """\"AND\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND\\"", "path": ["name","description"] }}}""" + """\"OR\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"OR\\"", "path": ["name","description"] }}}""" + """\"NOT\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"NOT\\"", "path": ["name","description"] }}}""" + """\"AND operator\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND operator\\"", "path": ["name","description"] }}}""" } From 31130fd16895ec0bcb79775cf52fe3440c486685 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:32:54 +0100 Subject: [PATCH 06/37] #25 - Added unit tests [skip ci] --- .../ODataSearchToMongoSearchParserTest.groovy | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy index 6051fe1..911ba53 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy @@ -58,14 +58,25 @@ class ODataSearchToMongoSearchParserTest extends AbstractSpecification { [((Document)result.getStageObjects().get(0)).toJson(settings, codec)] == [expectedBson.toJson(settings, codec)] where: - searchValue || expectedBsonJson - """database AND search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND search", "path": ["name","description"] }}}""" - """database OR search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database OR search", "path": ["name","description"] }}}""" - """database NOT legacy""" || """{ "\$search": { "index": "default", "queryString": { "query": "database NOT legacy", "path": ["name","description"] }}}""" - """\"AND\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND\\"", "path": ["name","description"] }}}""" - """\"OR\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"OR\\"", "path": ["name","description"] }}}""" - """\"NOT\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"NOT\\"", "path": ["name","description"] }}}""" - """\"AND operator\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND operator\\"", "path": ["name","description"] }}}""" + searchValue || expectedBsonJson + """database AND search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND search", "path": ["name","description"] }}}""" + """database OR search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database OR search", "path": ["name","description"] }}}""" + """database NOT legacy""" || """{ "\$search": { "index": "default", "queryString": { "query": "database NOT legacy", "path": ["name","description"] }}}""" + """\"AND\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND\\"", "path": ["name","description"] }}}""" + """\"OR\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"OR\\"", "path": ["name","description"] }}}""" + """\"NOT\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"NOT\\"", "path": ["name","description"] }}}""" + """\"AND operator\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND operator\\"", "path": ["name","description"] }}}""" + """\"rock AND roll\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"rock AND roll\\"", "path": ["name","description"] }}}""" + """"OR condition" AND database""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"OR condition\\" AND database", "path": ["name","description"] }}}""" + """"NOT operator" OR logic""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"NOT operator\\" OR logic", "path": ["name","description"] }}}""" + """(database OR search) AND index""" || """{ "\$search": { "index": "default", "queryString": { "query": "(database OR search) AND index", "path": ["name","description"] }}}""" + """("AND" OR "OR") AND logic""" || """{ "\$search": { "index": "default", "queryString": { "query": "(\\"AND\\" OR \\"OR\\") AND logic", "path": ["name","description"] }}}""" + """(database OR "AND") AND system""" || """{ "\$search": { "index": "default", "queryString": { "query": "(database OR \\"AND\\") AND system", "path": ["name","description"] }}}""" + """\"database OR search\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"database OR search\\"", "path": ["name","description"] }}}""" + """\"AND OR NOT\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND OR NOT\\"", "path": ["name","description"] }}}""" + """database AND ("OR" OR "NOT")""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND (\\"OR\\" OR \\"NOT\\")", "path": ["name","description"] }}}""" + """\"logical AND operator\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"logical AND operator\\"", "path": ["name","description"] }}}""" + """("database search" OR "full text") AND engine""" || """{ "\$search": { "index": "default", "queryString": { "query": "(\\"database search\\" OR \\"full text\\") AND engine", "path": ["name","description"] }}}""" } From 11def057ce5467f635b8cb7cd90e762e695cf2a2 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:35:41 +0100 Subject: [PATCH 07/37] #25 - Added unit tests [skip ci] --- .../operators/search/ODataSearchToMongoSearchParserTest.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy index 911ba53..f2322a1 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy @@ -77,6 +77,7 @@ class ODataSearchToMongoSearchParserTest extends AbstractSpecification { """database AND ("OR" OR "NOT")""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND (\\"OR\\" OR \\"NOT\\")", "path": ["name","description"] }}}""" """\"logical AND operator\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"logical AND operator\\"", "path": ["name","description"] }}}""" """("database search" OR "full text") AND engine""" || """{ "\$search": { "index": "default", "queryString": { "query": "(\\"database search\\" OR \\"full text\\") AND engine", "path": ["name","description"] }}}""" + """\"\\"AND\\" keyword\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"\\\\\\"AND\\\\\\" keyword\\"", "path": ["name","description"] }}}""" } From 8a37d065bfbe34ce6ff05d2b11b7482c8c0c1efe Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:36:41 +0100 Subject: [PATCH 08/37] #25 - Added unit tests [skip ci] --- .../ODataSearchToMongoSearchParserTest.groovy | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy index f2322a1..ef65468 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy @@ -58,26 +58,27 @@ class ODataSearchToMongoSearchParserTest extends AbstractSpecification { [((Document)result.getStageObjects().get(0)).toJson(settings, codec)] == [expectedBson.toJson(settings, codec)] where: - searchValue || expectedBsonJson - """database AND search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND search", "path": ["name","description"] }}}""" - """database OR search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database OR search", "path": ["name","description"] }}}""" - """database NOT legacy""" || """{ "\$search": { "index": "default", "queryString": { "query": "database NOT legacy", "path": ["name","description"] }}}""" - """\"AND\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND\\"", "path": ["name","description"] }}}""" - """\"OR\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"OR\\"", "path": ["name","description"] }}}""" - """\"NOT\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"NOT\\"", "path": ["name","description"] }}}""" - """\"AND operator\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND operator\\"", "path": ["name","description"] }}}""" - """\"rock AND roll\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"rock AND roll\\"", "path": ["name","description"] }}}""" - """"OR condition" AND database""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"OR condition\\" AND database", "path": ["name","description"] }}}""" - """"NOT operator" OR logic""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"NOT operator\\" OR logic", "path": ["name","description"] }}}""" - """(database OR search) AND index""" || """{ "\$search": { "index": "default", "queryString": { "query": "(database OR search) AND index", "path": ["name","description"] }}}""" - """("AND" OR "OR") AND logic""" || """{ "\$search": { "index": "default", "queryString": { "query": "(\\"AND\\" OR \\"OR\\") AND logic", "path": ["name","description"] }}}""" - """(database OR "AND") AND system""" || """{ "\$search": { "index": "default", "queryString": { "query": "(database OR \\"AND\\") AND system", "path": ["name","description"] }}}""" - """\"database OR search\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"database OR search\\"", "path": ["name","description"] }}}""" - """\"AND OR NOT\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND OR NOT\\"", "path": ["name","description"] }}}""" - """database AND ("OR" OR "NOT")""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND (\\"OR\\" OR \\"NOT\\")", "path": ["name","description"] }}}""" - """\"logical AND operator\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"logical AND operator\\"", "path": ["name","description"] }}}""" - """("database search" OR "full text") AND engine""" || """{ "\$search": { "index": "default", "queryString": { "query": "(\\"database search\\" OR \\"full text\\") AND engine", "path": ["name","description"] }}}""" - """\"\\"AND\\" keyword\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"\\\\\\"AND\\\\\\" keyword\\"", "path": ["name","description"] }}}""" + searchValue || expectedBsonJson + """database AND search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND search", "path": ["name","description"] }}}""" + """database OR search""" || """{ "\$search": { "index": "default", "queryString": { "query": "database OR search", "path": ["name","description"] }}}""" + """database NOT legacy""" || """{ "\$search": { "index": "default", "queryString": { "query": "database NOT legacy", "path": ["name","description"] }}}""" + """\"AND\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND\\"", "path": ["name","description"] }}}""" + """\"OR\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"OR\\"", "path": ["name","description"] }}}""" + """\"NOT\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"NOT\\"", "path": ["name","description"] }}}""" + """\"AND operator\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND operator\\"", "path": ["name","description"] }}}""" + """\"rock AND roll\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"rock AND roll\\"", "path": ["name","description"] }}}""" + """"OR condition" AND database""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"OR condition\\" AND database", "path": ["name","description"] }}}""" + """"NOT operator" OR logic""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"NOT operator\\" OR logic", "path": ["name","description"] }}}""" + """(database OR search) AND index""" || """{ "\$search": { "index": "default", "queryString": { "query": "(database OR search) AND index", "path": ["name","description"] }}}""" + """("AND" OR "OR") AND logic""" || """{ "\$search": { "index": "default", "queryString": { "query": "(\\"AND\\" OR \\"OR\\") AND logic", "path": ["name","description"] }}}""" + """(database OR "AND") AND system""" || """{ "\$search": { "index": "default", "queryString": { "query": "(database OR \\"AND\\") AND system", "path": ["name","description"] }}}""" + """\"database OR search\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"database OR search\\"", "path": ["name","description"] }}}""" + """\"AND OR NOT\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"AND OR NOT\\"", "path": ["name","description"] }}}""" + """database AND ("OR" OR "NOT")""" || """{ "\$search": { "index": "default", "queryString": { "query": "database AND (\\"OR\\" OR \\"NOT\\")", "path": ["name","description"] }}}""" + """\"logical AND operator\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"logical AND operator\\"", "path": ["name","description"] }}}""" + """("database search" OR "full text") AND engine""" || """{ "\$search": { "index": "default", "queryString": { "query": "(\\"database search\\" OR \\"full text\\") AND engine", "path": ["name","description"] }}}""" + """\"\\"AND\\" keyword\"""" || """{ "\$search": { "index": "default", "queryString": { "query": "\\"\\\\\\"AND\\\\\\" keyword\\"", "path": ["name","description"] }}}""" + """(database AND search) OR ("AND operator" AND logic)""" || """{ "\$search": { "index": "default", "queryString": { "query": "(database AND search) OR (\\"AND operator\\" AND logic)", "path": ["name","description"] }}}""" } From b45fa38be343e18684551dc93a379978e0e6a0c0 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:44:44 +0100 Subject: [PATCH 09/37] #25 - Added todo method [skip ci] --- .../search/SearchDocumentForQueryStringFactory.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java index 04bae4c..07e381c 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java @@ -17,6 +17,11 @@ private QueryStringParsingResult pares(SearchExpression searchExpression) { } } + protected String parseSearchExpressionToString(SearchExpression searchExpression) { + // TODO + return ""; + } + public abstract Bson build( SearchExpression searchExpression, QueryStringParsingResult queryStringParsingResult); From 35333b382188624d32fd661e0b1c3e2bd7d00eb5 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:58:40 +0100 Subject: [PATCH 10/37] #25 - Added implementation of parsed queryString [skip ci] --- .../SearchDocumentForQueryStringFactory.java | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java index 07e381c..d62d7ab 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java @@ -1,6 +1,11 @@ package com.github.starnowski.jamolingo.core.operators.search; +import org.apache.olingo.server.api.uri.queryoption.search.SearchBinary; +import org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind; import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression; +import org.apache.olingo.server.api.uri.queryoption.search.SearchTerm; +import org.apache.olingo.server.api.uri.queryoption.search.SearchUnary; +import org.apache.olingo.server.api.uri.queryoption.search.SearchUnaryOperatorKind; import org.bson.conversions.Bson; public abstract class SearchDocumentForQueryStringFactory implements SearchDocumentFactory { @@ -11,17 +16,54 @@ public Bson build(SearchExpression searchExpression) { private QueryStringParsingResult pares(SearchExpression searchExpression) { try { - return new QueryStringParsingResult("", true, null); + return new QueryStringParsingResult(parseSearchExpressionToString(searchExpression), true, null); } catch (Exception ex) { return new QueryStringParsingResult(null, false, ex); } } protected String parseSearchExpressionToString(SearchExpression searchExpression) { - // TODO + if (searchExpression instanceof SearchTerm) { + return formatTerm(((SearchTerm) searchExpression).getSearchTerm()); + } else if (searchExpression instanceof SearchBinary) { + SearchBinary binary = (SearchBinary) searchExpression; + String left = parseSearchExpressionToString(binary.getLeftOperand()); + String right = parseSearchExpressionToString(binary.getRightOperand()); + + if (binary.getLeftOperand() instanceof SearchBinary) { + left = "(" + left + ")"; + } + + if (binary.getRightOperand() instanceof SearchBinary) { + right = "(" + right + ")"; + } + + if (binary.getOperator() == SearchBinaryOperatorKind.AND + && binary.getRightOperand() instanceof SearchUnary) { + if (((SearchUnary) binary.getRightOperand()).getOperator() == SearchUnaryOperatorKind.NOT) { + return left + " " + right; + } + } + + return left + " " + binary.getOperator().toString() + " " + right; + } else if (searchExpression instanceof SearchUnary) { + SearchUnary unary = (SearchUnary) searchExpression; + String operand = parseSearchExpressionToString(unary.getOperand()); + if (unary.getOperand() instanceof SearchBinary) { + operand = "(" + operand + ")"; + } + return "NOT " + operand; + } return ""; } + private String formatTerm(String term) { + if (term.contains(" ") || term.equals("AND") || term.equals("OR") || term.equals("NOT")) { + return "\"" + term.replace("\\", "\\\\").replace("\"", "\\\\\"") + "\""; + } + return term; + } + public abstract Bson build( SearchExpression searchExpression, QueryStringParsingResult queryStringParsingResult); From 248240ee3b668748c4f243dd04bd9a8659107909 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:00:33 +0100 Subject: [PATCH 11/37] #25 - Fixed tests assertions [skip ci] --- .../search/ODataSearchToMongoSearchParserTest.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy index ef65468..bb10604 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy @@ -48,9 +48,9 @@ class ODataSearchToMongoSearchParserTest extends AbstractSpecification { Bson build(SearchExpression searchExpression, SearchDocumentForQueryStringFactory.QueryStringParsingResult queryStringParsingResult) { return new Document().append("index", "default") .append("queryString", new Document() - .append("path", Arrays.asList("name","description")) .append("query", queryStringParsingResult.getQuery()) - ) + .append("path", Arrays.asList("name","description")) + ) } }) From 87fc7bc3284cae163f8258b790cad83a91ab7466 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:11:28 +0100 Subject: [PATCH 12/37] #25 - Added logic that parse string query --- .../search/SearchDocumentForQueryStringFactory.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java index d62d7ab..986c0d8 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java @@ -16,7 +16,8 @@ public Bson build(SearchExpression searchExpression) { private QueryStringParsingResult pares(SearchExpression searchExpression) { try { - return new QueryStringParsingResult(parseSearchExpressionToString(searchExpression), true, null); + return new QueryStringParsingResult( + parseSearchExpressionToString(searchExpression), true, null); } catch (Exception ex) { return new QueryStringParsingResult(null, false, ex); } @@ -59,7 +60,7 @@ protected String parseSearchExpressionToString(SearchExpression searchExpression private String formatTerm(String term) { if (term.contains(" ") || term.equals("AND") || term.equals("OR") || term.equals("NOT")) { - return "\"" + term.replace("\\", "\\\\").replace("\"", "\\\\\"") + "\""; + return "\"" + term.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; } return term; } From e8f50d8462d5dd1defe41214cb86185756e804c2 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Thu, 12 Mar 2026 00:20:46 +0100 Subject: [PATCH 13/37] #25 - Added MongoDB Atlas container --- compat-driver-5.x/pom.xml | 5 +++ .../jamolingo/MongoAtlasResource.java | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java diff --git a/compat-driver-5.x/pom.xml b/compat-driver-5.x/pom.xml index 3781125..22b9005 100644 --- a/compat-driver-5.x/pom.xml +++ b/compat-driver-5.x/pom.xml @@ -72,6 +72,11 @@ 1.5.3 test + + org.testcontainers + testcontainers + test + com.github.starnowski.jamolingo perf diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java new file mode 100644 index 0000000..6fccba6 --- /dev/null +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java @@ -0,0 +1,42 @@ +package com.github.starnowski.jamolingo; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import java.util.Collections; +import java.util.Map; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +public class MongoAtlasResource implements QuarkusTestResourceLifecycleManager { + + private FixedPortMongoAtlasContainer mongoAtlasContainer; + + @Override + public Map start() { + mongoAtlasContainer = new FixedPortMongoAtlasContainer("mongodb/mongodb-atlas-local:7.0.9"); + mongoAtlasContainer.bindFixedPort(27019, 27017); + mongoAtlasContainer.waitingFor(Wait.forListeningPort()); + + mongoAtlasContainer.start(); + + return Collections.singletonMap( + "quarkus.mongodb.connection-string", "mongodb://localhost:27019"); + } + + @Override + public void stop() { + if (mongoAtlasContainer != null) { + mongoAtlasContainer.stop(); + } + } + + private static class FixedPortMongoAtlasContainer + extends GenericContainer { + public FixedPortMongoAtlasContainer(String image) { + super(image); + } + + public void bindFixedPort(int hostPort, int containerPort) { + super.addFixedExposedPort(hostPort, containerPort); + } + } +} From 15a15c982fc464dbac9ec371745394223756d234 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Thu, 12 Mar 2026 01:13:07 +0100 Subject: [PATCH 14/37] #37 - Renamed component --- ...chParser.java => ODataSearchToMongoAtlasSearchParser.java} | 2 +- ....groovy => ODataSearchToMongoAtlasSearchParserTest.groovy} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/{ODataSearchToMongoSearchParser.java => ODataSearchToMongoAtlasSearchParser.java} (96%) rename core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/{ODataSearchToMongoSearchParserTest.groovy => ODataSearchToMongoAtlasSearchParserTest.groovy} (97%) diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java similarity index 96% rename from core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParser.java rename to core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index f65eb1a..954e0cc 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -5,7 +5,7 @@ import org.bson.Document; import org.bson.conversions.Bson; -public class ODataSearchToMongoSearchParser { +public class ODataSearchToMongoAtlasSearchParser { public SearchOperatorResult parse( SearchOption searchOption, SearchDocumentFactory searchDocumentFactory) { diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy similarity index 97% rename from core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy rename to core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy index bb10604..899ed35 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy @@ -17,7 +17,7 @@ import org.bson.conversions.Bson import org.bson.json.JsonWriterSettings import spock.lang.Unroll -class ODataSearchToMongoSearchParserTest extends AbstractSpecification { +class ODataSearchToMongoAtlasSearchParserTest extends AbstractSpecification { /** @@ -40,7 +40,7 @@ class ODataSearchToMongoSearchParserTest extends AbstractSpecification { .parseUri("examples2", "\$search=" +searchValue , null, null) - ODataSearchToMongoSearchParser tested = new ODataSearchToMongoSearchParser() + ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser() when: def result = tested.parse(uriInfo.getSearchOption(), new SearchDocumentForQueryStringFactory() { From 18b235cb9c3dfc2dba528509becb19407a406e53 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Thu, 12 Mar 2026 01:44:44 +0100 Subject: [PATCH 15/37] #37 - Added Atlas docker tests --- compat-driver-5.x/pom.xml | 1 - .../operators/search/SearchOperatorTest.java | 139 ++++++++++++++++++ .../test/resources/bson/search/search1.json | 8 + .../test/resources/bson/search/search2.json | 8 + 4 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java create mode 100644 compat-driver-5.x/src/test/resources/bson/search/search1.json create mode 100644 compat-driver-5.x/src/test/resources/bson/search/search2.json diff --git a/compat-driver-5.x/pom.xml b/compat-driver-5.x/pom.xml index 22b9005..d3b82bf 100644 --- a/compat-driver-5.x/pom.xml +++ b/compat-driver-5.x/pom.xml @@ -34,7 +34,6 @@ com.github.starnowski.jamolingo core - test com.github.starnowski.jamolingo diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java new file mode 100644 index 0000000..6c14f3b --- /dev/null +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java @@ -0,0 +1,139 @@ +package com.github.starnowski.jamolingo.compat.driver.operators.search; + +import com.github.starnowski.jamolingo.MongoAtlasResource; +import com.github.starnowski.jamolingo.compat.driver.operators.filter.AbstractBaseFilterOperatorTest; +import com.github.starnowski.jamolingo.core.operators.search.ODataSearchToMongoAtlasSearchParser; +import com.github.starnowski.jamolingo.core.operators.search.SearchDocumentForQueryStringFactory; +import com.github.starnowski.jamolingo.core.operators.search.SearchDocumentForQueryStringFactory.QueryStringParsingResult; +import com.github.starnowski.jamolingo.core.operators.search.SearchOperatorResult; +import com.github.starnowski.jamolingo.junit5.MongoDocument; +import com.github.starnowski.jamolingo.junit5.MongoSetup; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import java.util.*; +import java.util.stream.Collectors; +import javax.xml.stream.XMLStreamException; +import org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException; +import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression; +import org.apache.olingo.server.core.uri.parser.Parser; +import org.apache.olingo.server.core.uri.parser.UriParserException; +import org.apache.olingo.server.core.uri.validator.UriValidationException; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@QuarkusTest +@QuarkusTestResource(MongoAtlasResource.class) +public class SearchOperatorTest extends AbstractBaseFilterOperatorTest { + + @ParameterizedTest + @MethodSource("provideSearchTests") + @MongoSetup( + mongoDocuments = { + @MongoDocument( + database = "testdb", + collection = "Items", + bsonFilePath = "bson/search/search1.json"), + @MongoDocument( + database = "testdb", + collection = "Items", + bsonFilePath = "bson/search/search2.json") + }) + public void shouldReturnExpectedDocumentsBasedOnSearchOperator( + String search, Set expectedPlainStrings) + throws UriValidationException, + UriParserException, + XMLStreamException, + ExpressionVisitException, + ODataApplicationException, + InterruptedException { + // GIVEN + MongoDatabase database = mongoClient.getDatabase("testdb"); + MongoCollection collection = database.getCollection("Items"); + ensureSearchIndex(collection); + + Edm edm = loadEmdProvider("edm/edm6_filter_main.xml"); + UriInfo uriInfo = + new Parser(edm, OData.newInstance()).parseUri("examples2", "$search=" + search, null, null); + ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser(); + + // WHEN + SearchOperatorResult result = + tested.parse( + uriInfo.getSearchOption(), + new SearchDocumentForQueryStringFactory() { + @Override + public Bson build( + SearchExpression searchExpression, + QueryStringParsingResult queryStringParsingResult) { + return new Document("index", "atlas_search_index") + .append( + "queryString", + new Document("query", queryStringParsingResult.getQuery()) + .append("path", "plainString")); + } + }); + List pipeline = new ArrayList<>(result.getStageObjects()); + System.out.println(new Document("pipeline", pipeline).toJson()); + + // THEN + List results = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + results.clear(); + collection.aggregate(pipeline).into(results); + if (results.size() == expectedPlainStrings.size()) { + break; + } + Thread.sleep(500); + } + + Assertions.assertEquals(expectedPlainStrings.size(), results.size()); + Set actual = + results.stream() + .map(d -> d.get("plainString")) + .filter(Objects::nonNull) + .map(s -> (String) s) + .collect(Collectors.toSet()); + Assertions.assertEquals(expectedPlainStrings, actual); + } + + private void ensureSearchIndex(MongoCollection collection) { + try { + collection.createSearchIndex( + "atlas_search_index", new Document("mappings", new Document("dynamic", true))); + // Wait for index to be ready + while (true) { + boolean ready = false; + for (Document index : collection.listSearchIndexes()) { + if ("atlas_search_index".equals(index.getString("name")) + && "READY".equals(index.getString("status"))) { + ready = true; + break; + } + } + if (ready) break; + Thread.sleep(500); + } + } catch (Exception e) { + // Index might already exist + } + } + + private static java.util.stream.Stream provideSearchTests() { + return java.util.stream.Stream.of( + Arguments.of("database", Set.of("database search")), + Arguments.of("search", Set.of("database search", "only search")), + Arguments.of("database AND search", Set.of("database search")), + Arguments.of("database OR \"only search\"", Set.of("database search", "only search"))); + } +} diff --git a/compat-driver-5.x/src/test/resources/bson/search/search1.json b/compat-driver-5.x/src/test/resources/bson/search/search1.json new file mode 100644 index 0000000..a9a4b60 --- /dev/null +++ b/compat-driver-5.x/src/test/resources/bson/search/search1.json @@ -0,0 +1,8 @@ +{ + "plainString": "database search", + "tags": [ + "mongo", + "atlas" + ], + "active": true +} diff --git a/compat-driver-5.x/src/test/resources/bson/search/search2.json b/compat-driver-5.x/src/test/resources/bson/search/search2.json new file mode 100644 index 0000000..acaf264 --- /dev/null +++ b/compat-driver-5.x/src/test/resources/bson/search/search2.json @@ -0,0 +1,8 @@ +{ + "plainString": "only search", + "tags": [ + "olingo", + "search" + ], + "active": true +} From 37fa7b53e88948a166ea3c6df829c205dc1bef19 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Thu, 12 Mar 2026 08:44:03 +0100 Subject: [PATCH 16/37] #37 - Tests fix --- compat-driver-5.x/pom.xml | 1 + .../com/github/starnowski/jamolingo/MongoAtlasResource.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compat-driver-5.x/pom.xml b/compat-driver-5.x/pom.xml index d3b82bf..22b9005 100644 --- a/compat-driver-5.x/pom.xml +++ b/compat-driver-5.x/pom.xml @@ -34,6 +34,7 @@ com.github.starnowski.jamolingo core + test com.github.starnowski.jamolingo diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java index 6fccba6..74a4704 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java @@ -19,7 +19,7 @@ public Map start() { mongoAtlasContainer.start(); return Collections.singletonMap( - "quarkus.mongodb.connection-string", "mongodb://localhost:27019"); + "quarkus.mongodb.connection-string", "mongodb://localhost:27019/?directConnection=true"); } @Override From ff467caa0ed1d43b93d293b3afa743b6c29708fd Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:58:10 +0100 Subject: [PATCH 17/37] #37 - Fixed tests --- .../jamolingo/MongoAtlasResource.java | 40 +++++++++++-------- .../operators/search/SearchOperatorTest.java | 9 +++-- .../src/test/resources/application.properties | 3 +- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java index 74a4704..1e620a0 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java @@ -1,6 +1,7 @@ package com.github.starnowski.jamolingo; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import java.time.Duration; import java.util.Collections; import java.util.Map; import org.testcontainers.containers.GenericContainer; @@ -8,18 +9,34 @@ public class MongoAtlasResource implements QuarkusTestResourceLifecycleManager { - private FixedPortMongoAtlasContainer mongoAtlasContainer; + private GenericContainer mongoAtlasContainer; @Override public Map start() { - mongoAtlasContainer = new FixedPortMongoAtlasContainer("mongodb/mongodb-atlas-local:7.0.9"); - mongoAtlasContainer.bindFixedPort(27019, 27017); - mongoAtlasContainer.waitingFor(Wait.forListeningPort()); + mongoAtlasContainer = + new GenericContainer<>("mongodb/mongodb-atlas-local:7.0.11") + .withPrivilegedMode(true) + .withCreateContainerCmdModifier( + cmd -> { + cmd.getHostConfig().withMemory(4 * 1024 * 1024 * 1024L); + cmd.getHostConfig().withShmSize(2 * 1024 * 1024 * 1024L); + }) + .withExposedPorts(27017, 27027) + .waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(5))); mongoAtlasContainer.start(); + try { + Thread.sleep(20000); // Wait 20 seconds for mongot + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + String connectionString = + String.format( + "mongodb://%s:%d/?directConnection=true", + mongoAtlasContainer.getHost(), mongoAtlasContainer.getMappedPort(27017)); - return Collections.singletonMap( - "quarkus.mongodb.connection-string", "mongodb://localhost:27019/?directConnection=true"); + return Collections.singletonMap("quarkus.mongodb.connection-string", connectionString); } @Override @@ -28,15 +45,4 @@ public void stop() { mongoAtlasContainer.stop(); } } - - private static class FixedPortMongoAtlasContainer - extends GenericContainer { - public FixedPortMongoAtlasContainer(String image) { - super(image); - } - - public void bindFixedPort(int hostPort, int containerPort) { - super.addFixedExposedPort(hostPort, containerPort); - } - } } diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java index 6c14f3b..e688894 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java @@ -1,7 +1,7 @@ package com.github.starnowski.jamolingo.compat.driver.operators.search; +import com.github.starnowski.jamolingo.AbstractItTest; import com.github.starnowski.jamolingo.MongoAtlasResource; -import com.github.starnowski.jamolingo.compat.driver.operators.filter.AbstractBaseFilterOperatorTest; import com.github.starnowski.jamolingo.core.operators.search.ODataSearchToMongoAtlasSearchParser; import com.github.starnowski.jamolingo.core.operators.search.SearchDocumentForQueryStringFactory; import com.github.starnowski.jamolingo.core.operators.search.SearchDocumentForQueryStringFactory.QueryStringParsingResult; @@ -13,6 +13,7 @@ import com.mongodb.client.MongoDatabase; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; import java.util.*; import java.util.stream.Collectors; import javax.xml.stream.XMLStreamException; @@ -34,7 +35,9 @@ @QuarkusTest @QuarkusTestResource(MongoAtlasResource.class) -public class SearchOperatorTest extends AbstractBaseFilterOperatorTest { +public class SearchOperatorTest extends AbstractItTest { + + @Inject protected MongoClient mongoClient; @ParameterizedTest @MethodSource("provideSearchTests") @@ -80,7 +83,7 @@ public Bson build( .append( "queryString", new Document("query", queryStringParsingResult.getQuery()) - .append("path", "plainString")); + .append("defaultPath", "plainString")); } }); List pipeline = new ArrayList<>(result.getStageObjects()); diff --git a/compat-driver-5.x/src/test/resources/application.properties b/compat-driver-5.x/src/test/resources/application.properties index 40b5083..d31ec51 100644 --- a/compat-driver-5.x/src/test/resources/application.properties +++ b/compat-driver-5.x/src/test/resources/application.properties @@ -1 +1,2 @@ -quarkus.mongodb.uuid-representation=STANDARD \ No newline at end of file +quarkus.mongodb.uuid-representation=STANDARD +quarkus.mongodb.devservices.enabled=false \ No newline at end of file From 63a1552c86441738d306d5da3ac54a5e73668a63 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:04:29 +0100 Subject: [PATCH 18/37] #37 - Code refactoring [skip ci] --- .../operators/search/SearchOperatorTest.java | 27 +++++++++---------- .../ODataSearchToMongoAtlasSearchOptions.java | 4 +++ .../ODataSearchToMongoAtlasSearchParser.java | 19 ++++++++++--- .../ODataSearchToMongoTextSearchOptions.java | 4 +++ .../ODataSearchToMongoTextSearchParser.java | 12 +++++++++ ...aSearchToMongoAtlasSearchParserTest.groovy | 10 +++---- 6 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java create mode 100644 core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java create mode 100644 core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java index e688894..931a17e 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java @@ -68,24 +68,23 @@ public void shouldReturnExpectedDocumentsBasedOnSearchOperator( Edm edm = loadEmdProvider("edm/edm6_filter_main.xml"); UriInfo uriInfo = new Parser(edm, OData.newInstance()).parseUri("examples2", "$search=" + search, null, null); - ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser(); + ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser(new SearchDocumentForQueryStringFactory() { + @Override + public Bson build( + SearchExpression searchExpression, + QueryStringParsingResult queryStringParsingResult) { + return new Document("index", "atlas_search_index") + .append( + "queryString", + new Document("query", queryStringParsingResult.getQuery()) + .append("defaultPath", "plainString")); + } + }); // WHEN SearchOperatorResult result = tested.parse( - uriInfo.getSearchOption(), - new SearchDocumentForQueryStringFactory() { - @Override - public Bson build( - SearchExpression searchExpression, - QueryStringParsingResult queryStringParsingResult) { - return new Document("index", "atlas_search_index") - .append( - "queryString", - new Document("query", queryStringParsingResult.getQuery()) - .append("defaultPath", "plainString")); - } - }); + uriInfo.getSearchOption()); List pipeline = new ArrayList<>(result.getStageObjects()); System.out.println(new Document("pipeline", pipeline).toJson()); diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java new file mode 100644 index 0000000..64c8759 --- /dev/null +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java @@ -0,0 +1,4 @@ +package com.github.starnowski.jamolingo.core.operators.search; + +public interface ODataSearchToMongoAtlasSearchOptions extends ODataSearchToMongoTextSearchOptions { +} diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index 954e0cc..3fb8c25 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -5,12 +5,23 @@ import org.bson.Document; import org.bson.conversions.Bson; -public class ODataSearchToMongoAtlasSearchParser { +public class ODataSearchToMongoAtlasSearchParser implements ODataSearchToMongoTextSearchParser{ - public SearchOperatorResult parse( - SearchOption searchOption, SearchDocumentFactory searchDocumentFactory) { + private final SearchDocumentFactory searchDocumentFactory; + + public ODataSearchToMongoAtlasSearchParser(SearchDocumentFactory searchDocumentFactory) { + this.searchDocumentFactory = searchDocumentFactory; + } + + @Override + public SearchOperatorResult parse(SearchOption searchOption) { + return parse(searchOption, null); + } + + @Override + public SearchOperatorResult parse(SearchOption searchOption, ODataSearchToMongoAtlasSearchOptions options) { return new DefaultSearchOperatorResult( - new Document("$search", searchDocumentFactory.build(searchOption.getSearchExpression()))); + new Document("$search", searchDocumentFactory.build(searchOption.getSearchExpression()))); } private static class DefaultSearchOperatorResult implements SearchOperatorResult { diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java new file mode 100644 index 0000000..2c303bf --- /dev/null +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java @@ -0,0 +1,4 @@ +package com.github.starnowski.jamolingo.core.operators.search; + +public interface ODataSearchToMongoTextSearchOptions { +} diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java new file mode 100644 index 0000000..d32c493 --- /dev/null +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java @@ -0,0 +1,12 @@ +package com.github.starnowski.jamolingo.core.operators.search; + +import org.apache.olingo.server.api.uri.queryoption.SearchOption; + +public interface ODataSearchToMongoTextSearchParser { + + SearchOperatorResult parse( + SearchOption searchOption); + + SearchOperatorResult parse( + SearchOption searchOption, T options); +} diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy index 899ed35..27cd883 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy @@ -40,20 +40,20 @@ class ODataSearchToMongoAtlasSearchParserTest extends AbstractSpecification { .parseUri("examples2", "\$search=" +searchValue , null, null) - ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser() - - when: - def result = tested.parse(uriInfo.getSearchOption(), new SearchDocumentForQueryStringFactory() { + ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser(new SearchDocumentForQueryStringFactory() { @Override Bson build(SearchExpression searchExpression, SearchDocumentForQueryStringFactory.QueryStringParsingResult queryStringParsingResult) { return new Document().append("index", "default") .append("queryString", new Document() .append("query", queryStringParsingResult.getQuery()) .append("path", Arrays.asList("name","description")) - ) + ) } }) + when: + def result = tested.parse(uriInfo.getSearchOption()) + then: [((Document)result.getStageObjects().get(0)).toJson(settings, codec)] == [expectedBson.toJson(settings, codec)] From f781d8bc474b1fc8b7c622033057c596d98b2acb Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:06:41 +0100 Subject: [PATCH 19/37] #37 - Code formating --- .../operators/search/SearchOperatorTest.java | 26 +++++++++---------- .../ODataSearchToMongoAtlasSearchOptions.java | 3 +-- .../ODataSearchToMongoAtlasSearchParser.java | 14 +++++----- .../ODataSearchToMongoTextSearchOptions.java | 3 +-- .../ODataSearchToMongoTextSearchParser.java | 6 ++--- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java index 931a17e..655e3b0 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java @@ -68,23 +68,23 @@ public void shouldReturnExpectedDocumentsBasedOnSearchOperator( Edm edm = loadEmdProvider("edm/edm6_filter_main.xml"); UriInfo uriInfo = new Parser(edm, OData.newInstance()).parseUri("examples2", "$search=" + search, null, null); - ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser(new SearchDocumentForQueryStringFactory() { - @Override - public Bson build( - SearchExpression searchExpression, - QueryStringParsingResult queryStringParsingResult) { - return new Document("index", "atlas_search_index") - .append( + ODataSearchToMongoAtlasSearchParser tested = + new ODataSearchToMongoAtlasSearchParser( + new SearchDocumentForQueryStringFactory() { + @Override + public Bson build( + SearchExpression searchExpression, + QueryStringParsingResult queryStringParsingResult) { + return new Document("index", "atlas_search_index") + .append( "queryString", new Document("query", queryStringParsingResult.getQuery()) - .append("defaultPath", "plainString")); - } - }); + .append("defaultPath", "plainString")); + } + }); // WHEN - SearchOperatorResult result = - tested.parse( - uriInfo.getSearchOption()); + SearchOperatorResult result = tested.parse(uriInfo.getSearchOption()); List pipeline = new ArrayList<>(result.getStageObjects()); System.out.println(new Document("pipeline", pipeline).toJson()); diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java index 64c8759..b874a47 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java @@ -1,4 +1,3 @@ package com.github.starnowski.jamolingo.core.operators.search; -public interface ODataSearchToMongoAtlasSearchOptions extends ODataSearchToMongoTextSearchOptions { -} +public interface ODataSearchToMongoAtlasSearchOptions extends ODataSearchToMongoTextSearchOptions {} diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index 3fb8c25..18fee49 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -5,13 +5,14 @@ import org.bson.Document; import org.bson.conversions.Bson; -public class ODataSearchToMongoAtlasSearchParser implements ODataSearchToMongoTextSearchParser{ +public class ODataSearchToMongoAtlasSearchParser + implements ODataSearchToMongoTextSearchParser { private final SearchDocumentFactory searchDocumentFactory; - public ODataSearchToMongoAtlasSearchParser(SearchDocumentFactory searchDocumentFactory) { - this.searchDocumentFactory = searchDocumentFactory; - } + public ODataSearchToMongoAtlasSearchParser(SearchDocumentFactory searchDocumentFactory) { + this.searchDocumentFactory = searchDocumentFactory; + } @Override public SearchOperatorResult parse(SearchOption searchOption) { @@ -19,9 +20,10 @@ public SearchOperatorResult parse(SearchOption searchOption) { } @Override - public SearchOperatorResult parse(SearchOption searchOption, ODataSearchToMongoAtlasSearchOptions options) { + public SearchOperatorResult parse( + SearchOption searchOption, ODataSearchToMongoAtlasSearchOptions options) { return new DefaultSearchOperatorResult( - new Document("$search", searchDocumentFactory.build(searchOption.getSearchExpression()))); + new Document("$search", searchDocumentFactory.build(searchOption.getSearchExpression()))); } private static class DefaultSearchOperatorResult implements SearchOperatorResult { diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java index 2c303bf..cdb3d75 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java @@ -1,4 +1,3 @@ package com.github.starnowski.jamolingo.core.operators.search; -public interface ODataSearchToMongoTextSearchOptions { -} +public interface ODataSearchToMongoTextSearchOptions {} diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java index d32c493..325a17b 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java @@ -4,9 +4,7 @@ public interface ODataSearchToMongoTextSearchParser { - SearchOperatorResult parse( - SearchOption searchOption); + SearchOperatorResult parse(SearchOption searchOption); - SearchOperatorResult parse( - SearchOption searchOption, T options); + SearchOperatorResult parse(SearchOption searchOption, T options); } From 96fd67fc8a753248535cccf3c8b9523803e0fa81 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:08:15 +0100 Subject: [PATCH 20/37] #37 - Updated project version --- common/json/pom.xml | 2 +- common/pom.xml | 2 +- compat-driver-5.x/pom.xml | 2 +- core/pom.xml | 2 +- demos/pom.xml | 2 +- demos/quarkus-webapp/pom.xml | 2 +- demos/spring-boot-webapp/pom.xml | 2 +- .../junit5-mongo-extension-quarkus/pom.xml | 2 +- .../junit5-mongo-extension-spring/pom.xml | 2 +- junit5-mongo-extension-parent/junit5-mongo-extension/pom.xml | 2 +- junit5-mongo-extension-parent/pom.xml | 2 +- perf/pom.xml | 2 +- pom.xml | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/common/json/pom.xml b/common/json/pom.xml index fbadfc2..822aafa 100644 --- a/common/json/pom.xml +++ b/common/json/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo common - 0.7.0 + 0.8.0-SNAPSHOT json diff --git a/common/pom.xml b/common/pom.xml index c26557a..a5c66f6 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo parent - 0.7.0 + 0.8.0-SNAPSHOT pom diff --git a/compat-driver-5.x/pom.xml b/compat-driver-5.x/pom.xml index 22b9005..6791668 100644 --- a/compat-driver-5.x/pom.xml +++ b/compat-driver-5.x/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo parent - 0.7.0 + 0.8.0-SNAPSHOT compat-driver-5.x diff --git a/core/pom.xml b/core/pom.xml index 8e32954..f773372 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo parent - 0.7.0 + 0.8.0-SNAPSHOT core diff --git a/demos/pom.xml b/demos/pom.xml index 44970f8..c15084a 100644 --- a/demos/pom.xml +++ b/demos/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo parent - 0.7.0 + 0.8.0-SNAPSHOT demos diff --git a/demos/quarkus-webapp/pom.xml b/demos/quarkus-webapp/pom.xml index fd6f699..44f23b9 100644 --- a/demos/quarkus-webapp/pom.xml +++ b/demos/quarkus-webapp/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo demos - 0.7.0 + 0.8.0-SNAPSHOT quarkus-webapp diff --git a/demos/spring-boot-webapp/pom.xml b/demos/spring-boot-webapp/pom.xml index d038314..4e51e29 100644 --- a/demos/spring-boot-webapp/pom.xml +++ b/demos/spring-boot-webapp/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo demos - 0.7.0 + 0.8.0-SNAPSHOT spring-boot-webapp diff --git a/junit5-mongo-extension-parent/junit5-mongo-extension-quarkus/pom.xml b/junit5-mongo-extension-parent/junit5-mongo-extension-quarkus/pom.xml index 148d071..f6915c8 100644 --- a/junit5-mongo-extension-parent/junit5-mongo-extension-quarkus/pom.xml +++ b/junit5-mongo-extension-parent/junit5-mongo-extension-quarkus/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo junit5-mongo-extension-parent - 0.7.0 + 0.8.0-SNAPSHOT junit5-mongo-extension-quarkus diff --git a/junit5-mongo-extension-parent/junit5-mongo-extension-spring/pom.xml b/junit5-mongo-extension-parent/junit5-mongo-extension-spring/pom.xml index 04b89ed..c51eb21 100644 --- a/junit5-mongo-extension-parent/junit5-mongo-extension-spring/pom.xml +++ b/junit5-mongo-extension-parent/junit5-mongo-extension-spring/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo junit5-mongo-extension-parent - 0.7.0 + 0.8.0-SNAPSHOT junit5-mongo-extension-spring diff --git a/junit5-mongo-extension-parent/junit5-mongo-extension/pom.xml b/junit5-mongo-extension-parent/junit5-mongo-extension/pom.xml index f13ca63..4330470 100644 --- a/junit5-mongo-extension-parent/junit5-mongo-extension/pom.xml +++ b/junit5-mongo-extension-parent/junit5-mongo-extension/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo junit5-mongo-extension-parent - 0.7.0 + 0.8.0-SNAPSHOT junit5-mongo-extension diff --git a/junit5-mongo-extension-parent/pom.xml b/junit5-mongo-extension-parent/pom.xml index 7d55f50..be24f4d 100644 --- a/junit5-mongo-extension-parent/pom.xml +++ b/junit5-mongo-extension-parent/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo parent - 0.7.0 + 0.8.0-SNAPSHOT pom diff --git a/perf/pom.xml b/perf/pom.xml index 3e2fb15..229b96b 100644 --- a/perf/pom.xml +++ b/perf/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo parent - 0.7.0 + 0.8.0-SNAPSHOT perf diff --git a/pom.xml b/pom.xml index b8a247f..f38ccaf 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.starnowski.jamolingo parent - 0.7.0 + 0.8.0-SNAPSHOT pom ${project.groupId}:${project.artifactId} From ad1ed43b083c543fb02524e66d33e022fdc43f35 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:19:43 +0100 Subject: [PATCH 21/37] #37 - Code refactoring --- .../search/ODataSearchToMongoAtlasSearchParser.java | 9 +++++---- .../search/ODataSearchToMongoTextSearchParser.java | 7 ++++--- .../search/SearchOperatorResultForAtlasSearch.java | 3 +++ 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResultForAtlasSearch.java diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index 18fee49..ca36d9e 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -6,7 +6,8 @@ import org.bson.conversions.Bson; public class ODataSearchToMongoAtlasSearchParser - implements ODataSearchToMongoTextSearchParser { + implements ODataSearchToMongoTextSearchParser< + ODataSearchToMongoAtlasSearchOptions, SearchOperatorResultForAtlasSearch> { private final SearchDocumentFactory searchDocumentFactory; @@ -15,18 +16,18 @@ public ODataSearchToMongoAtlasSearchParser(SearchDocumentFactory searchDocumentF } @Override - public SearchOperatorResult parse(SearchOption searchOption) { + public SearchOperatorResultForAtlasSearch parse(SearchOption searchOption) { return parse(searchOption, null); } @Override - public SearchOperatorResult parse( + public SearchOperatorResultForAtlasSearch parse( SearchOption searchOption, ODataSearchToMongoAtlasSearchOptions options) { return new DefaultSearchOperatorResult( new Document("$search", searchDocumentFactory.build(searchOption.getSearchExpression()))); } - private static class DefaultSearchOperatorResult implements SearchOperatorResult { + private static class DefaultSearchOperatorResult implements SearchOperatorResultForAtlasSearch { private final List stageObjects; diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java index 325a17b..56e8efd 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java @@ -2,9 +2,10 @@ import org.apache.olingo.server.api.uri.queryoption.SearchOption; -public interface ODataSearchToMongoTextSearchParser { +public interface ODataSearchToMongoTextSearchParser< + T extends ODataSearchToMongoTextSearchOptions, R extends SearchOperatorResult> { - SearchOperatorResult parse(SearchOption searchOption); + R parse(SearchOption searchOption); - SearchOperatorResult parse(SearchOption searchOption, T options); + R parse(SearchOption searchOption, T options); } diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResultForAtlasSearch.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResultForAtlasSearch.java new file mode 100644 index 0000000..8022c36 --- /dev/null +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResultForAtlasSearch.java @@ -0,0 +1,3 @@ +package com.github.starnowski.jamolingo.core.operators.search; + +public interface SearchOperatorResultForAtlasSearch extends SearchOperatorResult {} From df34fd7ae572ea755a842b1b0abd29daf3b226fd Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:32:44 +0100 Subject: [PATCH 22/37] #37 - Added stage that filters documents based on the text score --- .../operators/search/SearchOperatorTest.java | 28 +++++++ ...tODataSearchToMongoAtlasSearchOptions.java | 78 +++++++++++++++++++ .../ODataSearchToMongoAtlasSearchParser.java | 35 ++++++++- .../ODataSearchToMongoTextSearchOptions.java | 17 +++- 4 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java index 655e3b0..35fc8fb 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java @@ -2,6 +2,8 @@ import com.github.starnowski.jamolingo.AbstractItTest; import com.github.starnowski.jamolingo.MongoAtlasResource; +import com.github.starnowski.jamolingo.core.operators.search.DefaultODataSearchToMongoAtlasSearchOptions; +import com.github.starnowski.jamolingo.core.operators.search.ODataSearchToMongoAtlasSearchOptions; import com.github.starnowski.jamolingo.core.operators.search.ODataSearchToMongoAtlasSearchParser; import com.github.starnowski.jamolingo.core.operators.search.SearchDocumentForQueryStringFactory; import com.github.starnowski.jamolingo.core.operators.search.SearchDocumentForQueryStringFactory.QueryStringParsingResult; @@ -29,6 +31,7 @@ import org.bson.Document; import org.bson.conversions.Bson; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -39,6 +42,31 @@ public class SearchOperatorTest extends AbstractItTest { @Inject protected MongoClient mongoClient; + @Test + public void shouldAddMatchStageWhenDefaultTextScoreIsProvided() + throws UriValidationException, UriParserException { + // GIVEN + Edm edm = loadEmdProvider("edm/edm6_filter_main.xml"); + UriInfo uriInfo = + new Parser(edm, OData.newInstance()).parseUri("examples2", "$search=database", null, null); + ODataSearchToMongoAtlasSearchParser tested = + new ODataSearchToMongoAtlasSearchParser( + searchExpression -> new Document("queryString", new Document("query", "database"))); + ODataSearchToMongoAtlasSearchOptions options = + DefaultODataSearchToMongoAtlasSearchOptions.builder().withDefaultTextScore(1.5).build(); + + // WHEN + SearchOperatorResult result = tested.parse(uriInfo.getSearchOption(), options); + + // THEN + List stages = result.getStageObjects(); + Assertions.assertEquals(2, stages.size()); + Assertions.assertTrue(((Document) stages.get(0)).containsKey("$search")); + Assertions.assertTrue(((Document) stages.get(1)).containsKey("$match")); + Document matchStage = (Document) ((Document) stages.get(1)).get("$match"); + Assertions.assertEquals(new Document("$gte", 1.5), matchStage.get("score")); + } + @ParameterizedTest @MethodSource("provideSearchTests") @MongoSetup( diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java new file mode 100644 index 0000000..daf02b3 --- /dev/null +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java @@ -0,0 +1,78 @@ +package com.github.starnowski.jamolingo.core.operators.search; + +import java.util.Objects; + +/** Default implementation of ODataSearchToMongoAtlasSearchOptions. */ +public class DefaultODataSearchToMongoAtlasSearchOptions + implements ODataSearchToMongoAtlasSearchOptions { + + private Double defaultTextScore; + + @Override + public void setDefaultTextScore(Double defaultTextScore) { + this.defaultTextScore = defaultTextScore; + } + + @Override + public Double getDefaultTextScore() { + return defaultTextScore; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultODataSearchToMongoAtlasSearchOptions that = + (DefaultODataSearchToMongoAtlasSearchOptions) o; + return Objects.equals(defaultTextScore, that.defaultTextScore); + } + + @Override + public int hashCode() { + return Objects.hash(defaultTextScore); + } + + @Override + public String toString() { + return "DefaultODataSearchToMongoAtlasSearchOptions{" + + "defaultTextScore=" + + defaultTextScore + + '}'; + } + + /** + * Returns a new builder for DefaultODataSearchToMongoAtlasSearchOptions. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** Builder for DefaultODataSearchToMongoAtlasSearchOptions. */ + public static class Builder { + + private Double defaultTextScore; + + public Builder withDefaultTextScore(Double defaultTextScore) { + this.defaultTextScore = defaultTextScore; + return this; + } + + /** + * Builds a new DefaultODataSearchToMongoAtlasSearchOptions instance. + * + * @return a new instance + */ + public DefaultODataSearchToMongoAtlasSearchOptions build() { + DefaultODataSearchToMongoAtlasSearchOptions options = + new DefaultODataSearchToMongoAtlasSearchOptions(); + options.setDefaultTextScore(defaultTextScore); + return options; + } + } +} diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index ca36d9e..5300614 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -1,5 +1,7 @@ package com.github.starnowski.jamolingo.core.operators.search; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.apache.olingo.server.api.uri.queryoption.SearchOption; import org.bson.Document; @@ -23,16 +25,41 @@ public SearchOperatorResultForAtlasSearch parse(SearchOption searchOption) { @Override public SearchOperatorResultForAtlasSearch parse( SearchOption searchOption, ODataSearchToMongoAtlasSearchOptions options) { - return new DefaultSearchOperatorResult( - new Document("$search", searchDocumentFactory.build(searchOption.getSearchExpression()))); + List stages = new ArrayList<>(); + Document searchStage = + new Document("$search", searchDocumentFactory.build(searchOption.getSearchExpression())); + stages.add(searchStage); + if (options != null && options.getDefaultTextScore() != null) { + searchStage + .get("$search", Document.class) + .append("scoreDetails", true); // Optional, but can be useful + // We need to project the score to use it in $match + // But $search in Atlas can also use 'score' in some ways? + // Actually, to use { $meta: "searchScore" } in $match, it's NOT possible in Atlas Search + // directly + // unless it's projected first. + // Wait, MongoDB docs say: + // You can use $meta in $match only if it was already projected. + // BUT for Atlas Search, we often use the 'score' field if we project it. + + // Re-reading prompt: "checks if value of '{ $meta: \"textScore\" }' is larger or equal" + // This refers to standard MongoDB Text Search, but this parser is for Atlas Search. + // For Atlas Search it is "searchScore". + + stages.add( + new Document( + "$match", + new Document("score", new Document("$gte", options.getDefaultTextScore())))); + } + return new DefaultSearchOperatorResult(stages); } private static class DefaultSearchOperatorResult implements SearchOperatorResultForAtlasSearch { private final List stageObjects; - private DefaultSearchOperatorResult(Bson searchStage) { - this.stageObjects = List.of(searchStage); + private DefaultSearchOperatorResult(List stages) { + this.stageObjects = Collections.unmodifiableList(stages); } @Override diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java index cdb3d75..aeddebb 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java @@ -1,3 +1,18 @@ package com.github.starnowski.jamolingo.core.operators.search; -public interface ODataSearchToMongoTextSearchOptions {} +public interface ODataSearchToMongoTextSearchOptions { + + /** + * Sets the minimum text score required for a document to be returned. + * + * @param defaultTextScore the minimum text score + */ + void setDefaultTextScore(Double defaultTextScore); + + /** + * Returns the minimum text score required for a document to be returned. + * + * @return the minimum text score, or null if not set + */ + Double getDefaultTextScore(); +} From fca5c5f0ffe6f063e3a6c08bc69a5ffa21d153f4 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Sat, 14 Mar 2026 20:45:17 +0100 Subject: [PATCH 23/37] #37 - Fixed Atlas container starting --- .../jamolingo/MongoAtlasResource.java | 38 +++++++++++++++---- .../operators/search/SearchOperatorTest.java | 2 +- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java index 1e620a0..22273e6 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java @@ -1,9 +1,11 @@ package com.github.starnowski.jamolingo; +import com.mongodb.client.MongoCollection; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import java.time.Duration; import java.util.Collections; import java.util.Map; +import org.bson.Document; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; @@ -22,23 +24,45 @@ public Map start() { cmd.getHostConfig().withShmSize(2 * 1024 * 1024 * 1024L); }) .withExposedPorts(27017, 27027) + .withEnv("MONGOT_LOG_FILE", "/dev/stdout") .waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(5))); mongoAtlasContainer.start(); - try { - Thread.sleep(20000); // Wait 20 seconds for mongot - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - + Wait.forLogMessage(".*Starting TCP server.*", 2) + .withStartupTimeout(Duration.ofSeconds(30)) + .waitUntilReady(mongoAtlasContainer); + Wait.forLogMessage(".*Starting message server.*", 2) + .withStartupTimeout(Duration.ofSeconds(30)) + .waitUntilReady(mongoAtlasContainer); String connectionString = String.format( "mongodb://%s:%d/?directConnection=true", mongoAtlasContainer.getHost(), mongoAtlasContainer.getMappedPort(27017)); - return Collections.singletonMap("quarkus.mongodb.connection-string", connectionString); } + private void ensureSearchIndex(MongoCollection collection) { + try { + collection.createSearchIndex( + "test_index", new Document("mappings", new Document("dynamic", true))); + // Wait for index to be ready + while (true) { + boolean ready = false; + for (Document index : collection.listSearchIndexes()) { + if ("test_index".equals(index.getString("name")) + && "READY".equals(index.getString("status"))) { + ready = true; + break; + } + } + if (ready) break; + Thread.sleep(500); + } + } catch (Exception e) { + // Index might already exist + } + } + @Override public void stop() { if (mongoAtlasContainer != null) { diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java index 35fc8fb..5feb52c 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java @@ -44,7 +44,7 @@ public class SearchOperatorTest extends AbstractItTest { @Test public void shouldAddMatchStageWhenDefaultTextScoreIsProvided() - throws UriValidationException, UriParserException { + throws UriValidationException, UriParserException, XMLStreamException { // GIVEN Edm edm = loadEmdProvider("edm/edm6_filter_main.xml"); UriInfo uriInfo = From ab10f865f154355cfdb69b21bb839b7304802852 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:25:26 +0100 Subject: [PATCH 24/37] #37 - Added the $match that filter based on text search score --- .../ODataSearchToMongoAtlasSearchParser.java | 31 ++++++++++--- .../search/SearchDocumentFactory.java | 2 +- .../SearchDocumentForQueryStringFactory.java | 9 ++-- .../search/SearchOperatorResult.java | 19 +++++++- ...aSearchToMongoAtlasSearchParserTest.groovy | 43 ++++++++++++++++++- 5 files changed, 92 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index 5300614..27c81bf 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -25,10 +25,12 @@ public SearchOperatorResultForAtlasSearch parse(SearchOption searchOption) { @Override public SearchOperatorResultForAtlasSearch parse( SearchOption searchOption, ODataSearchToMongoAtlasSearchOptions options) { - List stages = new ArrayList<>(); + List searchStages = new ArrayList<>(); + List scoreFilterStages = new ArrayList<>(); Document searchStage = - new Document("$search", searchDocumentFactory.build(searchOption.getSearchExpression())); - stages.add(searchStage); + new Document( + "$search", searchDocumentFactory.build(searchOption.getSearchExpression(), options)); + searchStages.add(searchStage); if (options != null && options.getDefaultTextScore() != null) { searchStage .get("$search", Document.class) @@ -46,20 +48,27 @@ public SearchOperatorResultForAtlasSearch parse( // This refers to standard MongoDB Text Search, but this parser is for Atlas Search. // For Atlas Search it is "searchScore". - stages.add( + scoreFilterStages.add( new Document( "$match", new Document("score", new Document("$gte", options.getDefaultTextScore())))); } - return new DefaultSearchOperatorResult(stages); + List allStages = new ArrayList<>(searchStages); + allStages.addAll(scoreFilterStages); + return new DefaultSearchOperatorResult(allStages, searchStages, scoreFilterStages); } private static class DefaultSearchOperatorResult implements SearchOperatorResultForAtlasSearch { private final List stageObjects; + private final List searchStages; + private final List scoreFilterStages; - private DefaultSearchOperatorResult(List stages) { + private DefaultSearchOperatorResult( + List stages, List searchStages, List scoreFilterStages) { this.stageObjects = Collections.unmodifiableList(stages); + this.searchStages = Collections.unmodifiableList(searchStages); + this.scoreFilterStages = Collections.unmodifiableList(scoreFilterStages); } @Override @@ -67,6 +76,16 @@ public List getStageObjects() { return stageObjects; } + @Override + public List getSearchStages() { + return searchStages; + } + + @Override + public List getScoreFilterStages() { + return scoreFilterStages; + } + @Override public List getUsedMongoDocumentProperties() { return List.of(); diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java index cd7efd5..33a2316 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java @@ -5,5 +5,5 @@ public interface SearchDocumentFactory { - Bson build(SearchExpression searchExpression); + Bson build(SearchExpression searchExpression, ODataSearchToMongoAtlasSearchOptions options); } diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java index 986c0d8..ff1c151 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java @@ -10,8 +10,9 @@ public abstract class SearchDocumentForQueryStringFactory implements SearchDocumentFactory { @Override - public Bson build(SearchExpression searchExpression) { - return build(searchExpression, pares(searchExpression)); + public Bson build( + SearchExpression searchExpression, ODataSearchToMongoAtlasSearchOptions options) { + return build(searchExpression, pares(searchExpression), options); } private QueryStringParsingResult pares(SearchExpression searchExpression) { @@ -66,7 +67,9 @@ private String formatTerm(String term) { } public abstract Bson build( - SearchExpression searchExpression, QueryStringParsingResult queryStringParsingResult); + SearchExpression searchExpression, + QueryStringParsingResult queryStringParsingResult, + ODataSearchToMongoAtlasSearchOptions options); public static class QueryStringParsingResult { diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResult.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResult.java index a89e879..5c098ad 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResult.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResult.java @@ -1,5 +1,22 @@ package com.github.starnowski.jamolingo.core.operators.search; import com.github.starnowski.jamolingo.core.operators.OlingoOperatorResult; +import java.util.List; +import org.bson.conversions.Bson; -public interface SearchOperatorResult extends OlingoOperatorResult {} +public interface SearchOperatorResult extends OlingoOperatorResult { + + /** + * MongoDB aggregation pipeline stages related to search operations. + * + * @return list of Bson objects representing the stages + */ + List getSearchStages(); + + /** + * MongoDB aggregation pipeline stages that filter documents based on the score value. + * + * @return list of Bson objects representing the stages + */ + List getScoreFilterStages(); +} diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy index 27cd883..76c79b5 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy @@ -20,6 +20,47 @@ import spock.lang.Unroll class ODataSearchToMongoAtlasSearchParserTest extends AbstractSpecification { + def "should return separate search and score filter stages"(){ + given: + Edm edm = loadEmdProvider("edm/edm6_filter_main.xml") + JsonWriterSettings settings = JsonWriterSettings.builder().build() + CodecRegistry registry = CodecRegistries.fromRegistries( + CodecRegistries.fromProviders(new UuidCodecProvider(UuidRepresentation.STANDARD)), + MongoClientSettings.getDefaultCodecRegistry() + ) + DocumentCodec codec = new DocumentCodec(registry) + + UriInfo uriInfo = new Parser(edm, OData.newInstance()) + .parseUri("examples2", + "\$search=database" + , null, null) + ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser(new SearchDocumentForQueryStringFactory() { + @Override + Bson build(SearchExpression searchExpression, SearchDocumentForQueryStringFactory.QueryStringParsingResult queryStringParsingResult, ODataSearchToMongoAtlasSearchOptions options) { + return new Document().append("index", "default") + .append("queryString", new Document() + .append("query", queryStringParsingResult.getQuery()) + .append("path", Arrays.asList("name","description")) + ) + } + }) + ODataSearchToMongoAtlasSearchOptions options = DefaultODataSearchToMongoAtlasSearchOptions.builder().withDefaultTextScore(0.5d).build() + + when: + def result = tested.parse(uriInfo.getSearchOption(), options) + + then: + result.getSearchStages().size() == 1 + ((Document)result.getSearchStages().get(0)).get("\$search") != null + ((Document)result.getSearchStages().get(0)).get("\$search", Document.class).get("scoreDetails") == true + result.getScoreFilterStages().size() == 1 + ((Document)result.getScoreFilterStages().get(0)).get("\$match") != null + ((Document)result.getScoreFilterStages().get(0)).get("\$match", Document.class).get("score", Document.class).get("\$gte") == 0.5d + result.getStageObjects().size() == 2 + result.getStageObjects().get(0) == result.getSearchStages().get(0) + result.getStageObjects().get(1) == result.getScoreFilterStages().get(0) + } + /** * Verifies that the generated MongoDB $match stage matches the expected BSON document. */ @@ -42,7 +83,7 @@ class ODataSearchToMongoAtlasSearchParserTest extends AbstractSpecification { , null, null) ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser(new SearchDocumentForQueryStringFactory() { @Override - Bson build(SearchExpression searchExpression, SearchDocumentForQueryStringFactory.QueryStringParsingResult queryStringParsingResult) { + Bson build(SearchExpression searchExpression, SearchDocumentForQueryStringFactory.QueryStringParsingResult queryStringParsingResult, ODataSearchToMongoAtlasSearchOptions options) { return new Document().append("index", "default") .append("queryString", new Document() .append("query", queryStringParsingResult.getQuery()) From 6f4a1c02e0b6421166a2df1633b0ff70f8244f08 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:13:52 +0100 Subject: [PATCH 25/37] #37 - Setting the score field with score results [skip ci] --- .../operators/search/SearchOperatorTest.java | 85 +++++++++++++------ .../ODataSearchToMongoAtlasSearchParser.java | 15 +--- 2 files changed, 62 insertions(+), 38 deletions(-) diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java index 5feb52c..41d06e5 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/compat/driver/operators/search/SearchOperatorTest.java @@ -42,31 +42,6 @@ public class SearchOperatorTest extends AbstractItTest { @Inject protected MongoClient mongoClient; - @Test - public void shouldAddMatchStageWhenDefaultTextScoreIsProvided() - throws UriValidationException, UriParserException, XMLStreamException { - // GIVEN - Edm edm = loadEmdProvider("edm/edm6_filter_main.xml"); - UriInfo uriInfo = - new Parser(edm, OData.newInstance()).parseUri("examples2", "$search=database", null, null); - ODataSearchToMongoAtlasSearchParser tested = - new ODataSearchToMongoAtlasSearchParser( - searchExpression -> new Document("queryString", new Document("query", "database"))); - ODataSearchToMongoAtlasSearchOptions options = - DefaultODataSearchToMongoAtlasSearchOptions.builder().withDefaultTextScore(1.5).build(); - - // WHEN - SearchOperatorResult result = tested.parse(uriInfo.getSearchOption(), options); - - // THEN - List stages = result.getStageObjects(); - Assertions.assertEquals(2, stages.size()); - Assertions.assertTrue(((Document) stages.get(0)).containsKey("$search")); - Assertions.assertTrue(((Document) stages.get(1)).containsKey("$match")); - Document matchStage = (Document) ((Document) stages.get(1)).get("$match"); - Assertions.assertEquals(new Document("$gte", 1.5), matchStage.get("score")); - } - @ParameterizedTest @MethodSource("provideSearchTests") @MongoSetup( @@ -102,7 +77,8 @@ public void shouldReturnExpectedDocumentsBasedOnSearchOperator( @Override public Bson build( SearchExpression searchExpression, - QueryStringParsingResult queryStringParsingResult) { + QueryStringParsingResult queryStringParsingResult, + ODataSearchToMongoAtlasSearchOptions options) { return new Document("index", "atlas_search_index") .append( "queryString", @@ -137,6 +113,63 @@ public Bson build( Assertions.assertEquals(expectedPlainStrings, actual); } + @Test + @MongoSetup( + mongoDocuments = { + @MongoDocument( + database = "testdb", + collection = "Items", + bsonFilePath = "bson/search/search1.json"), + @MongoDocument( + database = "testdb", + collection = "Items", + bsonFilePath = "bson/search/search2.json") + }) + public void shouldReturnExpectedDocumentsBasedOnSearchOperatorWithDefaultScore() + throws UriValidationException, + UriParserException, + XMLStreamException, + ExpressionVisitException, + ODataApplicationException, + InterruptedException { + // GIVEN + MongoDatabase database = mongoClient.getDatabase("testdb"); + MongoCollection collection = database.getCollection("Items"); + ensureSearchIndex(collection); + + Edm edm = loadEmdProvider("edm/edm6_filter_main.xml"); + UriInfo uriInfo = + new Parser(edm, OData.newInstance()).parseUri("examples2", "$search=search", null, null); + ODataSearchToMongoAtlasSearchParser tested = + new ODataSearchToMongoAtlasSearchParser( + new SearchDocumentForQueryStringFactory() { + @Override + public Bson build( + SearchExpression searchExpression, + QueryStringParsingResult queryStringParsingResult, + ODataSearchToMongoAtlasSearchOptions options) { + return new Document("index", "atlas_search_index") + .append( + "queryString", + new Document("query", queryStringParsingResult.getQuery()) + .append("defaultPath", "plainString")); + } + }); + ODataSearchToMongoAtlasSearchOptions options = + new DefaultODataSearchToMongoAtlasSearchOptions(); + options.setDefaultTextScore(0.01d); + + // WHEN + SearchOperatorResult result = tested.parse(uriInfo.getSearchOption(), options); + List pipeline = new ArrayList<>(result.getStageObjects()); + System.out.println(new Document("pipeline", pipeline).toJson()); + + // THEN + List results = new ArrayList<>(); + collection.aggregate(pipeline).into(results); + Assertions.assertFalse(results.isEmpty()); + } + private void ensureSearchIndex(MongoCollection collection) { try { collection.createSearchIndex( diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index 27c81bf..6d1ff0b 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -35,19 +35,10 @@ public SearchOperatorResultForAtlasSearch parse( searchStage .get("$search", Document.class) .append("scoreDetails", true); // Optional, but can be useful - // We need to project the score to use it in $match - // But $search in Atlas can also use 'score' in some ways? - // Actually, to use { $meta: "searchScore" } in $match, it's NOT possible in Atlas Search - // directly - // unless it's projected first. - // Wait, MongoDB docs say: - // You can use $meta in $match only if it was already projected. - // BUT for Atlas Search, we often use the 'score' field if we project it. - - // Re-reading prompt: "checks if value of '{ $meta: \"textScore\" }' is larger or equal" - // This refers to standard MongoDB Text Search, but this parser is for Atlas Search. - // For Atlas Search it is "searchScore". + // TODO specify the score variable + scoreFilterStages.add( + new Document("$set", new Document("score", new Document("$meta", "searchScore")))); scoreFilterStages.add( new Document( "$match", From 736e8e32e2adfca32d273802fcf4158cb68a784a Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:19:09 +0100 Subject: [PATCH 26/37] #37 - Fixed setting stage that calculates minimal weight --- .../search/ODataSearchToMongoAtlasSearchParser.java | 5 +++-- .../ODataSearchToMongoAtlasSearchParserTest.groovy | 11 +++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index 6d1ff0b..1c55fd1 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -11,6 +11,7 @@ public class ODataSearchToMongoAtlasSearchParser implements ODataSearchToMongoTextSearchParser< ODataSearchToMongoAtlasSearchOptions, SearchOperatorResultForAtlasSearch> { + public static final String SEARCH_SCORE_DEFAULT_VARIABLE = "jamolingo_search_score"; private final SearchDocumentFactory searchDocumentFactory; public ODataSearchToMongoAtlasSearchParser(SearchDocumentFactory searchDocumentFactory) { @@ -38,11 +39,11 @@ public SearchOperatorResultForAtlasSearch parse( // TODO specify the score variable scoreFilterStages.add( - new Document("$set", new Document("score", new Document("$meta", "searchScore")))); + new Document("$set", new Document(SEARCH_SCORE_DEFAULT_VARIABLE, new Document("$meta", "searchScore")))); scoreFilterStages.add( new Document( "$match", - new Document("score", new Document("$gte", options.getDefaultTextScore())))); + new Document(SEARCH_SCORE_DEFAULT_VARIABLE, new Document("$gte", options.getDefaultTextScore())))); } List allStages = new ArrayList<>(searchStages); allStages.addAll(scoreFilterStages); diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy index 76c79b5..5673b38 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy @@ -53,12 +53,15 @@ class ODataSearchToMongoAtlasSearchParserTest extends AbstractSpecification { result.getSearchStages().size() == 1 ((Document)result.getSearchStages().get(0)).get("\$search") != null ((Document)result.getSearchStages().get(0)).get("\$search", Document.class).get("scoreDetails") == true - result.getScoreFilterStages().size() == 1 - ((Document)result.getScoreFilterStages().get(0)).get("\$match") != null - ((Document)result.getScoreFilterStages().get(0)).get("\$match", Document.class).get("score", Document.class).get("\$gte") == 0.5d - result.getStageObjects().size() == 2 + result.getScoreFilterStages().size() == 2 + ((Document)result.getScoreFilterStages().get(0)).get("\$set") != null + ((Document)result.getScoreFilterStages().get(0)).get("\$set", Document.class).get("jamolingo_search_score", Document.class).get("\$meta") == "searchScore" + ((Document)result.getScoreFilterStages().get(1)).get("\$match") != null + ((Document)result.getScoreFilterStages().get(1)).get("\$match", Document.class).get("jamolingo_search_score", Document.class).get("\$gte") == 0.5d + result.getStageObjects().size() == 3 result.getStageObjects().get(0) == result.getSearchStages().get(0) result.getStageObjects().get(1) == result.getScoreFilterStages().get(0) + result.getStageObjects().get(2) == result.getScoreFilterStages().get(1) } /** From aa5cf1affbe918f5682bf63e56ec4128952a74fa Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:23:41 +0100 Subject: [PATCH 27/37] #37 - Fixed setting stage that calculates minimal weight --- ...tODataSearchToMongoAtlasSearchOptions.java | 26 +++++++++++--- .../ODataSearchToMongoAtlasSearchParser.java | 19 ++++++---- .../ODataSearchToMongoTextSearchOptions.java | 21 ++++++++++- ...aSearchToMongoAtlasSearchParserTest.groovy | 36 +++++++++++++++++++ 4 files changed, 91 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java index daf02b3..4a56b0d 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java @@ -7,10 +7,12 @@ public class DefaultODataSearchToMongoAtlasSearchOptions implements ODataSearchToMongoAtlasSearchOptions { private Double defaultTextScore; + private String scoreFieldName; @Override - public void setDefaultTextScore(Double defaultTextScore) { + public void setDefaultTextScore(Double defaultTextScore, String scoreFieldName) { this.defaultTextScore = defaultTextScore; + this.scoreFieldName = scoreFieldName; } @Override @@ -18,6 +20,11 @@ public Double getDefaultTextScore() { return defaultTextScore; } + @Override + public String getScoreFieldName() { + return scoreFieldName; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -28,12 +35,13 @@ public boolean equals(Object o) { } DefaultODataSearchToMongoAtlasSearchOptions that = (DefaultODataSearchToMongoAtlasSearchOptions) o; - return Objects.equals(defaultTextScore, that.defaultTextScore); + return Objects.equals(defaultTextScore, that.defaultTextScore) + && Objects.equals(scoreFieldName, that.scoreFieldName); } @Override public int hashCode() { - return Objects.hash(defaultTextScore); + return Objects.hash(defaultTextScore, scoreFieldName); } @Override @@ -41,6 +49,9 @@ public String toString() { return "DefaultODataSearchToMongoAtlasSearchOptions{" + "defaultTextScore=" + defaultTextScore + + ", scoreFieldName='" + + scoreFieldName + + '\'' + '}'; } @@ -57,12 +68,19 @@ public static Builder builder() { public static class Builder { private Double defaultTextScore; + private String scoreFieldName = + ODataSearchToMongoAtlasSearchParser.SEARCH_SCORE_DEFAULT_VARIABLE; public Builder withDefaultTextScore(Double defaultTextScore) { this.defaultTextScore = defaultTextScore; return this; } + public Builder withScoreFieldName(String scoreFieldName) { + this.scoreFieldName = scoreFieldName; + return this; + } + /** * Builds a new DefaultODataSearchToMongoAtlasSearchOptions instance. * @@ -71,7 +89,7 @@ public Builder withDefaultTextScore(Double defaultTextScore) { public DefaultODataSearchToMongoAtlasSearchOptions build() { DefaultODataSearchToMongoAtlasSearchOptions options = new DefaultODataSearchToMongoAtlasSearchOptions(); - options.setDefaultTextScore(defaultTextScore); + options.setDefaultTextScore(defaultTextScore, scoreFieldName); return options; } } diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index 1c55fd1..589eca3 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -37,17 +37,17 @@ public SearchOperatorResultForAtlasSearch parse( .get("$search", Document.class) .append("scoreDetails", true); // Optional, but can be useful - // TODO specify the score variable + String scoreFieldName = options.getScoreFieldName(); scoreFilterStages.add( - new Document("$set", new Document(SEARCH_SCORE_DEFAULT_VARIABLE, new Document("$meta", "searchScore")))); + new Document("$set", new Document(scoreFieldName, new Document("$meta", "searchScore")))); scoreFilterStages.add( new Document( "$match", - new Document(SEARCH_SCORE_DEFAULT_VARIABLE, new Document("$gte", options.getDefaultTextScore())))); + new Document(scoreFieldName, new Document("$gte", options.getDefaultTextScore())))); } List allStages = new ArrayList<>(searchStages); allStages.addAll(scoreFilterStages); - return new DefaultSearchOperatorResult(allStages, searchStages, scoreFilterStages); + return new DefaultSearchOperatorResult(allStages, searchStages, scoreFilterStages, options); } private static class DefaultSearchOperatorResult implements SearchOperatorResultForAtlasSearch { @@ -55,12 +55,17 @@ private static class DefaultSearchOperatorResult implements SearchOperatorResult private final List stageObjects; private final List searchStages; private final List scoreFilterStages; + private final ODataSearchToMongoAtlasSearchOptions options; private DefaultSearchOperatorResult( - List stages, List searchStages, List scoreFilterStages) { + List stages, + List searchStages, + List scoreFilterStages, + ODataSearchToMongoAtlasSearchOptions options) { this.stageObjects = Collections.unmodifiableList(stages); this.searchStages = Collections.unmodifiableList(searchStages); this.scoreFilterStages = Collections.unmodifiableList(scoreFilterStages); + this.options = options; } @Override @@ -90,7 +95,9 @@ public List getWrittenMongoDocumentProperties() { @Override public List getAddedMongoDocumentProperties() { - // TODO $meta field is going to be added + if (options != null && options.getDefaultTextScore() != null) { + return List.of(options.getScoreFieldName()); + } return List.of(); } diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java index aeddebb..54ece8e 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchOptions.java @@ -1,5 +1,7 @@ package com.github.starnowski.jamolingo.core.operators.search; +import static com.github.starnowski.jamolingo.core.operators.search.ODataSearchToMongoAtlasSearchParser.SEARCH_SCORE_DEFAULT_VARIABLE; + public interface ODataSearchToMongoTextSearchOptions { /** @@ -7,7 +9,17 @@ public interface ODataSearchToMongoTextSearchOptions { * * @param defaultTextScore the minimum text score */ - void setDefaultTextScore(Double defaultTextScore); + default void setDefaultTextScore(Double defaultTextScore) { + setDefaultTextScore(defaultTextScore, SEARCH_SCORE_DEFAULT_VARIABLE); + } + + /** + * Sets the minimum text score and the field name for the score value. + * + * @param defaultTextScore the minimum text score + * @param scoreFieldName the name of the field that will store the score value + */ + void setDefaultTextScore(Double defaultTextScore, String scoreFieldName); /** * Returns the minimum text score required for a document to be returned. @@ -15,4 +27,11 @@ public interface ODataSearchToMongoTextSearchOptions { * @return the minimum text score, or null if not set */ Double getDefaultTextScore(); + + /** + * Returns the name of the field that will store the text search score value. + * + * @return the name of the field + */ + String getScoreFieldName(); } diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy index 5673b38..117aa63 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy @@ -62,6 +62,42 @@ class ODataSearchToMongoAtlasSearchParserTest extends AbstractSpecification { result.getStageObjects().get(0) == result.getSearchStages().get(0) result.getStageObjects().get(1) == result.getScoreFilterStages().get(0) result.getStageObjects().get(2) == result.getScoreFilterStages().get(1) + result.getAddedMongoDocumentProperties() == [ODataSearchToMongoAtlasSearchParser.SEARCH_SCORE_DEFAULT_VARIABLE] + } + + def "should return separate search and score filter stages with custom score field name"(){ + given: + Edm edm = loadEmdProvider("edm/edm6_filter_main.xml") + String customScoreField = "my_custom_score" + + UriInfo uriInfo = new Parser(edm, OData.newInstance()) + .parseUri("examples2", + "\$search=database" + , null, null) + ODataSearchToMongoAtlasSearchParser tested = new ODataSearchToMongoAtlasSearchParser(new SearchDocumentForQueryStringFactory() { + @Override + Bson build(SearchExpression searchExpression, SearchDocumentForQueryStringFactory.QueryStringParsingResult queryStringParsingResult, ODataSearchToMongoAtlasSearchOptions options) { + return new Document().append("index", "default") + .append("queryString", new Document() + .append("query", queryStringParsingResult.getQuery()) + .append("path", Arrays.asList("name","description")) + ) + } + }) + ODataSearchToMongoAtlasSearchOptions options = DefaultODataSearchToMongoAtlasSearchOptions.builder() + .withDefaultTextScore(0.5d) + .withScoreFieldName(customScoreField) + .build() + + when: + def result = tested.parse(uriInfo.getSearchOption(), options) + + then: + result.getSearchStages().size() == 1 + result.getScoreFilterStages().size() == 2 + ((Document)result.getScoreFilterStages().get(0)).get("\$set", Document.class).get(customScoreField) != null + ((Document)result.getScoreFilterStages().get(1)).get("\$match", Document.class).get(customScoreField) != null + result.getAddedMongoDocumentProperties() == [customScoreField] } /** From a669ea2903b9ea5a908d28dfb5f170b88bb9bda0 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:36:12 +0100 Subject: [PATCH 28/37] #37 - Added javadocs.toml command [skip ci] --- .gemini/commands/core/javadocs.toml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gemini/commands/core/javadocs.toml diff --git a/.gemini/commands/core/javadocs.toml b/.gemini/commands/core/javadocs.toml new file mode 100644 index 0000000..16944f2 --- /dev/null +++ b/.gemini/commands/core/javadocs.toml @@ -0,0 +1,6 @@ +description="Update javadocs in the core module" +prompt=""" +Analyze the local main git branch and current one that is currently checkout and check what change in code were added. +Based on your analyzis update javadocs to public methods in production that were changed. +Or add javadocs to new public methods and types in production code. +""" \ No newline at end of file From 6ac1a5fe3b6bfc9869d6bcee8825d1c340017708 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:37:58 +0100 Subject: [PATCH 29/37] #37 - Added javadocs [skip ci] --- ...tODataSearchToMongoAtlasSearchOptions.java | 12 +++++ .../ODataSearchToMongoAtlasSearchOptions.java | 4 ++ .../ODataSearchToMongoAtlasSearchParser.java | 11 ++++ .../ODataSearchToMongoTextSearchParser.java | 20 ++++++++ .../search/SearchDocumentFactory.java | 9 ++++ .../SearchDocumentForQueryStringFactory.java | 50 +++++++++++++++++-- .../SearchOperatorResultForAtlasSearch.java | 3 ++ 7 files changed, 104 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java index 4a56b0d..5cfa2b4 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/DefaultODataSearchToMongoAtlasSearchOptions.java @@ -71,11 +71,23 @@ public static class Builder { private String scoreFieldName = ODataSearchToMongoAtlasSearchParser.SEARCH_SCORE_DEFAULT_VARIABLE; + /** + * Sets the minimum text score. + * + * @param defaultTextScore the default text score + * @return the builder instance + */ public Builder withDefaultTextScore(Double defaultTextScore) { this.defaultTextScore = defaultTextScore; return this; } + /** + * Sets the score field name. + * + * @param scoreFieldName the name of the field to store the score + * @return the builder instance + */ public Builder withScoreFieldName(String scoreFieldName) { this.scoreFieldName = scoreFieldName; return this; diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java index b874a47..c583bdc 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchOptions.java @@ -1,3 +1,7 @@ package com.github.starnowski.jamolingo.core.operators.search; +/** + * Interface representing options specific to MongoDB Atlas Search translation. It extends {@link + * ODataSearchToMongoTextSearchOptions} to include general text search options. + */ public interface ODataSearchToMongoAtlasSearchOptions extends ODataSearchToMongoTextSearchOptions {} diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java index 589eca3..fdd3df9 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParser.java @@ -7,13 +7,24 @@ import org.bson.Document; import org.bson.conversions.Bson; +/** + * Parser responsible for converting OData search options into MongoDB Atlas Search aggregation + * pipeline stages. + */ public class ODataSearchToMongoAtlasSearchParser implements ODataSearchToMongoTextSearchParser< ODataSearchToMongoAtlasSearchOptions, SearchOperatorResultForAtlasSearch> { + /** Default variable name used to store the search score in the pipeline. */ public static final String SEARCH_SCORE_DEFAULT_VARIABLE = "jamolingo_search_score"; + private final SearchDocumentFactory searchDocumentFactory; + /** + * Constructs a new ODataSearchToMongoAtlasSearchParser. + * + * @param searchDocumentFactory the factory to build search documents + */ public ODataSearchToMongoAtlasSearchParser(SearchDocumentFactory searchDocumentFactory) { this.searchDocumentFactory = searchDocumentFactory; } diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java index 56e8efd..92c3512 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoTextSearchParser.java @@ -2,10 +2,30 @@ import org.apache.olingo.server.api.uri.queryoption.SearchOption; +/** + * Interface for parsing OData search options into MongoDB text search aggregation pipeline stages. + * + * @param the type of search options + * @param the type of search operator result + */ public interface ODataSearchToMongoTextSearchParser< T extends ODataSearchToMongoTextSearchOptions, R extends SearchOperatorResult> { + /** + * Parses the given OData search option into a search operator result. + * + * @param searchOption the OData search option to parse + * @return the resulting search operator result + */ R parse(SearchOption searchOption); + /** + * Parses the given OData search option into a search operator result, applying the provided + * options. + * + * @param searchOption the OData search option to parse + * @param options the search options to apply + * @return the resulting search operator result + */ R parse(SearchOption searchOption, T options); } diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java index 33a2316..b7e3560 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentFactory.java @@ -3,7 +3,16 @@ import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression; import org.bson.conversions.Bson; +/** Factory interface for building MongoDB Bson documents from OData search expressions. */ public interface SearchDocumentFactory { + /** + * Builds a Bson document representing the search stage based on the provided search expression + * and options. + * + * @param searchExpression the OData search expression + * @param options the search options + * @return the Bson document representing the search operation + */ Bson build(SearchExpression searchExpression, ODataSearchToMongoAtlasSearchOptions options); } diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java index ff1c151..89de883 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchDocumentForQueryStringFactory.java @@ -8,6 +8,10 @@ import org.apache.olingo.server.api.uri.queryoption.search.SearchUnaryOperatorKind; import org.bson.conversions.Bson; +/** + * Abstract factory class for building search documents based on query strings. It parses the OData + * search expression into a query string result, which is then used by concrete implementations. + */ public abstract class SearchDocumentForQueryStringFactory implements SearchDocumentFactory { @Override public Bson build( @@ -24,6 +28,12 @@ private QueryStringParsingResult pares(SearchExpression searchExpression) { } } + /** + * Parses the search expression and converts it to a formatted string. + * + * @param searchExpression the OData search expression + * @return the formatted string representing the query + */ protected String parseSearchExpressionToString(SearchExpression searchExpression) { if (searchExpression instanceof SearchTerm) { return formatTerm(((SearchTerm) searchExpression).getSearchTerm()); @@ -66,30 +76,60 @@ private String formatTerm(String term) { return term; } + /** + * Builds the MongoDB Bson document using the parsed query string result and options. + * + * @param searchExpression the original search expression + * @param queryStringParsingResult the result of parsing the query string + * @param options the search options + * @return the Bson document representing the search stage + */ public abstract Bson build( SearchExpression searchExpression, QueryStringParsingResult queryStringParsingResult, ODataSearchToMongoAtlasSearchOptions options); + /** Result of parsing the query string, including the query itself and status. */ public static class QueryStringParsingResult { + private final String query; + private final boolean success; + private final Exception cause; + + /** + * Gets the parsed query string. + * + * @return the query string + */ public String getQuery() { return query; } + /** + * Checks if the parsing was successful. + * + * @return true if successful, false otherwise + */ public boolean isSuccess() { return success; } - private final String query; - private final boolean success; - + /** + * Gets the exception that caused the parsing to fail, if any. + * + * @return the cause exception + */ public Exception getCause() { return cause; } - private final Exception cause; - + /** + * Constructs a new QueryStringParsingResult. + * + * @param query the parsed query string + * @param success the success status + * @param cause the exception cause + */ public QueryStringParsingResult(String query, boolean success, Exception cause) { this.query = query; this.success = success; diff --git a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResultForAtlasSearch.java b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResultForAtlasSearch.java index 8022c36..110eb6c 100644 --- a/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResultForAtlasSearch.java +++ b/core/src/main/java/com/github/starnowski/jamolingo/core/operators/search/SearchOperatorResultForAtlasSearch.java @@ -1,3 +1,6 @@ package com.github.starnowski.jamolingo.core.operators.search; +/** + * Represents the result of parsing an OData search option specifically for MongoDB Atlas Search. + */ public interface SearchOperatorResultForAtlasSearch extends SearchOperatorResult {} From dbf93710c13342978e8dcc1c64e304981027ec25 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:39:17 +0100 Subject: [PATCH 30/37] #37 - Updated the tests files --- .../search/ODataSearchToMongoAtlasSearchParserTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy index 117aa63..368901a 100644 --- a/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy +++ b/core/src/test/groovy/com/github/starnowski/jamolingo/core/operators/search/ODataSearchToMongoAtlasSearchParserTest.groovy @@ -62,7 +62,7 @@ class ODataSearchToMongoAtlasSearchParserTest extends AbstractSpecification { result.getStageObjects().get(0) == result.getSearchStages().get(0) result.getStageObjects().get(1) == result.getScoreFilterStages().get(0) result.getStageObjects().get(2) == result.getScoreFilterStages().get(1) - result.getAddedMongoDocumentProperties() == [ODataSearchToMongoAtlasSearchParser.SEARCH_SCORE_DEFAULT_VARIABLE] + result.getAddedMongoDocumentProperties() == ["jamolingo_search_score"] } def "should return separate search and score filter stages with custom score field name"(){ From 38cc4c339f3b236ae2e75e72a8bc4538c17e8953 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:44:43 +0100 Subject: [PATCH 31/37] #37 - Added the readme.toml command [skip ci] --- .gemini/commands/core/readme.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gemini/commands/core/readme.toml diff --git a/.gemini/commands/core/readme.toml b/.gemini/commands/core/readme.toml new file mode 100644 index 0000000..221ede7 --- /dev/null +++ b/.gemini/commands/core/readme.toml @@ -0,0 +1,5 @@ +description="Update README.md file in the core module" +prompt=""" +Analyze the local main git branch and current one that is currently checkout and check what change in code were added. +Based on your analyzis update README.md file in the core module and mention what was changed or added. +""" \ No newline at end of file From bda1cba23b03fd89d07eb307b2406fa910357d69 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:45:53 +0100 Subject: [PATCH 32/37] #37 - Updated README.md file [skip ci] --- core/README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/core/README.md b/core/README.md index 908444f..8e6f22d 100644 --- a/core/README.md +++ b/core/README.md @@ -90,6 +90,52 @@ List stages = result.getStageObjects(); // e.g. collection.aggregate(stages); ``` +#### $search + +The `$search` operator allows clients to perform full-text search. The `core` module translates this into MongoDB Atlas Search aggregation stages. + +**Translation Details:** +- Translates to a `$search` aggregation stage. +- Optionally adds `$set` and `$match` stages to filter by search score. +- Supports logical operators (`AND`, `OR`, `NOT`) within the search expression. +- Allows specifying a minimum score and a custom field name for the score value. + +**Usage:** + +The `ODataSearchToMongoAtlasSearchParser` class is responsible for this translation. + +```java +import com.github.starnowski.jamolingo.core.operators.search.ODataSearchToMongoAtlasSearchParser; +import com.github.starnowski.jamolingo.core.operators.search.ODataSearchToMongoAtlasSearchOptions; +import com.github.starnowski.jamolingo.core.operators.search.DefaultODataSearchToMongoAtlasSearchOptions; +import com.github.starnowski.jamolingo.core.operators.search.SearchOperatorResult; +import org.apache.olingo.server.api.uri.queryoption.SearchOption; +// ... other imports + +// 1. Initialize the parser with a SearchDocumentFactory +// SearchDocumentFactory factory = ...; +ODataSearchToMongoAtlasSearchParser parser = new ODataSearchToMongoAtlasSearchParser(factory); + +// 2. Obtain the SearchOption from the Olingo UriInfo +SearchOption searchOption = uriInfo.getSearchOption(); + +// 3. (Optional) Provide search options (e.g., minimum score) +ODataSearchToMongoAtlasSearchOptions options = DefaultODataSearchToMongoAtlasSearchOptions.builder() + .withDefaultTextScore(0.05) + .withScoreFieldName("search_score") + .build(); + +// 4. Parse the option +SearchOperatorResult result = parser.parse(searchOption, options); + +// 5. Use the result in your MongoDB aggregation pipeline +List stages = result.getStageObjects(); +// Or get specific stages +List searchStages = result.getSearchStages(); +List scoreFilterStages = result.getScoreFilterStages(); +// e.g. collection.aggregate(stages); +``` + #### $orderby The `$orderby` operator specifies the sort order of the returned items. The `core` module translates this into a MongoDB aggregation stage. From 1eb922748c7ec45fdd7aedfcb63a89c23594bd97 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:49:53 +0100 Subject: [PATCH 33/37] #37 - Updated README.md file in root module [skip ci] --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 30f1d12..ce2fa2f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ A Java library for translating OData queries and concepts into MongoDB aggregati ### Prerequisites * **Java 8** or higher. * **MongoDB 4.4** or higher (supporting aggregation pipelines and explain). +* **MongoDB Atlas** or **MongoDB Atlas Local** (required for `$search` operator support). ### Installation (Maven) Add the following dependencies to your `pom.xml`: @@ -33,13 +34,13 @@ Add the following dependencies to your `pom.xml`: com.github.starnowski.jamolingo core - 0.7.0 + 0.8.0-SNAPSHOT com.github.starnowski.jamolingo perf - 0.7.0 + 0.8.0-SNAPSHOT ``` @@ -84,6 +85,9 @@ The `core` module contains the primary logic for translating OData concepts and * Comparison (`eq`, `ne`, `in`, etc.) and Logical (`and`, `or`, `not`) operators. * String, Math, and Date/Time functions. * Collection operators (`any`, `all`) and `/$count`. +* Translates `$search` to MongoDB Atlas Search stages (`$search`, `$set`, `$match`) with support for: + * Full-text search with logical operators (`AND`, `OR`, `NOT`). + * Search score filtering and custom score field names. * Translates `$select` to MongoDB `$project` stages. * Translates `$orderby`, `$top`, `$skip`, and `$count` to corresponding MongoDB stages (`$sort`, `$limit`, `$skip`, `$count`). * Handles OData-to-MongoDB mapping configuration and supports customizing mappings via overrides. From 0be8e8625e3d826dcc64ddb3ce3513801f2a3ecc Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:50:09 +0100 Subject: [PATCH 34/37] #37 - Added the readme.toml command [skip ci] --- .gemini/commands/root/readme.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gemini/commands/root/readme.toml diff --git a/.gemini/commands/root/readme.toml b/.gemini/commands/root/readme.toml new file mode 100644 index 0000000..79ac1ab --- /dev/null +++ b/.gemini/commands/root/readme.toml @@ -0,0 +1,5 @@ +description="Update README.md file in the root module" +prompt=""" +Analyze the local main git branch and current one that is currently checkout and check what changes were done. +Based on your analyzis update README.md file in the root module and mention what was changed or added. +""" \ No newline at end of file From 52ca7c32f7726f0db83db9f2d37add9504e1d676 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:54:42 +0100 Subject: [PATCH 35/37] #37 - Updated the CHANGELOG.md file --- .gemini/commands/root/changelog.toml | 5 +++++ CHANGELOG.md | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 .gemini/commands/root/changelog.toml diff --git a/.gemini/commands/root/changelog.toml b/.gemini/commands/root/changelog.toml new file mode 100644 index 0000000..c71e0a7 --- /dev/null +++ b/.gemini/commands/root/changelog.toml @@ -0,0 +1,5 @@ +description="Update CHANGELOG.md file in the root module" +prompt=""" +Analyze the local main git branch and current one that is currently checkout and check what changes were done. +Based on your analyzis update GHANGELOG.md file in the root module. +""" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b8647dd..1f88425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * [Unreleased](#unreleased) +* [0.8.0](#080---2026-03-16) * [0.7.0](#070---2026-03-06) * [0.6.0](#060---2026-03-04) * [0.5.1](#051---2026-03-03) @@ -18,6 +19,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.8.0] - 2026-03-16 + +### Added + +#### Core Module +* Added support for `$search` operator translation to MongoDB Atlas Search aggregation stages. ([#37](https://github.com/starnowski/jamolingo/issues/37)) +* `com.github.starnowski.jamolingo.core.operators.search.ODataSearchToMongoAtlasSearchParser` class for parsing OData search options. ([#37](https://github.com/starnowski/jamolingo/issues/37)) +* `com.github.starnowski.jamolingo.core.operators.search.ODataSearchToMongoAtlasSearchOptions` and `DefaultODataSearchToMongoAtlasSearchOptions` for search configuration. ([#37](https://github.com/starnowski/jamolingo/issues/37)) +* Support for search score filtering using `$set` and `$match` stages with configurable field names. ([#37](https://github.com/starnowski/jamolingo/issues/37)) +* `com.github.starnowski.jamolingo.core.operators.search.SearchOperatorResult` and `SearchOperatorResultForAtlasSearch` interfaces. ([#37](https://github.com/starnowski/jamolingo/issues/37)) + +### Changed + +#### Administrative +* Updated project version to `0.8.0-SNAPSHOT`. + ## [0.7.0] - 2026-03-06 ### Added From d9802781e0e34dfec0345ff82f9154a79f6686a6 Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:57:38 +0100 Subject: [PATCH 36/37] #37 - Removed redundant method --- .../jamolingo/MongoAtlasResource.java | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java index 22273e6..dc94ee3 100644 --- a/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java +++ b/compat-driver-5.x/src/test/java/com/github/starnowski/jamolingo/MongoAtlasResource.java @@ -1,11 +1,9 @@ package com.github.starnowski.jamolingo; -import com.mongodb.client.MongoCollection; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import java.time.Duration; import java.util.Collections; import java.util.Map; -import org.bson.Document; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; @@ -41,28 +39,6 @@ public Map start() { return Collections.singletonMap("quarkus.mongodb.connection-string", connectionString); } - private void ensureSearchIndex(MongoCollection collection) { - try { - collection.createSearchIndex( - "test_index", new Document("mappings", new Document("dynamic", true))); - // Wait for index to be ready - while (true) { - boolean ready = false; - for (Document index : collection.listSearchIndexes()) { - if ("test_index".equals(index.getString("name")) - && "READY".equals(index.getString("status"))) { - ready = true; - break; - } - } - if (ready) break; - Thread.sleep(500); - } - } catch (Exception e) { - // Index might already exist - } - } - @Override public void stop() { if (mongoAtlasContainer != null) { From cadcdf69e08519af84d0ef6d5fd5f4fea9f3e28f Mon Sep 17 00:00:00 2001 From: starnowski <33316705+starnowski@users.noreply.github.com> Date: Mon, 16 Mar 2026 02:00:40 +0100 Subject: [PATCH 37/37] #37 - Updated the CHANGELOG.md file --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f88425..87dcec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,9 +32,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -#### Administrative -* Updated project version to `0.8.0-SNAPSHOT`. - ## [0.7.0] - 2026-03-06 ### Added