Skip to content

Commit 499e600

Browse files
NathanQingyangXukatcharovstIncMale
authored
implement java.sql.ResultSet (#30)
* implement java.sql.ResultSet * use the new TODO tag * finish implementing MongoConnection#prepareStatement(String,int,int) * sync up with latest main branch * enrich testing cases * make testing more robust by making column ordered randomly both during insertion and in $project list * make use of multi-line formatter to fix spotless issue * resolve conflict with latest main branch * Update src/integrationTest/java/com/mongodb/hibernate/jdbc/MongoPreparedStatementIntegrationTests.java Co-authored-by: Maxim Katcharov <[email protected]> * add try block to ResultSet in MongoPreparedStatementIntegrationTests * Update src/integrationTest/java/com/mongodb/hibernate/jdbc/MongoStatementIntegrationTests.java Co-authored-by: Maxim Katcharov <[email protected]> * add try block to ResultSet in MongoStatementIntegrationTests; move `rs.next()` after metadata checking * remove confusing exception message for MongoResultSet#getObject() * simplify EXAMPLE_MQL to empty doc in MongoConnectionTests's ResultSetSupportTests * remove confusing `when&&then` without `given` in MongoResultSetTests * decrease duplication in MongoResultSetTests's method sources * add exhaustive getXXX() unit testing logic * remove unnecessary usage of `@Nested` in integration testing cases * add round-trip testing case to combine PreparedStatement and ResultSet together * change column index checking exception message * change SQLException message in MongoStatement#executeQueryCommand() * rename testing case in MongoResultSetTests to testIsIdempotent() * clean up adapter classes; add logic and testing case to close ResultSet when the originating Statement is closed * improve implementation of MongoStatement#executeQueryCommmand by removing the usage of stream * rename CloseTests to ClosedTests and move the close statement as first statement in each testing cases * revert back changes to ResultSetAdapter to avoid touching it again and again in the future * fix conflict with latest main branch * Update src/test/java/com/mongodb/hibernate/jdbc/MongoResultSetTests.java Co-authored-by: Maxim Katcharov <[email protected]> * update based on some code review comments * rename some methods in MongoResultSetTests * refactor the MongoResultSetTests#testGetValues() based on @MethodSource * add validation of "false" BsonBoolean in MongoResultSetTests#testGetValues * merge in latest main * refactor getters testing avoiding the usage of MethodSource * change testing method name from `getOpenPreconditionInvocations` * improve MongoResultSetTests * add Getter testing cases into MongoResultSetTests * switch to testing mode in Java driver * drop support of the "byte", "short" and "float" sql types for both `MongoPreparedStatement` and `MongoResultSet` * refactor MongoStatementIntegrationTests and MongoPreparedStatementIntegrationTests to remove code duplication * refactor JDBC closed and range checking testing code to align with MongoResultSetTests * remove TODO-HIBERNATE-21 comments from MongoStatement * add ticket references to `setQueryTimeout()` and `setFetchSize()` implementation; remove unused methods after Hibernate codebase walkthrough * resolve conflict with latest main * implemented MongoStatement#getMaxRows() and MongoStatement#setMaxRows(int) methods * add logic to use maxRows in MongoStatement#executeQueryCommand() and testing cases * addesses two code review comments by Maxim * Update src/main/java/com/mongodb/hibernate/jdbc/MongoStatement.java Co-authored-by: Valentin Kovalenko <[email protected]> * improve MongoExtension by creating MongoClient only once for top class (not `@Nested` classes) * add parameter setting checking in MongoPreparedStatement#executeQuery() and MongoPreparedStatement#executeUpdate * add logic to throw SQLException from PreparedStatement for the 4 Statement methods which accept SQL * add logic to ensure last open ResultSet is closed when Statement invokes query methods returning ResultSet * improve coding in MongoStatement#executeQueryCommand() * move `startTransactionIfNeeded` into try block to catch potential RuntimeException * rename MongoStatement#getFieldNamesFromProjectDocument to #getFieldNamesFromProjectStage; removed javadoc * remove MongoStatement#setMaxRows() implementation for it is not used in Hibernate for our dialect * remove javadocs from adapter classes; add missing adapter methods in ResultSetMetaDataAdapter * remove ResultSetMetadata stuff and leave ticket references to select native query tickets * Update src/main/java/com/mongodb/hibernate/jdbc/MongoResultSet.java Co-authored-by: Valentin Kovalenko <[email protected]> * make range checking logic consistent between MongoResultSet and MongoPreparedStatement * make MongoResultSet checkClosed() method consistent with MongoStatement's * throw exception for MongoResultSet#findColumn until we work on native query * improve MongoResultSet#close warning message * rename Function parameter name in MongoResultSet#getValue() * add missing MongoResultSet#getBytes() implementation * simplify the implementation of MongoResultSet#wasNull * clean up jdbc classes * merge in latest main branch * code refactor to combine setter and used state into one class in MongoPreparedStatement * improve the error handling of the 4 forbidden methods accepting SQL in MongoPreparedStatement * add closing last open ResultSet for all execution methods of MongoStatement and MongoPreparedStatement * move `lastReadColumnValueWasNull` assignment statement after `toJavaConverter` is applied * change logic to thrown exception if some field is missing * change as per review comments * add unit testing cases to ensure last open resultSet is closed when executeXXX methods are invoked in both MongoPreparedStatement and MongoStatement * Update src/main/java/com/mongodb/hibernate/jdbc/MongoResultSet.java Co-authored-by: Valentin Kovalenko <[email protected]> * improve fields emptiness checking in MongoResultSet * add `@DynamicInsert` forbidding logic in MongoAdditionalMappingContributor * add logic to add _id unless suppressed or included explicitly already in $project stage * Update src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionTests.java Co-authored-by: Valentin Kovalenko <[email protected]> * Update src/test/java/com/mongodb/hibernate/jdbc/MongoStatementTests.java Co-authored-by: Valentin Kovalenko <[email protected]> * Update src/test/java/com/mongodb/hibernate/jdbc/MongoStatementTests.java Co-authored-by: Valentin Kovalenko <[email protected]> * made changes as per code review comments * fix a compiling error * fix a spotless issue * further improvements to some jdbc unit testing classes as per code review comments * simplify MongoStatement#getFieldNamesFromProjectStage() --------- Co-authored-by: Maxim Katcharov <[email protected]> Co-authored-by: Valentin Kovalenko <[email protected]>
1 parent 5664060 commit 499e600

15 files changed

+2629
-505
lines changed

src/integrationTest/java/com/mongodb/hibernate/jdbc/MongoPreparedStatementIntegrationTests.java

Lines changed: 244 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,39 @@
1717
package com.mongodb.hibernate.jdbc;
1818

1919
import static com.mongodb.hibernate.internal.MongoConstants.ID_FIELD_NAME;
20+
import static com.mongodb.hibernate.jdbc.MongoStatementIntegrationTests.doAndTerminateTransaction;
21+
import static com.mongodb.hibernate.jdbc.MongoStatementIntegrationTests.doWithSpecifiedAutoCommit;
22+
import static com.mongodb.hibernate.jdbc.MongoStatementIntegrationTests.insertTestData;
2023
import static org.assertj.core.api.Assertions.assertThat;
24+
import static org.junit.jupiter.api.Assertions.assertAll;
25+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
2126
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
import static org.junit.jupiter.api.Assertions.assertFalse;
28+
import static org.junit.jupiter.api.Assertions.assertNull;
29+
import static org.junit.jupiter.api.Assertions.assertTrue;
2230

2331
import com.mongodb.client.MongoCollection;
2432
import com.mongodb.client.model.Sorts;
33+
import com.mongodb.hibernate.jdbc.MongoStatementIntegrationTests.SqlExecutable;
2534
import com.mongodb.hibernate.junit.InjectMongoCollection;
2635
import com.mongodb.hibernate.junit.MongoExtension;
36+
import java.math.BigDecimal;
2737
import java.sql.Connection;
2838
import java.sql.SQLException;
2939
import java.util.List;
40+
import java.util.Random;
3041
import java.util.function.Function;
3142
import org.bson.BsonDocument;
3243
import org.hibernate.Session;
3344
import org.hibernate.SessionFactory;
3445
import org.hibernate.cfg.Configuration;
46+
import org.hibernate.jdbc.Work;
3547
import org.junit.jupiter.api.AutoClose;
3648
import org.junit.jupiter.api.BeforeAll;
3749
import org.junit.jupiter.api.BeforeEach;
3850
import org.junit.jupiter.api.Nested;
51+
import org.junit.jupiter.api.Test;
3952
import org.junit.jupiter.api.extension.ExtendWith;
40-
import org.junit.jupiter.params.ParameterizedTest;
41-
import org.junit.jupiter.params.provider.ValueSource;
4253

4354
@ExtendWith(MongoExtension.class)
4455
class MongoPreparedStatementIntegrationTests {
@@ -62,23 +73,145 @@ void beforeEach() {
6273
session = sessionFactory.openSession();
6374
}
6475

65-
@Nested
66-
class ExecuteUpdateTests {
76+
@Test
77+
void testExecuteQuery() {
78+
79+
insertTestData(
80+
session,
81+
"""
82+
{
83+
insert: "books",
84+
documents: [
85+
{ _id: 1, publishYear: 1867, title: "War and Peace", author: "Leo Tolstoy", comment: "reference only", vintage: false },
86+
{ _id: 2, publishYear: 1878, title: "Anna Karenina", author: "Leo Tolstoy", vintage: true, comment: null},
87+
{ _id: 3, publishYear: 1866, author: "Fyodor Dostoevsky", title: "Crime and Punishment", vintage: false, comment: null },
88+
]
89+
}""");
90+
91+
doWorkAwareOfAutoCommit(connection -> {
92+
try (var pstmt = connection.prepareStatement(
93+
"""
94+
{
95+
aggregate: "books",
96+
pipeline: [
97+
{ $match: { author: { $eq: { $undefined: true } } } },
98+
{ $project: { author: 1, _id: 0, vintage: 1, publishYear: 1, comment: 1, title: 1 } }
99+
]
100+
}""")) {
101+
102+
pstmt.setString(1, "Leo Tolstoy");
103+
104+
try (var rs = pstmt.executeQuery()) {
105+
106+
assertTrue(rs.next());
107+
assertAll(
108+
() -> assertEquals("Leo Tolstoy", rs.getString(1)),
109+
() -> assertFalse(rs.getBoolean(2)),
110+
() -> assertEquals(1867, rs.getInt(3)),
111+
() -> assertEquals("reference only", rs.getString(4)),
112+
() -> assertEquals("War and Peace", rs.getString(5)));
113+
assertTrue(rs.next());
114+
assertAll(
115+
() -> assertEquals("Leo Tolstoy", rs.getString(1)),
116+
() -> assertTrue(rs.getBoolean(2)),
117+
() -> assertEquals(1878, rs.getInt(3)),
118+
() -> assertNull(rs.getString(4)),
119+
() -> assertEquals("Anna Karenina", rs.getString(5)));
120+
assertFalse(rs.next());
121+
}
122+
}
123+
});
124+
}
125+
126+
@Test
127+
void testPreparedStatementAndResultSetRoundTrip() {
128+
129+
var random = new Random();
130+
131+
boolean booleanValue = random.nextBoolean();
132+
double doubleValue = random.nextDouble();
133+
int intValue = random.nextInt();
134+
long longValue = random.nextLong();
135+
136+
byte[] bytes = new byte[64];
137+
random.nextBytes(bytes);
138+
String stringValue = new String(bytes);
139+
140+
BigDecimal bigDecimalValue = new BigDecimal(random.nextInt());
141+
142+
doWorkAwareOfAutoCommit(connection -> {
143+
try (var pstmt = connection.prepareStatement(
144+
"""
145+
{
146+
insert: "books",
147+
documents: [
148+
{
149+
_id: 1,
150+
booleanField: { $undefined: true },
151+
doubleField: { $undefined: true },
152+
intField: { $undefined: true },
153+
longField: { $undefined: true },
154+
stringField: { $undefined: true },
155+
bigDecimalField: { $undefined: true },
156+
bytesField: { $undefined: true }
157+
}
158+
]
159+
}""")) {
160+
161+
pstmt.setBoolean(1, booleanValue);
162+
pstmt.setDouble(2, doubleValue);
163+
pstmt.setInt(3, intValue);
164+
pstmt.setLong(4, longValue);
165+
pstmt.setString(5, stringValue);
166+
pstmt.setBigDecimal(6, bigDecimalValue);
167+
pstmt.setBytes(7, bytes);
168+
169+
pstmt.executeUpdate();
170+
}
171+
});
67172

68-
@BeforeEach
69-
void beforeEach() {
70-
session.doWork(conn -> {
71-
conn.createStatement()
72-
.executeUpdate(
73-
"""
173+
doWorkAwareOfAutoCommit(connection -> {
174+
try (var pstmt = connection.prepareStatement(
175+
"""
176+
{
177+
aggregate: "books",
178+
pipeline: [
179+
{ $match: { _id: { $eq: { $undefined: true } } } },
180+
{ $project:
74181
{
75-
delete: "books",
76-
deletes: [
77-
{ q: {}, limit: 0 }
78-
]
79-
}""");
80-
});
81-
}
182+
_id: 0,
183+
booleanField: 1,
184+
doubleField: 1,
185+
intField: 1,
186+
longField: 1,
187+
stringField: 1,
188+
bigDecimalField: 1,
189+
bytesField: 1
190+
}
191+
}
192+
]
193+
}""")) {
194+
195+
pstmt.setInt(1, 1);
196+
try (var rs = pstmt.executeQuery()) {
197+
198+
assertTrue(rs.next());
199+
assertAll(
200+
() -> assertEquals(booleanValue, rs.getBoolean(1)),
201+
() -> assertEquals(doubleValue, rs.getDouble(2)),
202+
() -> assertEquals(intValue, rs.getInt(3)),
203+
() -> assertEquals(longValue, rs.getLong(4)),
204+
() -> assertEquals(stringValue, rs.getString(5)),
205+
() -> assertEquals(bigDecimalValue, rs.getBigDecimal(6)),
206+
() -> assertArrayEquals(bytes, rs.getBytes(7)));
207+
assertFalse(rs.next());
208+
}
209+
}
210+
});
211+
}
212+
213+
@Nested
214+
class ExecuteUpdateTests {
82215

83216
private static final String INSERT_MQL =
84217
"""
@@ -109,11 +242,51 @@ void beforeEach() {
109242
]
110243
}""";
111244

112-
@ParameterizedTest
113-
@ValueSource(booleans = {true, false})
114-
void testUpdate(boolean autoCommit) {
245+
@Test
246+
void testInsert() {
247+
Function<Connection, MongoPreparedStatement> pstmtProvider = connection -> {
248+
try {
249+
var pstmt = (MongoPreparedStatement)
250+
connection.prepareStatement(
251+
"""
252+
{
253+
insert: "books",
254+
documents: [
255+
{
256+
_id: 1,
257+
title: {$undefined: true},
258+
author: {$undefined: true},
259+
outOfStock: false,
260+
tags: [ "classic", "tolstoy" ]
261+
}
262+
]
263+
}""");
264+
pstmt.setString(1, "War and Peace");
265+
pstmt.setString(2, "Leo Tolstoy");
266+
return pstmt;
267+
} catch (SQLException e) {
268+
throw new RuntimeException(e);
269+
}
270+
};
271+
assertExecuteUpdate(
272+
pstmtProvider,
273+
1,
274+
List.of(
275+
BsonDocument.parse(
276+
"""
277+
{
278+
_id: 1,
279+
title: "War and Peace",
280+
author: "Leo Tolstoy",
281+
outOfStock: false,
282+
tags: [ "classic", "tolstoy" ]
283+
}""")));
284+
}
285+
286+
@Test
287+
void testUpdate() {
115288

116-
prepareData();
289+
insertTestData(session, INSERT_MQL);
117290

118291
var expectedDocs = List.of(
119292
BsonDocument.parse(
@@ -171,36 +344,67 @@ void testUpdate(boolean autoCommit) {
171344
throw new RuntimeException(e);
172345
}
173346
};
174-
assertExecuteUpdate(pstmtProvider, autoCommit, 2, expectedDocs);
347+
assertExecuteUpdate(pstmtProvider, 2, expectedDocs);
175348
}
176349

177-
private void prepareData() {
178-
session.doWork(connection -> {
179-
connection.setAutoCommit(true);
180-
var statement = connection.createStatement();
181-
statement.executeUpdate(INSERT_MQL);
182-
});
350+
@Test
351+
void testDelete() {
352+
insertTestData(session, INSERT_MQL);
353+
354+
Function<Connection, MongoPreparedStatement> pstmtProvider = connection -> {
355+
try {
356+
var pstmt = (MongoPreparedStatement)
357+
connection.prepareStatement(
358+
"""
359+
{
360+
delete: "books",
361+
deletes: [
362+
{
363+
q: { author: { $undefined: true } },
364+
limit: 0
365+
}
366+
]
367+
}""");
368+
pstmt.setString(1, "Leo Tolstoy");
369+
return pstmt;
370+
} catch (SQLException e) {
371+
throw new RuntimeException(e);
372+
}
373+
};
374+
assertExecuteUpdate(
375+
pstmtProvider,
376+
2,
377+
List.of(
378+
BsonDocument.parse(
379+
"""
380+
{
381+
_id: 3,
382+
title: "Crime and Punishment",
383+
author: "Fyodor Dostoevsky",
384+
outOfStock: false,
385+
tags: [ "classic", "dostoevsky", "literature" ]
386+
}""")));
183387
}
184388

185389
private void assertExecuteUpdate(
186390
Function<Connection, MongoPreparedStatement> pstmtProvider,
187-
boolean autoCommit,
188391
int expectedUpdatedRowCount,
189392
List<? extends BsonDocument> expectedDocuments) {
190-
session.doWork(connection -> {
191-
connection.setAutoCommit(autoCommit);
393+
doWorkAwareOfAutoCommit(connection -> {
192394
try (var pstmt = pstmtProvider.apply(connection)) {
193-
try {
194-
assertEquals(expectedUpdatedRowCount, pstmt.executeUpdate());
195-
} finally {
196-
if (!autoCommit) {
197-
connection.commit();
198-
}
199-
}
200-
assertThat(mongoCollection.find().sort(Sorts.ascending(ID_FIELD_NAME)))
201-
.containsExactlyElementsOf(expectedDocuments);
395+
assertEquals(expectedUpdatedRowCount, pstmt.executeUpdate());
202396
}
203397
});
398+
assertThat(mongoCollection.find().sort(Sorts.ascending(ID_FIELD_NAME)))
399+
.containsExactlyElementsOf(expectedDocuments);
204400
}
205401
}
402+
403+
private void doWorkAwareOfAutoCommit(Work work) {
404+
session.doWork(connection -> doAwareOfAutoCommit(connection, () -> work.execute(connection)));
405+
}
406+
407+
void doAwareOfAutoCommit(Connection connection, SqlExecutable work) throws SQLException {
408+
doWithSpecifiedAutoCommit(false, connection, () -> doAndTerminateTransaction(connection, work));
409+
}
206410
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2024-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.jdbc;
18+
19+
import static com.mongodb.hibernate.jdbc.MongoStatementIntegrationTests.doWithSpecifiedAutoCommit;
20+
21+
import com.mongodb.hibernate.jdbc.MongoStatementIntegrationTests.SqlExecutable;
22+
import java.sql.Connection;
23+
import java.sql.SQLException;
24+
25+
class MongoPreparedStatementWithAutoCommitIntegrationTests extends MongoPreparedStatementIntegrationTests {
26+
@Override
27+
void doAwareOfAutoCommit(Connection connection, SqlExecutable work) throws SQLException {
28+
doWithSpecifiedAutoCommit(true, connection, work);
29+
}
30+
}

0 commit comments

Comments
 (0)