Skip to content

Commit 4f9ef0e

Browse files
committed
Improve how identifiers are treated
1 parent 854f152 commit 4f9ef0e

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
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;
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.Column;
26+
import jakarta.persistence.Entity;
27+
import jakarta.persistence.Id;
28+
import jakarta.persistence.Table;
29+
import org.bson.BsonDocument;
30+
import org.bson.BsonInt32;
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(
40+
annotatedClasses = {
41+
IdentifierIntegrationTests.WithSpaceAndDotMixedCase.class,
42+
IdentifierIntegrationTests.InSingleQuotesMixedCase.class,
43+
IdentifierIntegrationTests.StartingWithSingleQuote.class,
44+
IdentifierIntegrationTests.EndingWithSingleQuote.class,
45+
IdentifierIntegrationTests.InDoubleQuotesMixedCase.class,
46+
IdentifierIntegrationTests.StartingWithDoubleQuote.class,
47+
IdentifierIntegrationTests.EndingWithDoubleQuote.class
48+
})
49+
@ExtendWith(MongoExtension.class)
50+
class IdentifierIntegrationTests implements SessionFactoryScopeAware {
51+
@InjectMongoCollection(WithSpaceAndDotMixedCase.COLLECTION_NAME)
52+
private static MongoCollection<BsonDocument> mongoCollectionWithSpaceAndDotMixedCase;
53+
54+
@InjectMongoCollection(InSingleQuotesMixedCase.ACTUAL_COLLECTION_NAME)
55+
private static MongoCollection<BsonDocument> mongoCollectionInSingleQuotesMixedCase;
56+
57+
@InjectMongoCollection(StartingWithSingleQuote.COLLECTION_NAME)
58+
private static MongoCollection<BsonDocument> mongoCollectionStartingWithSingleQuote;
59+
60+
@InjectMongoCollection(EndingWithSingleQuote.COLLECTION_NAME)
61+
private static MongoCollection<BsonDocument> mongoCollectionEndingWithSingleQuote;
62+
63+
@InjectMongoCollection(InDoubleQuotesMixedCase.ACTUAL_COLLECTION_NAME)
64+
private static MongoCollection<BsonDocument> mongoCollectionInDoubleQuotesMixedCase;
65+
66+
@InjectMongoCollection(StartingWithDoubleQuote.COLLECTION_NAME)
67+
private static MongoCollection<BsonDocument> mongoCollectionStartingWithDoubleQuote;
68+
69+
@InjectMongoCollection(EndingWithDoubleQuote.COLLECTION_NAME)
70+
private static MongoCollection<BsonDocument> mongoCollectionEndingWithDoubleQuote;
71+
72+
private SessionFactoryScope sessionFactoryScope;
73+
74+
@Test
75+
void withSpaceAndDotMixedCase() {
76+
var item = new WithSpaceAndDotMixedCase();
77+
sessionFactoryScope.inTransaction(session -> session.persist(item));
78+
assertThat(mongoCollectionWithSpaceAndDotMixedCase.find())
79+
.containsExactly(new BsonDocument()
80+
.append(ID_FIELD_NAME, new BsonInt32(item.id))
81+
.append(WithSpaceAndDotMixedCase.FIELD_NAME, new BsonInt32(item.v)));
82+
sessionFactoryScope.inTransaction(session -> session.get(WithSpaceAndDotMixedCase.class, item.id));
83+
}
84+
85+
@Test
86+
void inSingleQuotesMixedCase() {
87+
var item = new InSingleQuotesMixedCase();
88+
sessionFactoryScope.inTransaction(session -> session.persist(item));
89+
assertThat(mongoCollectionInSingleQuotesMixedCase.find())
90+
.containsExactly(new BsonDocument()
91+
.append(ID_FIELD_NAME, new BsonInt32(item.id))
92+
.append(InSingleQuotesMixedCase.ACTUAL_FIELD_NAME, new BsonInt32(item.v)));
93+
sessionFactoryScope.inTransaction(session -> session.get(InSingleQuotesMixedCase.class, item.id));
94+
}
95+
96+
@Test
97+
void startingWithSingleQuote() {
98+
var item = new StartingWithSingleQuote();
99+
sessionFactoryScope.inTransaction(session -> session.persist(item));
100+
assertThat(mongoCollectionStartingWithSingleQuote.find())
101+
.containsExactly(new BsonDocument()
102+
.append(ID_FIELD_NAME, new BsonInt32(item.id))
103+
.append(StartingWithSingleQuote.FIELD_NAME, new BsonInt32(item.v)));
104+
sessionFactoryScope.inTransaction(session -> session.get(StartingWithSingleQuote.class, item.id));
105+
}
106+
107+
@Test
108+
void endingWithSingleQuote() {
109+
var item = new EndingWithSingleQuote();
110+
sessionFactoryScope.inTransaction(session -> session.persist(item));
111+
assertThat(mongoCollectionEndingWithSingleQuote.find())
112+
.containsExactly(new BsonDocument()
113+
.append(ID_FIELD_NAME, new BsonInt32(item.id))
114+
.append(EndingWithSingleQuote.FIELD_NAME, new BsonInt32(item.v)));
115+
sessionFactoryScope.inTransaction(session -> session.get(EndingWithSingleQuote.class, item.id));
116+
}
117+
118+
@Test
119+
void inDoubleQuotesMixedCase() {
120+
var item = new InDoubleQuotesMixedCase();
121+
sessionFactoryScope.inTransaction(session -> session.persist(item));
122+
assertThat(mongoCollectionInDoubleQuotesMixedCase.find())
123+
.containsExactly(new BsonDocument()
124+
.append(ID_FIELD_NAME, new BsonInt32(item.id))
125+
.append(InDoubleQuotesMixedCase.ACTUAL_FIELD_NAME, new BsonInt32(item.v)));
126+
sessionFactoryScope.inTransaction(session -> session.get(InDoubleQuotesMixedCase.class, item.id));
127+
}
128+
129+
@Test
130+
void startingWithDoubleQuote() {
131+
var item = new StartingWithDoubleQuote();
132+
sessionFactoryScope.inTransaction(session -> session.persist(item));
133+
assertThat(mongoCollectionStartingWithDoubleQuote.find())
134+
.containsExactly(new BsonDocument()
135+
.append(ID_FIELD_NAME, new BsonInt32(item.id))
136+
.append(StartingWithDoubleQuote.FIELD_NAME, new BsonInt32(item.v)));
137+
sessionFactoryScope.inTransaction(session -> session.get(StartingWithDoubleQuote.class, item.id));
138+
}
139+
140+
@Test
141+
void endingWithDoubleQuote() {
142+
var item = new EndingWithDoubleQuote();
143+
sessionFactoryScope.inTransaction(session -> session.persist(item));
144+
assertThat(mongoCollectionEndingWithDoubleQuote.find())
145+
.containsExactly(new BsonDocument()
146+
.append(ID_FIELD_NAME, new BsonInt32(item.id))
147+
.append(EndingWithDoubleQuote.FIELD_NAME, new BsonInt32(item.v)));
148+
sessionFactoryScope.inTransaction(session -> session.get(EndingWithDoubleQuote.class, item.id));
149+
}
150+
151+
@Override
152+
public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) {
153+
this.sessionFactoryScope = sessionFactoryScope;
154+
}
155+
156+
@Entity
157+
@Table(name = WithSpaceAndDotMixedCase.COLLECTION_NAME)
158+
static class WithSpaceAndDotMixedCase {
159+
static final String COLLECTION_NAME = "collection with space and .dot Mixed Case";
160+
static final String FIELD_NAME = "field with space Mixed Case";
161+
162+
@Id
163+
int id;
164+
165+
@Column(name = WithSpaceAndDotMixedCase.FIELD_NAME)
166+
int v;
167+
}
168+
169+
@Entity
170+
@Table(name = InSingleQuotesMixedCase.COLLECTION_NAME)
171+
static class InSingleQuotesMixedCase {
172+
static final String COLLECTION_NAME = "`collection in single quotes Mixed Case`";
173+
static final String FIELD_NAME = "`field in single quotes Mixed Case`";
174+
static final String ACTUAL_COLLECTION_NAME = "collection in single quotes Mixed Case";
175+
static final String ACTUAL_FIELD_NAME = "field in single quotes Mixed Case";
176+
177+
@Id
178+
int id;
179+
180+
@Column(name = InSingleQuotesMixedCase.FIELD_NAME)
181+
int v;
182+
}
183+
184+
@Entity
185+
@Table(name = StartingWithSingleQuote.COLLECTION_NAME)
186+
static class StartingWithSingleQuote {
187+
static final String COLLECTION_NAME = "`collection starting with single quote";
188+
static final String FIELD_NAME = "`field starting with single quote";
189+
190+
@Id
191+
int id;
192+
193+
@Column(name = StartingWithSingleQuote.FIELD_NAME)
194+
int v;
195+
}
196+
197+
@Entity
198+
@Table(name = EndingWithSingleQuote.COLLECTION_NAME)
199+
static class EndingWithSingleQuote {
200+
static final String COLLECTION_NAME = "collection ending with single quote`";
201+
static final String FIELD_NAME = "field ending with single quote`";
202+
203+
@Id
204+
int id;
205+
206+
@Column(name = EndingWithSingleQuote.FIELD_NAME)
207+
int v;
208+
}
209+
210+
@Entity
211+
@Table(name = InDoubleQuotesMixedCase.COLLECTION_NAME)
212+
static class InDoubleQuotesMixedCase {
213+
static final String COLLECTION_NAME = "\"collection in double quotes Mixed Case\"";
214+
static final String FIELD_NAME = "\"field in double quotes Mixed Case\"";
215+
static final String ACTUAL_COLLECTION_NAME = "collection in double quotes Mixed Case";
216+
static final String ACTUAL_FIELD_NAME = "field in double quotes Mixed Case";
217+
218+
@Id
219+
int id;
220+
221+
@Column(name = InDoubleQuotesMixedCase.FIELD_NAME)
222+
int v;
223+
}
224+
225+
@Entity
226+
@Table(name = StartingWithDoubleQuote.COLLECTION_NAME)
227+
static class StartingWithDoubleQuote {
228+
static final String COLLECTION_NAME = "\"collection starting with double quote";
229+
static final String FIELD_NAME = "\"field starting with double quote";
230+
231+
@Id
232+
int id;
233+
234+
@Column(name = StartingWithDoubleQuote.FIELD_NAME)
235+
int v;
236+
}
237+
238+
@Entity
239+
@Table(name = EndingWithDoubleQuote.COLLECTION_NAME)
240+
static class EndingWithDoubleQuote {
241+
static final String COLLECTION_NAME = "collection ending with double quote\"";
242+
static final String FIELD_NAME = "field ending with double quote\"";
243+
244+
@Id
245+
int id;
246+
247+
@Column(name = EndingWithDoubleQuote.FIELD_NAME)
248+
int v;
249+
}
250+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
3030
import org.hibernate.service.ServiceRegistry;
3131
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
32+
import org.jspecify.annotations.Nullable;
3233

3334
/**
3435
* A MongoDB {@link Dialect} for {@linkplain #getMinimumSupportedVersion() version 6.0 and above}. Must be used together
@@ -91,4 +92,9 @@ public void contribute(TypeContributions typeContributions, ServiceRegistry serv
9192
typeContributions.contributeJavaType(ObjectIdJavaType.INSTANCE);
9293
typeContributions.contributeJdbcType(ObjectIdJdbcType.INSTANCE);
9394
}
95+
96+
@Override
97+
public @Nullable String toQuotedIdentifier(@Nullable String name) {
98+
return name;
99+
}
94100
}

0 commit comments

Comments
 (0)