Skip to content

Commit 1850b77

Browse files
simple DML translation (#102)
HIBERNATE-46
1 parent 7abf169 commit 1850b77

20 files changed

+1321
-644
lines changed
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
/*
2+
* Copyright 2025-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.hibernate.query;
18+
19+
import static com.mongodb.hibernate.MongoTestAssertions.assertIterableEq;
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
22+
import static org.hibernate.cfg.JdbcSettings.DIALECT;
23+
import static org.mockito.ArgumentMatchers.any;
24+
import static org.mockito.Mockito.doAnswer;
25+
import static org.mockito.Mockito.spy;
26+
27+
import com.mongodb.client.MongoCollection;
28+
import com.mongodb.hibernate.TestCommandListener;
29+
import com.mongodb.hibernate.dialect.MongoDialect;
30+
import com.mongodb.hibernate.junit.MongoExtension;
31+
import java.util.Set;
32+
import java.util.function.Consumer;
33+
import org.assertj.core.api.InstanceOfAssertFactories;
34+
import org.bson.BsonDocument;
35+
import org.hibernate.dialect.Dialect;
36+
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
37+
import org.hibernate.engine.spi.SessionFactoryImplementor;
38+
import org.hibernate.query.MutationQuery;
39+
import org.hibernate.query.SelectionQuery;
40+
import org.hibernate.sql.ast.SqlAstTranslator;
41+
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
42+
import org.hibernate.sql.ast.tree.MutationStatement;
43+
import org.hibernate.sql.ast.tree.select.SelectStatement;
44+
import org.hibernate.sql.exec.spi.AbstractJdbcOperationQuery;
45+
import org.hibernate.sql.exec.spi.JdbcOperation;
46+
import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation;
47+
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
48+
import org.hibernate.sql.model.ast.TableMutation;
49+
import org.hibernate.sql.model.jdbc.JdbcMutationOperation;
50+
import org.hibernate.testing.orm.junit.ServiceRegistry;
51+
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
52+
import org.hibernate.testing.orm.junit.ServiceRegistryScopeAware;
53+
import org.hibernate.testing.orm.junit.SessionFactory;
54+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
55+
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
56+
import org.hibernate.testing.orm.junit.Setting;
57+
import org.junit.jupiter.api.extension.ExtendWith;
58+
import org.mockito.stubbing.Answer;
59+
60+
@SessionFactory(exportSchema = false)
61+
@ServiceRegistry(
62+
settings =
63+
@Setting(
64+
name = DIALECT,
65+
value =
66+
"com.mongodb.hibernate.query.AbstractQueryIntegrationTests$TranslateResultAwareDialect"))
67+
@ExtendWith(MongoExtension.class)
68+
public abstract class AbstractQueryIntegrationTests implements SessionFactoryScopeAware, ServiceRegistryScopeAware {
69+
70+
private SessionFactoryScope sessionFactoryScope;
71+
72+
private TestCommandListener testCommandListener;
73+
74+
@Override
75+
public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) {
76+
this.sessionFactoryScope = sessionFactoryScope;
77+
}
78+
79+
@Override
80+
public void injectServiceRegistryScope(ServiceRegistryScope serviceRegistryScope) {
81+
this.testCommandListener = serviceRegistryScope.getRegistry().requireService(TestCommandListener.class);
82+
}
83+
84+
protected SessionFactoryScope getSessionFactoryScope() {
85+
return sessionFactoryScope;
86+
}
87+
88+
protected TestCommandListener getTestCommandListener() {
89+
return testCommandListener;
90+
}
91+
92+
protected <T> void assertSelectionQuery(
93+
String hql,
94+
Class<T> resultType,
95+
Consumer<SelectionQuery<T>> queryPostProcessor,
96+
String expectedMql,
97+
Iterable<T> expectedResultList,
98+
Set<String> expectedAffectedCollections) {
99+
assertSelectionQuery(
100+
hql,
101+
resultType,
102+
queryPostProcessor,
103+
expectedMql,
104+
resultList -> assertIterableEq(expectedResultList, resultList),
105+
expectedAffectedCollections);
106+
}
107+
108+
protected <T> void assertSelectionQuery(
109+
String hql,
110+
Class<T> resultType,
111+
String expectedMql,
112+
Iterable<T> expectedResultList,
113+
Set<String> expectedAffectedCollections) {
114+
assertSelectionQuery(hql, resultType, null, expectedMql, expectedResultList, expectedAffectedCollections);
115+
}
116+
117+
protected <T> void assertSelectionQuery(
118+
String hql,
119+
Class<T> resultType,
120+
Consumer<SelectionQuery<T>> queryPostProcessor,
121+
String expectedMql,
122+
Consumer<Iterable<? extends T>> resultListVerifier,
123+
Set<String> expectedAffectedCollections) {
124+
sessionFactoryScope.inTransaction(session -> {
125+
var selectionQuery = session.createSelectionQuery(hql, resultType);
126+
if (queryPostProcessor != null) {
127+
queryPostProcessor.accept(selectionQuery);
128+
}
129+
var resultList = selectionQuery.getResultList();
130+
131+
assertActualCommand(BsonDocument.parse(expectedMql));
132+
133+
resultListVerifier.accept(resultList);
134+
135+
assertAffectedCollections(expectedAffectedCollections);
136+
});
137+
}
138+
139+
protected <T> void assertSelectionQuery(
140+
String hql,
141+
Class<T> resultType,
142+
String expectedMql,
143+
Consumer<Iterable<? extends T>> resultListVerifier,
144+
Set<String> expectedAffectedCollections) {
145+
assertSelectionQuery(hql, resultType, null, expectedMql, resultListVerifier, expectedAffectedCollections);
146+
}
147+
148+
protected <T> void assertSelectQueryFailure(
149+
String hql,
150+
Class<T> resultType,
151+
Consumer<SelectionQuery<T>> queryPostProcessor,
152+
Class<? extends Exception> expectedExceptionType,
153+
String expectedExceptionMessage,
154+
Object... expectedExceptionMessageParameters) {
155+
sessionFactoryScope.inTransaction(session -> assertThatThrownBy(() -> {
156+
var selectionQuery = session.createSelectionQuery(hql, resultType);
157+
if (queryPostProcessor != null) {
158+
queryPostProcessor.accept(selectionQuery);
159+
}
160+
selectionQuery.getResultList();
161+
})
162+
.isInstanceOf(expectedExceptionType)
163+
.hasMessage(expectedExceptionMessage, expectedExceptionMessageParameters));
164+
}
165+
166+
protected void assertSelectQueryFailure(
167+
String hql,
168+
Class<?> resultType,
169+
Class<? extends Exception> expectedExceptionType,
170+
String expectedExceptionMessage,
171+
Object... expectedExceptionMessageParameters) {
172+
assertSelectQueryFailure(
173+
hql,
174+
resultType,
175+
null,
176+
expectedExceptionType,
177+
expectedExceptionMessage,
178+
expectedExceptionMessageParameters);
179+
}
180+
181+
protected void assertActualCommand(BsonDocument expectedCommand) {
182+
var capturedCommands = testCommandListener.getStartedCommands();
183+
184+
assertThat(capturedCommands)
185+
.singleElement()
186+
.asInstanceOf(InstanceOfAssertFactories.MAP)
187+
.containsAllEntriesOf(expectedCommand);
188+
}
189+
190+
protected void assertMutationQuery(
191+
String hql,
192+
Consumer<MutationQuery> queryPostProcessor,
193+
int expectedMutationCount,
194+
String expectedMql,
195+
MongoCollection<BsonDocument> collection,
196+
Iterable<? extends BsonDocument> expectedDocuments,
197+
Set<String> expectedAffectedCollections) {
198+
sessionFactoryScope.inTransaction(session -> {
199+
var query = session.createMutationQuery(hql);
200+
if (queryPostProcessor != null) {
201+
queryPostProcessor.accept(query);
202+
}
203+
var mutationCount = query.executeUpdate();
204+
assertActualCommand(BsonDocument.parse(expectedMql));
205+
assertThat(mutationCount).isEqualTo(expectedMutationCount);
206+
});
207+
assertThat(collection.find()).containsExactlyElementsOf(expectedDocuments);
208+
assertAffectedCollections(expectedAffectedCollections);
209+
}
210+
211+
protected void assertMutationQueryFailure(
212+
String hql,
213+
Consumer<MutationQuery> queryPostProcessor,
214+
Class<? extends Exception> expectedExceptionType,
215+
String expectedExceptionMessage,
216+
Object... expectedExceptionMessageParameters) {
217+
sessionFactoryScope.inTransaction(session -> assertThatThrownBy(() -> {
218+
var query = session.createMutationQuery(hql);
219+
if (queryPostProcessor != null) {
220+
queryPostProcessor.accept(query);
221+
}
222+
query.executeUpdate();
223+
})
224+
.isInstanceOf(expectedExceptionType)
225+
.hasMessage(expectedExceptionMessage, expectedExceptionMessageParameters));
226+
}
227+
228+
private void assertAffectedCollections(Set<String> expectedAffectedCollections) {
229+
assertThat(((TranslateResultAwareDialect) getSessionFactoryScope()
230+
.getSessionFactory()
231+
.getJdbcServices()
232+
.getDialect())
233+
.capturedTranslateResult.getAffectedTableNames())
234+
.containsExactlyInAnyOrderElementsOf(expectedAffectedCollections);
235+
}
236+
237+
protected static final class TranslateResultAwareDialect extends Dialect {
238+
private final Dialect delegate;
239+
private AbstractJdbcOperationQuery capturedTranslateResult;
240+
241+
public TranslateResultAwareDialect(DialectResolutionInfo info) {
242+
super(info);
243+
delegate = new MongoDialect(info);
244+
}
245+
246+
@Override
247+
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
248+
return new SqlAstTranslatorFactory() {
249+
@Override
250+
public SqlAstTranslator<JdbcOperationQuerySelect> buildSelectTranslator(
251+
SessionFactoryImplementor sessionFactory, SelectStatement statement) {
252+
return createCapturingTranslator(
253+
delegate.getSqlAstTranslatorFactory().buildSelectTranslator(sessionFactory, statement));
254+
}
255+
256+
@Override
257+
public SqlAstTranslator<? extends JdbcOperationQueryMutation> buildMutationTranslator(
258+
SessionFactoryImplementor sessionFactory, MutationStatement statement) {
259+
return createCapturingTranslator(
260+
delegate.getSqlAstTranslatorFactory().buildMutationTranslator(sessionFactory, statement));
261+
}
262+
263+
@Override
264+
public <O extends JdbcMutationOperation> SqlAstTranslator<O> buildModelMutationTranslator(
265+
TableMutation<O> mutation, SessionFactoryImplementor sessionFactory) {
266+
return delegate.getSqlAstTranslatorFactory().buildModelMutationTranslator(mutation, sessionFactory);
267+
}
268+
269+
private <T extends JdbcOperation> SqlAstTranslator<T> createCapturingTranslator(
270+
SqlAstTranslator<T> originalTranslator) {
271+
var translatorSpy = spy(originalTranslator);
272+
doAnswer((Answer<AbstractJdbcOperationQuery>) invocation -> {
273+
capturedTranslateResult = (AbstractJdbcOperationQuery) invocation.callRealMethod();
274+
return capturedTranslateResult;
275+
})
276+
.when(translatorSpy)
277+
.translate(any(), any());
278+
return translatorSpy;
279+
}
280+
};
281+
}
282+
}
283+
}

src/integrationTest/java/com/mongodb/hibernate/query/select/Book.java renamed to src/integrationTest/java/com/mongodb/hibernate/query/Book.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,32 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.mongodb.hibernate.query.select;
17+
package com.mongodb.hibernate.query;
1818

1919
import jakarta.persistence.Entity;
2020
import jakarta.persistence.Id;
2121
import jakarta.persistence.Table;
2222
import java.math.BigDecimal;
2323

2424
@Entity(name = "Book")
25-
@Table(name = "books")
26-
class Book {
25+
@Table(name = Book.COLLECTION_NAME)
26+
public class Book {
27+
public static final String COLLECTION_NAME = "books";
28+
2729
@Id
28-
int id;
30+
public int id;
2931

3032
// TODO-HIBERNATE-48 dummy values are set for currently null value is not supported
31-
String title = "";
32-
Boolean outOfStock = false;
33-
Integer publishYear = 0;
34-
Long isbn13 = 0L;
35-
Double discount = 0.0;
36-
BigDecimal price = new BigDecimal("0.0");
33+
public String title = "";
34+
public Boolean outOfStock = false;
35+
public Integer publishYear = 0;
36+
public Long isbn13 = 0L;
37+
public Double discount = 0.0;
38+
public BigDecimal price = new BigDecimal("0.0");
3739

38-
Book() {}
40+
public Book() {}
3941

40-
Book(int id, String title, Integer publishYear, Boolean outOfStock) {
42+
public Book(int id, String title, Integer publishYear, Boolean outOfStock) {
4143
this.id = id;
4244
this.title = title;
4345
this.publishYear = publishYear;

0 commit comments

Comments
 (0)