Skip to content

Commit 8c3bbe7

Browse files
committed
1 parent 6ceec9d commit 8c3bbe7

15 files changed

+1099
-32
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: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import jakarta.persistence.Entity;
3232
import jakarta.persistence.Id;
3333
import jakarta.persistence.Table;
34+
import java.util.Collection;
35+
import java.util.List;
3436
import java.util.Objects;
3537
import org.bson.BsonDocument;
3638
import org.hibernate.annotations.Parent;
@@ -47,10 +49,11 @@
4749
@DomainModel(
4850
annotatedClasses = {
4951
EmbeddableIntegrationTests.ItemWithFlattenedValues.class,
50-
EmbeddableIntegrationTests.ItemWithOmittedEmptyValue.class
52+
EmbeddableIntegrationTests.ItemWithOmittedEmptyValue.class,
53+
EmbeddableIntegrationTests.ItemWithFlattenedValueHavingArrayAndCollection.class
5154
})
5255
@ExtendWith(MongoExtension.class)
53-
class EmbeddableIntegrationTests implements SessionFactoryScopeAware {
56+
public class EmbeddableIntegrationTests implements SessionFactoryScopeAware {
5457
@InjectMongoCollection("items")
5558
private static MongoCollection<BsonDocument> mongoCollection;
5659

@@ -127,6 +130,42 @@ void testFlattenedEmptyValue() {
127130
assertEq(updatedItem, loadedItem);
128131
}
129132

133+
@Test
134+
void testFlattenedValueHavingArrayAndCollection() {
135+
var item = new ItemWithFlattenedValueHavingArrayAndCollection(
136+
1, new PairOfArrayAndCollection(new int[] {2, 3}, List.of(4, 5)));
137+
sessionFactoryScope.inTransaction(session -> session.persist(item));
138+
assertCollectionContainsExactly(
139+
"""
140+
{
141+
_id: 1,
142+
flattened_ints: [2, 3]
143+
flattened_intsCollection: [4, 5]
144+
}
145+
""");
146+
var loadedItem = sessionFactoryScope.fromTransaction(
147+
session -> session.find(ItemWithFlattenedValueHavingArrayAndCollection.class, item.id));
148+
assertEq(item, loadedItem);
149+
var updatedItem = sessionFactoryScope.fromTransaction(session -> {
150+
var result = session.find(ItemWithFlattenedValueHavingArrayAndCollection.class, item.id);
151+
result.flattened.ints[0] = -result.flattened.ints[0];
152+
result.flattened.intsCollection.remove(5);
153+
result.flattened.intsCollection.add(-5);
154+
return result;
155+
});
156+
assertCollectionContainsExactly(
157+
"""
158+
{
159+
_id: 1,
160+
flattened_ints: [-2, 3]
161+
flattened_intsCollection: [4, -5]
162+
}
163+
""");
164+
loadedItem = sessionFactoryScope.fromTransaction(
165+
session -> session.find(ItemWithFlattenedValueHavingArrayAndCollection.class, updatedItem.id));
166+
assertEq(updatedItem, loadedItem);
167+
}
168+
130169
@Override
131170
public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) {
132171
this.sessionFactoryScope = sessionFactoryScope;
@@ -160,7 +199,7 @@ static class ItemWithFlattenedValues {
160199
}
161200

162201
@Embeddable
163-
static class Single {
202+
public static class Single {
164203
int a;
165204

166205
Single() {}
@@ -237,6 +276,37 @@ static class ItemWithOmittedEmptyValue {
237276
@Embeddable
238277
static class Empty {}
239278

279+
@Entity
280+
@Table(name = "items")
281+
static class ItemWithFlattenedValueHavingArrayAndCollection {
282+
@Id
283+
int id;
284+
285+
@AttributeOverride(name = "ints", column = @Column(name = "flattened_ints"))
286+
@AttributeOverride(name = "intsCollection", column = @Column(name = "flattened_intsCollection"))
287+
PairOfArrayAndCollection flattened;
288+
289+
ItemWithFlattenedValueHavingArrayAndCollection() {}
290+
291+
ItemWithFlattenedValueHavingArrayAndCollection(int id, PairOfArrayAndCollection flattened) {
292+
this.id = id;
293+
this.flattened = flattened;
294+
}
295+
}
296+
297+
@Embeddable
298+
static class PairOfArrayAndCollection { // VAKOTODO use all types
299+
int[] ints;
300+
Collection<Integer> intsCollection;
301+
302+
PairOfArrayAndCollection() {}
303+
304+
PairOfArrayAndCollection(int[] ints, Collection<Integer> intsCollection) {
305+
this.ints = ints;
306+
this.intsCollection = intsCollection;
307+
}
308+
}
309+
240310
@Nested
241311
class Unsupported {
242312
@Test

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

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import jakarta.persistence.Entity;
3232
import jakarta.persistence.Id;
3333
import jakarta.persistence.Table;
34+
import java.util.Collection;
35+
import java.util.List;
3436
import org.bson.BsonDocument;
3537
import org.hibernate.annotations.Parent;
3638
import org.hibernate.annotations.Struct;
@@ -48,6 +50,7 @@
4850
annotatedClasses = {
4951
StructAggregateEmbeddableIntegrationTests.ItemWithNestedValues.class,
5052
StructAggregateEmbeddableIntegrationTests.ItemWithOmittedEmptyValue.class,
53+
StructAggregateEmbeddableIntegrationTests.ItemWithNestedValueHavingArrayAndCollection.class,
5154
StructAggregateEmbeddableIntegrationTests.Unsupported.ItemWithNestedValueHavingNonInsertable.class,
5255
StructAggregateEmbeddableIntegrationTests.Unsupported.ItemWithNestedValueHavingAllNonInsertable.class,
5356
StructAggregateEmbeddableIntegrationTests.Unsupported.ItemWithNestedValueHavingNonUpdatable.class,
@@ -56,7 +59,7 @@
5659
StructAggregateEmbeddableIntegrationTests.Unsupported.Concrete.class
5760
})
5861
@ExtendWith(MongoExtension.class)
59-
class StructAggregateEmbeddableIntegrationTests implements SessionFactoryScopeAware {
62+
public class StructAggregateEmbeddableIntegrationTests implements SessionFactoryScopeAware {
6063
@InjectMongoCollection("items")
6164
private static MongoCollection<BsonDocument> mongoCollection;
6265

@@ -146,6 +149,46 @@ void testNestedEmptyValue() {
146149
assertEq(updatedItem, loadedItem);
147150
}
148151

152+
@Test
153+
void testNestedValueHavingArrayAndCollection() {
154+
var item = new ItemWithNestedValueHavingArrayAndCollection(
155+
1, new PairOfArrayAndCollection(new int[] {2, 3}, List.of(4, 5)));
156+
sessionFactoryScope.inTransaction(session -> session.persist(item));
157+
assertCollectionContainsExactly(
158+
"""
159+
{
160+
_id: 1,
161+
nested: {
162+
ints: [2, 3],
163+
intsCollection: [4, 5]
164+
}
165+
}
166+
""");
167+
var loadedItem = sessionFactoryScope.fromTransaction(
168+
session -> session.find(ItemWithNestedValueHavingArrayAndCollection.class, item.id));
169+
assertEq(item, loadedItem);
170+
var updatedItem = sessionFactoryScope.fromTransaction(session -> {
171+
var result = session.find(ItemWithNestedValueHavingArrayAndCollection.class, item.id);
172+
result.nested.ints[0] = -2;
173+
result.nested.intsCollection.remove(5);
174+
result.nested.intsCollection.add(-5);
175+
return result;
176+
});
177+
assertCollectionContainsExactly(
178+
"""
179+
{
180+
_id: 1,
181+
nested: {
182+
ints: [-2, 3],
183+
intsCollection: [4, -5]
184+
}
185+
}
186+
""");
187+
loadedItem = sessionFactoryScope.fromTransaction(
188+
session -> session.find(ItemWithNestedValueHavingArrayAndCollection.class, updatedItem.id));
189+
assertEq(updatedItem, loadedItem);
190+
}
191+
149192
@Override
150193
public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) {
151194
this.sessionFactoryScope = sessionFactoryScope;
@@ -177,7 +220,7 @@ static class ItemWithNestedValues {
177220
@Embeddable
178221
@Struct(name = "Single")
179222
static class Single {
180-
int a;
223+
public int a;
181224

182225
Single() {}
183226

@@ -242,6 +285,36 @@ static class ItemWithOmittedEmptyValue {
242285
@Struct(name = "Empty")
243286
static class Empty {}
244287

288+
@Entity
289+
@Table(name = "items")
290+
static class ItemWithNestedValueHavingArrayAndCollection {
291+
@Id
292+
int id;
293+
294+
PairOfArrayAndCollection nested;
295+
296+
ItemWithNestedValueHavingArrayAndCollection() {}
297+
298+
ItemWithNestedValueHavingArrayAndCollection(int id, PairOfArrayAndCollection nested) {
299+
this.id = id;
300+
this.nested = nested;
301+
}
302+
}
303+
304+
@Embeddable
305+
@Struct(name = "PairOfArrayAndCollection")
306+
static class PairOfArrayAndCollection { // VAKOTODO use all types
307+
int[] ints;
308+
Collection<Integer> intsCollection;
309+
310+
PairOfArrayAndCollection() {}
311+
312+
PairOfArrayAndCollection(int[] ints, Collection<Integer> intsCollection) {
313+
this.ints = ints;
314+
this.intsCollection = intsCollection;
315+
}
316+
}
317+
245318
@Nested
246319
class Unsupported {
247320
@Test

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+
}

0 commit comments

Comments
 (0)