Skip to content

Commit dc82a3c

Browse files
loiclefevrebeikov
authored andcommitted
HHH-17909 Use domain type for NAMED_ENUM
1 parent af269ae commit dc82a3c

File tree

4 files changed

+536
-10
lines changed

4 files changed

+536
-10
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@
9595
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
9696
import org.hibernate.type.descriptor.sql.internal.ArrayDdlTypeImpl;
9797
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
98+
import org.hibernate.type.descriptor.sql.internal.NamedNativeEnumDdlTypeImpl;
99+
import org.hibernate.type.descriptor.sql.internal.NamedNativeOrdinalEnumDdlTypeImpl;
98100
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
99101
import org.hibernate.type.spi.TypeConfiguration;
100102

@@ -167,7 +169,7 @@ public class OracleDialect extends Dialect {
167169
public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw";
168170

169171
private static final String yqmSelect =
170-
"(trunc(%2$s, 'MONTH') + numtoyminterval(%1$s, 'MONTH') + (least(extract(day from %2$s), extract(day from last_day(trunc(%2$s, 'MONTH') + numtoyminterval(%1$s, 'MONTH')))) - 1))";
172+
"(trunc(%2$s, 'MONTH') + numtoyminterval(%1$s, 'MONTH') + (least(extract(day from %2$s), extract(day from last_day(trunc(%2$s, 'MONTH') + numtoyminterval(%1$s, 'MONTH')))) - 1))";
171173

172174
private static final String ADD_YEAR_EXPRESSION = String.format( yqmSelect, "?2*12", "?3" );
173175
private static final String ADD_QUARTER_EXPRESSION = String.format( yqmSelect, "?2*3", "?3" );
@@ -716,10 +718,10 @@ protected String columnType(int sqlTypeCode) {
716718
switch ( sqlTypeCode ) {
717719
case BOOLEAN:
718720
if ( getVersion().isSameOrAfter( 23 ) ) {
719-
return super.columnType( sqlTypeCode );
721+
return super.columnType( sqlTypeCode );
720722
}
721723
else {
722-
return "number(1,0)";
724+
return "number(1,0)";
723725
}
724726
case TINYINT:
725727
return "number(3,0)";
@@ -746,8 +748,8 @@ protected String columnType(int sqlTypeCode) {
746748
return "date";
747749
case TIME:
748750
return "timestamp($p)";
749-
// the only difference between date and timestamp
750-
// on Oracle is that date has no fractional seconds
751+
// the only difference between date and timestamp
752+
// on Oracle is that date has no fractional seconds
751753
case TIME_WITH_TIMEZONE:
752754
return "timestamp($p) with time zone";
753755

@@ -781,6 +783,11 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
781783

782784
ddlTypeRegistry.addDescriptor( new ArrayDdlTypeImpl( this, false ) );
783785
ddlTypeRegistry.addDescriptor( TABLE, new ArrayDdlTypeImpl( this, false ) );
786+
787+
if(getVersion().isSameOrAfter(23)) {
788+
ddlTypeRegistry.addDescriptor(new NamedNativeEnumDdlTypeImpl(this));
789+
ddlTypeRegistry.addDescriptor( new NamedNativeOrdinalEnumDdlTypeImpl( this ) );
790+
}
784791
}
785792

786793
@Override
@@ -973,8 +980,14 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
973980
typeContributions.getTypeConfiguration()
974981
.getJavaTypeRegistry()
975982
.getDescriptor( Object.class )
976-
)
983+
)
977984
);
985+
986+
if(getVersion().isSameOrAfter(23)) {
987+
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
988+
jdbcTypeRegistry.addDescriptor(OracleEnumJdbcType.INSTANCE);
989+
jdbcTypeRegistry.addDescriptor(OracleOrdinalEnumJdbcType.INSTANCE);
990+
}
978991
}
979992

980993
@Override
@@ -1256,10 +1269,10 @@ public boolean useFollowOnLocking(String sql, QueryOptions queryOptions) {
12561269
}
12571270

