Skip to content

Commit 8e44231

Browse files
committed
WIP: Use "?" for parameter markers in both native query and query translation
1 parent 6ceec9d commit 8e44231

File tree

7 files changed

+167
-6
lines changed

7 files changed

+167
-6
lines changed

src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,38 @@ void testFindByPrimaryKeyWithNullValueField() {
210210
}
211211
}
212212

213+
@Nested
214+
class NativeQueryTests {
215+
216+
@Test
217+
void testNative() {
218+
var book = new Book();
219+
book.id = 1;
220+
book.title = "In Search of Lost Time";
221+
book.author = "Marcel Proust";
222+
book.publishYear = 1913;
223+
224+
sessionFactoryScope.inTransaction(session -> session.persist(book));
225+
226+
var nativeQuery =
227+
"""
228+
{
229+
aggregate: "books",
230+
pipeline: [
231+
{ $match : { _id: { $eq: :id } } },
232+
{ $project: { _id: 1, publishYear: 1, title: 1, author: 1 } }
233+
]
234+
}
235+
""";
236+
sessionFactoryScope.inTransaction(session -> {
237+
var query = session.createNativeQuery(nativeQuery, Book.class)
238+
.setParameter("id", book.id);
239+
var queriedBook = query.getSingleResult();
240+
assertThat(queriedBook).usingRecursiveComparison().isEqualTo(book);
241+
});
242+
}
243+
}
244+
213245
private static void assertCollectionContainsExactly(BsonDocument expectedDoc) {
214246
assertThat(mongoCollection.find()).containsExactly(expectedDoc);
215247
}

src/main/java/com/mongodb/hibernate/internal/MongoConstants.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ public final class MongoConstants {
2424
private MongoConstants() {}
2525

2626
public static final JsonWriterSettings EXTENDED_JSON_WRITER_SETTINGS =
27-
JsonWriterSettings.builder().outputMode(JsonMode.EXTENDED).build();
27+
JsonWriterSettings.builder().outputMode(JsonMode.EXTENDED)
28+
.undefinedConverter((bsonUndefined, strictJsonWriter) -> strictJsonWriter.writeRaw("?"))
29+
.build();
2830

2931
public static final String MONGO_DBMS_NAME = "MongoDB";
3032
public static final String MONGO_JDBC_DRIVER_NAME = "MongoDB Java Driver JDBC Adapter";

src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,14 @@
7979
import java.util.List;
8080
import java.util.Optional;
8181
import java.util.Set;
82+
83+
import org.bson.BsonUndefined;
84+
import org.bson.json.Converter;
85+
import org.bson.json.JsonMode;
8286
import org.bson.BsonValue;
8387
import org.bson.json.JsonWriter;
88+
import org.bson.json.JsonWriterSettings;
89+
import org.bson.json.StrictJsonWriter;
8490
import org.hibernate.engine.spi.SessionFactoryImplementor;
8591
import org.hibernate.internal.util.collections.Stack;
8692
import org.hibernate.persister.entity.EntityPersister;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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.jdbc;
18+
19+
class MongoParameterRecognizer {
20+
21+
static String replace(String json) {
22+
StringBuilder builder = new StringBuilder(json.length());
23+
24+
int i = 0;
25+
while (i < json.length()) {
26+
char c = json.charAt(i++);
27+
switch (c) {
28+
case '{':
29+
case '}':
30+
case '[':
31+
case ']':
32+
case ':':
33+
case ',':
34+
case ' ':
35+
builder.append(c);
36+
break;
37+
case '\'':
38+
case '"':
39+
i = scanString(c, i, json, builder);
40+
break;
41+
case '?':
42+
builder.append("{$undefined: true}");
43+
break;
44+
default:
45+
if (c == '-' || Character.isDigit(c)) {
46+
i = scanNumber(c, i, json, builder);
47+
} else if (c == '$' || c == '_' || Character.isLetter(c)) {
48+
i = scanUnquotedString(c, i, json, builder);
49+
} else {
50+
builder.append(c); // or throw exception, as this isn't valid JSON
51+
}
52+
}
53+
}
54+
return builder.toString();
55+
}
56+
57+
private static int scanNumber(char firstCharacter, int startIndex, String json, StringBuilder builder) {
58+
builder.append(firstCharacter);
59+
int i = startIndex;
60+
char c = json.charAt(i++);
61+
while (i < json.length() && Character.isDigit(c)) {
62+
builder.append(c);
63+
c = json.charAt(i++);
64+
}
65+
return i - 1;
66+
}
67+
68+
private static int scanUnquotedString(final char firstCharacter, final int startIndex, final String json, final StringBuilder builder) {
69+
builder.append(firstCharacter);
70+
int i = startIndex;
71+
char c = json.charAt(i++);
72+
while (i < json.length() && Character.isLetterOrDigit(c)) {
73+
builder.append(c);
74+
c = json.charAt(i++);
75+
}
76+
return i - 1;
77+
}
78+
79+
private static int scanString(final char quoteCharacter, final int startIndex, final String json, final StringBuilder builder) {
80+
int i = startIndex;
81+
builder.append(quoteCharacter);
82+
while (i < json.length()) {
83+
char c = json.charAt(i++);
84+
if (c == '\\') {
85+
builder.append(c);
86+
if (i < json.length()) {
87+
c = json.charAt(i++);
88+
builder.append(c);
89+
}
90+
} else if (c == quoteCharacter) {
91+
builder.append(c);
92+
return i;
93+
} else {
94+
builder.append(c);
95+
}
96+
}
97+
return i;
98+
}
99+
}

src/main/java/com/mongodb/hibernate/jdbc/MongoPreparedStatement.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ final class MongoPreparedStatement extends MongoStatement implements PreparedSta
5656
MongoDatabase mongoDatabase, ClientSession clientSession, MongoConnection mongoConnection, String mql)
5757
throws SQLSyntaxErrorException {
5858
super(mongoDatabase, clientSession, mongoConnection);
59-
this.command = MongoStatement.parse(mql);
59+
this.command = MongoStatement.parse(MongoParameterRecognizer.replace(mql));
6060
this.parameterValueSetters = new ArrayList<>();
6161
parseParameters(command, parameterValueSetters);
6262
}

