Skip to content

Commit 8ac53b2

Browse files
committed
Add support for ObjectId
1 parent 044882a commit 8ac53b2

File tree

8 files changed

+380
-1
lines changed

8 files changed

+380
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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.id;
18+
19+
import static com.mongodb.hibernate.internal.MongoConstants.ID_FIELD_NAME;
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
import com.mongodb.client.MongoCollection;
23+
import com.mongodb.hibernate.junit.InjectMongoCollection;
24+
import com.mongodb.hibernate.junit.MongoExtension;
25+
import jakarta.persistence.Entity;
26+
import jakarta.persistence.Id;
27+
import jakarta.persistence.Table;
28+
import org.bson.BsonDocument;
29+
import org.bson.BsonObjectId;
30+
import org.bson.types.ObjectId;
31+
import org.hibernate.testing.orm.junit.DomainModel;
32+
import org.hibernate.testing.orm.junit.SessionFactory;
33+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
34+
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
35+
import org.junit.jupiter.api.Test;
36+
import org.junit.jupiter.api.extension.ExtendWith;
37+
38+
@SessionFactory(exportSchema = false)
39+
@DomainModel(annotatedClasses = {ObjectIdFieldTypeIntegrationTests.Item.class})
40+
@ExtendWith(MongoExtension.class)
41+
class ObjectIdFieldTypeIntegrationTests implements SessionFactoryScopeAware {
42+
@InjectMongoCollection("items")
43+
private static MongoCollection<BsonDocument> mongoCollection;
44+
45+
private SessionFactoryScope sessionFactoryScope;
46+
47+
@Test
48+
void insert() {
49+
var id = new ObjectId(1, 0);
50+
sessionFactoryScope.inTransaction(session -> {
51+
var item = new Item();
52+
item.id = id;
53+
session.persist(item);
54+
});
55+
assertThat(mongoCollection.find()).containsExactly(new BsonDocument(ID_FIELD_NAME, new BsonObjectId(id)));
56+
}
57+
58+
// VAKOTODO add read tests
59+
60+
@Override
61+
public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) {
62+
this.sessionFactoryScope = sessionFactoryScope;
63+
}
64+
65+
@Entity
66+
@Table(name = "items")
67+
static class Item {
68+
@Id
69+
ObjectId id;
70+
}
71+
}

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

+11
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717
package com.mongodb.hibernate.dialect;
1818

1919
import com.mongodb.hibernate.internal.translate.MongoTranslatorFactory;
20+
import com.mongodb.hibernate.internal.type.ObjectIdJavaType;
21+
import com.mongodb.hibernate.internal.type.ObjectIdJdbcType;
2022
import com.mongodb.hibernate.jdbc.MongoConnectionProvider;
23+
import org.hibernate.boot.model.TypeContributions;
2124
import org.hibernate.dialect.DatabaseVersion;
2225
import org.hibernate.dialect.Dialect;
2326
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
27+
import org.hibernate.service.ServiceRegistry;
2428
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
2529