12581271
return DISTINCT_KEYWORD_PATTERN.matcher( sql ).find()
1259-
|| GROUP_BY_KEYWORD_PATTERN.matcher( sql ).find()
1260-
|| UNION_KEYWORD_PATTERN.matcher( sql ).find()
1261-
|| ORDER_BY_KEYWORD_PATTERN.matcher( sql ).find() && queryOptions.hasLimit()
1262-
|| queryOptions.hasLimit() && queryOptions.getLimit().getFirstRow() != null;
1272+
|| GROUP_BY_KEYWORD_PATTERN.matcher( sql ).find()
1273+
|| UNION_KEYWORD_PATTERN.matcher( sql ).find()
1274+
|| ORDER_BY_KEYWORD_PATTERN.matcher( sql ).find() && queryOptions.hasLimit()
1275+
|| queryOptions.hasLimit() && queryOptions.getLimit().getFirstRow() != null;
12631276
}
12641277

12651278
@Override
@@ -1638,4 +1651,29 @@ public int getDriverMajorVersion() {
16381651
public int getDriverMinorVersion() {
16391652
return driverMinorVersion;
16401653
}
1654+
1655+
@Override
1656+
public String getEnumTypeDeclaration(String name, String[] values) {
1657+
return getVersion().isSameOrAfter(23) ? name : super.getEnumTypeDeclaration(name, values);
1658+
}
1659+
1660+
@Override
1661+
public String[] getCreateEnumTypeCommand(String name, String[] values) {
1662+
final StringBuilder domain = new StringBuilder();
1663+
domain.append( "create domain " )
1664+
.append( name )
1665+
.append( " as enum (" );
1666+
String separator = "";
1667+
for ( String value : values ) {
1668+
domain.append( separator ).append( value );
1669+
separator = ", ";
1670+
}
1671+
domain.append( ')' );
1672+
return new String[] { domain.toString() };
1673+
}
1674+
1675+
@Override
1676+
public String[] getDropEnumTypeCommand(String name) {
1677+
return new String[] { "drop domain if exists " + name + " force" };
1678+
}
16411679
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.dialect;
8+
9+
import org.hibernate.boot.model.relational.Database;
10+
import org.hibernate.boot.model.relational.NamedAuxiliaryDatabaseObject;
11+
import org.hibernate.engine.jdbc.Size;
12+
import org.hibernate.type.descriptor.ValueBinder;
13+
import org.hibernate.type.descriptor.ValueExtractor;
14+
import org.hibernate.type.descriptor.WrapperOptions;
15+
import org.hibernate.type.descriptor.converter.internal.EnumHelper;
16+
import org.hibernate.type.descriptor.java.JavaType;
17+
import org.hibernate.type.descriptor.jdbc.BasicBinder;
18+
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
19+
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
20+
import org.hibernate.type.descriptor.jdbc.JdbcType;
21+
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
22+
import org.hibernate.type.spi.TypeConfiguration;
23+
24+
import java.sql.CallableStatement;
25+
import java.sql.PreparedStatement;
26+
import java.sql.ResultSet;
27+
import java.sql.SQLException;
28+
import java.sql.Types;
29+
import java.util.Arrays;
30+
31+
import static java.util.Collections.emptySet;
32+
import static org.hibernate.type.SqlTypes.NAMED_ENUM;
33+
34+
/**
35+
* Represents a named {@code enum} type on Oracle 23ai+.
36+
* <p>
37+
* Hibernate does <em>not</em> automatically use this for enums
38+
* mapped as {@link jakarta.persistence.EnumType#STRING}, and
39+
* instead this type must be explicitly requested using:
40+
* <pre>
41+
* &#64;JdbcTypeCode(SqlTypes.NAMED_ENUM)
42+
* </pre>
43+
*
44+
* @see org.hibernate.type.SqlTypes#NAMED_ENUM
45+
* @see OracleDialect#getEnumTypeDeclaration(String, String[])
46+
* @see OracleDialect#getCreateEnumTypeCommand(String, String[])
47+
*
48+
* @author Loïc Lefèvre
49+
*/
50+
public class OracleEnumJdbcType implements JdbcType {
51+
52+
public static final OracleEnumJdbcType INSTANCE = new OracleEnumJdbcType();
53+
54+
@Override
55+
public int getJdbcTypeCode() {
56+
return Types.VARCHAR;
57+
}
58+
59+
@Override
60+
public int getDefaultSqlTypeCode() {
61+
return NAMED_ENUM;
62+
}
63+
64+
@Override
65+
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
66+
return (appender, value, dialect, wrapperOptions) -> appender.appendSql( dialect.getEnumTypeDeclaration( (Class<? extends Enum<?>>) javaType.getJavaType() )+"." + ((Enum<?>) value).name() );
67+
}
68+
69+
@Override
70+
public String getFriendlyName() {
71+
return "ENUM";
72+
}
73+
74+
@Override
75+
public String toString() {
76+
return "EnumTypeDescriptor";
77+
}
78+
79+
@Override
80+
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
81+
return new BasicBinder<>( javaType, this ) {
82+
@Override
83+
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException {
84+
st.setNull( index, getJdbcTypeCode() );
85+
}
86+
87+
@Override
88+
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException {
89+
st.setNull( name, getJdbcTypeCode() );
90+
}
91+
92+
@Override
93+
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
94+
throws SQLException {
95+
st.setString( index, ((Enum<?>) value).name() );
96+
}
97+
98+
@Override
99+
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
100+
throws SQLException {
101+
st.setString( name, ((Enum<?>) value).name() );
102+
}
103+
};
104+
}
105+
106+
@Override
107+
public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) {
108+
return new BasicExtractor<>( javaType, this ) {
109+
@Override
110+
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
111+
return getJavaType().wrap( rs.getString( paramIndex ), options );
112+
}
113+
114+
@Override
115+
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
116+
return getJavaType().wrap( statement.getString( index ), options );
117+
}
118+
119+
@Override
120+
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
121+
return getJavaType().wrap( statement.getString( name ), options );
122+
}
123+
};
124+
}
125+
126+
@Override
127+
public void addAuxiliaryDatabaseObjects(
128+
JavaType<?> javaType,
129+
Size columnSize,
130+
Database database,
131+
JdbcTypeIndicators context) {
132+
addAuxiliaryDatabaseObjects( javaType, database, true );
133+
}
134+
135+
@Override
136+
public void addAuxiliaryDatabaseObjects(
137+
JavaType<?> javaType,
138+
Size columnSize,
139+
Database database,
140+
TypeConfiguration typeConfiguration) {
141+
addAuxiliaryDatabaseObjects( javaType, database, true );
142+
}
143+
144+
private void addAuxiliaryDatabaseObjects(
145+
JavaType<?> javaType,
146+
Database database,
147+
boolean sortEnumValues) {
148+
final Dialect dialect = database.getDialect();
149+
final Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) javaType.getJavaType();
150+
final String enumTypeName = enumClass.getSimpleName();
151+
final String[] enumeratedValues = EnumHelper.getEnumeratedValues( enumClass );
152+
if ( sortEnumValues ) {
153+
Arrays.sort( enumeratedValues );
154+
}
155+
final String[] create = getCreateEnumTypeCommand(
156+
javaType.getJavaTypeClass().getSimpleName(),
157+
enumeratedValues
158+
);
159+
final String[] drop = dialect.getDropEnumTypeCommand( enumClass );
160+
if ( create != null && create.length > 0 ) {
161+
database.addAuxiliaryDatabaseObject(
162+
new NamedAuxiliaryDatabaseObject(
163+
enumTypeName,
164+
database.getDefaultNamespace(),
165+
create,
166+
drop,
167+
emptySet(),
168+
true
169+
)
170+
);
171+
}
172+
}
173+
174+
/**
175+
* Used to generate the CREATE DDL command for Data Use Case Domain based on VARCHAR2 values.
176+
*
177+
* @param name
178+
* @param values
179+
* @return the DDL command to create that enum
180+
*/
181+
public String[] getCreateEnumTypeCommand(String name, String[] values) {
182+
final StringBuilder domain = new StringBuilder();
183+
domain.append( "create domain " )
184+
.append( name )
185+
.append( " as enum (" );
186+
String separator = "";
187+
for ( String value : values ) {
188+
domain.append( separator ).append( value ).append("='").append(value).append("'");
189+
separator = ", ";
190+
}
191+
domain.append( ')' );
192+
return new String[] { domain.toString() };
193+
}
194+
}

0 commit comments

Comments
 (0)