diff --git a/build.gradle.kts b/build.gradle.kts
index f102af0b..24187ff1 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -132,6 +132,7 @@ dependencies {
testImplementation(libs.bundles.test.common)
testImplementation(libs.mockito.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+ testCompileOnly(libs.checker.qual)
integrationTestImplementation(libs.bundles.test.common)
@Suppress("UnstableApiUsage")
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d6093fb9..2e091a53 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -23,6 +23,7 @@ mongo-java-driver-sync = "5.3.1"
slf4j-api = "2.0.16"
logback-classic = "1.5.16"
mockito = "5.16.0"
+checker-qual = "3.49.1"
plugin-spotless = "7.0.2"
plugin-errorprone = "4.1.0"
@@ -42,6 +43,7 @@ mongo-java-driver-sync = { module = "org.mongodb:mongodb-driver-sync", version.r
sl4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-classic" }
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
+checker-qual = { module = "org.checkerframework:checker-qual", version.ref = "checker-qual" }
[bundles]
test-common = ["junit-jupiter", "assertj", "logback-classic"]
diff --git a/src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java
index f61d4a01..78c08eff 100644
--- a/src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java
+++ b/src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java
@@ -198,6 +198,45 @@ void testDynamicUpdate() {
}
}
+ @Nested
+ class SelectTests {
+
+ @Test
+ void testGetByPrimaryKeyWithoutNullValueField() {
+ var book = new Book();
+ book.id = 1;
+ book.author = "Marcel Proust";
+ book.title = "In Search of Lost Time";
+ book.publishYear = 1913;
+
+ sessionFactoryScope.inTransaction(session -> session.persist(book));
+
+ var loadedBook = sessionFactoryScope.fromTransaction(session -> session.get(Book.class, 1));
+ assertThat(loadedBook)
+ .isNotNull()
+ .usingRecursiveComparison()
+ .withStrictTypeChecking()
+ .isEqualTo(book);
+ }
+
+ @Test
+ void testGetByPrimaryKeyWithNullValueField() {
+ var book = new Book();
+ book.id = 1;
+ book.title = "Brave New World";
+ book.publishYear = 1932;
+
+ sessionFactoryScope.inTransaction(session -> session.persist(book));
+
+ var loadedBook = sessionFactoryScope.fromTransaction(session -> session.get(Book.class, 1));
+ assertThat(loadedBook)
+ .isNotNull()
+ .usingRecursiveComparison()
+ .withStrictTypeChecking()
+ .isEqualTo(book);
+ }
+ }
+
private static void assertCollectionContainsExactly(BsonDocument expectedDoc) {
assertThat(mongoCollection.find()).containsExactly(expectedDoc);
}
diff --git a/src/integrationTest/resources/logback-test.xml b/src/integrationTest/resources/logback-test.xml
index a70fad20..c6ffeff3 100644
--- a/src/integrationTest/resources/logback-test.xml
+++ b/src/integrationTest/resources/logback-test.xml
@@ -10,7 +10,9 @@
+
+
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java
index 5fe51746..b3fcff3d 100644
--- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java
@@ -19,8 +19,13 @@
import static com.mongodb.hibernate.internal.MongoAssertions.assertNotNull;
import static com.mongodb.hibernate.internal.MongoAssertions.assertTrue;
import static com.mongodb.hibernate.internal.MongoConstants.ID_FIELD_NAME;
+import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_AGGREGATE;
import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_MUTATION;
+import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_NAME;
+import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.FIELD_PATH;
import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.FIELD_VALUE;
+import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.FILTER;
+import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.PROJECT_STAGE_SPECIFICATIONS;
import static com.mongodb.hibernate.internal.translate.mongoast.filter.AstComparisonFilterOperator.EQ;
import static java.lang.String.format;
@@ -31,16 +36,24 @@
import com.mongodb.hibernate.internal.translate.mongoast.AstFieldUpdate;
import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
import com.mongodb.hibernate.internal.translate.mongoast.AstParameterMarker;
+import com.mongodb.hibernate.internal.translate.mongoast.command.AstCommand;
import com.mongodb.hibernate.internal.translate.mongoast.command.AstDeleteCommand;
import com.mongodb.hibernate.internal.translate.mongoast.command.AstInsertCommand;
import com.mongodb.hibernate.internal.translate.mongoast.command.AstUpdateCommand;
+import com.mongodb.hibernate.internal.translate.mongoast.command.aggregate.AstAggregateCommand;
+import com.mongodb.hibernate.internal.translate.mongoast.command.aggregate.AstMatchStage;
+import com.mongodb.hibernate.internal.translate.mongoast.command.aggregate.AstProjectStage;
+import com.mongodb.hibernate.internal.translate.mongoast.command.aggregate.AstProjectStageIncludeSpecification;
+import com.mongodb.hibernate.internal.translate.mongoast.command.aggregate.AstProjectStageSpecification;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstComparisonFilterOperation;
+import com.mongodb.hibernate.internal.translate.mongoast.filter.AstComparisonFilterOperator;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFieldOperationFilter;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilter;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilterFieldPath;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bson.json.JsonMode;
@@ -48,7 +61,10 @@
import org.bson.json.JsonWriterSettings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
+import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.internal.SqlFragmentPredicate;
+import org.hibernate.query.spi.QueryOptions;
+import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.tree.expression.Conversion;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
@@ -143,6 +159,8 @@ abstract class AbstractMqlTranslator implements SqlAstT
private final List parameterBinders = new ArrayList<>();
+ private final Set affectedTableNames = new HashSet<>();
+
AbstractMqlTranslator(SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory;
assertNotNull(sessionFactory
@@ -178,7 +196,7 @@ public Stack getCurrentClauseStack() {
@Override
public Set getAffectedTableNames() {
- throw new FeatureNotSupportedException("TODO-HIBERNATE-22 https://jira.mongodb.org/browse/HIBERNATE-22");
+ return affectedTableNames;
}
List getParameterBinders() {
@@ -197,12 +215,12 @@ static String renderMongoAstNode(AstNode rootAstNode) {
}
@SuppressWarnings("overloads")
- R acceptAndYield(Statement statement, AstVisitorValueDescriptor resultDescriptor) {
+ R acceptAndYield(Statement statement, AstVisitorValueDescriptor resultDescriptor) {
return astVisitorValueHolder.execute(resultDescriptor, () -> statement.accept(this));
}
@SuppressWarnings("overloads")
- R acceptAndYield(SqlAstNode node, AstVisitorValueDescriptor resultDescriptor) {
+ R acceptAndYield(SqlAstNode node, AstVisitorValueDescriptor resultDescriptor) {
return astVisitorValueHolder.execute(resultDescriptor, () -> node.accept(this));
}
@@ -217,17 +235,16 @@ public void visitStandardTableInsert(TableInsertStandard tableInsert) {
var astElements = new ArrayList(tableInsert.getNumberOfValueBindings());
for (var columnValueBinding : tableInsert.getValueBindings()) {
var fieldName = columnValueBinding.getColumnReference().getColumnExpression();
-
var valueExpression = columnValueBinding.getValueExpression();
if (valueExpression == null) {
throw new FeatureNotSupportedException();
}
var fieldValue = acceptAndYield(valueExpression, FIELD_VALUE);
-
astElements.add(new AstElement(fieldName, fieldValue));
}
astVisitorValueHolder.yield(
- COLLECTION_MUTATION, new AstInsertCommand(tableInsert.getTableName(), new AstDocument(astElements)));
+ COLLECTION_MUTATION,
+ new AstInsertCommand(tableInsert.getMutatingTable().getTableName(), new AstDocument(astElements)));
}
@Override
@@ -301,7 +318,111 @@ public void visitParameter(JdbcParameter jdbcParameter) {
@Override
public void visitSelectStatement(SelectStatement selectStatement) {
- throw new FeatureNotSupportedException("TODO-HIBERNATE-22 https://jira.mongodb.org/browse/HIBERNATE-22");
+ if (!selectStatement.getQueryPart().isRoot()) {
+ throw new FeatureNotSupportedException("Subquery not supported");
+ }
+ if (!selectStatement.getCteStatements().isEmpty()
+ || !selectStatement.getCteObjects().isEmpty()) {
+ throw new FeatureNotSupportedException("CTE not supported");
+ }
+ selectStatement.getQueryPart().accept(this);
+ }
+
+ @Override
+ public void visitQuerySpec(QuerySpec querySpec) {
+ if (!querySpec.getGroupByClauseExpressions().isEmpty()) {
+ throw new FeatureNotSupportedException("GroupBy not supported");
+ }
+ if (querySpec.hasSortSpecifications()) {
+ throw new FeatureNotSupportedException("Sorting not supported");
+ }
+ if (querySpec.hasOffsetOrFetchClause()) {
+ throw new FeatureNotSupportedException("TO-DO-HIBERNATE-70 https://jira.mongodb.org/browse/HIBERNATE-70");
+ }
+
+ var collection = acceptAndYield(querySpec.getFromClause(), COLLECTION_NAME);
+
+ var whereClauseRestrictions = querySpec.getWhereClauseRestrictions();
+ var filter = whereClauseRestrictions == null || whereClauseRestrictions.isEmpty()
+ ? null
+ : acceptAndYield(whereClauseRestrictions, FILTER);
+
+ var projectStageSpecifications = acceptAndYield(querySpec.getSelectClause(), PROJECT_STAGE_SPECIFICATIONS);
+
+ var stages = filter == null
+ ? List.of(new AstProjectStage(projectStageSpecifications))
+ : List.of(new AstMatchStage(filter), new AstProjectStage(projectStageSpecifications));
+ astVisitorValueHolder.yield(COLLECTION_AGGREGATE, new AstAggregateCommand(collection, stages));
+ }
+
+ @Override
+ public void visitFromClause(FromClause fromClause) {
+ if (fromClause.getRoots().size() != 1) {
+ throw new FeatureNotSupportedException();
+ }
+ var tableGroup = fromClause.getRoots().get(0);
+
+ if (!(tableGroup.getModelPart() instanceof EntityPersister entityPersister)
+ || entityPersister.getQuerySpaces().length != 1) {
+ throw new FeatureNotSupportedException();
+ }
+
+ affectedTableNames.add(((String[]) entityPersister.getQuerySpaces())[0]);
+ tableGroup.getPrimaryTableReference().accept(this);
+ }
+
+ @Override
+ public void visitNamedTableReference(NamedTableReference namedTableReference) {
+ astVisitorValueHolder.yield(COLLECTION_NAME, namedTableReference.getTableExpression());
+ }
+
+ @Override
+ public void visitRelationalPredicate(ComparisonPredicate comparisonPredicate) {
+ var astComparisonFilterOperator = getAstComparisonFilterOperator(comparisonPredicate.getOperator());
+
+ var fieldPath = acceptAndYield(comparisonPredicate.getLeftHandExpression(), FIELD_PATH);
+ var fieldValue = acceptAndYield(comparisonPredicate.getRightHandExpression(), FIELD_VALUE);
+
+ var filter = new AstFieldOperationFilter(
+ new AstFilterFieldPath(fieldPath),
+ new AstComparisonFilterOperation(astComparisonFilterOperator, fieldValue));
+ astVisitorValueHolder.yield(FILTER, filter);
+ }
+
+ private static AstComparisonFilterOperator getAstComparisonFilterOperator(ComparisonOperator operator) {
+ return switch (operator) {
+ case EQUAL -> EQ;
+ default -> throw new FeatureNotSupportedException("Unsupported operator: " + operator.name());
+ };
+ }
+
+ @Override
+ public void visitSelectClause(SelectClause selectClause) {
+ if (selectClause.isDistinct()) {
+ throw new FeatureNotSupportedException();
+ }
+ var projectStageSpecifications = new ArrayList(
+ selectClause.getSqlSelections().size());
+
+ for (SqlSelection sqlSelection : selectClause.getSqlSelections()) {
+ if (sqlSelection.isVirtual()) {
+ continue;
+ }
+ if (!(sqlSelection.getExpression() instanceof ColumnReference columnReference)) {
+ throw new FeatureNotSupportedException();
+ }
+ var field = acceptAndYield(columnReference, FIELD_PATH);
+ projectStageSpecifications.add(new AstProjectStageIncludeSpecification(field));
+ }
+ astVisitorValueHolder.yield(PROJECT_STAGE_SPECIFICATIONS, projectStageSpecifications);
+ }
+
+ @Override
+ public void visitColumnReference(ColumnReference columnReference) {
+ if (columnReference.isColumnExpressionFormula()) {
+ throw new FeatureNotSupportedException();
+ }
+ astVisitorValueHolder.yield(FIELD_PATH, columnReference.getColumnExpression());
}
@Override
@@ -329,11 +450,6 @@ public void visitQueryGroup(QueryGroup queryGroup) {
throw new FeatureNotSupportedException();
}
- @Override
- public void visitQuerySpec(QuerySpec querySpec) {
- throw new FeatureNotSupportedException();
- }
-
@Override
public void visitSortSpecification(SortSpecification sortSpecification) {
throw new FeatureNotSupportedException();
@@ -344,21 +460,11 @@ public void visitOffsetFetchClause(QueryPart queryPart) {
throw new FeatureNotSupportedException();
}
- @Override
- public void visitSelectClause(SelectClause selectClause) {
- throw new FeatureNotSupportedException();
- }
-
@Override
public void visitSqlSelection(SqlSelection sqlSelection) {
throw new FeatureNotSupportedException();
}
- @Override
- public void visitFromClause(FromClause fromClause) {
- throw new FeatureNotSupportedException();
- }
-
@Override
public void visitTableGroup(TableGroup tableGroup) {
throw new FeatureNotSupportedException();
@@ -369,11 +475,6 @@ public void visitTableGroupJoin(TableGroupJoin tableGroupJoin) {
throw new FeatureNotSupportedException();
}
- @Override
- public void visitNamedTableReference(NamedTableReference namedTableReference) {
- throw new FeatureNotSupportedException();
- }
-
@Override
public void visitValuesTableReference(ValuesTableReference valuesTableReference) {
throw new FeatureNotSupportedException();
@@ -394,11 +495,6 @@ public void visitTableReferenceJoin(TableReferenceJoin tableReferenceJoin) {
throw new FeatureNotSupportedException();
}
- @Override
- public void visitColumnReference(ColumnReference columnReference) {
- throw new FeatureNotSupportedException();
- }
-
@Override
public void visitNestedColumnReference(NestedColumnReference nestedColumnReference) {
throw new FeatureNotSupportedException();
@@ -609,11 +705,6 @@ public void visitThruthnessPredicate(ThruthnessPredicate thruthnessPredicate) {
throw new FeatureNotSupportedException();
}
- @Override
- public void visitRelationalPredicate(ComparisonPredicate comparisonPredicate) {
- throw new FeatureNotSupportedException();
- }
-
@Override
public void visitSelfRenderingPredicate(SelfRenderingPredicate selfRenderingPredicate) {
throw new FeatureNotSupportedException();
@@ -653,4 +744,56 @@ public void visitOptionalTableUpdate(OptionalTableUpdate optionalTableUpdate) {
public void visitCustomTableUpdate(TableUpdateCustomSql tableUpdateCustomSql) {
throw new FeatureNotSupportedException();
}
+
+ void checkQueryOptionsSupportability(QueryOptions queryOptions) {
+ if (queryOptions.getTimeout() != null) {
+ throw new FeatureNotSupportedException("'timeout' inQueryOptions not supported");
+ }
+ if (queryOptions.getFlushMode() != null) {
+ throw new FeatureNotSupportedException("'flushMode' in QueryOptions not supported");
+ }
+ if (Boolean.TRUE.equals(queryOptions.isReadOnly())) {
+ throw new FeatureNotSupportedException("'readOnly' in QueryOptions not supported");
+ }
+ if (queryOptions.getAppliedGraph() != null) {
+ throw new FeatureNotSupportedException("'appliedGraph' in QueryOptions not supported");
+ }
+ if (queryOptions.getTupleTransformer() != null) {
+ throw new FeatureNotSupportedException("'tupleTransformer' in QueryOptions not supported");
+ }
+ if (queryOptions.getResultListTransformer() != null) {
+ throw new FeatureNotSupportedException("'resultListTransformer' in QueryOptions not supported");
+ }
+ if (Boolean.TRUE.equals(queryOptions.isResultCachingEnabled())) {
+ throw new FeatureNotSupportedException("'resultCaching' in QueryOptions not supported");
+ }
+ if (queryOptions.getDisabledFetchProfiles() != null
+ && !queryOptions.getDisabledFetchProfiles().isEmpty()) {
+ throw new FeatureNotSupportedException("'disabledFetchProfiles' in QueryOptions not supported");
+ }
+ if (queryOptions.getEnabledFetchProfiles() != null
+ && !queryOptions.getEnabledFetchProfiles().isEmpty()) {
+ throw new FeatureNotSupportedException("'enabledFetchProfiles' in QueryOptions not supported");
+ }
+ if (Boolean.TRUE.equals(queryOptions.getQueryPlanCachingEnabled())) {
+ throw new FeatureNotSupportedException("'queryPlanCaching' in QueryOptions not supported");
+ }
+ if (queryOptions.getLockOptions() != null
+ && !queryOptions.getLockOptions().isEmpty()) {
+ throw new FeatureNotSupportedException("'lockOptions' in QueryOptions not supported");
+ }
+ if (queryOptions.getComment() != null) {
+ throw new FeatureNotSupportedException("TO-DO-HIBERNATE-53 https://jira.mongodb.org/browse/HIBERNATE-53");
+ }
+ if (queryOptions.getDatabaseHints() != null
+ && !queryOptions.getDatabaseHints().isEmpty()) {
+ throw new FeatureNotSupportedException("'databaseHints' in QueryOptions not supported");
+ }
+ if (queryOptions.getFetchSize() != null) {
+ throw new FeatureNotSupportedException("TO-DO-HIBERNATE-54 https://jira.mongodb.org/browse/HIBERNATE-54");
+ }
+ if (queryOptions.getLimit() != null && !queryOptions.getLimit().isEmpty()) {
+ throw new FeatureNotSupportedException("TO-DO-HIBERNATE-70 https://jira.mongodb.org/browse/HIBERNATE-70");
+ }
+ }
}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptor.java b/src/main/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptor.java
index f261657d..399a4931 100644
--- a/src/main/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptor.java
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptor.java
@@ -19,19 +19,31 @@
import static com.mongodb.hibernate.internal.MongoAssertions.assertNotNull;
import static com.mongodb.hibernate.internal.MongoAssertions.fail;
-import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
import com.mongodb.hibernate.internal.translate.mongoast.AstValue;
+import com.mongodb.hibernate.internal.translate.mongoast.command.AstCommand;
+import com.mongodb.hibernate.internal.translate.mongoast.command.aggregate.AstProjectStageSpecification;
+import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilter;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
@SuppressWarnings("UnusedTypeParameter")
final class AstVisitorValueDescriptor {
- static final AstVisitorValueDescriptor COLLECTION_MUTATION = new AstVisitorValueDescriptor<>();
+ static final AstVisitorValueDescriptor COLLECTION_MUTATION = new AstVisitorValueDescriptor<>();
+ static final AstVisitorValueDescriptor COLLECTION_AGGREGATE = new AstVisitorValueDescriptor<>();
+
+ static final AstVisitorValueDescriptor COLLECTION_NAME = new AstVisitorValueDescriptor<>();
+
+ static final AstVisitorValueDescriptor FIELD_PATH = new AstVisitorValueDescriptor<>();
static final AstVisitorValueDescriptor FIELD_VALUE = new AstVisitorValueDescriptor<>();
+ static final AstVisitorValueDescriptor> PROJECT_STAGE_SPECIFICATIONS =
+ new AstVisitorValueDescriptor<>();
+ static final AstVisitorValueDescriptor FILTER = new AstVisitorValueDescriptor<>();
+
private static final Map, String> CONSTANT_TOSTRING_CONTENT_MAP;
static {
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/TableMutationMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/ModelMutationMqlTranslator.java
similarity index 81%
rename from src/main/java/com/mongodb/hibernate/internal/translate/TableMutationMqlTranslator.java
rename to src/main/java/com/mongodb/hibernate/internal/translate/ModelMutationMqlTranslator.java
index 7b9ecc27..071c8fdf 100644
--- a/src/main/java/com/mongodb/hibernate/internal/translate/TableMutationMqlTranslator.java
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/ModelMutationMqlTranslator.java
@@ -26,11 +26,11 @@
import org.hibernate.sql.model.jdbc.JdbcMutationOperation;
import org.jspecify.annotations.Nullable;
-final class TableMutationMqlTranslator extends AbstractMqlTranslator {
+final class ModelMutationMqlTranslator extends AbstractMqlTranslator {
private final TableMutation tableMutation;
- TableMutationMqlTranslator(TableMutation tableMutation, SessionFactoryImplementor sessionFactory) {
+ ModelMutationMqlTranslator(TableMutation tableMutation, SessionFactoryImplementor sessionFactory) {
super(sessionFactory);
this.tableMutation = tableMutation;
}
@@ -38,9 +38,9 @@ final class TableMutationMqlTranslator extends
@Override
public O translate(@Nullable JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) {
assertNull(jdbcParameterBindings);
- // QueryOptions class is not applicable to table mutation so a dummy value is always passed in
+ checkQueryOptionsSupportability(queryOptions);
- var rootAstNode = acceptAndYield(tableMutation, COLLECTION_MUTATION);
- return tableMutation.createMutationOperation(renderMongoAstNode(rootAstNode), getParameterBinders());
+ var mutationCommand = acceptAndYield(tableMutation, COLLECTION_MUTATION);
+ return tableMutation.createMutationOperation(renderMongoAstNode(mutationCommand), getParameterBinders());
}
}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/MongoTranslatorFactory.java b/src/main/java/com/mongodb/hibernate/internal/translate/MongoTranslatorFactory.java
index b2a9c05f..9a1a87fe 100644
--- a/src/main/java/com/mongodb/hibernate/internal/translate/MongoTranslatorFactory.java
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/MongoTranslatorFactory.java
@@ -30,8 +30,7 @@ public final class MongoTranslatorFactory implements SqlAstTranslatorFactory {
@Override
public SqlAstTranslator buildSelectTranslator(
SessionFactoryImplementor sessionFactoryImplementor, SelectStatement selectStatement) {
- // TODO-HIBERNATE-22 https://jira.mongodb.org/browse/HIBERNATE-22
- return new NoopSqlAstTranslator<>();
+ return new SelectMqlTranslator(sessionFactoryImplementor, selectStatement);
}
@Override
@@ -44,6 +43,6 @@ public SqlAstTranslator extends JdbcOperationQueryMutation> buildMutationTrans
@Override
public SqlAstTranslator buildModelMutationTranslator(
TableMutation tableMutation, SessionFactoryImplementor sessionFactoryImplementor) {
- return new TableMutationMqlTranslator<>(tableMutation, sessionFactoryImplementor);
+ return new ModelMutationMqlTranslator<>(tableMutation, sessionFactoryImplementor);
}
}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslator.java
new file mode 100644
index 00000000..dfa6e131
--- /dev/null
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslator.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate;
+
+import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_AGGREGATE;
+import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst;
+
+import com.mongodb.hibernate.internal.FeatureNotSupportedException;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
+import org.hibernate.query.spi.QueryOptions;
+import org.hibernate.sql.ast.tree.Statement;
+import org.hibernate.sql.ast.tree.select.SelectStatement;
+import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
+import org.hibernate.sql.exec.spi.JdbcParameterBindings;
+import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducerProvider;
+import org.jspecify.annotations.Nullable;
+
+final class SelectMqlTranslator extends AbstractMqlTranslator {
+
+ private final SelectStatement selectStatement;
+ private final JdbcValuesMappingProducerProvider jdbcValuesMappingProducerProvider;
+
+ SelectMqlTranslator(SessionFactoryImplementor sessionFactory, SelectStatement selectStatement) {
+ super(sessionFactory);
+ this.selectStatement = selectStatement;
+ jdbcValuesMappingProducerProvider =
+ sessionFactory.getServiceRegistry().requireService(JdbcValuesMappingProducerProvider.class);
+ }
+
+ @Override
+ public JdbcOperationQuerySelect translate(
+ @Nullable JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) {
+
+ logSqlAst(selectStatement);
+
+ if (jdbcParameterBindings != null) {
+ throw new FeatureNotSupportedException();
+ }
+ checkQueryOptionsSupportability(queryOptions);
+
+ var aggregateCommand = acceptAndYield((Statement) selectStatement, COLLECTION_AGGREGATE);
+ var jdbcValuesMappingProducer =
+ jdbcValuesMappingProducerProvider.buildMappingProducer(selectStatement, getSessionFactory());
+
+ return new JdbcOperationQuerySelect(
+ renderMongoAstNode(aggregateCommand),
+ getParameterBinders(),
+ jdbcValuesMappingProducer,
+ getAffectedTableNames());
+ }
+}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstCommand.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstCommand.java
new file mode 100644
index 00000000..c76c9083
--- /dev/null
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstCommand.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command;
+
+import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
+
+public interface AstCommand extends AstNode {}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstDeleteCommand.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstDeleteCommand.java
index 75a83e5c..6c2e10f6 100644
--- a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstDeleteCommand.java
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstDeleteCommand.java
@@ -16,11 +16,10 @@
package com.mongodb.hibernate.internal.translate.mongoast.command;
-import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilter;
import org.bson.BsonWriter;
-public record AstDeleteCommand(String collection, AstFilter filter) implements AstNode {
+public record AstDeleteCommand(String collection, AstFilter filter) implements AstCommand {
@Override
public void render(BsonWriter writer) {
writer.writeStartDocument();
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java
index 586b7e5e..a8358991 100644
--- a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java
@@ -17,10 +17,9 @@
package com.mongodb.hibernate.internal.translate.mongoast.command;
import com.mongodb.hibernate.internal.translate.mongoast.AstDocument;
-import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
import org.bson.BsonWriter;
-public record AstInsertCommand(String collection, AstDocument document) implements AstNode {
+public record AstInsertCommand(String collection, AstDocument document) implements AstCommand {
@Override
public void render(BsonWriter writer) {
writer.writeStartDocument();
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstUpdateCommand.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstUpdateCommand.java
index 0e93a639..8a6d58eb 100644
--- a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstUpdateCommand.java
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstUpdateCommand.java
@@ -17,13 +17,12 @@
package com.mongodb.hibernate.internal.translate.mongoast.command;
import com.mongodb.hibernate.internal.translate.mongoast.AstFieldUpdate;
-import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilter;
import java.util.List;
import org.bson.BsonWriter;
public record AstUpdateCommand(String collection, AstFilter filter, List extends AstFieldUpdate> updates)
- implements AstNode {
+ implements AstCommand {
@Override
public void render(BsonWriter writer) {
writer.writeStartDocument();
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstAggregateCommand.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstAggregateCommand.java
new file mode 100644
index 00000000..5e287332
--- /dev/null
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstAggregateCommand.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import com.mongodb.hibernate.internal.translate.mongoast.command.AstCommand;
+import java.util.List;
+import org.bson.BsonWriter;
+
+public record AstAggregateCommand(String collection, List extends AstStage> stages) implements AstCommand {
+
+ @Override
+ public void render(BsonWriter writer) {
+ writer.writeStartDocument();
+ {
+ writer.writeString("aggregate", collection);
+ writer.writeName("pipeline");
+ writer.writeStartArray();
+ {
+ stages.forEach(stage -> stage.render(writer));
+ }
+ writer.writeEndArray();
+ }
+ writer.writeEndDocument();
+ }
+}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstMatchStage.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstMatchStage.java
new file mode 100644
index 00000000..d219aaf4
--- /dev/null
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstMatchStage.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilter;
+import org.bson.BsonWriter;
+
+public record AstMatchStage(AstFilter filter) implements AstStage {
+ @Override
+ public void render(BsonWriter writer) {
+ writer.writeStartDocument();
+ {
+ writer.writeName("$match");
+ filter.render(writer);
+ }
+ writer.writeEndDocument();
+ }
+}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStage.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStage.java
new file mode 100644
index 00000000..86806794
--- /dev/null
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStage.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import java.util.List;
+import org.bson.BsonWriter;
+
+public record AstProjectStage(List extends AstProjectStageSpecification> specifications) implements AstStage {
+ @Override
+ public void render(BsonWriter writer) {
+ writer.writeStartDocument();
+ {
+ writer.writeName("$project");
+ writer.writeStartDocument();
+ {
+ specifications.forEach(specification -> specification.render(writer));
+ }
+ writer.writeEndDocument();
+ }
+ writer.writeEndDocument();
+ }
+}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageIncludeSpecification.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageIncludeSpecification.java
new file mode 100644
index 00000000..b5acdcce
--- /dev/null
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageIncludeSpecification.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import org.bson.BsonWriter;
+
+public record AstProjectStageIncludeSpecification(String field) implements AstProjectStageSpecification {
+ @Override
+ public void render(BsonWriter writer) {
+ writer.writeBoolean(field, true);
+ }
+}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageSpecification.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageSpecification.java
new file mode 100644
index 00000000..50959471
--- /dev/null
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageSpecification.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
+
+public interface AstProjectStageSpecification extends AstNode {}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstStage.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstStage.java
new file mode 100644
index 00000000..84751d5b
--- /dev/null
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstStage.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
+
+public interface AstStage extends AstNode {}
diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/package-info.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/package-info.java
new file mode 100644
index 00000000..266c34c5
--- /dev/null
+++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2024-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** The program elements within this package are not part of the public API and may be removed or changed at any time */
+@NullMarked
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslatorTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslatorTests.java
new file mode 100644
index 00000000..c7201eb1
--- /dev/null
+++ b/src/test/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslatorTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import com.mongodb.hibernate.internal.extension.service.StandardServiceRegistryScopedState;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
+import org.hibernate.persister.entity.EntityPersister;
+import org.hibernate.query.spi.QueryOptions;
+import org.hibernate.service.spi.ServiceRegistryImplementor;
+import org.hibernate.spi.NavigablePath;
+import org.hibernate.sql.ast.spi.SqlAliasBaseImpl;
+import org.hibernate.sql.ast.tree.from.NamedTableReference;
+import org.hibernate.sql.ast.tree.from.StandardTableGroup;
+import org.hibernate.sql.ast.tree.select.QuerySpec;
+import org.hibernate.sql.ast.tree.select.SelectStatement;
+import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducerProvider;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.MockMakers;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class SelectMqlTranslatorTests {
+
+ @Test
+ void testAffectedTableNames(
+ @Mock EntityPersister entityPersister,
+ @Mock(mockMaker = MockMakers.PROXY) SessionFactoryImplementor sessionFactory,
+ @Mock JdbcValuesMappingProducerProvider jdbcValuesMappingProducerProvider,
+ @Mock(mockMaker = MockMakers.PROXY) ServiceRegistryImplementor serviceRegistry,
+ @Mock StandardServiceRegistryScopedState standardServiceRegistryScopedState) {
+
+ var tableName = "books";
+ SelectStatement selectFromTableName;
+ { // prepare `selectFromTableName`
+ doReturn(new String[] {tableName}).when(entityPersister).getQuerySpaces();
+
+ var namedTableReference = new NamedTableReference(tableName, "b1_0");
+
+ var querySpec = new QuerySpec(true);
+ var tableGroup = new StandardTableGroup(
+ false,
+ new NavigablePath("Book"),
+ entityPersister,
+ null,
+ namedTableReference,
+ new SqlAliasBaseImpl("b1"),
+ sessionFactory);
+ querySpec.getFromClause().addRoot(tableGroup);
+ selectFromTableName = new SelectStatement(querySpec);
+ }
+ { // prepare `sessionFactory`
+ doReturn(serviceRegistry).when(sessionFactory).getServiceRegistry();
+ doReturn(jdbcValuesMappingProducerProvider)
+ .when(serviceRegistry)
+ .requireService(eq(JdbcValuesMappingProducerProvider.class));
+ doReturn(standardServiceRegistryScopedState)
+ .when(serviceRegistry)
+ .requireService(eq(StandardServiceRegistryScopedState.class));
+ }
+
+ var translator = new SelectMqlTranslator(sessionFactory, selectFromTableName);
+
+ translator.translate(null, QueryOptions.NONE);
+
+ assertThat(translator.getAffectedTableNames()).containsExactly(tableName);
+ }
+}
diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/AstNodeAssertions.java b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/AstNodeAssertions.java
index eab60d59..85dfc3a2 100644
--- a/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/AstNodeAssertions.java
+++ b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/AstNodeAssertions.java
@@ -27,9 +27,23 @@ public final class AstNodeAssertions {
private AstNodeAssertions() {}
public static void assertRender(String expectedJson, AstNode node) {
+ doAssertRender(expectedJson, node, false);
+ }
+
+ public static void assertElementRender(String expectedJson, AstNode node) {
+ doAssertRender(expectedJson, node, true);
+ }
+
+ private static void doAssertRender(String expectedJson, AstNode node, boolean isElement) {
try (var stringWriter = new StringWriter();
var jsonWriter = new JsonWriter(stringWriter)) {
+ if (isElement) {
+ jsonWriter.writeStartDocument();
+ }
node.render(jsonWriter);
+ if (isElement) {
+ jsonWriter.writeEndDocument();
+ }
jsonWriter.flush();
var actualJson = stringWriter.toString();
assertEquals(expectedJson, actualJson);
diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstAggregateCommandTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstAggregateCommandTests.java
new file mode 100644
index 00000000..66c9b80a
--- /dev/null
+++ b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstAggregateCommandTests.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import static com.mongodb.hibernate.internal.translate.mongoast.AstNodeAssertions.assertRender;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class AstAggregateCommandTests {
+
+ @Test
+ void testRendering() {
+ var aggregateCommand = new AstAggregateCommand(
+ "books", List.of(new AstProjectStage(List.of()), new AstProjectStage(List.of())));
+ var expectedJson =
+ """
+ {"aggregate": "books", "pipeline": [{"$project": {}}, {"$project": {}}]}\
+ """;
+ assertRender(expectedJson, aggregateCommand);
+ }
+}
diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstMatchStageTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstMatchStageTests.java
new file mode 100644
index 00000000..9d85a422
--- /dev/null
+++ b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstMatchStageTests.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import static com.mongodb.hibernate.internal.translate.mongoast.AstNodeAssertions.assertRender;
+import static com.mongodb.hibernate.internal.translate.mongoast.filter.AstComparisonFilterOperator.EQ;
+
+import com.mongodb.hibernate.internal.translate.mongoast.AstLiteralValue;
+import com.mongodb.hibernate.internal.translate.mongoast.filter.AstComparisonFilterOperation;
+import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFieldOperationFilter;
+import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilterFieldPath;
+import org.bson.BsonString;
+import org.junit.jupiter.api.Test;
+
+class AstMatchStageTests {
+
+ @Test
+ void testRendering() {
+ var astFilter = new AstFieldOperationFilter(
+ new AstFilterFieldPath("title"),
+ new AstComparisonFilterOperation(EQ, new AstLiteralValue(new BsonString("Jane Eyre"))));
+ var astMatchStage = new AstMatchStage(astFilter);
+
+ var expectedJson = """
+ {"$match": {"title": {"$eq": "Jane Eyre"}}}\
+ """;
+ assertRender(expectedJson, astMatchStage);
+ }
+}
diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageIncludeSpecificationTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageIncludeSpecificationTests.java
new file mode 100644
index 00000000..cb3b7f45
--- /dev/null
+++ b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageIncludeSpecificationTests.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import static com.mongodb.hibernate.internal.translate.mongoast.AstNodeAssertions.assertElementRender;
+
+import org.junit.jupiter.api.Test;
+
+class AstProjectStageIncludeSpecificationTests {
+
+ @Test
+ void testRendering() {
+ var projectStageIncludeSpecification = new AstProjectStageIncludeSpecification("name");
+ var expectedJson = """
+ {"name": true}\
+ """;
+ assertElementRender(expectedJson, projectStageIncludeSpecification);
+ }
+}
diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageTests.java
new file mode 100644
index 00000000..fcdde16a
--- /dev/null
+++ b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/aggregate/AstProjectStageTests.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2025-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.mongodb.hibernate.internal.translate.mongoast.command.aggregate;
+
+import static com.mongodb.hibernate.internal.translate.mongoast.AstNodeAssertions.assertRender;
+
+import java.util.Collections;
+import org.junit.jupiter.api.Test;
+
+class AstProjectStageTests {
+
+ @Test
+ void testRendering() {
+ var astProjectStage = new AstProjectStage(Collections.emptyList());
+ var expectedJson = """
+ {"$project": {}}\
+ """;
+ assertRender(expectedJson, astProjectStage);
+ }
+}