Skip to content

Commit 79aa178

Browse files
committed
HHH-16516 rework CamelCaseToUnderscoresNamingStrategy and handle quoted identifiers
1 parent b6ee791 commit 79aa178

File tree

7 files changed

+115
-103
lines changed

7 files changed

+115
-103
lines changed

documentation/src/main/asciidoc/introduction/Advanced.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,9 +582,10 @@ We happen to not much like the naming rules defined by JPA, which specify that m
582582
We bet you could easily come up with a much better `ImplicitNamingStrategy` than that!
583583
(Hint: it should always produce legit mixed case identifiers.)
584584
====
585+
585586
[TIP]
586587
====
587-
A popular `PhysicalNamingStrategy` produces snake case identifiers.
588+
The popular link:{doc-javadoc-url}org/hibernate/boot/model/naming/PhysicalNamingStrategySnakeCaseImpl.html[`PhysicalNamingStrategySnakeCaseImpl`] produces snake case identifiers.
588589
====
589590

590591
Custom naming strategies may be enabled using the configuration properties we already mentioned without much explanation back in <<minimizing>>.

hibernate-core/src/main/java/org/hibernate/boot/model/naming/CamelCaseToUnderscoresNamingStrategy.java

Lines changed: 3 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,88 +4,9 @@
44
*/
55
package org.hibernate.boot.model.naming;
66

7-
import java.util.Locale;
8-
9-
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
10-
117
/**
12-
* Originally copied from Spring Boot as this strategy is popular there
13-
* (original name is SpringPhysicalNamingStrategy).
14-
*
15-
* @author Phillip Webb
16-
* @author Madhura Bhave
8+
* @deprecated Use {@link PhysicalNamingStrategySnakeCaseImpl}.
179
*/
18-
public class CamelCaseToUnderscoresNamingStrategy implements PhysicalNamingStrategy {
19-
20-
@Override
21-
public Identifier toPhysicalCatalogName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
22-
return apply( logicalName, jdbcEnvironment );
23-
}
24-
25-
@Override
26-
public Identifier toPhysicalSchemaName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
27-
return apply( logicalName, jdbcEnvironment );
28-
}
29-
30-
@Override
31-
public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
32-
return apply( logicalName, jdbcEnvironment );
33-
}
34-
35-
@Override
36-
public Identifier toPhysicalSequenceName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
37-
return apply( logicalName, jdbcEnvironment );
38-
}
39-
40-
@Override
41-
public Identifier toPhysicalColumnName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
42-
return apply( logicalName, jdbcEnvironment );
43-
}
44-
45-
private Identifier apply(final Identifier name, final JdbcEnvironment jdbcEnvironment) {
46-
if ( name == null ) {
47-
return null;
48-
}
49-
StringBuilder builder = new StringBuilder( name.getText().replace( '.', '_' ) );
50-
for ( int i = 1; i < builder.length() - 1; i++ ) {
51-
if ( isUnderscoreRequired( builder.charAt( i - 1 ), builder.charAt( i ), builder.charAt( i + 1 ) ) ) {
52-
builder.insert( i++, '_' );
53-
}
54-
}
55-
return getIdentifier( builder.toString(), name.isQuoted(), jdbcEnvironment );
56-
}
57-
58-
/**
59-
* Get an identifier for the specified details. By default this method will return an identifier
60-
* with the name adapted based on the result of {@link #isCaseInsensitive(JdbcEnvironment)}
61-
*
62-
* @param name the name of the identifier
63-
* @param quoted if the identifier is quoted
64-
* @param jdbcEnvironment the JDBC environment
65-
*
66-
* @return an identifier instance
67-
*/
68-
protected Identifier getIdentifier(String name, final boolean quoted, final JdbcEnvironment jdbcEnvironment) {
69-
if ( isCaseInsensitive( jdbcEnvironment ) ) {
70-
name = name.toLowerCase( Locale.ROOT );
71-
}
72-
return new Identifier( name, quoted );
73-
}
74-
75-
/**
76-
* Specify whether the database is case sensitive.
77-
*
78-
* @param jdbcEnvironment the JDBC environment which can be used to determine case
79-
*
80-
* @return true if the database is case insensitive sensitivity
81-
*/
82-
protected boolean isCaseInsensitive(JdbcEnvironment jdbcEnvironment) {
83-
return true;
84-
}
85-
86-
private boolean isUnderscoreRequired(final char before, final char current, final char after) {
87-
return ( Character.isLowerCase( before ) || Character.isDigit( before ) ) && Character.isUpperCase( current ) && ( Character.isLowerCase(
88-
after ) || Character.isDigit( after ) );
89-
}
90-
10+
@Deprecated(since = "7", forRemoval = true)
11+
public class CamelCaseToUnderscoresNamingStrategy extends PhysicalNamingStrategySnakeCaseImpl {
9112
}