src/main/java/com/mongodb/hibernate/jdbc/MongoResultSet.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,18 @@ public double getDouble(int columnIndex) throws SQLException {
218218
@Override
219219
public ResultSetMetaData getMetaData() throws SQLException {
220220
checkClosed();
221-
return new MongoResultSetMetadata();
221+
return new MongoResultSetMetadata(fieldNames);
222222
}
223223

224224
@Override
225225
public int findColumn(String columnLabel) throws SQLException {
226226
checkClosed();
227-
throw new SQLFeatureNotSupportedException("To be implemented in scope of native query tickets");
227+
for (int i = 0; i < fieldNames.size(); i++) {
228+
if (fieldNames.get(i).equals(columnLabel)) {
229+
return i + 1;
230+
}
231+
}
232+
throw new SQLException("Unknown column label " + columnLabel);
228233
}
229234

230235
@Override
@@ -271,5 +276,22 @@ private void checkColumnIndex(int columnIndex) throws SQLException {
271276
}
272277
}
273278

274-
private static final class MongoResultSetMetadata implements ResultSetMetaDataAdapter {}
279+
private static final class MongoResultSetMetadata implements ResultSetMetaDataAdapter {
280+
private final List<String> fieldNames;
281+
282+
public MongoResultSetMetadata(List<String> fieldNames) {
283+
this.fieldNames = fieldNames;
284+
}
285+
286+
@Override
287+
public int getColumnCount() {
288+
return fieldNames.size();
289+
}
290+
291+
292+
@Override
293+
public String getColumnLabel(int column) {
294+
return fieldNames.get(column - 1);
295+
}
296+
}
275297
}

src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommandTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void testRendering() {
4141

4242
var expectedJson =
4343
"""
44-
{"insert": "books", "documents": [{"title": "War and Peace", "year": {"$numberInt": "1867"}, "_id": {"$undefined": true}}]}\
44+
{"insert": "books", "documents": [{"title": "War and Peace", "year": {"$numberInt": "1867"}, "_id": ?}]}\
4545
""";
4646
assertRendering(expectedJson, insertCommand);
4747
}

0 commit comments

Comments
 (0)