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 @@ <logger name="org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator" level="debug" additivity="true"/> <logger name="org.hibernate.SQL" level="debug" additivity="true"/> <logger name="org.hibernate.orm.jdbc.bind" level="trace" additivity="true"/> + <logger name="org.hibernate.orm.jdbc.extract" level="trace" additivity="true"/> <logger name="org.hibernate.persister.entity" level="debug" additivity="true"/> + <logger name="org.hibernate.orm.sql.ast.tree" level="debug" additivity="true"/> <logger name="org.mongodb.driver" level="warn" additivity="true"/> <logger name="com.mongodb.hibernate" level="debug" additivity="true"/> <root level="info"> 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<T extends JdbcOperation> implements SqlAstT private final List<JdbcParameterBinder> parameterBinders = new ArrayList<>(); + private final Set<String> affectedTableNames = new HashSet<>(); + AbstractMqlTranslator(SessionFactoryImplementor sessionFactory) { this.sessionFactory = sessionFactory; assertNotNull(sessionFactory @@ -178,7 +196,7 @@ public Stack<Clause> getCurrentClauseStack() { @Override public Set<String> getAffectedTableNames() { - throw new FeatureNotSupportedException("TODO-HIBERNATE-22 https://jira.mongodb.org/browse/HIBERNATE-22"); + return affectedTableNames; } List<JdbcParameterBinder> getParameterBinders() { @@ -197,12 +215,12 @@ static String renderMongoAstNode(AstNode rootAstNode) { } @SuppressWarnings("overloads") - <R extends AstNode> R acceptAndYield(Statement statement, AstVisitorValueDescriptor<R> resultDescriptor) { + <R extends AstCommand> R acceptAndYield(Statement statement, AstVisitorValueDescriptor<R> resultDescriptor) { return astVisitorValueHolder.execute(resultDescriptor, () -> statement.accept(this)); } @SuppressWarnings("overloads") - <R extends AstNode> R acceptAndYield(SqlAstNode node, AstVisitorValueDescriptor<R> resultDescriptor) { + <R> R acceptAndYield(SqlAstNode node, AstVisitorValueDescriptor<R> resultDescriptor) { return astVisitorValueHolder.execute(resultDescriptor, () -> node.accept(this)); } @@ -217,17 +235,16 @@ public void visitStandardTableInsert(TableInsertStandard tableInsert) { var astElements = new ArrayList<AstElement>(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<AstProjectStageSpecification>( + 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<T> { - static final AstVisitorValueDescriptor<AstNode> COLLECTION_MUTATION = new AstVisitorValueDescriptor<>(); + static final AstVisitorValueDescriptor<AstCommand> COLLECTION_MUTATION = new AstVisitorValueDescriptor<>(); + static final AstVisitorValueDescriptor<AstCommand> COLLECTION_AGGREGATE = new AstVisitorValueDescriptor<>(); + + static final AstVisitorValueDescriptor<String> COLLECTION_NAME = new AstVisitorValueDescriptor<>(); + + static final AstVisitorValueDescriptor<String> FIELD_PATH = new AstVisitorValueDescriptor<>(); static final AstVisitorValueDescriptor<AstValue> FIELD_VALUE = new AstVisitorValueDescriptor<>(); + static final AstVisitorValueDescriptor<List<AstProjectStageSpecification>> PROJECT_STAGE_SPECIFICATIONS = + new AstVisitorValueDescriptor<>(); + static final AstVisitorValueDescriptor<AstFilter> FILTER = new AstVisitorValueDescriptor<>(); + private static final Map<AstVisitorValueDescriptor<?>, 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<O extends JdbcMutationOperation> extends AbstractMqlTranslator<O> { +final class ModelMutationMqlTranslator<O extends JdbcMutationOperation> extends AbstractMqlTranslator<O> { private final TableMutation<O> tableMutation; - TableMutationMqlTranslator(TableMutation<O> tableMutation, SessionFactoryImplementor sessionFactory) { + ModelMutationMqlTranslator(TableMutation<O> tableMutation, SessionFactoryImplementor sessionFactory) { super(sessionFactory); this.tableMutation = tableMutation; } @@ -38,9 +38,9 @@ final class TableMutationMqlTranslator<O extends JdbcMutationOperation> 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<JdbcOperationQuerySelect> 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 <O extends JdbcMutationOperation> SqlAstTranslator<O> buildModelMutationTranslator( TableMutation<O> 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<JdbcOperationQuerySelect> { + + 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); + } +}