hibernate-core/src/main/java/org/hibernate/boot/model/naming/ImplicitNamingStrategyJpaCompliantImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public Identifier determinePrimaryTableName(ImplicitEntityNameSource source) {
3636
throw new HibernateException( "Entity naming information was not provided." );
3737
}
3838

39-
String tableName = transformEntityName( source.getEntityNaming() );
39+
final String tableName = transformEntityName( source.getEntityNaming() );
4040

4141
if ( tableName == null ) {
4242
// todo : add info to error message - but how to know what to write since we failed to interpret the naming source
@@ -136,9 +136,9 @@ public Identifier determineJoinColumnName(ImplicitJoinColumnNameSource source) {
136136

137137
// todo : we need to better account for "referencing relationship property"
138138

139-
final String name;
139+
final String referencedColumnName = source.getReferencedColumnName().getText();
140140

141-
String referencedColumnName = source.getReferencedColumnName().getText();
141+
final String name;
142142
if ( source.getNature() == ELEMENT_COLLECTION
143143
|| source.getAttributePath() == null ) {
144144
name = transformEntityName( source.getEntityNaming() )
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.boot.model.naming;
6+
7+
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
8+
9+
import java.util.Locale;
10+
11+
import static java.lang.Character.isDigit;
12+
import static java.lang.Character.isLowerCase;
13+
import static java.lang.Character.isUpperCase;
14+
15+
/**
16+
* Converts {@code camelCase} or {@code MixedCase} logical names to {@code snake_case}.
17+
*
18+
* @author Phillip Webb
19+
* @author Madhura Bhave
20+
*/
21+
// Originally copied from Spring's SpringPhysicalNamingStrategy as this strategy is popular there.
22+
public class PhysicalNamingStrategySnakeCaseImpl implements PhysicalNamingStrategy {
23+
24+
@Override
25+
public Identifier toPhysicalCatalogName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
26+
return apply( logicalName );
27+
}
28+
29+
@Override
30+
public Identifier toPhysicalSchemaName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
31+
return apply( logicalName );
32+
}
33+
34+
@Override
35+
public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
36+
return apply( logicalName );
37+
}
38+
39+
@Override
40+
public Identifier toPhysicalSequenceName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
41+
return apply( logicalName );
42+
}
43+
44+
@Override
45+
public Identifier toPhysicalColumnName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
46+
return apply( logicalName );
47+
}
48+
49+
private Identifier apply(final Identifier name) {
50+
if ( name == null ) {
51+
return null;
52+
}
53+
else if ( name.isQuoted() ) {
54+
return quotedIdentifier( name );
55+
}
56+
else {
57+
return unquotedIdentifier( name );
58+
}
59+
}
60+
61+
private String camelCaseToSnakeCase(String name) {
62+
final StringBuilder builder = new StringBuilder( name.replace( '.', '_' ) );
63+
for ( int i = 1; i < builder.length() - 1; i++ ) {
64+
if ( isUnderscoreRequired( builder.charAt( i - 1 ), builder.charAt( i ), builder.charAt( i + 1 ) ) ) {
65+
builder.insert( i++, '_' );
66+
}
67+
}
68+
return builder.toString();
69+
}
70+
71+
protected Identifier unquotedIdentifier(Identifier name) {
72+
return new Identifier( camelCaseToSnakeCase( name.getText() ).toLowerCase( Locale.ROOT ) );
73+
}
74+
75+
protected Identifier quotedIdentifier(Identifier quotedName) {
76+
return quotedName;
77+
}
78+
79+
private boolean isUnderscoreRequired(final char before, final char current, final char after) {
80+
return ( isLowerCase( before ) || isDigit( before ) )
81+
&& isUpperCase( current )
82+
&& ( isLowerCase( after ) || isDigit( after ) );
83+
}
84+
}

hibernate-core/src/test/java/org/hibernate/orm/test/annotations/namingstrategy/CamelCaseToUnderscoresNamingStrategyTest.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
*/
55
package org.hibernate.orm.test.annotations.namingstrategy;
66

7+
import jakarta.persistence.Column;
78
import org.hibernate.boot.Metadata;
89
import org.hibernate.boot.MetadataSources;
9-
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
10+
import org.hibernate.boot.model.naming.PhysicalNamingStrategySnakeCaseImpl;
1011
import org.hibernate.cfg.Environment;
1112
import org.hibernate.mapping.PersistentClass;
12-
import org.hibernate.mapping.Selectable;
1313
import org.hibernate.service.ServiceRegistry;
1414

