Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e054398
#25 - Added basic types [skip ci]
starnowski Mar 11, 2026
c8d9ad7
#25 - Added unit tests [skip ci]
starnowski Mar 11, 2026
9f75ab7
#25 - Added unit tests [skip ci]
starnowski Mar 11, 2026
6432663
#25 - Added unit tests [skip ci]
starnowski Mar 11, 2026
ea5ae8c
#25 - Added unit tests [skip ci]
starnowski Mar 11, 2026
31130fd
#25 - Added unit tests [skip ci]
starnowski Mar 11, 2026
11def05
#25 - Added unit tests [skip ci]
starnowski Mar 11, 2026
8a37d06
#25 - Added unit tests [skip ci]
starnowski Mar 11, 2026
b45fa38
#25 - Added todo method [skip ci]
starnowski Mar 11, 2026
35333b3
#25 - Added implementation of parsed queryString [skip ci]
starnowski Mar 11, 2026
248240e
#25 - Fixed tests assertions [skip ci]
starnowski Mar 11, 2026
87fc7bc
#25 - Added logic that parse string query
starnowski Mar 11, 2026
e8f50d8
#25 - Added MongoDB Atlas container
starnowski Mar 11, 2026
15a15c9
#37 - Renamed component
starnowski Mar 12, 2026
18b235c
#37 - Added Atlas docker tests
starnowski Mar 12, 2026
37fa7b5
#37 - Tests fix
starnowski Mar 12, 2026
ff467ca
#37 - Fixed tests
starnowski Mar 12, 2026
63a1552
#37 - Code refactoring [skip ci]
starnowski Mar 12, 2026
f781d8b
#37 - Code formating
starnowski Mar 12, 2026
96fd67f
#37 - Updated project version
starnowski Mar 12, 2026
ad1ed43
#37 - Code refactoring
starnowski Mar 12, 2026
df34fd7
#37 - Added stage that filters documents based on the text score
starnowski Mar 12, 2026
fca5c5f
#37 - Fixed Atlas container starting
starnowski Mar 14, 2026
ab10f86
#37 - Added the $match that filter based on text search score
starnowski Mar 15, 2026
6f4a1c0
#37 - Setting the score field with score results [skip ci]
starnowski Mar 16, 2026
736e8e3
#37 - Fixed setting stage that calculates minimal weight
starnowski Mar 16, 2026
aa5cf1a
#37 - Fixed setting stage that calculates minimal weight
starnowski Mar 16, 2026
a669ea2
#37 - Added javadocs.toml command [skip ci]
starnowski Mar 16, 2026
6ac1a5f
#37 - Added javadocs [skip ci]
starnowski Mar 16, 2026
dbf9371
#37 - Updated the tests files
starnowski Mar 16, 2026
38cc4c3
#37 - Added the readme.toml command [skip ci]
starnowski Mar 16, 2026
bda1cba
#37 - Updated README.md file [skip ci]
starnowski Mar 16, 2026
1eb9227
#37 - Updated README.md file in root module [skip ci]
starnowski Mar 16, 2026
0be8e86
#37 - Added the readme.toml command [skip ci]
starnowski Mar 16, 2026
52ca7c3
#37 - Updated the CHANGELOG.md file
starnowski Mar 16, 2026
d980278
#37 - Removed redundant method
starnowski Mar 16, 2026
cadcdf6
#37 - Updated the CHANGELOG.md file
starnowski Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gemini/commands/core/javadocs.toml
Original file line number Diff line number Diff line change
@@ -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.
"""
5 changes: 5 additions & 0 deletions .gemini/commands/core/readme.toml
Original file line number Diff line number Diff line change
@@ -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.
"""
5 changes: 5 additions & 0 deletions .gemini/commands/root/changelog.toml
Original file line number Diff line number Diff line change
@@ -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.
"""
5 changes: 5 additions & 0 deletions .gemini/commands/root/readme.toml
Original file line number Diff line number Diff line change
@@ -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.
"""
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -18,6 +19,19 @@ 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