2630
/**
@@ -48,4 +52,11 @@ protected DatabaseVersion getMinimumSupportedVersion() {
4852
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
4953
return new MongoTranslatorFactory();
5054
}
55+
56+
@Override
57+
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
58+
super.contribute(typeContributions, serviceRegistry);
59+
typeContributions.contributeJavaType(ObjectIdJavaType.INSTANCE);
60+
typeContributions.contributeJdbcType(ObjectIdJdbcType.INSTANCE);
61+
}
5162
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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 static com.mongodb.hibernate.internal.MongoAssertions.assertTrue;
20+
21+
import com.mongodb.hibernate.internal.MongoAssertions;
22+
import java.lang.reflect.Field;
23+
import java.lang.reflect.Modifier;
24+
import java.sql.SQLType;
25+
import java.util.Arrays;
26+
import java.util.function.Predicate;
27+
import java.util.function.ToIntFunction;
28+
import org.hibernate.type.SqlTypes;
29+
30+
public enum MqlType implements SQLType {
31+
OBJECT_ID(11_000);
32+
33+
static {
34+
assertTrue(maxHibernateSqlType() < minType());
35+
}
36+
37+
MqlType(int type) {
38+
this.type = type;
39+
}
40+
41+
private final int type;
42+
43+
@Override
44+
public String getName() {
45+
return name();
46+
}
47+
48+
@Override
49+
public String getVendor() {
50+
return "MongoDB";
51+
}
52+
53+
@Override
54+
public Integer getVendorTypeNumber() {
55+
return type;
56+
}
57+
58+
private static int minType() {
59+
return Arrays.stream(MqlType.values())
60+
.mapToInt(MqlType::getVendorTypeNumber)
61+
.min()
62+
.orElseThrow(MongoAssertions::fail);
63+
}
64+
65+
private static int maxHibernateSqlType() {
66+
Predicate<Field> publicStaticFinal = field -> {
67+
var modifiers = field.getModifiers();
68+
return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);
69+
};
70+
ToIntFunction<Field> valueExtractor = field -> {
71+
try {
72+
return field.getInt(null);
73+
} catch (IllegalAccessException e) {
74+
throw new RuntimeException(e);
75+
}
76+
};
77+
return Arrays.stream(SqlTypes.class.getDeclaredFields())
78+
.filter(field -> field.getType().equals(int.class))
79+
.filter(publicStaticFinal)
80+
.mapToInt(valueExtractor)
81+
.max()
82+
.orElseThrow(MongoAssertions::fail);
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.FeatureNotSupportedException;
20+
import java.io.Serial;
21+
import org.bson.types.ObjectId;
22+
import org.hibernate.type.descriptor.WrapperOptions;
23+
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
24+
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
25+
import org.hibernate.type.descriptor.jdbc.JdbcType;
26+
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
27+
28+
public final class ObjectIdJavaType extends AbstractClassJavaType<ObjectId> {
29+
@Serial
30+
private static final long serialVersionUID = 1L;
31+
32+
public static final ObjectIdJavaType INSTANCE = new ObjectIdJavaType();
33+
34+
private ObjectIdJavaType() {
35+
super(ObjectId.class, ImmutableMutabilityPlan.instance());
36+
}
37+
38+
@Override
39+
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
40+
return ObjectIdJdbcType.INSTANCE;
41+
}
42+
43+
@Override
44+
public <X> X unwrap(ObjectId value, Class<X> type, WrapperOptions options) {
45+
throw new FeatureNotSupportedException();
46+
}
47+
48+
@Override
49+
public <X> ObjectId wrap(X value, WrapperOptions options) {
50+
throw new FeatureNotSupportedException();
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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.FeatureNotSupportedException;
20+
import java.io.Serial;
21+
import java.sql.CallableStatement;
22+
import java.sql.PreparedStatement;
23+
import java.sql.ResultSet;
24+
import java.sql.SQLException;
25+
import java.sql.SQLFeatureNotSupportedException;
26+
import org.bson.types.ObjectId;
27+
import org.hibernate.type.descriptor.ValueBinder;
28+
import org.hibernate.type.descriptor.ValueExtractor;
29+
import org.hibernate.type.descriptor.WrapperOptions;
30+
import org.hibernate.type.descriptor.java.JavaType;
31+
import org.hibernate.type.descriptor.jdbc.BasicBinder;
32+
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
33+
import org.hibernate.type.descriptor.jdbc.JdbcType;
34+
35+
public final class ObjectIdJdbcType implements JdbcType {
36+
@Serial
37+
private static final long serialVersionUID = 1L;
38+
39+
private static final MqlType MQL_TYPE = MqlType.OBJECT_ID;
40+
private static final ObjectIdJavaType JAVA_TYPE = ObjectIdJavaType.INSTANCE;
41+
public static final ObjectIdJdbcType INSTANCE = new ObjectIdJdbcType();
42+
43+
private ObjectIdJdbcType() {}
44+
45+
@Override
46+
public int getJdbcTypeCode() {
47+
return MQL_TYPE.getVendorTypeNumber();
48+
}
49+
50+
@Override
51+
public String getFriendlyName() {
52+
return MQL_TYPE.getName();
53+
}
54+
55+
@Override
56+
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
57+
if (!javaType.equals(JAVA_TYPE)) {
58+
throw new FeatureNotSupportedException();
59+
}
60+
@SuppressWarnings("unchecked")
61+
var result = (ValueBinder<X>) Binder.INSTANCE;
62+
return result;
63+
}
64+
65+
@Override
66+
public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) {
67+
if (!javaType.equals(JAVA_TYPE)) {
68+
throw new FeatureNotSupportedException();
69+
}
70+
@SuppressWarnings("unchecked")
71+
var result = (ValueExtractor<X>) Extractor.INSTANCE;
72+
return result;
73+
}
74+
75+
private static final class Binder extends BasicBinder<ObjectId> {
76+
@Serial
77+
private static final long serialVersionUID = 1L;
78+
79+
static Binder INSTANCE = new Binder();
80+
81+
private Binder() {
82+
super(JAVA_TYPE, ObjectIdJdbcType.INSTANCE);
83+
}
84+
85+
@Override
86+
protected void doBind(PreparedStatement st, ObjectId value, int index, WrapperOptions options)
87+
throws SQLException {
88+
st.setObject(index, value, getJdbcType().getJdbcTypeCode());
89+
}
90+
91+
@Override
92+
protected void doBind(CallableStatement st, ObjectId value, String name, WrapperOptions options)
93+
throws SQLException {
94+
throw new SQLFeatureNotSupportedException();
95+
}
96+
}
97+
98+
private static final class Extractor extends BasicExtractor<ObjectId> {
99+
@Serial
100+
private static final long serialVersionUID = 1L;
101+
102+
static Extractor INSTANCE = new Extractor();
103+
104+
private Extractor() {
105+
super(JAVA_TYPE, ObjectIdJdbcType.INSTANCE);
106+
}
107+
108+
@Override
109+
protected ObjectId doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
110+
return rs.getObject(paramIndex, getJavaType().getJavaTypeClass());
111+
}
112+
113+
@Override
114+
protected ObjectId doExtract(CallableStatement statement, int index, WrapperOptions options)
115+
throws SQLException {
116+
throw new SQLFeatureNotSupportedException();
117+
}
118+
119+
@Override
120+
protected ObjectId doExtract(CallableStatement statement, String name, WrapperOptions options)
121+
throws SQLException {
122+
throw new SQLFeatureNotSupportedException();
123+
}
124+
}
125+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
/** The program elements within this package are not part of the public API and may be removed or changed at any time */
18+
@NullMarked
19+
package com.mongodb.hibernate.internal.type;
20+
21+
import org.jspecify.annotations.NullMarked;

0 commit comments

Comments
 (0)