1515
import org.hibernate.testing.ServiceRegistryBuilder;
@@ -50,29 +50,33 @@ public void testWithWordWithDigitNamingStrategy() throws Exception {
5050
Metadata metadata = new MetadataSources( serviceRegistry )
5151
.addAnnotatedClass( B.class )
5252
.getMetadataBuilder()
53-
.applyPhysicalNamingStrategy( new CamelCaseToUnderscoresNamingStrategy() )
53+
.applyPhysicalNamingStrategy( new PhysicalNamingStrategySnakeCaseImpl() )
5454
.build();
5555

5656
PersistentClass entityBinding = metadata.getEntityBinding( B.class.getName() );
5757
assertEquals(
5858
"word_with_digit_d1",
59-
( (Selectable) entityBinding.getProperty( "wordWithDigitD1" ).getSelectables().get( 0 ) ).getText()
59+
entityBinding.getProperty( "wordWithDigitD1" ).getSelectables().get( 0 ).getText()
6060
);
6161
assertEquals(
6262
"abcd_efgh_i21",
63-
( (Selectable) entityBinding.getProperty( "AbcdEfghI21" ).getSelectables().get( 0 ) ).getText()
63+
entityBinding.getProperty( "AbcdEfghI21" ).getSelectables().get( 0 ).getText()
6464
);
6565
assertEquals(
6666
"hello1",
67-
( (Selectable) entityBinding.getProperty( "hello1" ).getSelectables().get( 0 ) ).getText()
67+
entityBinding.getProperty( "hello1" ).getSelectables().get( 0 ).getText()
6868
);
6969
assertEquals(
7070
"hello1_d2",
71-
( (Selectable) entityBinding.getProperty( "hello1D2" ).getSelectables().get( 0 ) ).getText()
71+
entityBinding.getProperty( "hello1D2" ).getSelectables().get( 0 ).getText()
7272
);
7373
assertEquals(
7474
"hello3d4",
75-
( (Selectable) entityBinding.getProperty( "hello3d4" ).getSelectables().get( 0 ) ).getText()
75+
entityBinding.getProperty( "hello3d4" ).getSelectables().get( 0 ).getText()
76+
);
77+
assertEquals(
78+
"Quoted-ColumnName",
79+
entityBinding.getProperty( "quoted" ).getSelectables().get( 0 ).getText()
7680
);
7781
}
7882

@@ -85,6 +89,8 @@ class B implements java.io.Serializable {
8589
protected String hello1;
8690
protected String hello1D2;
8791
protected String hello3d4;
92+
@Column(name = "\"Quoted-ColumnName\"")
93+
protected String quoted;
8894

8995
public String getAbcdEfghI21() {
9096
return AbcdEfghI21;

hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetomany/orderby/IdClassAndOrderByTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
*/
55
package org.hibernate.orm.test.annotations.onetomany.orderby;
66

7-
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
87
import org.hibernate.boot.model.naming.Identifier;
98
import org.hibernate.boot.model.naming.ImplicitJoinTableNameSource;
109
import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl;
10+
import org.hibernate.boot.model.naming.PhysicalNamingStrategySnakeCaseImpl;
1111
import org.hibernate.cfg.AvailableSettings;
1212

1313
import org.hibernate.testing.orm.junit.JiraKey;
@@ -42,7 +42,7 @@ public class IdClassAndOrderByTest {
4242
public static class PhysicalNamingStrategyProvider implements SettingProvider.Provider<String> {
4343
@Override
4444
public String getSetting() {
45-
return CamelCaseToUnderscoresNamingStrategy.class.getName();
45+
return PhysicalNamingStrategySnakeCaseImpl.class.getName();
4646
}
4747
}
4848

hibernate-core/src/test/java/org/hibernate/orm/test/namingstrategy/ejb3joincolumn/PhysicalNamingStrategyImpl.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
99
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
1010

11+
import static org.hibernate.boot.model.naming.Identifier.toIdentifier;
12+
1113
/**
1214
* @author Anton Wimmer
1315
* @author Steve Ebersole
@@ -20,19 +22,17 @@ public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardIm
2022

2123
@Override
2224
public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
23-
return Identifier.toIdentifier(makeCleanIdentifier("tbl_" + logicalName.getText()), logicalName.isQuoted());
25+
return toIdentifier( makeCleanIdentifier("tbl_" + logicalName.getText()), logicalName.isQuoted() );
2426
}
2527

2628
@Override
2729
public Identifier toPhysicalColumnName(Identifier logicalName, JdbcEnvironment jdbcEnvironment) {
28-
if ( logicalName.getText().equals("DTYPE") ) {
29-
return logicalName;
30-
}
31-
32-
return Identifier.toIdentifier(makeCleanIdentifier("c_" + logicalName.getText()), logicalName.isQuoted());
30+
return logicalName.getText().equals( "DTYPE" )
31+
? logicalName
32+
: toIdentifier( makeCleanIdentifier( "c_" + logicalName.getText() ), logicalName.isQuoted() );
3333
}
3434

3535
private String makeCleanIdentifier(String s) {
36-
return s.substring(0, Math.min(s.length(), 63)).toLowerCase();
36+
return s.substring( 0, Math.min(s.length(), 63) ).toLowerCase();
3737
}
3838
}

0 commit comments

Comments
 (0)