## [0.7.0] - 2026-03-06

### Added
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand All @@ -33,13 +34,13 @@ Add the following dependencies to your `pom.xml`:
<dependency>
<groupId>com.github.starnowski.jamolingo</groupId>
<artifactId>core</artifactId>
<version>0.7.0</version>
<version>0.8.0-SNAPSHOT</version>
</dependency>
<!-- Optional: for performance analysis -->
<dependency>
<groupId>com.github.starnowski.jamolingo</groupId>
<artifactId>perf</artifactId>
<version>0.7.0</version>
<version>0.8.0-SNAPSHOT</version>
</dependency>
```

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion common/json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.github.starnowski.jamolingo</groupId>
<artifactId>common</artifactId>
<version>0.7.0</version>
<version>0.8.0-SNAPSHOT</version>
</parent>

<artifactId>json</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.github.starnowski.jamolingo</groupId>
<artifactId>parent</artifactId>
<version>0.7.0</version>
<version>0.8.0-SNAPSHOT</version>
</parent>
<packaging>pom</packaging>
<modules>
Expand Down
7 changes: 6 additions & 1 deletion compat-driver-5.x/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.github.starnowski.jamolingo</groupId>
<artifactId>parent</artifactId>
<version>0.7.0</version>
<version>0.8.0-SNAPSHOT</version>
</parent>

<artifactId>compat-driver-5.x</artifactId>
Expand Down Expand Up @@ -72,6 +72,11 @@
<version>1.5.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.starnowski.jamolingo</groupId>
<artifactId>perf</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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;
import org.testcontainers.containers.wait.strategy.Wait;

public class MongoAtlasResource implements QuarkusTestResourceLifecycleManager {

private GenericContainer<?> mongoAtlasContainer;

@Override
public Map<String, String> start() {
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)
.withEnv("MONGOT_LOG_FILE", "/dev/stdout")
.waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(5)));

mongoAtlasContainer.start();
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);
}

@Override
public void stop() {
if (mongoAtlasContainer != null) {
mongoAtlasContainer.stop();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
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.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;
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 jakarta.inject.Inject;
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.api.Test;
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 AbstractItTest {

@Inject protected MongoClient mongoClient;

@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<String> expectedPlainStrings)
throws UriValidationException,
UriParserException,
XMLStreamException,
ExpressionVisitException,
ODataApplicationException,
InterruptedException {
// GIVEN
MongoDatabase database = mongoClient.getDatabase("testdb");
MongoCollection<Document> 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"));
}
});

// WHEN
SearchOperatorResult result = tested.parse(uriInfo.getSearchOption());
List<Bson> pipeline = new ArrayList<>(result.getStageObjects());
System.out.println(new Document("pipeline", pipeline).toJson());

// THEN
List<Document> 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<String> actual =
results.stream()
.map(d -> d.get("plainString"))
.filter(Objects::nonNull)
.map(s -> (String) s)
.collect(Collectors.toSet());
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<Document> 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<Bson> pipeline = new ArrayList<>(result.getStageObjects());
System.out.println(new Document("pipeline", pipeline).toJson());

// THEN
List<Document> results = new ArrayList<>();
collection.aggregate(pipeline).into(results);
Assertions.assertFalse(results.isEmpty());
}

private void ensureSearchIndex(MongoCollection<Document> 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<Arguments> 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")));
}
}
3 changes: 2 additions & 1 deletion compat-driver-5.x/src/test/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
quarkus.mongodb.uuid-representation=STANDARD
quarkus.mongodb.uuid-representation=STANDARD
quarkus.mongodb.devservices.enabled=false
8 changes: 8 additions & 0 deletions compat-driver-5.x/src/test/resources/bson/search/search1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"plainString": "database search",
"tags": [
"mongo",
"atlas"
],
"active": true
}
Loading
Loading