Skip to content

Commit a32c9c4

Browse files
committed
1 parent 6ceec9d commit a32c9c4

15 files changed

+1583
-52
lines changed

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

Lines changed: 580 additions & 0 deletions
Large diffs are not rendered by default.

src/integrationTest/java/com/mongodb/hibernate/embeddable/EmbeddableIntegrationTests.java

Lines changed: 243 additions & 12 deletions
Large diffs are not rendered by default.

src/integrationTest/java/com/mongodb/hibernate/embeddable/StructAggregateEmbeddableIntegrationTests.java

Lines changed: 270 additions & 11 deletions
Large diffs are not rendered by default.

src/main/java/com/mongodb/hibernate/dialect/MongoAggregateSupport.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.hibernate.dialect.aggregate.AggregateSupportImpl;
2424
import org.hibernate.mapping.AggregateColumn;
2525
import org.hibernate.mapping.Column;
26+
import org.hibernate.type.SqlTypes;
2627

2728
final class MongoAggregateSupport extends AggregateSupportImpl {
2829
static final MongoAggregateSupport INSTANCE = new MongoAggregateSupport();
@@ -38,10 +39,12 @@ public String aggregateComponentCustomReadExpression(
3839
AggregateColumn aggregateColumn,
3940
Column column) {
4041
var aggregateColumnType = aggregateColumn.getTypeCode();
41-
if (aggregateColumnType == MongoStructJdbcType.JDBC_TYPE.getVendorTypeNumber()) {
42+
if (aggregateColumnType == MongoStructJdbcType.JDBC_TYPE.getVendorTypeNumber()
43+
// VAKOTODO use MongoArrayJdbcType.getJdbcTypeCode?
44+
|| aggregateColumnType == SqlTypes.STRUCT_ARRAY) {
4245
return format(
43-
"unused from %s.aggregateComponentCustomReadExpression",
44-
MongoAggregateSupport.class.getSimpleName());
46+
"unused from %s.aggregateComponentCustomReadExpression for SQL type code [%d]",
47+
MongoAggregateSupport.class.getSimpleName(), aggregateColumnType);
4548
}
4649
throw new FeatureNotSupportedException(format("The SQL type code [%d] is not supported", aggregateColumnType));
4750
}
@@ -53,17 +56,21 @@ public String aggregateComponentAssignmentExpression(
5356
AggregateColumn aggregateColumn,
5457
Column column) {
5558
var aggregateColumnType = aggregateColumn.getTypeCode();
56-
if (aggregateColumnType == MongoStructJdbcType.JDBC_TYPE.getVendorTypeNumber()) {
59+
if (aggregateColumnType == MongoStructJdbcType.JDBC_TYPE.getVendorTypeNumber()
60+
// VAKOTODO use MongoArrayJdbcType.getJdbcTypeCode?
61+
|| aggregateColumnType == SqlTypes.STRUCT_ARRAY) {
5762
return format(
58-
"unused from %s.aggregateComponentAssignmentExpression",
59-
MongoAggregateSupport.class.getSimpleName());
63+
"unused from %s.aggregateComponentAssignmentExpression for SQL type code [%d]",
64+
MongoAggregateSupport.class.getSimpleName(), aggregateColumnType);
6065
}
6166
throw new FeatureNotSupportedException(format("The SQL type code [%d] is not supported", aggregateColumnType));
6267
}
6368

6469
@Override
6570
public boolean requiresAggregateCustomWriteExpressionRenderer(int aggregateSqlTypeCode) {
66-
if (aggregateSqlTypeCode == MongoStructJdbcType.JDBC_TYPE.getVendorTypeNumber()) {
71+
if (aggregateSqlTypeCode == MongoStructJdbcType.JDBC_TYPE.getVendorTypeNumber()
72+
// VAKOTODO use MongoArrayJdbcType.getJdbcTypeCode?
73+
|| aggregateSqlTypeCode == SqlTypes.STRUCT_ARRAY) {
6774
return false;
6875
}
6976
throw new FeatureNotSupportedException(format("The SQL type code [%d] is not supported", aggregateSqlTypeCode));

src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.mongodb.hibernate.internal.translate.MongoTranslatorFactory;
2323
import com.mongodb.hibernate.internal.type.MongoStructJdbcType;
24+
import com.mongodb.hibernate.internal.type.MqlType;
2425
import com.mongodb.hibernate.internal.type.ObjectIdJavaType;
2526
import com.mongodb.hibernate.internal.type.ObjectIdJdbcType;
2627
import com.mongodb.hibernate.jdbc.MongoConnectionProvider;
@@ -31,6 +32,7 @@
3132
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
3233
import org.hibernate.service.ServiceRegistry;
3334
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
35+
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
3436
import org.jspecify.annotations.Nullable;
3537

3638
/**
@@ -91,9 +93,25 @@ public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
9193
@Override
9294
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
9395
super.contribute(typeContributions, serviceRegistry);
96+
contributeObjectIdType(typeContributions);
97+
typeContributions.contributeJdbcType(MongoStructJdbcType.INSTANCE);
98+
}
99+
100+
private void contributeObjectIdType(TypeContributions typeContributions) {
94101
typeContributions.contributeJavaType(ObjectIdJavaType.INSTANCE);
95102
typeContributions.contributeJdbcType(ObjectIdJdbcType.INSTANCE);
96-
typeContributions.contributeJdbcType(MongoStructJdbcType.INSTANCE);
103+
var objectIdTypeCode = MqlType.OBJECT_ID.getVendorTypeNumber();
104+
typeContributions
105+
.getTypeConfiguration()
106+
.getDdlTypeRegistry()
107+
.addDescriptorIfAbsent(
108+
objectIdTypeCode,
109+
new DdlTypeImpl(
110+
objectIdTypeCode,
111+
format(
112+
"unused from %s.contribute for SQL type code [%d]",
113+
MongoDialect.class.getSimpleName(), objectIdTypeCode),
114+
this));
97115
}
98116

99117
@Override
@@ -105,4 +123,9 @@ public void contribute(TypeContributions typeContributions, ServiceRegistry serv
105123
public AggregateSupport getAggregateSupport() {
106124
return MongoAggregateSupport.INSTANCE;
107125
}
126+
127+
@Override
128+
public boolean supportsStandardArrays() {
129+
return true;
130+
}
108131
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.internal.type;
18+
19+
import com.mongodb.hibernate.internal.MongoAssertions;
20+
import java.io.Serial;
21+
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
22+
import org.hibernate.type.descriptor.jdbc.JdbcType;
23+
24+
/** Thread-safe. */
25+
public final class MongoArrayJdbcType extends ArrayJdbcType { // VAKOTODO remove
26+
@Serial
27+
private static final long serialVersionUID = 1L;
28+
29+
public static final MongoArrayJdbcType INSTANCE = new MongoArrayJdbcType();
30+
31+
private MongoArrayJdbcType() {
32+
// VAKOTODO pass a dummy implementation?
33+
this(MongoAssertions.assertNotNull(null));
34+
}
35+
36+
public MongoArrayJdbcType(JdbcType elementJdbcType) {
37+
super(elementJdbcType);
38+
}
39+
}

src/main/java/com/mongodb/hibernate/internal/type/MongoStructJdbcType.java

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@
2020
import static com.mongodb.hibernate.internal.MongoAssertions.assertNotNull;
2121
import static com.mongodb.hibernate.internal.MongoAssertions.assertTrue;
2222
import static com.mongodb.hibernate.internal.MongoAssertions.fail;
23+
import static com.mongodb.hibernate.internal.type.ValueConversions.toArrayDomainValue;
2324
import static com.mongodb.hibernate.internal.type.ValueConversions.toBsonValue;
2425
import static com.mongodb.hibernate.internal.type.ValueConversions.toDomainValue;
2526

2627
import com.mongodb.hibernate.internal.FeatureNotSupportedException;
2728
import java.io.Serial;
29+
import java.lang.reflect.ParameterizedType;
2830
import java.sql.CallableStatement;
2931
import java.sql.JDBCType;
3032
import java.sql.PreparedStatement;
3133
import java.sql.ResultSet;
3234
import java.sql.SQLException;
3335
import java.sql.SQLFeatureNotSupportedException;
36+
import java.util.Collection;
3437
import org.bson.BsonDocument;
3538
import org.bson.BsonValue;
3639
import org.hibernate.annotations.Struct;
@@ -91,10 +94,8 @@ public EmbeddableMappingType getEmbeddableMappingType() {
9194

9295
@Override
9396
public BsonDocument createJdbcValue(Object domainValue, WrapperOptions options) throws SQLException {
94-
var embeddableMappingType = assertNotNull(this.embeddableMappingType);
95-
if (embeddableMappingType.isPolymorphic()) {
96-
throw new FeatureNotSupportedException("Polymorphic mapping is not supported");
97-
}
97+
var embeddableMappingType = getEmbeddableMappingType();
98+
forbidPolymorphic(embeddableMappingType);
9899
var result = new BsonDocument();
99100
var jdbcValueCount = embeddableMappingType.getJdbcValueCount();
100101
for (int columnIndex = 0; columnIndex < jdbcValueCount; columnIndex++) {
@@ -134,21 +135,65 @@ public BsonDocument createJdbcValue(Object domainValue, WrapperOptions options)
134135

135136
@Override
136137
public Object[] extractJdbcValues(Object rawJdbcValue, WrapperOptions options) throws SQLException {
138+
var embeddableMappingType = getEmbeddableMappingType();
139+
forbidPolymorphic(embeddableMappingType);
137140
if (!(rawJdbcValue instanceof BsonDocument bsonDocument)) {
138141
throw fail();
139142
}
140143
var result = new Object[bsonDocument.size()];
141144
var elementIdx = 0;
142145
for (var value : bsonDocument.values()) {
143146
assertNotNull(value);
144-
result[elementIdx++] =
145-
value instanceof BsonDocument ? extractJdbcValues(value, options) : toDomainValue(value);
147+
var jdbcMapping =
148+
embeddableMappingType.getJdbcValueSelectable(elementIdx).getJdbcMapping();
149+
var mappedJavaType = jdbcMapping.getMappedJavaType();
150+
var mappedJavaTypeClass = mappedJavaType.getJavaTypeClass();
151+
Object domainValue;
152+
if (jdbcMapping.getJdbcType().getJdbcTypeCode() == JDBC_TYPE.getVendorTypeNumber()) {
153+
if (!(jdbcMapping.getJdbcValueExtractor() instanceof Extractor<?> structValueExtractor)) {
154+
throw fail();
155+
}
156+
if (!(structValueExtractor.getJdbcType() instanceof MongoStructJdbcType structJdbcType)) {
157+
throw fail();
158+
}
159+
domainValue = structJdbcType.extractJdbcValues(value, options);
160+
} else if (mappedJavaTypeClass.equals(byte[].class)) {
161+
domainValue = toDomainValue(value, mappedJavaTypeClass);
162+
} else if (mappedJavaTypeClass.equals(char[].class)) {
163+
// VAKOTODO replace toArrayDomainValue with toDomainValue that accepts nullable arrayContentsType?
164+
// this is to avoid checking for char[].class outside of ValueConversions
165+
domainValue =
166+
toArrayDomainValue(value, mappedJavaTypeClass, null).getArray();
167+
} else if (mappedJavaTypeClass.isArray()) {
168+
domainValue =
169+
toArrayDomainValue(value, mappedJavaTypeClass, null).getArray();
170+
} else if (Collection.class.isAssignableFrom(mappedJavaTypeClass)) { // VAKOTODO use equals?
171+
if (!(mappedJavaType.getJavaType() instanceof ParameterizedType parameterizedCollection)) {
172+
throw fail();
173+
}
174+
var collectionTypeArguments = parameterizedCollection.getActualTypeArguments();
175+
assertTrue(collectionTypeArguments.length == 1);
176+
if (!(collectionTypeArguments[0] instanceof Class<?> collectionTypeArgument)) {
177+
throw fail();
178+
}
179+
domainValue = toArrayDomainValue(value, mappedJavaTypeClass, collectionTypeArgument)
180+
.getArray();
181+
} else {
182+
domainValue = toDomainValue(value, mappedJavaTypeClass);
183+
}
184+
result[elementIdx++] = domainValue;
146185
}
147186
return result;
148187
}
149188

189+
private static void forbidPolymorphic(EmbeddableMappingType embeddableMappingType) {
190+
if (embeddableMappingType.isPolymorphic()) {
191+
throw new FeatureNotSupportedException("Polymorphic mapping is not supported");
192+
}
193+
}
194+
150195
@Override
151-
public int getJdbcTypeCode() {
196+
public int getJdbcTypeCode() { // VAKOTODO use instead of exposing JDBC_TYPE?
152197
return JDBC_TYPE.getVendorTypeNumber();
153198
}
154199

@@ -172,11 +217,16 @@ private final class Binder<X> extends BasicBinder<X> {
172217
}
173218

174219
@Override
175-
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
220+
public Object getBindValue(X value, WrapperOptions options) throws SQLException {
176221
if (!(getJdbcType() instanceof MongoStructJdbcType structJdbcType)) {
177222
throw fail();
178223
}
179-
st.setObject(index, structJdbcType.createJdbcValue(value, options), structJdbcType.getJdbcTypeCode());
224+
return structJdbcType.createJdbcValue(value, options);
225+
}
226+
227+
@Override
228+
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
229+
st.setObject(index, getBindValue(value, options), getJdbcType().getJdbcTypeCode());
180230
}
181231

182232
@Override

src/main/java/com/mongodb/hibernate/internal/type/ObjectIdJavaType.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
4545

4646
@Override
4747
public <X> X unwrap(ObjectId value, Class<X> type, WrapperOptions options) {
48-
throw new FeatureNotSupportedException();
48+
if (type.equals(Object.class)) {
49+
return type.cast(value);
50+
} else {
51+
throw new FeatureNotSupportedException();
52+
}
4953
}
5054

5155
@Override

src/main/java/com/mongodb/hibernate/internal/type/ObjectIdJdbcType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public final class ObjectIdJdbcType implements JdbcType {
4444
private ObjectIdJdbcType() {}
4545

4646
@Override
47-
public int getJdbcTypeCode() {
47+
public int getJdbcTypeCode() { // VAKOTODO use instead of exposing MQL_TYPE?
4848
return MQL_TYPE.getVendorTypeNumber();
4949
}
5050

0 commit comments

Comments
 (0)