diff --git a/ci/build.sh b/ci/build.sh index d9db53d4ca27..24e80d5cb739 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -14,6 +14,8 @@ elif [ "$RDBMS" == "mariadb" ] || [ "$RDBMS" == "mariadb_10_6" ]; then goal="-Pdb=mariadb_ci" elif [ "$RDBMS" == "postgresql" ] || [ "$RDBMS" == "postgresql_13" ]; then goal="-Pdb=pgsql_ci" +elif [ "$RDBMS" == "gaussdb" ]; then + goal="-Pdb=gaussdb -DdbHost=localhost:8000" elif [ "$RDBMS" == "edb" ] || [ "$RDBMS" == "edb_13" ]; then goal="-Pdb=edb_ci -DdbHost=localhost:5444" elif [ "$RDBMS" == "oracle" ]; then diff --git a/ci/database-start.sh b/ci/database-start.sh index da8f3a3e78b6..a6949226d6e8 100755 --- a/ci/database-start.sh +++ b/ci/database-start.sh @@ -8,6 +8,8 @@ elif [ "$RDBMS" == 'mariadb' ]; then bash $DIR/../docker_db.sh mariadb elif [ "$RDBMS" == 'postgresql' ]; then bash $DIR/../docker_db.sh postgresql +elif [ "$RDBMS" == 'gaussdb' ]; then + bash $DIR/../docker_db.sh gaussdb elif [ "$RDBMS" == 'edb' ]; then bash $DIR/../docker_db.sh edb elif [ "$RDBMS" == 'db2' ]; then diff --git a/docker_db.sh b/docker_db.sh index 3d4dd9ff05d3..a409c0b864b7 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -211,6 +211,45 @@ postgresql_17() { $CONTAINER_CLI exec postgres bash -c '/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y && apt install -y postgresql-17-pgvector && psql -U hibernate_orm_test -d hibernate_orm_test -c "create extension vector;"' } +gaussdb() { + $CONTAINER_CLI rm -f opengauss || true + + # config param + CONTAINER_NAME=opengauss + IMAGE=opengauss/opengauss:7.0.0-RC1.B023 + PORT=8000 + DB_USER=hibernate_orm_test + DB_PASSWORD=Hibernate_orm_test@1234 + DB_NAME=hibernate_orm_test + PSQL_IMAGE=postgres:14 + + echo "start OpenGauss container..." + $CONTAINER_CLI run --name ${CONTAINER_NAME} \ + --privileged=true \ + -e GS_USERNAME=${DB_USER} \ + -e GS_PASSWORD=${DB_PASSWORD} \ + -e GS_PORT=${PORT} \ + -p ${PORT}:8000 \ + -d ${IMAGE} + + echo "wait OpenGauss starting..." + sleep 30 + + echo " Initialize the database using the PostgreSQL client container..." + + $CONTAINER_CLI run --rm --network=host ${PSQL_IMAGE} \ + bash -c " + PGPASSWORD='${DB_PASSWORD}' psql -h localhost -p ${PORT} -U ${DB_USER} -d postgres -c \"CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};\" && + PGPASSWORD='${DB_PASSWORD}' psql -h localhost -p ${PORT} -U ${DB_USER} -d ${DB_NAME} -c \"CREATE SCHEMA test AUTHORIZATION ${DB_USER};\" + " + + echo "Initialization completed" + echo "connection information" + echo " Host: localhost" + echo " Port: ${PORT}" + echo " Database: ${DB_NAME}" +} + edb() { edb_17 } @@ -1089,6 +1128,7 @@ if [ -z ${1} ]; then echo -e "\toracle" echo -e "\toracle_23" echo -e "\toracle_21" + echo -e "\tgaussdb" echo -e "\tpostgresql" echo -e "\tpostgresql_17" echo -e "\tpostgresql_16" diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalTransactionIsolationConfigTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalTransactionIsolationConfigTest.java index b95ae6c3cb26..352b9484047b 100644 --- a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalTransactionIsolationConfigTest.java +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalTransactionIsolationConfigTest.java @@ -6,6 +6,7 @@ import org.hibernate.community.dialect.AltibaseDialect; import org.hibernate.community.dialect.TiDBDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.agroal.internal.AgroalConnectionProvider; @@ -17,6 +18,7 @@ */ @SkipForDialect(value = TiDBDialect.class, comment = "Doesn't support SERIALIZABLE isolation") @SkipForDialect(value = AltibaseDialect.class, comment = "Altibase cannot change isolation level in autocommit mode") +@SkipForDialect(value = GaussDBDialect.class, comment = "GaussDB does not support SERIALIZABLE isolation") public class AgroalTransactionIsolationConfigTest extends BaseTransactionIsolationConfigTest { @Override protected ConnectionProvider getConnectionProviderUnderTest() { diff --git a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3p0TransactionIsolationConfigTest.java b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3p0TransactionIsolationConfigTest.java index 7e002cc81ca5..062235e9eb0a 100644 --- a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3p0TransactionIsolationConfigTest.java +++ b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3p0TransactionIsolationConfigTest.java @@ -8,6 +8,7 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.c3p0.internal.C3P0ConnectionProvider; import org.hibernate.community.dialect.AltibaseDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.SybaseASEDialect; import org.hibernate.community.dialect.TiDBDialect; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; @@ -23,6 +24,7 @@ @SkipForDialect(value = TiDBDialect.class, comment = "Doesn't support SERIALIZABLE isolation") @SkipForDialect(value = AltibaseDialect.class, comment = "Altibase cannot change isolation level in autocommit mode") @SkipForDialect(value = SybaseASEDialect.class, comment = "JtdsConnection.isValid not implemented") +@SkipForDialect(value = GaussDBDialect.class, comment = "GaussDB does not support SERIALIZABLE isolation") public class C3p0TransactionIsolationConfigTest extends BaseTransactionIsolationConfigTest { private StandardServiceRegistry ssr; diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CommunityDatabase.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CommunityDatabase.java index 73011693dbd7..23e9b3bc4dfb 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CommunityDatabase.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CommunityDatabase.java @@ -228,6 +228,21 @@ public String getDriverClassName(String jdbcUrl) { ? "org.apache.derby.jdbc.ClientDriver" : "org.apache.derby.jdbc.EmbeddedDriver"; } + }, + + GAUSSDB { + @Override + public Dialect createDialect(DialectResolutionInfo info) { + return new GaussDBDialect(info); + } + @Override + public boolean productNameMatches(String databaseName) { + return "GaussDB".equals( databaseName ); + } + @Override + public String getDriverClassName(String jdbcUrl) { + return "com.huawei.gaussdb.jdbc.Driver"; + } }; /** diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcType.java new file mode 100644 index 000000000000..0f8f03695b30 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcType.java @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicPluralJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +/** + * Descriptor for {@link Types#ARRAY ARRAY} handling. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLArrayJdbcType. + */ +public class GaussDBArrayJdbcType extends ArrayJdbcType { + + public GaussDBArrayJdbcType(JdbcType elementJdbcType) { + super( elementJdbcType ); + } + + @Override + public ValueBinder getBinder(final JavaType javaTypeDescriptor) { + @SuppressWarnings("unchecked") + final BasicPluralJavaType pluralJavaType = (BasicPluralJavaType) javaTypeDescriptor; + final ValueBinder elementBinder = getElementJdbcType().getBinder( pluralJavaType.getElementJavaType() ); + return new BasicBinder<>( javaTypeDescriptor, this ) { + + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + st.setArray( index, getArray( value, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final java.sql.Array arr = getArray( value, options ); + try { + st.setObject( name, arr, java.sql.Types.ARRAY ); + } + catch (SQLException ex) { + throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex ); + } + } + + @Override + public Object getBindValue(X value, WrapperOptions options) throws SQLException { + return ( (GaussDBArrayJdbcType) getJdbcType() ).getArray( this, elementBinder, value, options ); + } + + private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException { + final GaussDBArrayJdbcType arrayJdbcType = (GaussDBArrayJdbcType) getJdbcType(); + final Object[] objects; + + final JdbcType elementJdbcType = arrayJdbcType.getElementJdbcType(); + if ( elementJdbcType instanceof AggregateJdbcType ) { + // The GaussDB JDBC driver does not support arrays of structs, which contain byte[] + final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType; + final Object[] domainObjects = getJavaType().unwrap( + value, + Object[].class, + options + ); + objects = new Object[domainObjects.length]; + for ( int i = 0; i < domainObjects.length; i++ ) { + if ( domainObjects[i] != null ) { + objects[i] = aggregateJdbcType.createJdbcValue( domainObjects[i], options ); + } + } + } + else { + objects = arrayJdbcType.getArray( this, elementBinder, value, options ); + } + + final SharedSessionContractImplementor session = options.getSession(); + final String typeName = arrayJdbcType.getElementTypeName( getJavaType(), session ); + return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection() + .createArrayOf( typeName, objects ); + } + }; + } + + @Override + public String toString() { + return "GaussDBArrayTypeDescriptor(" + getElementJdbcType().toString() + ")"; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcTypeConstructor.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcTypeConstructor.java new file mode 100644 index 000000000000..4486e96c2041 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBArrayJdbcTypeConstructor.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import java.sql.Types; + +import org.hibernate.dialect.Dialect; +import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Factory for {@link GaussDBArrayJdbcType}. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLArrayJdbcTypeConstructor. + */ +public class GaussDBArrayJdbcTypeConstructor implements JdbcTypeConstructor { + + public static final GaussDBArrayJdbcTypeConstructor INSTANCE = new GaussDBArrayJdbcTypeConstructor(); + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + BasicType elementType, + ColumnTypeInformation columnTypeInformation) { + return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation ); + } + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + JdbcType elementType, + ColumnTypeInformation columnTypeInformation) { + return new GaussDBArrayJdbcType( elementType ); + } + + @Override + public int getDefaultSqlTypeCode() { + return Types.ARRAY; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCallableStatementSupport.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCallableStatementSupport.java new file mode 100644 index 000000000000..73e7a09a12f9 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCallableStatementSupport.java @@ -0,0 +1,177 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import java.util.List; + +import org.hibernate.HibernateException; +import org.hibernate.dialect.type.AbstractPostgreSQLStructJdbcType; +import org.hibernate.procedure.internal.AbstractStandardCallableStatementSupport; +import org.hibernate.procedure.spi.FunctionReturnImplementor; +import org.hibernate.procedure.spi.ProcedureCallImplementor; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.type.OutputableType; +import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; +import org.hibernate.sql.exec.internal.JdbcCallImpl; +import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; +import org.hibernate.sql.exec.spi.JdbcOperationQueryCall; +import org.hibernate.type.SqlTypes; + +import jakarta.persistence.ParameterMode; + +/** + * GaussDB implementation of CallableStatementSupport. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLCallableStatementSupport. + */ +public class GaussDBCallableStatementSupport extends AbstractStandardCallableStatementSupport { + /** + * Singleton access + */ + public static final GaussDBCallableStatementSupport INSTANCE = new GaussDBCallableStatementSupport( true ); + + private final boolean supportsProcedures; + + private GaussDBCallableStatementSupport(boolean supportsProcedures) { + this.supportsProcedures = supportsProcedures; + } + + @Override + public JdbcOperationQueryCall interpretCall(ProcedureCallImplementor procedureCall) { + final String procedureName = procedureCall.getProcedureName(); + final FunctionReturnImplementor functionReturn = procedureCall.getFunctionReturn(); + final ProcedureParameterMetadataImplementor parameterMetadata = procedureCall.getParameterMetadata(); + final boolean firstParamIsRefCursor = parameterMetadata.getParameterCount() != 0 + && isFirstParameterModeRefCursor( parameterMetadata ); + + final List> registrations = parameterMetadata.getRegistrationsAsList(); + final int paramStringSizeEstimate; + if ( functionReturn == null && parameterMetadata.hasNamedParameters() ) { + // That's just a rough estimate. I guess most params will have fewer than 8 chars on average + paramStringSizeEstimate = registrations.size() * 10; + } + else { + // For every param rendered as '?' we have a comma, hence the estimate + paramStringSizeEstimate = registrations.size() * 2; + } + final JdbcCallImpl.Builder builder = new JdbcCallImpl.Builder(); + + final int jdbcParameterOffset; + final int startIndex; + final CallMode callMode; + if ( functionReturn != null ) { + if ( functionReturn.getJdbcTypeCode() == SqlTypes.REF_CURSOR ) { + if ( firstParamIsRefCursor ) { + // validate that the parameter strategy is positional (cannot mix, and REF_CURSOR is inherently positional) + if ( parameterMetadata.hasNamedParameters() ) { + throw new HibernateException( "Cannot mix named parameters and REF_CURSOR parameter on GaussDB" ); + } + callMode = CallMode.CALL_RETURN; + startIndex = 1; + jdbcParameterOffset = 1; + builder.addParameterRegistration( registrations.get( 0 ).toJdbcParameterRegistration( 1, procedureCall ) ); + } + else { + callMode = CallMode.TABLE_FUNCTION; + startIndex = 0; + jdbcParameterOffset = 1; + } + } + else { + callMode = CallMode.FUNCTION; + startIndex = 0; + jdbcParameterOffset = 1; + } + } + else if ( supportsProcedures ) { + jdbcParameterOffset = 1; + startIndex = 0; + callMode = CallMode.NATIVE_CALL; + } + else if ( firstParamIsRefCursor ) { + // validate that the parameter strategy is positional (cannot mix, and REF_CURSOR is inherently positional) + if ( parameterMetadata.hasNamedParameters() ) { + throw new HibernateException( "Cannot mix named parameters and REF_CURSOR parameter on GaussDB" ); + } + jdbcParameterOffset = 1; + startIndex = 1; + callMode = CallMode.CALL_RETURN; + builder.addParameterRegistration( registrations.get( 0 ).toJdbcParameterRegistration( 1, procedureCall ) ); + } + else { + jdbcParameterOffset = 1; + startIndex = 0; + callMode = CallMode.CALL; + } + + final StringBuilder buffer = new StringBuilder( callMode.start.length() + callMode.end.length() + procedureName.length() + paramStringSizeEstimate ) + .append( callMode.start ); + buffer.append( procedureName ); + + if ( startIndex == registrations.size() ) { + buffer.append( '(' ); + } + else { + char sep = '('; + for ( int i = startIndex; i < registrations.size(); i++ ) { + final ProcedureParameterImplementor parameter = registrations.get( i ); + if ( !supportsProcedures && parameter.getMode() == ParameterMode.REF_CURSOR ) { + throw new HibernateException( + "GaussDB supports only one REF_CURSOR parameter, but multiple were registered" ); + } + buffer.append( sep ); + final JdbcCallParameterRegistration registration = parameter.toJdbcParameterRegistration( + i + jdbcParameterOffset, + procedureCall + ); + final OutputableType type = registration.getParameterType(); + final String castType; + if ( parameter.getName() != null ) { + buffer.append( parameter.getName() ).append( " => " ); + } + if ( type != null && type.getJdbcType() instanceof AbstractPostgreSQLStructJdbcType ) { + // We have to cast struct type parameters so that GaussDB understands nulls + castType = ( (AbstractPostgreSQLStructJdbcType) type.getJdbcType() ).getStructTypeName(); + buffer.append( "cast(" ); + } + else { + castType = null; + } + buffer.append( "?" ); + if ( castType != null ) { + buffer.append( " as " ).append( castType ).append( ')' ); + } + sep = ','; + builder.addParameterRegistration( registration ); + } + } + + buffer.append( callMode.end ); + builder.setCallableName( buffer.toString() ); + return builder.buildJdbcCall(); + } + + private static boolean isFirstParameterModeRefCursor(ProcedureParameterMetadataImplementor parameterMetadata) { + return parameterMetadata.getRegistrationsAsList().get( 0 ).getMode() == ParameterMode.REF_CURSOR; + } + + enum CallMode { + TABLE_FUNCTION("select * from ", ")"), + FUNCTION("select ", ")"), + NATIVE_CALL("call ", ")"), + CALL_RETURN("{?=call ", ")}"), + CALL("{call ", ")}"); + + private final String start; + private final String end; + + CallMode(String start, String end) { + this.start = start; + this.end = end; + } + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingInetJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingInetJdbcType.java new file mode 100644 index 000000000000..81cfd92d3ee6 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingInetJdbcType.java @@ -0,0 +1,113 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import java.net.InetAddress; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.dialect.Dialect; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +/** + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLCastingInetJdbcType. + */ +public class GaussDBCastingInetJdbcType implements JdbcType { + + public static final GaussDBCastingInetJdbcType INSTANCE = new GaussDBCastingInetJdbcType(); + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( "cast(" ); + appender.append( writeExpression ); + appender.append( " as inet)" ); + } + + @Override + public int getJdbcTypeCode() { + return SqlTypes.VARBINARY; + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.INET; + } + + @Override + public String toString() { + return "InetSecondJdbcType"; + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaType) { + // No literal support for now + return null; + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setString( index, getStringValue( value, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setString( name, getStringValue( value, options ) ); + } + + private String getStringValue(X value, WrapperOptions options) { + return getJavaType().unwrap( value, InetAddress.class, options ).getHostAddress(); + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new BasicExtractor<>( javaType, this ) { + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return getObject( rs.getString( paramIndex ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return getObject( statement.getString( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return getObject( statement.getString( name ), options ); + } + + private X getObject(String inetString, WrapperOptions options) throws SQLException { + if ( inetString == null ) { + return null; + } + return getJavaType().wrap( inetString, options ); + } + }; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingIntervalSecondJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingIntervalSecondJdbcType.java new file mode 100644 index 000000000000..221066afd170 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingIntervalSecondJdbcType.java @@ -0,0 +1,155 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import java.math.BigDecimal; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AdjustableJdbcType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; + +/** + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLCastingIntervalSecondJdbcType. + */ +public class GaussDBCastingIntervalSecondJdbcType implements AdjustableJdbcType { + + public static final GaussDBCastingIntervalSecondJdbcType INSTANCE = new GaussDBCastingIntervalSecondJdbcType(); + + @Override + public JdbcType resolveIndicatedType(JdbcTypeIndicators indicators, JavaType domainJtd) { + final int scale = indicators.getColumnScale() == JdbcTypeIndicators.NO_COLUMN_SCALE + ? domainJtd.getDefaultSqlScale( indicators.getDialect(), this ) + : indicators.getColumnScale(); + if ( scale > 6 ) { + // Since the maximum allowed scale on GaussDB is 6 (microsecond precision), + // we have to switch to the numeric type if the value is greater + return indicators.getTypeConfiguration().getJdbcTypeRegistry().getDescriptor( SqlTypes.NUMERIC ); + } + else { + return this; + } + } + + @Override + public Expression wrapTopLevelSelectionExpression(Expression expression) { + return new SelfRenderingExpression() { + @Override + public void renderToSql( + SqlAppender sqlAppender, + SqlAstTranslator walker, + SessionFactoryImplementor sessionFactory) { + sqlAppender.append( "extract(epoch from " ); + expression.accept( walker ); + sqlAppender.append( ')' ); + } + + @Override + public JdbcMappingContainer getExpressionType() { + return expression.getExpressionType(); + } + }; + } + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( '(' ); + appender.append( writeExpression ); + appender.append( "*interval'1 second')" ); + } + + @Override + public int getJdbcTypeCode() { + return SqlTypes.NUMERIC; + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.INTERVAL_SECOND; + } + + @Override + public String toString() { + return "IntervalSecondJdbcType"; + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaType) { + // No literal support for now + return null; + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setBigDecimal( index, getBigDecimalValue( value, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setBigDecimal( name, getBigDecimalValue( value, options ) ); + } + + private BigDecimal getBigDecimalValue(X value, WrapperOptions options) { + return getJavaType().unwrap( value, BigDecimal.class, options ).movePointLeft( 9 ); + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new BasicExtractor<>( javaType, this ) { + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return getObject( rs.getBigDecimal( paramIndex ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return getObject( statement.getBigDecimal( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return getObject( statement.getBigDecimal( name ), options ); + } + + private X getObject(BigDecimal bigDecimal, WrapperOptions options) throws SQLException { + if ( bigDecimal == null ) { + return null; + } + return getJavaType().wrap( bigDecimal.movePointRight( 9 ), options ); + } + }; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingJsonArrayJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingJsonArrayJdbcType.java new file mode 100644 index 000000000000..fcc995ccd7bf --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingJsonArrayJdbcType.java @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import org.hibernate.dialect.Dialect; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType; + +/** + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLCastingJsonArrayJdbcType. + */ +public class GaussDBCastingJsonArrayJdbcType extends JsonArrayJdbcType { + + private final boolean jsonb; + + public GaussDBCastingJsonArrayJdbcType(JdbcType elementJdbcType, boolean jsonb) { + super( elementJdbcType ); + this.jsonb = jsonb; + } + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( "cast(" ); + appender.append( writeExpression ); + appender.append( " as " ); + if ( jsonb ) { + appender.append( "jsonb)" ); + } + else { + appender.append( "json)" ); + } + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingJsonArrayJdbcTypeConstructor.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingJsonArrayJdbcTypeConstructor.java new file mode 100644 index 000000000000..1afd94084d61 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingJsonArrayJdbcTypeConstructor.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import org.hibernate.dialect.Dialect; +import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; +import org.hibernate.type.BasicType; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Factory for {@link GaussDBCastingJsonArrayJdbcType}. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLCastingJsonArrayJdbcTypeConstructor. + */ +public class GaussDBCastingJsonArrayJdbcTypeConstructor implements JdbcTypeConstructor { + + public static final GaussDBCastingJsonArrayJdbcTypeConstructor JSONB_INSTANCE = new GaussDBCastingJsonArrayJdbcTypeConstructor( true ); + public static final GaussDBCastingJsonArrayJdbcTypeConstructor JSON_INSTANCE = new GaussDBCastingJsonArrayJdbcTypeConstructor( false ); + + private final boolean jsonb; + + public GaussDBCastingJsonArrayJdbcTypeConstructor(boolean jsonb) { + this.jsonb = jsonb; + } + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + BasicType elementType, + ColumnTypeInformation columnTypeInformation) { + return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation ); + } + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + JdbcType elementType, + ColumnTypeInformation columnTypeInformation) { + return new GaussDBCastingJsonArrayJdbcType( elementType, jsonb ); + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.JSON_ARRAY; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingJsonJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingJsonJdbcType.java new file mode 100644 index 000000000000..3d14060478bf --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBCastingJsonJdbcType.java @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import org.hibernate.dialect.Dialect; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.JsonJdbcType; + +/** + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLCastingJsonJdbcType. + */ +public class GaussDBCastingJsonJdbcType extends JsonJdbcType { + + public static final GaussDBCastingJsonJdbcType JSON_INSTANCE = new GaussDBCastingJsonJdbcType( false, null ); + public static final GaussDBCastingJsonJdbcType JSONB_INSTANCE = new GaussDBCastingJsonJdbcType( true, null ); + + private final boolean jsonb; + + public GaussDBCastingJsonJdbcType(boolean jsonb, EmbeddableMappingType embeddableMappingType) { + super( embeddableMappingType ); + this.jsonb = jsonb; + } + + @Override + public int getDdlTypeCode() { + return SqlTypes.JSON; + } + + @Override + public AggregateJdbcType resolveAggregateJdbcType( + EmbeddableMappingType mappingType, + String sqlType, + RuntimeModelCreationContext creationContext) { + return new GaussDBCastingJsonJdbcType( jsonb, mappingType ); + } + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( "cast(" ); + appender.append( writeExpression ); + appender.append( " as " ); + if ( jsonb ) { + appender.append( "jsonb)" ); + } + else { + appender.append( "json)" ); + } + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBDialect.java new file mode 100644 index 000000000000..39ccff5b83d8 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBDialect.java @@ -0,0 +1,1377 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import jakarta.persistence.GenerationType; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Timeout; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.Length; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.QueryTimeoutException; +import org.hibernate.Timeouts; +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.boot.model.TypeContributions; +import org.hibernate.community.dialect.identity.GaussDBIdentityColumnSupport; +import org.hibernate.community.dialect.sequence.GaussDBSequenceSupport; +import org.hibernate.dialect.BooleanDecoder; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.DatabaseVersion; +import org.hibernate.dialect.DmlTargetColumnQualifierSupport; +import org.hibernate.dialect.FunctionalDependencyAnalysisSupport; +import org.hibernate.dialect.FunctionalDependencyAnalysisSupportImpl; +import org.hibernate.dialect.NationalizationSupport; +import org.hibernate.dialect.RowLockStrategy; +import org.hibernate.dialect.SelectItemReferenceStrategy; +import org.hibernate.dialect.TimeZoneSupport; +import org.hibernate.dialect.aggregate.AggregateSupport; +import org.hibernate.dialect.identity.IdentityColumnSupport; +import org.hibernate.dialect.pagination.LimitHandler; +import org.hibernate.dialect.pagination.LimitLimitHandler; +import org.hibernate.dialect.sequence.SequenceSupport; +import org.hibernate.dialect.unique.CreateTableUniqueDelegate; +import org.hibernate.dialect.unique.UniqueDelegate; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; +import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.exception.LockAcquisitionException; +import org.hibernate.exception.LockTimeoutException; +import org.hibernate.exception.spi.SQLExceptionConversionDelegate; +import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; +import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; +import org.hibernate.internal.util.JdbcExceptionHelper; +import org.hibernate.mapping.AggregateColumn; +import org.hibernate.mapping.Table; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.mutation.EntityMutationTarget; +import org.hibernate.procedure.spi.CallableStatementSupport; +import org.hibernate.query.SemanticException; +import org.hibernate.query.common.FetchClauseType; +import org.hibernate.query.common.TemporalUnit; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.sqm.CastType; +import org.hibernate.query.sqm.IntervalType; +import org.hibernate.query.sqm.mutation.internal.cte.CteInsertStrategy; +import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.ParameterMarkerStrategy; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperation; +import org.hibernate.sql.model.MutationOperation; +import org.hibernate.sql.model.internal.OptionalTableUpdate; +import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation; +import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; +import org.hibernate.tool.schema.internal.StandardTableExporter; +import org.hibernate.tool.schema.spi.Exporter; +import org.hibernate.type.JavaObjectType; +import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; +import org.hibernate.type.descriptor.jdbc.BlobJdbcType; +import org.hibernate.type.descriptor.jdbc.ClobJdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType; +import org.hibernate.type.descriptor.jdbc.SqlTypedJdbcType; +import org.hibernate.type.descriptor.jdbc.XmlJdbcType; +import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; +import org.hibernate.type.descriptor.sql.internal.ArrayDdlTypeImpl; +import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType; +import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; +import org.hibernate.type.descriptor.sql.internal.NamedNativeEnumDdlTypeImpl; +import org.hibernate.type.descriptor.sql.internal.NamedNativeOrdinalEnumDdlTypeImpl; +import org.hibernate.type.descriptor.sql.internal.Scale6IntervalSecondDdlType; +import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; +import org.hibernate.type.spi.TypeConfiguration; + +import java.sql.CallableStatement; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; +import static org.hibernate.query.common.TemporalUnit.EPOCH; +import static org.hibernate.type.SqlTypes.ARRAY; +import static org.hibernate.type.SqlTypes.BINARY; +import static org.hibernate.type.SqlTypes.CHAR; +import static org.hibernate.type.SqlTypes.FLOAT; +import static org.hibernate.type.SqlTypes.GEOGRAPHY; +import static org.hibernate.type.SqlTypes.GEOMETRY; +import static org.hibernate.type.SqlTypes.INET; +import static org.hibernate.type.SqlTypes.JSON; +import static org.hibernate.type.SqlTypes.LONG32NVARCHAR; +import static org.hibernate.type.SqlTypes.LONG32VARBINARY; +import static org.hibernate.type.SqlTypes.LONG32VARCHAR; +import static org.hibernate.type.SqlTypes.NCHAR; +import static org.hibernate.type.SqlTypes.NCLOB; +import static org.hibernate.type.SqlTypes.NVARCHAR; +import static org.hibernate.type.SqlTypes.OTHER; +import static org.hibernate.type.SqlTypes.SQLXML; +import static org.hibernate.type.SqlTypes.STRUCT; +import static org.hibernate.type.SqlTypes.TIME; +import static org.hibernate.type.SqlTypes.TIMESTAMP; +import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; +import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; +import static org.hibernate.type.SqlTypes.TIME_UTC; +import static org.hibernate.type.SqlTypes.TINYINT; +import static org.hibernate.type.SqlTypes.UUID; +import static org.hibernate.type.SqlTypes.VARBINARY; +import static org.hibernate.type.SqlTypes.VARCHAR; +import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate; +import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime; +import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime; +import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMicros; +import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis; + +/** + * A {@linkplain Dialect SQL dialect} for GaussDB V2.0-8.201 and above. + *

+ * Please refer to the + * GaussDB documentation. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLDialect. + */ +public class GaussDBDialect extends Dialect { + protected final static DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 2 ); + + private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this); + private final StandardTableExporter gaussDBTableExporter = new StandardTableExporter( this ) { + @Override + protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) { + final JdbcType jdbcType = aggregateColumn.getType().getJdbcType(); + if ( jdbcType.isXml() ) { + // Requires the use of xmltable which is not supported in check constraints + return; + } + super.applyAggregateColumnCheck( buf, aggregateColumn ); + } + }; + + private final OptionalTableUpdateStrategy optionalTableUpdateStrategy; + + public GaussDBDialect() { + this(MINIMUM_VERSION); + } + + public GaussDBDialect(DialectResolutionInfo info) { + this( info.makeCopyOrDefault( MINIMUM_VERSION )); + registerKeywords( info ); + } + + public GaussDBDialect(DatabaseVersion version) { + super( version ); + this.optionalTableUpdateStrategy = determineOptionalTableUpdateStrategy( version ); + } + + @Override + public boolean supportsColumnCheck() { + return false; + } + + private static OptionalTableUpdateStrategy determineOptionalTableUpdateStrategy(DatabaseVersion version) { + return version.isSameOrAfter( DatabaseVersion.make( 15, 0 ) ) + ? GaussDBDialect::usingMerge + : GaussDBDialect::withoutMerge; + } + + @Override + protected DatabaseVersion getMinimumSupportedVersion() { + return MINIMUM_VERSION; + } + + @Override + public boolean getDefaultNonContextualLobCreation() { + return true; + } + + @Override + protected String columnType(int sqlTypeCode) { + return switch (sqlTypeCode) { + // no tinyint + case TINYINT -> "smallint"; + + // there are no nchar/nvarchar types + case NCHAR -> columnType( CHAR ); + case NVARCHAR -> columnType( VARCHAR ); + + case LONG32VARCHAR, LONG32NVARCHAR -> "text"; + case NCLOB -> "clob"; + + case BINARY, VARBINARY, LONG32VARBINARY -> "bytea"; + + case TIMESTAMP_UTC -> columnType( TIMESTAMP_WITH_TIMEZONE ); + + default -> super.columnType( sqlTypeCode ); + }; + } + + @Override + protected String castType(int sqlTypeCode) { + return switch (sqlTypeCode) { + case CHAR, NCHAR, VARCHAR, NVARCHAR -> "varchar"; + case LONG32VARCHAR, LONG32NVARCHAR -> "text"; + case NCLOB -> "clob"; + case BINARY, VARBINARY, LONG32VARBINARY -> "bytea"; + default -> super.castType( sqlTypeCode ); + }; + } + + @Override + protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + super.registerColumnTypes( typeContributions, serviceRegistry ); + final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry(); + + // We need to configure that the array type uses the raw element type for casts + ddlTypeRegistry.addDescriptor( new ArrayDdlTypeImpl( this, true ) ); + + // Register this type to be able to support Float[] + // The issue is that the JDBC driver can't handle createArrayOf( "float(24)", ... ) + // It requires the use of "real" or "float4" + // Alternatively we could introduce a new API in Dialect for creating such base names + ddlTypeRegistry.addDescriptor( + CapacityDependentDdlType.builder( FLOAT, columnType( FLOAT ), castType( FLOAT ), this ) + .withTypeCapacity( 24, "float4" ) + .build() + ); + + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "xml", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) ); + ddlTypeRegistry.addDescriptor( new Scale6IntervalSecondDdlType( this ) ); + + // Prefer jsonb if possible + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); + + ddlTypeRegistry.addDescriptor( new NamedNativeEnumDdlTypeImpl( this ) ); + ddlTypeRegistry.addDescriptor( new NamedNativeOrdinalEnumDdlTypeImpl( this ) ); + } + + @Override + public int getMaxVarcharLength() { + return 10_485_760; + } + + @Override + public int getMaxVarcharCapacity() { + // 1GB-85-4 according to GaussDB docs + return 1_073_741_727; + } + + @Override + public int getMaxVarbinaryLength() { + //has no varbinary-like type + return Length.LONG32; + } + + @Override + public int getDefaultStatementBatchSize() { + return 15; + } + + @Override + public JdbcType resolveSqlTypeDescriptor( + String columnTypeName, + int jdbcTypeCode, + int precision, + int scale, + JdbcTypeRegistry jdbcTypeRegistry) { + switch ( jdbcTypeCode ) { + case OTHER: + switch ( columnTypeName ) { + case "uuid": + jdbcTypeCode = UUID; + break; + case "json": + case "jsonb": + jdbcTypeCode = JSON; + break; + case "xml": + jdbcTypeCode = SQLXML; + break; + case "inet": + jdbcTypeCode = INET; + break; + case "geometry": + jdbcTypeCode = GEOMETRY; + break; + case "geography": + jdbcTypeCode = GEOGRAPHY; + break; + } + break; + case TIME: + // The GaussDB JDBC driver reports TIME for timetz, but we use it only for mapping OffsetTime to UTC + if ( "timetz".equals( columnTypeName ) ) { + jdbcTypeCode = TIME_UTC; + } + break; + case TIMESTAMP: + // The GaussDB JDBC driver reports TIMESTAMP for timestamptz, but we use it only for mapping Instant + if ( "timestamptz".equals( columnTypeName ) ) { + jdbcTypeCode = TIMESTAMP_UTC; + } + break; + case ARRAY: + // GaussDB names array types by prepending an underscore to the base name + if ( columnTypeName.charAt( 0 ) == '_' ) { + final String componentTypeName = columnTypeName.substring( 1 ); + final Integer sqlTypeCode = resolveSqlTypeCode( componentTypeName, jdbcTypeRegistry.getTypeConfiguration() ); + if ( sqlTypeCode != null ) { + return jdbcTypeRegistry.resolveTypeConstructorDescriptor( + jdbcTypeCode, + jdbcTypeRegistry.getDescriptor( sqlTypeCode ), + ColumnTypeInformation.EMPTY + ); + } + final SqlTypedJdbcType elementDescriptor = jdbcTypeRegistry.findSqlTypedDescriptor( componentTypeName ); + if ( elementDescriptor != null ) { + return jdbcTypeRegistry.resolveTypeConstructorDescriptor( + jdbcTypeCode, + elementDescriptor, + ColumnTypeInformation.EMPTY + ); + } + } + break; + case STRUCT: + final SqlTypedJdbcType descriptor = jdbcTypeRegistry.findSqlTypedDescriptor( + // Skip the schema + columnTypeName.substring( columnTypeName.indexOf( '.' ) + 1 ) + ); + if ( descriptor != null ) { + return descriptor; + } + break; + } + return jdbcTypeRegistry.getDescriptor( jdbcTypeCode ); + } + + @Override + protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration typeConfiguration) { + return switch (columnTypeName) { + case "bool" -> Types.BOOLEAN; + case "float4" -> Types.REAL; // Use REAL instead of FLOAT to get Float as recommended Java type + case "float8" -> Types.DOUBLE; + case "int2" -> Types.SMALLINT; + case "int4" -> Types.INTEGER; + case "int8" -> Types.BIGINT; + default -> super.resolveSqlTypeCode( columnTypeName, typeConfiguration ); + }; + } + + @Override + public String getEnumTypeDeclaration(String name, String[] values) { + return name; + } + + @Override + public String[] getCreateEnumTypeCommand(String name, String[] values) { + StringBuilder type = new StringBuilder(); + type.append( "create type " ) + .append( name ) + .append( " as enum (" ); + String separator = ""; + for ( String value : values ) { + type.append( separator ).append('\'').append( value ).append('\''); + separator = ","; + } + type.append( ')' ); + String cast1 = "create cast (varchar as " + + name + + ") with inout as implicit"; + String cast2 = "create cast (" + + name + + " as varchar) with inout as implicit"; + return new String[] { type.toString(), cast1, cast2 }; + } + + @Override + public String[] getDropEnumTypeCommand(String name) { + return new String[] { "drop type if exists " + name + " cascade" }; + } + + @Override + public String currentTime() { + return "localtime"; + } + + @Override + public String currentTimestamp() { + return "localtimestamp"; + } + + @Override + public String currentTimestampWithTimeZone() { + return "current_timestamp"; + } + + /** + * The {@code extract()} function returns {@link TemporalUnit#DAY_OF_WEEK} + * numbered from 0 to 6. This isn't consistent with what most other + * databases do, so here we adjust the result by generating + * {@code (extract(dow,arg)+1))}. + */ + @Override + public String extractPattern(TemporalUnit unit) { + return switch (unit) { + case DAY_OF_WEEK -> "(" + super.extractPattern( unit ) + "+1)"; + default -> super.extractPattern(unit); + }; + } + + @Override + public String castPattern(CastType from, CastType to) { + if ( from == CastType.STRING && to == CastType.BOOLEAN ) { + return "cast(?1 as ?2)"; + } + + if ( to == CastType.STRING ) { + switch ( from ) { + case BOOLEAN: + case INTEGER_BOOLEAN: + case TF_BOOLEAN: + case YN_BOOLEAN: + return BooleanDecoder.toString( from ); + case DATE: + return "to_char(?1,'YYYY-MM-DD')"; + case TIME: + return "cast(?1 as ?2)"; + case TIMESTAMP: + return "to_char(?1,'YYYY-MM-DD HH24:MI:SS.FF9')"; + case OFFSET_TIMESTAMP: + return "to_char(?1,'YYYY-MM-DD HH24:MI:SS.FF9TZH:TZM')"; + case ZONE_TIMESTAMP: + return "to_char(?1,'YYYY-MM-DD HH24:MI:SS.FF9 TZR')"; + } + } + + return super.castPattern( from, to ); + } + + /** + * {@code microsecond} is the smallest unit for an {@code interval}, + * and the highest precision for a {@code timestamp}, so we could + * use it as the "native" precision, but it's more convenient to use + * whole seconds (with the fractional part), since we want to use + * {@code extract(epoch from ...)} in our emulation of + * {@code timestampdiff()}. + */ + @Override + public long getFractionalSecondPrecisionInNanos() { + return 1_000_000_000; //seconds + } + + @Override @SuppressWarnings("deprecation") + public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { + return intervalType != null + ? "(?2+?3)" + : "cast(?3+" + intervalPattern( unit ) + " as " + temporalType.name().toLowerCase() + ")"; + } + + private static String intervalPattern(TemporalUnit unit) { + return switch (unit) { + case NANOSECOND -> "(?2)/1e3*interval '1 microsecond'"; + case NATIVE -> "(?2)*interval '1 second'"; + case QUARTER -> "(?2)*interval '3 month'"; // quarter is not supported in interval literals + case WEEK -> "(?2)*interval '7 day'"; // week is not supported in interval literals + default -> "(?2)*interval '1 " + unit + "'"; + }; + } + + @Override @SuppressWarnings("deprecation") + public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { + if ( unit == null ) { + return "(?3-?2)"; + } + return switch (unit) { + case YEAR -> "extract(year from ?3-?2)"; + case QUARTER -> "(extract(year from ?3-?2)*4+extract(month from ?3-?2)/3)"; + case MONTH -> "(extract(year from ?3-?2)*12+extract(month from ?3-?2))"; + case WEEK -> "(extract(day from ?3-?2)/7)"; // week is not supported by extract() when the argument is a duration + case DAY -> "extract(day from ?3-?2)"; + // in order to avoid multiple calls to extract(), + // we use extract(epoch from x - y) * factor for + // all the following units: + case HOUR, MINUTE, SECOND, NANOSECOND, NATIVE -> + "extract(epoch from ?3-?2)" + EPOCH.conversionFactor( unit, this ); + default -> throw new SemanticException( "Unrecognized field: " + unit ); + }; + } + + @Override + public TimeZoneSupport getTimeZoneSupport() { + return TimeZoneSupport.NORMALIZE; + } + + @Override + public void initializeFunctionRegistry(FunctionContributions functionContributions) { + super.initializeFunctionRegistry(functionContributions); + + GaussDBFunctionRegistry functionRegistry = new GaussDBFunctionRegistry( functionContributions ); + functionRegistry.register(); + } + + @Override + public @Nullable String getDefaultOrdinalityColumnName() { + return "ordinality"; + } + + @Override + public NameQualifierSupport getNameQualifierSupport() { + // This method is overridden so the correct value will be returned when + // DatabaseMetaData is not available. + return NameQualifierSupport.SCHEMA; + } + + @Override + public String getCurrentSchemaCommand() { + return "select current_schema()"; + } + + @Override + public boolean supportsDistinctFromPredicate() { + return true; + } + + @Override + public boolean supportsIfExistsBeforeTableName() { + return true; + } + + @Override + public boolean supportsIfExistsBeforeTypeName() { + return true; + } + + @Override + public boolean supportsIfExistsBeforeConstraintName() { + return true; + } + + @Override + public boolean supportsIfExistsAfterAlterTable() { + return true; + } + + @Override + public String getBeforeDropStatement() { + // NOTICE: table "nonexistent" does not exist, skipping + // as a JDBC SQLWarning + return "set client_min_messages = WARNING"; + } + + @Override + public String getAlterColumnTypeString(String columnName, String columnType, String columnDefinition) { + // would need multiple statements to 'set not null'/'drop not null', 'set default'/'drop default', 'set generated', etc + return "alter column " + columnName + " set data type " + columnType; + } + + @Override + public boolean supportsAlterColumnType() { + return true; + } + + @Override + public boolean supportsValuesList() { + return true; + } + + @Override + public boolean supportsPartitionBy() { + return true; + } + + @Override + public boolean supportsNonQueryWithCTE() { + return true; + } + @Override + public boolean supportsConflictClauseForInsertCTE() { + return true; + } + + @Override + public SequenceSupport getSequenceSupport() { + return GaussDBSequenceSupport.INSTANCE; + } + + @Override + public String getCascadeConstraintsString() { + return " cascade"; + } + + @Override + public String getQuerySequencesString() { + return "select * from information_schema.sequences"; + } + + @Override + public LimitHandler getLimitHandler() { + return LimitLimitHandler.INSTANCE; + } + + @Override + public String getForUpdateString(String aliases) { + return getForUpdateString() + " of " + aliases; + } + + @Override + public String getForUpdateString(String aliases, LockOptions lockOptions) { + // parent's implementation for (aliases, lockOptions) ignores aliases + if ( aliases.isEmpty() ) { + LockMode lockMode = lockOptions.getLockMode(); + for ( Map.Entry entry : lockOptions.getAliasSpecificLocks() ) { + // seek the highest lock mode + if ( entry.getValue().greaterThan(lockMode) ) { + aliases = entry.getKey(); + } + } + } + LockMode lockMode = lockOptions.getAliasSpecificLockMode( aliases ); + if ( lockMode == null ) { + lockMode = lockOptions.getLockMode(); + } + return switch (lockMode) { + case PESSIMISTIC_READ -> getReadLockString( aliases, lockOptions.getTimeOut() ); + case PESSIMISTIC_WRITE -> getWriteLockString( aliases, lockOptions.getTimeOut() ); + case UPGRADE_NOWAIT, PESSIMISTIC_FORCE_INCREMENT -> getForUpdateNowaitString( aliases ); + case UPGRADE_SKIPLOCKED -> getForUpdateSkipLockedString( aliases ); + default -> ""; + }; + } + + @Override + public String getNoColumnsInsertString() { + return "default values"; + } + + @Override + public String getCaseInsensitiveLike(){ + return "ilike"; + } + + @Override + public boolean supportsCaseInsensitiveLike() { + return true; + } + + @Override + public GenerationType getNativeValueGenerationStrategy() { + return GenerationType.SEQUENCE; + } + + @Override + public boolean supportsOuterJoinForUpdate() { + return false; + } + + @Override + public boolean useInputStreamToInsertBlob() { + return false; + } + + @Override + public boolean useConnectionToCreateLob() { + return false; + } + + @Override + public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) { + // TODO: adapt this to handle named enum types! + return "cast(null as " + typeConfiguration.getDdlTypeRegistry().getDescriptor( sqlType ).getRawTypeName() + ")"; + } + + @Override + public String quoteCollation(String collation) { + return '\"' + collation + '\"'; + } + + @Override + public boolean supportsCommentOn() { + return true; + } + + @Override + public boolean supportsCurrentTimestampSelection() { + return true; + } + + @Override + public boolean isCurrentTimestampSelectStringCallable() { + return false; + } + + @Override + public String getCurrentTimestampSelectString() { + return "select now()"; + } + + @Override + public boolean supportsTupleCounts() { + return true; + } + + @Override + public boolean supportsIsTrue() { + return true; + } + + @Override + public boolean requiresParensForTupleDistinctCounts() { + return true; + } + + @Override + public void appendBooleanValueString(SqlAppender appender, boolean bool) { + appender.appendSql( bool ); + } + + @Override + public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData) + throws SQLException { + + if ( dbMetaData == null ) { + builder.setUnquotedCaseStrategy( IdentifierCaseStrategy.LOWER ); + builder.setQuotedCaseStrategy( IdentifierCaseStrategy.MIXED ); + } + + return super.buildIdentifierHelper( builder, dbMetaData ); + } + + @Override + public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( + EntityMappingType rootEntityDescriptor, + RuntimeModelCreationContext runtimeModelCreationContext) { + return new CteMutationStrategy( rootEntityDescriptor, runtimeModelCreationContext ); + } + + @Override + public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( + EntityMappingType rootEntityDescriptor, + RuntimeModelCreationContext runtimeModelCreationContext) { + return new CteInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext ); + } + + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new GaussDBSqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + + @Override + public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() { + return EXTRACTOR; + } + + /** + * Constraint-name extractor for constraint violation exceptions. + * Originally contributed by Denny Bartelt. + */ + private static final ViolatedConstraintNameExtractor EXTRACTOR = + new TemplatedViolatedConstraintNameExtractor( sqle -> { + final String sqlState = JdbcExceptionHelper.extractSqlState( sqle ); + if ( sqlState != null ) { + int state; + try { + state = Integer.parseInt(sqlState); + } + catch (NumberFormatException e) { + state = 23001; // or some default value + } + switch ( state ) { + // CHECK VIOLATION + case 23514: + return extractUsingTemplate( "violates check constraint \"", "\"", sqle.getMessage() ); + // UNIQUE VIOLATION + case 23505: + return extractUsingTemplate( "violates unique constraint \"", "\"", sqle.getMessage() ); + // FOREIGN KEY VIOLATION + case 23503: + return extractUsingTemplate( "violates foreign key constraint \"", "\"", sqle.getMessage() ); + // NOT NULL VIOLATION + case 23502: + return extractUsingTemplate( + "null value in column \"", + "\" violates not-null constraint", + sqle.getMessage() + ); + // TODO: RESTRICT VIOLATION + case 23001: + return null; + } + } + return null; + } ); + + @Override + public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() { + return (sqlException, message, sql) -> { + final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException ); + if ( sqlState != null ) { + switch ( sqlState ) { + case "40P01": + // DEADLOCK DETECTED + return new LockAcquisitionException( message, sqlException, sql ); + case "55P03": + // LOCK NOT AVAILABLE + return new LockTimeoutException( message, sqlException, sql ); + case "57014": + return new QueryTimeoutException( message, sqlException, sql ); + } + } + return null; + }; + } + + @Override + public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException { + // Register the type of the out param - GaussDB uses Types.OTHER + statement.registerOutParameter( col++, Types.OTHER ); + return col; + } + + @Override + public ResultSet getResultSet(CallableStatement ps) throws SQLException { + ps.execute(); + return (ResultSet) ps.getObject( 1 ); + } + + // Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public boolean supportsLobValueChangePropagation() { + return false; + } + + @Override + public boolean supportsUnboundedLobLocatorMaterialization() { + return false; + } + + @Override + public SelectItemReferenceStrategy getGroupBySelectItemReferenceStrategy() { + return SelectItemReferenceStrategy.POSITION; + } + + @Override + public CallableStatementSupport getCallableStatementSupport() { + return GaussDBCallableStatementSupport.INSTANCE; + } + + @Override + public ResultSet getResultSet(CallableStatement statement, int position) throws SQLException { + if ( position != 1 ) { + throw new UnsupportedOperationException( "GaussDB only supports REF_CURSOR parameters as the first parameter" ); + } + return (ResultSet) statement.getObject( 1 ); + } + + @Override + public ResultSet getResultSet(CallableStatement statement, String name) throws SQLException { + throw new UnsupportedOperationException( "GaussDB only supports accessing REF_CURSOR parameters by position" ); + } + + @Override + public boolean qualifyIndexName() { + return false; + } + + @Override + public IdentityColumnSupport getIdentityColumnSupport() { + return GaussDBIdentityColumnSupport.INSTANCE; + } + + @Override + public boolean supportsExpectedLobUsagePattern() { + return false; + } + + @Override + public NationalizationSupport getNationalizationSupport() { + return NationalizationSupport.IMPLICIT; + } + + @Override + public int getMaxIdentifierLength() { + return 63; + } + + @Override + public boolean supportsStandardArrays() { + return true; + } + + @Override + public boolean supportsJdbcConnectionLobCreation(DatabaseMetaData databaseMetaData) { + return false; + } + + @Override + public boolean supportsMaterializedLobAccess() { + // Prefer using text and bytea over oid (LOB), because oid is very restricted. + // If someone really wants a type bigger than 1GB, they should ask for it by using @Lob explicitly + return false; + } + + @Override + public boolean supportsTemporalLiteralOffset() { + return true; + } + + @Override + public void appendDatetimeFormat(SqlAppender appender, String format) { + throw new UnsupportedOperationException( "GaussDB not support datetime format yet" ); + } + + @Override + public String translateExtractField(TemporalUnit unit) { + return switch (unit) { + //WEEK means the ISO week number + case DAY_OF_MONTH -> "day"; + case DAY_OF_YEAR -> "doy"; + case DAY_OF_WEEK -> "dow"; + default -> super.translateExtractField( unit ); + }; + } + + @Override + public AggregateSupport getAggregateSupport() { + return null; + } + + @Override + public void appendBinaryLiteral(SqlAppender appender, byte[] bytes) { + appender.appendSql( "bytea '\\x" ); + PrimitiveByteArrayJavaType.INSTANCE.appendString( appender, bytes ); + appender.appendSql( '\'' ); + } + + @Override + public void appendDateTimeLiteral( + SqlAppender appender, + TemporalAccessor temporalAccessor, + @SuppressWarnings("deprecation") + TemporalType precision, + TimeZone jdbcTimeZone) { + switch ( precision ) { + case DATE: + appender.appendSql( "date '" ); + appendAsDate( appender, temporalAccessor ); + appender.appendSql( '\'' ); + break; + case TIME: + if ( supportsTemporalLiteralOffset() && temporalAccessor.isSupported( ChronoField.OFFSET_SECONDS ) ) { + appender.appendSql( "time with time zone '" ); + appendAsTime( appender, temporalAccessor, true, jdbcTimeZone ); + } + else { + appender.appendSql( "time '" ); + appendAsLocalTime( appender, temporalAccessor ); + } + appender.appendSql( '\'' ); + break; + case TIMESTAMP: + if ( supportsTemporalLiteralOffset() && temporalAccessor.isSupported( ChronoField.OFFSET_SECONDS ) ) { + appender.appendSql( "timestamp with time zone '" ); + appendAsTimestampWithMicros( appender, temporalAccessor, true, jdbcTimeZone ); + appender.appendSql( '\'' ); + } + else { + appender.appendSql( "timestamp '" ); + appendAsTimestampWithMicros( appender, temporalAccessor, false, jdbcTimeZone ); + appender.appendSql( '\'' ); + } + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void appendDateTimeLiteral( + SqlAppender appender, + Date date, + @SuppressWarnings("deprecation") + TemporalType precision, + TimeZone jdbcTimeZone) { + switch ( precision ) { + case DATE: + appender.appendSql( "date '" ); + appendAsDate( appender, date ); + appender.appendSql( '\'' ); + break; + case TIME: + appender.appendSql( "time with time zone '" ); + appendAsTime( appender, date, jdbcTimeZone ); + appender.appendSql( '\'' ); + break; + case TIMESTAMP: + appender.appendSql( "timestamp with time zone '" ); + appendAsTimestampWithMicros( appender, date, jdbcTimeZone ); + appender.appendSql( '\'' ); + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void appendDateTimeLiteral( + SqlAppender appender, + Calendar calendar, + @SuppressWarnings("deprecation") + TemporalType precision, + TimeZone jdbcTimeZone) { + switch ( precision ) { + case DATE: + appender.appendSql( "date '" ); + appendAsDate( appender, calendar ); + appender.appendSql( '\'' ); + break; + case TIME: + appender.appendSql( "time with time zone '" ); + appendAsTime( appender, calendar, jdbcTimeZone ); + appender.appendSql( '\'' ); + break; + case TIMESTAMP: + appender.appendSql( "timestamp with time zone '" ); + appendAsTimestampWithMillis( appender, calendar, jdbcTimeZone ); + appender.appendSql( '\'' ); + break; + default: + throw new IllegalArgumentException(); + } + } + + private String withTimeout(String lockString, Timeout timeout) { + return switch (timeout.milliseconds()) { + case Timeouts.NO_WAIT_MILLI -> supportsNoWait() ? lockString + " nowait" : lockString; + case Timeouts.SKIP_LOCKED_MILLI -> supportsSkipLocked() ? lockString + " skip locked" : lockString; + default -> lockString; + }; + } + + @Override + public String getWriteLockString(Timeout timeout) { + return withTimeout( getForUpdateString(), timeout ); + } + + @Override + public String getWriteLockString(String aliases, Timeout timeout) { + return withTimeout( getForUpdateString( aliases ), timeout ); + } + + @Override + public String getReadLockString(Timeout timeout) { + return withTimeout(" for share", timeout ); + } + + @Override + public String getReadLockString(String aliases, Timeout timeout) { + return withTimeout(" for share of " + aliases, timeout ); + } + + private String withTimeout(String lockString, int timeout) { + return switch (timeout) { + case Timeouts.NO_WAIT_MILLI -> supportsNoWait() ? lockString + " nowait" : lockString; + case Timeouts.SKIP_LOCKED_MILLI -> supportsSkipLocked() ? lockString + " skip locked" : lockString; + default -> lockString; + }; + } + + @Override + public String getWriteLockString(int timeout) { + return withTimeout( getForUpdateString(), timeout ); + } + + @Override + public String getWriteLockString(String aliases, int timeout) { + return withTimeout( getForUpdateString( aliases ), timeout ); + } + + @Override + public String getReadLockString(int timeout) { + return withTimeout(" for share", timeout ); + } + + @Override + public String getReadLockString(String aliases, int timeout) { + return withTimeout(" for share of " + aliases, timeout ); + } + + @Override + public String getForUpdateNowaitString() { + return supportsNoWait() + ? " for update nowait" + : getForUpdateString(); + } + + @Override + public String getForUpdateNowaitString(String aliases) { + return supportsNoWait() + ? " for update of " + aliases + " nowait" + : getForUpdateString(aliases); + } + + @Override + public String getForUpdateSkipLockedString() { + return supportsSkipLocked() + ? " for update skip locked" + : getForUpdateString(); + } + + @Override + public String getForUpdateSkipLockedString(String aliases) { + return supportsSkipLocked() + ? " for update of " + aliases + " skip locked" + : getForUpdateString( aliases ); + } + + @Override + public boolean supportsNoWait() { + return true; + } + + @Override + public boolean supportsWait() { + return false; + } + + @Override + public boolean supportsSkipLocked() { + return true; + } + + @Override + public boolean supportsInsertReturning() { + return true; + } + + @Override + public boolean supportsOffsetInSubquery() { + return true; + } + + @Override + public boolean supportsWindowFunctions() { + return true; + } + + @Override + public boolean supportsLateral() { + return false; + } + + @Override + public boolean supportsRecursiveCTE() { + return false; + } + + @Override + public boolean supportsOrderByInSubquery() { + return false; + } + + @Override + public boolean supportsFetchClause(FetchClauseType type) { + return false; + } + + @Override + public String getForUpdateString() { + return " for update"; + } + + @Override + public boolean supportsFilterClause() { + return false; + } + + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_REFERENCE; + } + + @Override + public RowLockStrategy getWriteRowLockStrategy() { + return RowLockStrategy.TABLE; + } + + @Override + public void augmentRecognizedTableTypes(List tableTypesList) { + super.augmentRecognizedTableTypes( tableTypesList ); + tableTypesList.add( "MATERIALIZED VIEW" ); + tableTypesList.add( "PARTITIONED TABLE" ); + } + + @Override + public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + super.contributeTypes(typeContributions, serviceRegistry); + contributeGaussDBTypes( typeContributions); + } + + /** + * Allow for extension points to override this only + */ + protected void contributeGaussDBTypes(TypeContributions typeContributions) { + final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() + .getJdbcTypeRegistry(); + // For how BLOB affects Hibernate, see: + // http://in.relation.to/15492.lace + jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.BLOB_BINDING ); + jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING ); + jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE ); + + jdbcTypeRegistry.addDescriptorIfAbsent( GaussDBCastingInetJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( GaussDBCastingIntervalSecondJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( GaussDBStructuredJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( GaussDBCastingJsonJdbcType.JSONB_INSTANCE ); + jdbcTypeRegistry.addTypeConstructorIfAbsent( GaussDBCastingJsonArrayJdbcTypeConstructor.JSONB_INSTANCE ); + + // GaussDB requires a custom binder for binding untyped nulls as VARBINARY + typeContributions.contributeJdbcType( ObjectNullAsBinaryTypeJdbcType.INSTANCE ); + + // Until we remove StandardBasicTypes, we have to keep this + typeContributions.contributeType( + new JavaObjectType( + ObjectNullAsBinaryTypeJdbcType.INSTANCE, + typeContributions.getTypeConfiguration() + .getJavaTypeRegistry() + .getDescriptor( Object.class ) + ) + ); + + jdbcTypeRegistry.addDescriptor( GaussDBEnumJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptor( GaussDBOrdinalEnumJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptor( GaussDBUUIDJdbcType.INSTANCE ); + + // Replace the standard array constructor + jdbcTypeRegistry.addTypeConstructor( GaussDBArrayJdbcTypeConstructor.INSTANCE ); + } + + @Override + public UniqueDelegate getUniqueDelegate() { + return uniqueDelegate; + } + + @Override + public Exporter getTableExporter() { + return gaussDBTableExporter; + } + + /** + * @return {@code true}, but only because we can "batch" truncate + */ + @Override + public boolean canBatchTruncate() { + return true; + } + + @Override + public String getQueryHintString(String sql, String hints) { + return "/*+ " + hints + " */ " + sql; + } + + @Override + public String addSqlHintOrComment(String sql, QueryOptions queryOptions, boolean commentsEnabled) { + // GaussDB's extension pg_hint_plan needs the hint to be the first comment + if ( commentsEnabled && queryOptions.getComment() != null ) { + sql = prependComment( sql, queryOptions.getComment() ); + } + if ( queryOptions.getDatabaseHints() != null && !queryOptions.getDatabaseHints().isEmpty() ) { + sql = getQueryHintString( sql, queryOptions.getDatabaseHints() ); + } + return sql; + } + + @FunctionalInterface + private interface OptionalTableUpdateStrategy { + MutationOperation buildMutationOperation( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory); + } + + @Override + public MutationOperation createOptionalTableUpdateOperation( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { + return optionalTableUpdateStrategy.buildMutationOperation( mutationTarget, optionalTableUpdate, factory ); + } + + private static MutationOperation usingMerge( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { + final GaussDBSqlAstTranslator translator = new GaussDBSqlAstTranslator<>( factory, optionalTableUpdate ); + return translator.createMergeOperation( optionalTableUpdate ); + } + + private static MutationOperation withoutMerge( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { + return new OptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory ); + } + + private static class NativeParameterMarkers implements ParameterMarkerStrategy { + /** + * Singleton access + */ + public static final NativeParameterMarkers INSTANCE = new NativeParameterMarkers(); + + @Override + public String createMarker(int position, JdbcType jdbcType) { + return "$" + position; + } + } + + @Override + public int getDefaultIntervalSecondScale() { + // The maximum scale for `interval second` is 6 unfortunately + return 6; + } + + @Override + public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() { + return DmlTargetColumnQualifierSupport.TABLE_ALIAS; + } + + @Override + public boolean supportsFromClauseInUpdate() { + return true; + } + + @Override + public boolean supportsBindingNullSqlTypeForSetNull() { + return true; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBEnumJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBEnumJdbcType.java new file mode 100644 index 000000000000..07bd77d81a04 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBEnumJdbcType.java @@ -0,0 +1,169 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.NamedAuxiliaryDatabaseObject; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.Size; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Arrays; + +import static java.util.Collections.emptySet; +import static org.hibernate.type.SqlTypes.NAMED_ENUM; +import static org.hibernate.type.SqlTypes.OTHER; +import static org.hibernate.type.descriptor.converter.internal.EnumHelper.getEnumeratedValues; + +/** + * Represents a named {@code enum} type on GaussDB. + *

+ * Hibernate does not automatically use this for enums + * mapped as {@link jakarta.persistence.EnumType#STRING}, and + * instead this type must be explicitly requested using: + *

+ * @JdbcTypeCode(SqlTypes.NAMED_ENUM)
+ * 
+ * + * @see org.hibernate.type.SqlTypes#NAMED_ENUM + * @see GaussDBDialect#getEnumTypeDeclaration(String, String[]) + * @see GaussDBDialect#getCreateEnumTypeCommand(String, String[]) + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLEnumJdbcType. + */ +public class GaussDBEnumJdbcType implements JdbcType { + + public static final GaussDBEnumJdbcType INSTANCE = new GaussDBEnumJdbcType(); + + @Override + public int getJdbcTypeCode() { + return OTHER; + } + + @Override + public int getDefaultSqlTypeCode() { + return NAMED_ENUM; + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaType) { + @SuppressWarnings("unchecked") + final Class> enumClass = (Class>) javaType.getJavaType(); + return (appender, value, dialect, wrapperOptions) -> { + appender.appendSql( "'" ); + appender.appendSql( ((Enum) value).name() ); + appender.appendSql( "'::" ); + appender.appendSql( dialect.getEnumTypeDeclaration( enumClass ) ); + }; + } + + @Override + public String getFriendlyName() { + return "ENUM"; + } + + @Override + public String toString() { + return "EnumTypeDescriptor"; + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException { + st.setNull( index, Types.OTHER ); + } + + @Override + protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException { + st.setNull( name, Types.OTHER ); + } + + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setObject( index, getJavaType().unwrap( value, String.class, options ), Types.OTHER ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setObject( name, getJavaType().unwrap( value, String.class, options ), Types.OTHER ); + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new BasicExtractor<>( javaType, this ) { + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return getJavaType().wrap( rs.getObject( paramIndex ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return getJavaType().wrap( statement.getObject( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return getJavaType().wrap( statement.getObject( name ), options ); + } + }; + } + + @Override + public void addAuxiliaryDatabaseObjects( + JavaType javaType, + BasicValueConverter valueConverter, + Size columnSize, + Database database, + JdbcTypeIndicators context) { + @SuppressWarnings("unchecked") + final Class> enumClass = (Class>) javaType.getJavaType(); + @SuppressWarnings("unchecked") + final String[] enumeratedValues = + valueConverter == null + ? getEnumeratedValues( enumClass ) + : getEnumeratedValues( enumClass, (BasicValueConverter,?>) valueConverter ) ; + if ( getDefaultSqlTypeCode() == NAMED_ENUM ) { + Arrays.sort( enumeratedValues ); + } + final Dialect dialect = database.getDialect(); + final String[] create = + dialect.getCreateEnumTypeCommand( javaType.getJavaTypeClass().getSimpleName(), enumeratedValues ); + final String[] drop = dialect.getDropEnumTypeCommand( enumClass ); + if ( create != null && create.length > 0 ) { + database.addAuxiliaryDatabaseObject( + new NamedAuxiliaryDatabaseObject( + enumClass.getSimpleName(), + database.getDefaultNamespace(), + create, + drop, + emptySet(), + true + ) + ); + } + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBFunctionRegistry.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBFunctionRegistry.java new file mode 100644 index 000000000000..434fe93f9b04 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBFunctionRegistry.java @@ -0,0 +1,186 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.community.dialect.function.GaussDBMinMaxFunction; +import org.hibernate.community.dialect.function.GaussDBTruncFunction; +import org.hibernate.community.dialect.function.GaussDBTruncRoundFunction; +import org.hibernate.community.dialect.function.array.GaussDBArrayConcatElementFunction; +import org.hibernate.community.dialect.function.array.GaussDBArrayConcatFunction; +import org.hibernate.community.dialect.function.array.GaussDBArrayConstructorFunction; +import org.hibernate.community.dialect.function.array.GaussDBArrayContainsOperatorFunction; +import org.hibernate.community.dialect.function.array.GaussDBArrayFillFunction; +import org.hibernate.community.dialect.function.array.GaussDBArrayRemoveFunction; +import org.hibernate.community.dialect.function.array.GaussDBArrayRemoveIndexFunction; +import org.hibernate.community.dialect.function.array.GaussDBArrayReplaceFunction; +import org.hibernate.community.dialect.function.array.GaussDBArraySetFunction; +import org.hibernate.community.dialect.function.json.GaussDBJsonObjectFunction; +import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.function.array.ArrayIncludesOperatorFunction; +import org.hibernate.dialect.function.array.ArrayIntersectsOperatorFunction; +import org.hibernate.query.sqm.function.SqmFunctionRegistry; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * GaussDB functions register. + * + * @author liubao + */ +public class GaussDBFunctionRegistry { + private final FunctionContributions functionContributions; + + private final SqmFunctionRegistry functionRegistry; + + private final TypeConfiguration typeConfiguration; + + public GaussDBFunctionRegistry(FunctionContributions functionContributions) { + this.functionContributions = functionContributions; + this.functionRegistry = functionContributions.getFunctionRegistry(); + this.typeConfiguration = functionContributions.getTypeConfiguration(); + } + + public void register() { + CommonFunctionFactory functionFactory = new CommonFunctionFactory( functionContributions); + functionFactory.cot(); + functionFactory.radians(); + functionFactory.degrees(); + functionFactory.log(); + functionFactory.mod_operator(); + functionFactory.moreHyperbolic(); + functionFactory.cbrt(); + functionFactory.pi(); + functionFactory.log10_log(); + functionFactory.trim2(); + functionFactory.repeat(); + functionFactory.initcap(); + functionFactory.substr(); + functionFactory.substring_substr(); + //also natively supports ANSI-style substring() + functionFactory.translate(); + functionFactory.toCharNumberDateTimestamp(); + functionFactory.localtimeLocaltimestamp(); + functionFactory.bitLength_pattern( "bit_length(?1)", "length(?1)*8" ); + functionFactory.octetLength_pattern( "octet_length(?1)", "length(?1)" ); + functionFactory.ascii(); + functionFactory.char_chr(); + functionFactory.position(); + functionFactory.bitandorxornot_operator(); + functionFactory.bitAndOr(); + functionFactory.everyAny_boolAndOr(); + functionFactory.median_percentileCont( false ); + functionFactory.stddev(); + functionFactory.stddevPopSamp(); + functionFactory.variance(); + functionFactory.varPopSamp(); + functionFactory.covarPopSamp(); + functionFactory.corr(); + functionFactory.regrLinearRegressionAggregates(); + functionFactory.insert_overlay(); + functionFactory.overlay(); + functionFactory.soundex(); //was introduced apparently + functionFactory.locate_positionSubstring(); + functionFactory.windowFunctions(); + functionFactory.listagg_stringAgg( "varchar" ); + functionFactory.arrayAggregate(); + functionFactory.arraySlice_operator(); + functionFactory.makeDateTimeTimestamp(); + // Note that GaussDB doesn't support the OVER clause for ordered set-aggregate functions + functionFactory.inverseDistributionOrderedSetAggregates(); + functionFactory.hypotheticalOrderedSetAggregates(); + functionFactory.dateTrunc(); + functionFactory.hex( "encode(?1, 'hex')" ); + functionFactory.sha( "sha256(?1)" ); + functionFactory.md5( "decode(md5(?1), 'hex')" ); + functionFactory.format_toChar(); + + functionContributions.getFunctionRegistry().register( "min", new GaussDBMinMaxFunction( "min" ) ); + functionContributions.getFunctionRegistry().register( "max", new GaussDBMinMaxFunction( "max" ) ); + + // uses # instead of ^ for XOR + functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1 # ?2)" ) + .setExactArgumentCount( 2 ) + .setArgumentTypeResolver( StandardFunctionArgumentTypeResolvers.ARGUMENT_OR_IMPLIED_RESULT_TYPE ) + .register(); + + functionContributions.getFunctionRegistry().register( + "round", new GaussDBTruncRoundFunction( "round", true ) + ); + functionContributions.getFunctionRegistry().register( + "trunc", + new GaussDBTruncFunction( true, functionContributions.getTypeConfiguration() ) + ); + functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); + + array_gaussdb(); + arrayRemoveIndex_gaussdb(); + arrayConcat_gaussdb(); + arrayPrepend_gaussdb(); + arrayAppend_gaussdb(); + arrayContains_gaussdb(); + arrayIntersects_gaussdb(); + arrayRemove_gaussdb(); + arrayReplace_gaussdb(); + arraySet_gaussdb(); + arrayFill_gaussdb(); + jsonObject_gaussdb(); + } + + public void array_gaussdb() { + functionRegistry.register( "array", new GaussDBArrayConstructorFunction( false ) ); + functionRegistry.register( "array_list", new GaussDBArrayConstructorFunction( true ) ); + } + + public void arrayContains_gaussdb() { + functionRegistry.register( "array_contains_nullable", new GaussDBArrayContainsOperatorFunction( true, typeConfiguration ) ); + functionRegistry.register( "array_includes", new ArrayIncludesOperatorFunction( false, typeConfiguration ) ); + functionRegistry.register( "array_includes_nullable", new ArrayIncludesOperatorFunction( true, typeConfiguration ) ); + } + + public void arrayIntersects_gaussdb() { + functionRegistry.register( "array_intersects", new ArrayIntersectsOperatorFunction( false, typeConfiguration ) ); + functionRegistry.register( "array_intersects_nullable", new ArrayIntersectsOperatorFunction( true, typeConfiguration ) ); + functionRegistry.registerAlternateKey( "array_overlaps", "array_intersects" ); + functionRegistry.registerAlternateKey( "array_overlaps_nullable", "array_intersects_nullable" ); + } + + public void arrayConcat_gaussdb() { + functionRegistry.register( "array_concat", new GaussDBArrayConcatFunction() ); + } + + public void arrayPrepend_gaussdb() { + functionRegistry.register( "array_prepend", new GaussDBArrayConcatElementFunction( true ) ); + } + + public void arrayAppend_gaussdb() { + functionRegistry.register( "array_append", new GaussDBArrayConcatElementFunction( false ) ); + } + + public void arraySet_gaussdb() { + functionRegistry.register( "array_set", new GaussDBArraySetFunction() ); + } + + public void arrayRemove_gaussdb() { + functionRegistry.register( "array_remove", new GaussDBArrayRemoveFunction()); + } + + public void arrayRemoveIndex_gaussdb() { + functionRegistry.register( "array_remove_index", new GaussDBArrayRemoveIndexFunction( false) ); + } + + public void arrayReplace_gaussdb() { + functionRegistry.register( "array_replace", new GaussDBArrayReplaceFunction() ); + } + + public void arrayFill_gaussdb() { + functionRegistry.register( "array_fill", new GaussDBArrayFillFunction( false ) ); + functionRegistry.register( "array_fill_list", new GaussDBArrayFillFunction( true ) ); + } + + public void jsonObject_gaussdb() { + functionRegistry.register( "json_object", new GaussDBJsonObjectFunction( typeConfiguration ) ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBOrdinalEnumJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBOrdinalEnumJdbcType.java new file mode 100644 index 000000000000..1e91a01d9bfd --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBOrdinalEnumJdbcType.java @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import jakarta.persistence.EnumType; + +import static org.hibernate.type.SqlTypes.NAMED_ORDINAL_ENUM; + +/** + * Represents a named {@code enum} type on GaussDB. + *

+ * Hibernate does not automatically use this for enums + * mapped as {@link EnumType#ORDINAL}, and + * instead this type must be explicitly requested using: + *

+ * @JdbcTypeCode(SqlTypes.NAMED_ORDINAL_ENUM)
+ * 
+ * + * @see org.hibernate.type.SqlTypes#NAMED_ORDINAL_ENUM + * @see GaussDBDialect#getEnumTypeDeclaration(String, String[]) + * @see GaussDBDialect#getCreateEnumTypeCommand(String, String[]) + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLOrdinalEnumJdbcType. + */ +public class GaussDBOrdinalEnumJdbcType extends GaussDBEnumJdbcType { + + public static final GaussDBOrdinalEnumJdbcType INSTANCE = new GaussDBOrdinalEnumJdbcType(); + + @Override + public int getDefaultSqlTypeCode() { + return NAMED_ORDINAL_ENUM; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBSqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBSqlAstTranslator.java new file mode 100644 index 000000000000..81c439d66c1e --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBSqlAstTranslator.java @@ -0,0 +1,309 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; +import org.hibernate.query.sqm.ComparisonOperator; +import org.hibernate.query.common.FetchClauseType; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.spi.SqlAstTranslatorWithMerge; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteMaterialization; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.sql.ast.tree.expression.Summarization; +import org.hibernate.sql.ast.tree.from.NamedTableReference; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.insert.ConflictClause; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate; +import org.hibernate.sql.ast.tree.predicate.InArrayPredicate; +import org.hibernate.sql.ast.tree.predicate.LikePredicate; +import org.hibernate.sql.ast.tree.predicate.NullnessPredicate; +import org.hibernate.sql.ast.tree.select.QueryGroup; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl; +import org.hibernate.sql.exec.spi.JdbcOperation; +import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert; +import org.hibernate.sql.model.internal.TableInsertStandard; +import org.hibernate.type.SqlTypes; + +/** + * A SQL AST translator for GaussDB. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLSqlAstTranslator. + */ +public class GaussDBSqlAstTranslator extends SqlAstTranslatorWithMerge { + + public GaussDBSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + public void visitInArrayPredicate(InArrayPredicate inArrayPredicate) { + inArrayPredicate.getTestExpression().accept( this ); + appendSql( " = any (" ); + inArrayPredicate.getArrayParameter().accept( this ); + appendSql( ")" ); + } + + @Override + protected String getArrayContainsFunction() { + return super.getArrayContainsFunction(); + } + + @Override + protected void renderInsertIntoNoColumns(TableInsertStandard tableInsert) { + renderIntoIntoAndTable( tableInsert ); + appendSql( "default values" ); + } + + @Override + protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) { + visitInsertStatement( sqlAst ); + + return new JdbcOperationQueryInsertImpl( + getSql(), + getParameterBinders(), + getAffectedTableNames(), + null + ); + } + + @Override + protected void renderTableReferenceIdentificationVariable(TableReference tableReference) { + final String identificationVariable = tableReference.getIdentificationVariable(); + if ( identificationVariable != null ) { + final Clause currentClause = getClauseStack().getCurrent(); + if ( currentClause == Clause.INSERT ) { + // GaussDB requires the "as" keyword for inserts + appendSql( " as " ); + } + else { + append( WHITESPACE ); + } + append( tableReference.getIdentificationVariable() ); + } + } + + @Override + protected void renderDmlTargetTableExpression(NamedTableReference tableReference) { + super.renderDmlTargetTableExpression( tableReference ); + final Statement currentStatement = getStatementStack().getCurrent(); + if ( !( currentStatement instanceof UpdateStatement updateStatement ) + || !hasNonTrivialFromClause( updateStatement.getFromClause() ) ) { + // For UPDATE statements we render a full FROM clause and a join condition to match target table rows, + // but for that to work, we have to omit the alias for the target table reference here + renderTableReferenceIdentificationVariable( tableReference ); + } + } + + @Override + protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) { + renderFromClauseJoiningDmlTargetReference( statement ); + } + + @Override + protected void visitConflictClause(ConflictClause conflictClause) { + visitOnDuplicateKeyConflictClauseWithDoNothing( conflictClause ); + } + + @Override + protected void renderExpressionAsClauseItem(Expression expression) { + expression.accept( this ); + } + + @Override + protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) { + final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType(); + if ( lhsExpressionType != null && lhsExpressionType.getJdbcTypeCount() == 1 + && lhsExpressionType.getSingleJdbcMapping().getJdbcType().getDdlTypeCode() == SqlTypes.SQLXML ) { + // In GaussDB, XMLTYPE is not "comparable", so we have to cast the two parts to varchar for this purpose + switch ( operator ) { + case EQUAL: + case NOT_DISTINCT_FROM: + case NOT_EQUAL: + case DISTINCT_FROM: + appendSql( "cast(" ); + lhs.accept( this ); + appendSql( " as text)" ); + appendSql( operator.sqlText() ); + appendSql( "cast(" ); + rhs.accept( this ); + appendSql( " as text)" ); + return; + default: + // Fall through + break; + } + } + renderComparisonStandard( lhs, operator, rhs ); + } + + @Override + public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanExpressionPredicate) { + final boolean isNegated = booleanExpressionPredicate.isNegated(); + if ( isNegated ) { + appendSql( "not(" ); + } + booleanExpressionPredicate.getExpression().accept( this ); + if ( isNegated ) { + appendSql( CLOSE_PARENTHESIS ); + } + } + + @Override + public void visitNullnessPredicate(NullnessPredicate nullnessPredicate) { + final Expression expression = nullnessPredicate.getExpression(); + final JdbcMappingContainer expressionType = expression.getExpressionType(); + if ( isStruct( expressionType ) ) { + // Surprise, the null predicate checks if all components of the struct are null or not, + // rather than the column itself, so we have to use the distinct from predicate to implement this instead + expression.accept( this ); + if ( nullnessPredicate.isNegated() ) { + appendSql( " is distinct from null" ); + } + else { + appendSql( " is not distinct from null" ); + } + } + else { + super.visitNullnessPredicate( nullnessPredicate ); + } + } + + @Override + protected void renderMaterializationHint(CteMaterialization materialization) { + if ( materialization == CteMaterialization.NOT_MATERIALIZED ) { + appendSql( "not " ); + } + appendSql( "materialized " ); + } + + @Override + protected String getForUpdate() { + return getDialect().getForUpdateString(); + } + + @Override + protected String getForShare(int timeoutMillis) { + // Note that `for key share` is inappropriate as that only means "prevent PK changes" + return " for share"; + } + + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { + // Check if current query part is already row numbering to avoid infinite recursion + if ( getQueryPartForRowNumbering() == queryPart || isRowsOnlyFetchClauseType( queryPart ) ) { + return false; + } + return !getDialect().supportsFetchClause( queryPart.getFetchClauseType() ); + } + + @Override + public void visitQueryGroup(QueryGroup queryGroup) { + if ( shouldEmulateFetchClause( queryGroup ) ) { + emulateFetchOffsetWithWindowFunctions( queryGroup, true ); + } + else { + super.visitQueryGroup( queryGroup ); + } + } + + @Override + public void visitQuerySpec(QuerySpec querySpec) { + if ( shouldEmulateFetchClause( querySpec ) ) { + emulateFetchOffsetWithWindowFunctions( querySpec, true ); + } + else { + super.visitQuerySpec( querySpec ); + } + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + if ( !isRowNumberingCurrentQueryPart() ) { + if ( getDialect().supportsFetchClause( FetchClauseType.ROWS_ONLY ) ) { + renderOffsetFetchClause( queryPart, true ); + } + else { + renderLimitOffsetClause( queryPart ); + } + } + } + + @Override + protected void renderStandardCycleClause(CteStatement cte) { + super.renderStandardCycleClause( cte ); + if ( cte.getCycleMarkColumn() != null && cte.getCyclePathColumn() == null && getDialect().supportsRecursiveCycleUsingClause() ) { + appendSql( " using " ); + appendSql( determineCyclePathColumnName( cte ) ); + } + } + + @Override + protected void renderPartitionItem(Expression expression) { + // We render an empty group instead of literals as some DBs don't support grouping by literals + // Note that integer literals, which refer to select item positions, are handled in #visitGroupByClause + if ( expression instanceof Literal ) { + appendSql( "()" ); + } + else if ( expression instanceof Summarization summarization ) { + appendSql( summarization.getKind().sqlText() ); + appendSql( OPEN_PARENTHESIS ); + renderCommaSeparated( summarization.getGroupings() ); + appendSql( CLOSE_PARENTHESIS ); + } + else { + expression.accept( this ); + } + } + + @Override + public void visitLikePredicate(LikePredicate likePredicate) { + // We need a custom implementation here because GaussDB + // uses the backslash character as default escape character + // According to the documentation, we can overcome this by specifying an empty escape character + // See https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE + likePredicate.getMatchExpression().accept( this ); + if ( likePredicate.isNegated() ) { + appendSql( " not" ); + } + if ( likePredicate.isCaseSensitive() ) { + appendSql( " like " ); + } + else { + appendSql( WHITESPACE ); + appendSql( getDialect().getCaseInsensitiveLike() ); + appendSql( WHITESPACE ); + } + likePredicate.getPattern().accept( this ); + if ( likePredicate.getEscapeCharacter() != null ) { + appendSql( " escape " ); + likePredicate.getEscapeCharacter().accept( this ); + } + else { + appendSql( " escape ''''" ); + } + } + + @Override + public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) { + if ( isIntegerDivisionEmulationRequired( arithmeticExpression ) ) { + appendSql( "floor" ); + } + appendSql( OPEN_PARENTHESIS ); + visitArithmeticOperand( arithmeticExpression.getLeftHandOperand() ); + appendSql( arithmeticExpression.getOperator().getOperatorSqlTextString() ); + visitArithmeticOperand( arithmeticExpression.getRightHandOperand() ); + appendSql( CLOSE_PARENTHESIS ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBStructuredJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBStructuredJdbcType.java new file mode 100644 index 000000000000..5fc201fb86c8 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBStructuredJdbcType.java @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.type.AbstractPostgreSQLStructJdbcType; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; + +/** + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLStructCastingJdbcType. + */ +public class GaussDBStructuredJdbcType extends AbstractPostgreSQLStructJdbcType { + + public static final GaussDBStructuredJdbcType INSTANCE = new GaussDBStructuredJdbcType(); + public GaussDBStructuredJdbcType() { + this( null, null, null ); + } + + private GaussDBStructuredJdbcType( + EmbeddableMappingType embeddableMappingType, + String typeName, + int[] orderMapping) { + super( embeddableMappingType, typeName, orderMapping ); + } + + @Override + public AggregateJdbcType resolveAggregateJdbcType( + EmbeddableMappingType mappingType, + String sqlType, + RuntimeModelCreationContext creationContext) { + return new GaussDBStructuredJdbcType( + mappingType, + sqlType, + creationContext.getBootModel() + .getDatabase() + .getDefaultNamespace() + .locateUserDefinedType( Identifier.toIdentifier( sqlType ) ) + .getOrderMapping() + ); + } + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( "cast(" ); + appender.append( writeExpression ); + appender.append( " as " ); + appender.append( getStructTypeName() ); + appender.append( ')' ); + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final String stringValue = ( (GaussDBStructuredJdbcType) getJdbcType() ).toString( + value, + getJavaType(), + options + ); + st.setString( index, stringValue ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final String stringValue = ( (GaussDBStructuredJdbcType) getJdbcType() ).toString( + value, + getJavaType(), + options + ); + st.setString( name, stringValue ); + } + + @Override + public Object getBindValue(X value, WrapperOptions options) throws SQLException { + return ( (GaussDBStructuredJdbcType) getJdbcType() ).getBindValue( value, options ); + } + }; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBUUIDJdbcType.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBUUIDJdbcType.java new file mode 100644 index 000000000000..d3ab6393212d --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/GaussDBUUIDJdbcType.java @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.UUID; + +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; + +/** + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLUUIDJdbcType. + */ +public class GaussDBUUIDJdbcType extends UUIDJdbcType { + + /** + * Singleton access + */ + public static final GaussDBUUIDJdbcType INSTANCE = new GaussDBUUIDJdbcType(); + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException { + st.setNull( index, getJdbcType().getJdbcTypeCode(), "uuid" ); + } + + @Override + protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException { + st.setNull( name, getJdbcType().getJdbcTypeCode(), "uuid" ); + } + + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setObject( index, getJavaType().unwrap( value, UUID.class, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setObject( name, getJavaType().unwrap( value, UUID.class, options ) ); + } + }; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/GaussDBMinMaxFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/GaussDBMinMaxFunction.java new file mode 100644 index 000000000000..769aa7aad61b --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/GaussDBMinMaxFunction.java @@ -0,0 +1,116 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function; + +import java.util.List; + +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.function.FunctionKind; +import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.type.SqlTypes; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.COMPARABLE; + +/** + * GaussDB doesn't support min/max for uuid yet, + * but since that type is comparable we want to support this operation. + * The workaround is to cast uuid to text and aggregate that, which preserves the ordering, + * and finally cast the result back to uuid. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLDialect. + */ +public class GaussDBMinMaxFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public GaussDBMinMaxFunction(String name) { + super( + name, + FunctionKind.AGGREGATE, + new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 1 ), COMPARABLE ), + StandardFunctionReturnTypeResolvers.useFirstNonNull(), + StandardFunctionArgumentTypeResolvers.IMPLIED_RESULT_TYPE + ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + render( sqlAppender, sqlAstArguments, null, returnType, walker ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + Predicate filter, + ReturnableType returnType, + SqlAstTranslator translator) { + final boolean caseWrapper = filter != null && !translator.getSessionFactory().getJdbcServices().getDialect().supportsFilterClause(); + sqlAppender.appendSql( getName() ); + sqlAppender.appendSql( '(' ); + final Expression arg = (Expression) sqlAstArguments.get( 0 ); + final String castTarget; + if ( caseWrapper ) { + translator.getCurrentClauseStack().push( Clause.WHERE ); + sqlAppender.appendSql( "case when " ); + filter.accept( translator ); + translator.getCurrentClauseStack().pop(); + sqlAppender.appendSql( " then " ); + castTarget = renderArgument( sqlAppender, translator, arg ); + sqlAppender.appendSql( " else null end)" ); + } + else { + castTarget = renderArgument( sqlAppender, translator, arg ); + sqlAppender.appendSql( ')' ); + if ( filter != null ) { + translator.getCurrentClauseStack().push( Clause.WHERE ); + sqlAppender.appendSql( " filter (where " ); + filter.accept( translator ); + sqlAppender.appendSql( ')' ); + translator.getCurrentClauseStack().pop(); + } + } + if ( castTarget != null ) { + sqlAppender.appendSql( "::" ); + sqlAppender.appendSql( castTarget ); + } + } + + private String renderArgument(SqlAppender sqlAppender, SqlAstTranslator translator, Expression arg) { + final JdbcMapping sourceMapping = arg.getExpressionType().getSingleJdbcMapping(); + // Cast uuid expressions to "text" first, aggregate that, and finally cast to uuid again + if ( sourceMapping.getJdbcType().getDefaultSqlTypeCode() == SqlTypes.UUID ) { + sqlAppender.appendSql( "cast(" ); + arg.accept( translator ); + sqlAppender.appendSql( " as text)" ); + return "uuid"; + } + else { + arg.accept( translator ); + return null; + } + } + + @Override + public String getArgumentListSignature() { + return "(COMPARABLE arg)"; + } + +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/GaussDBTruncFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/GaussDBTruncFunction.java new file mode 100644 index 000000000000..30387e51fce3 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/GaussDBTruncFunction.java @@ -0,0 +1,74 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.dialect.function.TruncFunction; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; +import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; +import org.hibernate.type.spi.TypeConfiguration; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL_UNIT; + +/** + * Custom {@link TruncFunction} for GaussDB which uses the dialect-specific function for numeric truncation + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLTruncFunction. + */ +public class GaussDBTruncFunction extends TruncFunction { + private final GaussDBTruncRoundFunction gaussDBTruncRoundFunction; + + public GaussDBTruncFunction(boolean supportsTwoArguments, TypeConfiguration typeConfiguration) { + super( + "trunc(?1)", + null, + DatetimeTrunc.DATE_TRUNC, + null, + typeConfiguration + ); + this.gaussDBTruncRoundFunction = new GaussDBTruncRoundFunction( "trunc", supportsTwoArguments ); + } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine) { + final List> args = new ArrayList<>( arguments ); + if ( arguments.size() != 2 || !( arguments.get( 1 ) instanceof SqmExtractUnit ) ) { + // numeric truncation + return gaussDBTruncRoundFunction.generateSqmFunctionExpression( + arguments, + impliedResultType, + queryEngine + ); + } + // datetime truncation + return new SelfRenderingSqmFunction<>( + this, + datetimeRenderingSupport, + args, + impliedResultType, + new ArgumentTypesValidator( + StandardArgumentsValidators.exactly( 2 ), + TEMPORAL, + TEMPORAL_UNIT + ), + getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + getName() + ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/GaussDBTruncRoundFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/GaussDBTruncRoundFunction.java new file mode 100644 index 000000000000..458480ccec2b --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/GaussDBTruncRoundFunction.java @@ -0,0 +1,124 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function; + +import java.util.List; + +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor; +import org.hibernate.query.sqm.function.FunctionRenderer; +import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; +import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC; + +/** + * GaussDB only supports the two-argument {@code trunc} and {@code round} functions + * with the following signatures: + *
    + *
  • {@code trunc(numeric, integer)}
  • + *
  • {@code round(numeric, integer)}
  • + *
+ *

+ * This custom function falls back to using {@code floor} as a workaround only when necessary, + * e.g. when there are 2 arguments to the function and either: + *

    + *
  • The first argument is not of type {@code numeric}
  • + * or + *
  • The dialect doesn't support the two-argument {@code trunc} function
  • + *
+ * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLTruncRoundFunction. + */ +public class GaussDBTruncRoundFunction extends AbstractSqmFunctionDescriptor implements FunctionRenderer { + private final boolean supportsTwoArguments; + + public GaussDBTruncRoundFunction(String name, boolean supportsTwoArguments) { + super( + name, + new ArgumentTypesValidator( StandardArgumentsValidators.between( 1, 2 ), NUMERIC, INTEGER ), + StandardFunctionReturnTypeResolvers.useArgType( 1 ), + StandardFunctionArgumentTypeResolvers.invariant( NUMERIC, INTEGER ) + ); + this.supportsTwoArguments = supportsTwoArguments; + } + + @Override + public void render( + SqlAppender sqlAppender, + List arguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final int numberOfArguments = arguments.size(); + final Expression firstArg = (Expression) arguments.get( 0 ); + final JdbcType jdbcType = firstArg.getExpressionType().getSingleJdbcMapping().getJdbcType(); + if ( numberOfArguments == 1 || supportsTwoArguments && jdbcType.isDecimal() ) { + // use native two-argument function + sqlAppender.appendSql( getName() ); + sqlAppender.appendSql( "(" ); + firstArg.accept( walker ); + if ( numberOfArguments > 1 ) { + sqlAppender.appendSql( ", " ); + arguments.get( 1 ).accept( walker ); + } + sqlAppender.appendSql( ")" ); + } + else { + // workaround using floor + if ( getName().equals( "trunc" ) ) { + sqlAppender.appendSql( "sign(" ); + firstArg.accept( walker ); + sqlAppender.appendSql( ")*floor(abs(" ); + firstArg.accept( walker ); + sqlAppender.appendSql( ")*1e" ); + arguments.get( 1 ).accept( walker ); + } + else { + sqlAppender.appendSql( "floor(" ); + firstArg.accept( walker ); + sqlAppender.appendSql( "*1e" ); + arguments.get( 1 ).accept( walker ); + sqlAppender.appendSql( "+0.5" ); + } + sqlAppender.appendSql( ")/1e" ); + arguments.get( 1 ).accept( walker ); + } + } + + @Override + public String getArgumentListSignature() { + return "(NUMERIC number[, INTEGER places])"; + } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine) { + return new SelfRenderingSqmFunction<>( + this, + this, + arguments, + impliedResultType, + getArgumentsValidator(), + getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + getName() + ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayConcatElementFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayConcatElementFunction.java new file mode 100644 index 000000000000..e851034d73fb --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayConcatElementFunction.java @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.array; + +import java.util.List; + +import org.hibernate.dialect.function.array.ArrayConcatElementFunction; +import org.hibernate.dialect.function.array.DdlTypeHelper; +import org.hibernate.engine.jdbc.Size; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; +import org.hibernate.metamodel.mapping.SqlTypedMapping; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.type.BasicPluralType; + +/** + * GaussDB variant of the function to properly return {@code null} when the array argument is null. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLArrayConcatElementFunction. + */ +public class GaussDBArrayConcatElementFunction extends ArrayConcatElementFunction { + + public GaussDBArrayConcatElementFunction(boolean prepend) { + super( "", "||", "", prepend ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression firstArgument = (Expression) sqlAstArguments.get( 0 ); + final Expression secondArgument = (Expression) sqlAstArguments.get( 1 ); + final Expression arrayArgument; + final Expression elementArgument; + if ( prepend ) { + elementArgument = firstArgument; + arrayArgument = secondArgument; + } + else { + arrayArgument = firstArgument; + elementArgument = secondArgument; + } + final String elementCastType; + if ( needsElementCasting( elementArgument ) ) { + final JdbcMappingContainer arrayType = arrayArgument.getExpressionType(); + final Size size = arrayType instanceof SqlTypedMapping ? ( (SqlTypedMapping) arrayType ).toSize() : null; + elementCastType = DdlTypeHelper.getCastTypeName( + ( (BasicPluralType) returnType ).getElementType(), + size, + walker.getSessionFactory().getTypeConfiguration() + ); + } + else { + elementCastType = null; + } + sqlAppender.append( "case when " ); + walker.render( arrayArgument, SqlAstNodeRenderingMode.DEFAULT ); + sqlAppender.append( " is not null then " ); + if ( prepend && elementCastType != null) { + sqlAppender.append( "cast(" ); + walker.render( firstArgument, SqlAstNodeRenderingMode.DEFAULT ); + sqlAppender.append( " as " ); + sqlAppender.append( elementCastType ); + sqlAppender.append( ')' ); + } + else { + walker.render( firstArgument, SqlAstNodeRenderingMode.DEFAULT ); + } + sqlAppender.append( "||" ); + if ( !prepend && elementCastType != null) { + sqlAppender.append( "cast(" ); + walker.render( secondArgument, SqlAstNodeRenderingMode.DEFAULT ); + sqlAppender.append( " as " ); + sqlAppender.append( elementCastType ); + sqlAppender.append( ')' ); + } + else { + walker.render( secondArgument, SqlAstNodeRenderingMode.DEFAULT ); + } + sqlAppender.append( " end" ); + } + + private static boolean needsElementCasting(Expression elementExpression) { + // GaussDB needs casting of null and string literal expressions + return elementExpression instanceof Literal && ( + elementExpression.getExpressionType().getSingleJdbcMapping().getJdbcType().isString() + || ( (Literal) elementExpression ).getLiteralValue() == null + ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayConcatFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayConcatFunction.java new file mode 100644 index 000000000000..7ee92bf9802c --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayConcatFunction.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.array; + +import java.util.List; + +import org.hibernate.dialect.function.array.ArrayConcatFunction; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; + +/** + * GaussDB variant of the function to properly return {@code null} when one of the arguments is null. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLArrayConcatFunction. + */ +public class GaussDBArrayConcatFunction extends ArrayConcatFunction { + + public GaussDBArrayConcatFunction() { + super( "", "||", "" ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + sqlAppender.append( "case when " ); + String separator = ""; + for ( SqlAstNode node : sqlAstArguments ) { + sqlAppender.append( separator ); + node.accept( walker ); + sqlAppender.append( " is not null" ); + separator = " and "; + } + + sqlAppender.append( " then " ); + super.render( sqlAppender, sqlAstArguments, returnType, walker ); + sqlAppender.append( " end" ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayConstructorFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayConstructorFunction.java new file mode 100644 index 000000000000..0e3632371485 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayConstructorFunction.java @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.array; + +import java.util.List; + +import org.hibernate.dialect.function.array.ArrayConstructorFunction; +import org.hibernate.dialect.function.array.DdlTypeHelper; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.BasicPluralType; +import org.hibernate.type.BasicType; + +/** + * Special array constructor function that also applies a cast to the array literal, + * based on the inferred result type. GaussDB needs this, + * because by default it assumes a {@code text[]}, which is not compatible with {@code varchar[]}. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLArrayConstructorFunction. + */ +public class GaussDBArrayConstructorFunction extends ArrayConstructorFunction { + + public GaussDBArrayConstructorFunction(boolean list) { + super( list, true ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + String arrayTypeName = null; + if ( returnType instanceof BasicPluralType pluralType ) { + if ( needsArrayCasting( pluralType.getElementType() ) ) { + arrayTypeName = DdlTypeHelper.getCastTypeName( + returnType, + walker.getSessionFactory().getTypeConfiguration() + ); + sqlAppender.append( "cast(" ); + } + } + super.render( sqlAppender, sqlAstArguments, returnType, walker ); + if ( arrayTypeName != null ) { + sqlAppender.appendSql( " as " ); + sqlAppender.appendSql( arrayTypeName ); + sqlAppender.appendSql( ')' ); + } + } + + private static boolean needsArrayCasting(BasicType elementType) { + // GaussDB doesn't do implicit conversion between text[] and varchar[], so we need casting + return elementType.getJdbcType().isString(); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayContainsOperatorFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayContainsOperatorFunction.java new file mode 100644 index 000000000000..02469114a03e --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayContainsOperatorFunction.java @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.array; + +import org.hibernate.dialect.function.array.ArrayContainsUnnestFunction; +import org.hibernate.dialect.function.array.DdlTypeHelper; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.type.BasicPluralType; +import org.hibernate.type.spi.TypeConfiguration; + +import java.util.List; + +/** + * Special array contains function that also applies a cast to the element argument. PostgreSQL needs this, + * because by default it assumes a {@code text[]}, which is not compatible with {@code varchar[]}. + * @author chenzhida + * + * Notes: Original code of this class is based on ArrayContainsOperatorFunction. + * ArrayContainsOperatorFunction is only used by PostgreSQL and has some different with GaussDB, Maybe + * it's better to have a name with PostgreSQLArrayContainsOperatorFunction + */ +public class GaussDBArrayContainsOperatorFunction extends ArrayContainsUnnestFunction { + + public GaussDBArrayContainsOperatorFunction(boolean nullable, TypeConfiguration typeConfiguration) { + super( nullable, typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression haystackExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression needleExpression = (Expression) sqlAstArguments.get( 1 ); + final JdbcMappingContainer needleTypeContainer = needleExpression.getExpressionType(); + final JdbcMapping needleType = needleTypeContainer == null ? null : needleTypeContainer.getSingleJdbcMapping(); + if ( needleType == null || needleType instanceof BasicPluralType ) { + LOG.deprecatedArrayContainsWithArray(); + if ( nullable ) { + super.render( sqlAppender, sqlAstArguments, returnType, walker ); + } + else { + haystackExpression.accept( walker ); + sqlAppender.append( "@>" ); + needleExpression.accept( walker ); + } + } + else { + if ( nullable ) { + sqlAppender.append( "(array_positions(" ); + haystackExpression.accept( walker ); + sqlAppender.append( ',' ); + needleExpression.accept( walker ); + sqlAppender.append( "))[1] is not null" ); + } + else { + haystackExpression.accept( walker ); + sqlAppender.append( "@>" ); + if ( needsArrayCasting( needleExpression ) ) { + sqlAppender.append( "cast(array[" ); + needleExpression.accept( walker ); + sqlAppender.append( "] as " ); + sqlAppender.append( DdlTypeHelper.getCastTypeName( + haystackExpression.getExpressionType(), + walker.getSessionFactory().getTypeConfiguration() + ) ); + sqlAppender.append( ')' ); + } + else { + sqlAppender.append( "array[" ); + needleExpression.accept( walker ); + sqlAppender.append( ']' ); + } + } + } + } + + private static boolean needsArrayCasting(Expression elementExpression) { + // Gauss doesn't do implicit conversion between text[] and varchar[], so we need casting + return elementExpression.getExpressionType().getSingleJdbcMapping().getJdbcType().isString(); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayFillFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayFillFunction.java new file mode 100644 index 000000000000..3fc80659253e --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayFillFunction.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.array; + +import java.util.List; + +import org.hibernate.dialect.function.array.AbstractArrayFillFunction; +import org.hibernate.dialect.function.array.DdlTypeHelper; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; + +/** + * Custom casting for the array fill function. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLArrayFillFunction. + */ +public class GaussDBArrayFillFunction extends AbstractArrayFillFunction { + + public GaussDBArrayFillFunction(boolean list) { + super( list ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + sqlAppender.append( "array_fill(" ); + final String elementCastType; + final Expression elementExpression = (Expression) sqlAstArguments.get( 0 ); + if ( needsElementCasting( elementExpression ) ) { + elementCastType = DdlTypeHelper.getCastTypeName( + elementExpression.getExpressionType(), + walker.getSessionFactory().getTypeConfiguration() + ); + sqlAppender.append( "cast(" ); + } + else { + elementCastType = null; + } + sqlAstArguments.get( 0 ).accept( walker ); + if ( elementCastType != null ) { + sqlAppender.append( " as " ); + sqlAppender.append( elementCastType ); + sqlAppender.append( ')' ); + } + sqlAppender.append( ",array[" ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( "])" ); + } + + private static boolean needsElementCasting(Expression elementExpression) { + // GaussDB needs casting of null and string literal expressions + return elementExpression instanceof Literal && ( + elementExpression.getExpressionType().getSingleJdbcMapping().getJdbcType().isString() + || ( (Literal) elementExpression ).getLiteralValue() == null + ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayRemoveFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayRemoveFunction.java new file mode 100644 index 000000000000..9f755ccd1d3c --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayRemoveFunction.java @@ -0,0 +1,65 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.array; + +import org.hibernate.dialect.function.array.AbstractArrayRemoveFunction; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; + +import java.util.List; + +/** + * GaussDB array_remove function. + * @author chenzhida + */ +public class GaussDBArrayRemoveFunction extends AbstractArrayRemoveFunction { + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression indexExpression = (Expression) sqlAstArguments.get( 1 ); + + sqlAppender.append( "CASE WHEN "); + arrayExpression.accept( walker ); + sqlAppender.append( " IS NULL THEN NULL ELSE COALESCE(( SELECT array_agg(val) FROM unnest("); + arrayExpression.accept( walker ); + sqlAppender.append( ") AS val" ); + + if ( indexExpression instanceof Literal ) { + Literal literal = (Literal) indexExpression; + Object literalValue = literal.getLiteralValue(); + if ( literalValue != null ) { + appendWhere( sqlAppender, walker, indexExpression ); + } + else { + sqlAppender.append( " where val IS NOT NULL" ); + } + } + else { + appendWhere( sqlAppender, walker, indexExpression ); + } + sqlAppender.append( "), CAST(ARRAY[] AS VARCHAR[]) ) END AS result_array" ); + } + + /** + * can not get value if type like string + * @param sqlAppender + * @param walker + * @param indexExpression + */ + private static void appendWhere(SqlAppender sqlAppender, SqlAstTranslator walker, Expression indexExpression) { + sqlAppender.append( " where val IS NULL OR val not in (" ); + indexExpression.accept( walker ); + sqlAppender.append( ")" ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayRemoveIndexFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayRemoveIndexFunction.java new file mode 100644 index 000000000000..49de75cace1d --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayRemoveIndexFunction.java @@ -0,0 +1,70 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.array; + +import org.hibernate.dialect.function.array.ArrayRemoveIndexUnnestFunction; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; + +import java.util.List; + +/** + * GaussDB array_remove index function. + * @author chenzhida + */ +public class GaussDBArrayRemoveIndexFunction extends ArrayRemoveIndexUnnestFunction { + + private final boolean castEmptyArrayLiteral; + + public GaussDBArrayRemoveIndexFunction(boolean castEmptyArrayLiteral) { + super( castEmptyArrayLiteral ); + this.castEmptyArrayLiteral = castEmptyArrayLiteral; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression indexExpression = (Expression) sqlAstArguments.get( 1 ); + + sqlAppender.append( "case when "); + arrayExpression.accept( walker ); + sqlAppender.append( " IS NOT NULL THEN COALESCE((SELECT array_agg(" ); + arrayExpression.accept( walker ); + sqlAppender.append( "[idx]) FROM generate_subscripts(" ); + arrayExpression.accept( walker ); + sqlAppender.append( ", 1) AS idx " ); + + if ( indexExpression instanceof Literal ) { + Literal literal = (Literal) indexExpression; + Object literalValue = literal.getLiteralValue(); + if ( literalValue != null ) { + appendWhere( sqlAppender, walker, indexExpression ); + } + } + else { + appendWhere( sqlAppender, walker, indexExpression ); + } + + sqlAppender.append( "), CAST(ARRAY[] AS VARCHAR ARRAY)) " ); + if ( castEmptyArrayLiteral ) { + sqlAppender.append( "ELSE CAST(ARRAY[] AS VARCHAR ARRAY) " ); + } + sqlAppender.append( "END AS result_array" ); + } + + private static void appendWhere(SqlAppender sqlAppender, SqlAstTranslator walker, Expression indexExpression) { + sqlAppender.append( "where idx not in (" ); + indexExpression.accept( walker ); + sqlAppender.append( ")" ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayReplaceFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayReplaceFunction.java new file mode 100644 index 000000000000..f6984df1bb63 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArrayReplaceFunction.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.array; + +import org.hibernate.dialect.function.array.ArrayReplaceUnnestFunction; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; + +import java.util.List; + +/** + * GaussDB array_replace function. + * @author chenzhida + */ +public class GaussDBArrayReplaceFunction extends ArrayReplaceUnnestFunction { + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + + sqlAppender.append( "CASE WHEN "); + sqlAstArguments.get( 0 ).accept( walker ); + sqlAppender.append( " IS NULL THEN NULL ELSE COALESCE((SELECT array_agg(CASE "); + final Expression originValueExpression = (Expression) sqlAstArguments.get( 1 ); + if ( originValueExpression instanceof Literal ) { + Literal literal = (Literal) originValueExpression; + Object literalValue = literal.getLiteralValue(); + if ( literalValue != null ) { + sqlAppender.append( "WHEN val = "); + sqlAstArguments.get( 1 ).accept( walker ); + } + else { + sqlAppender.append( "WHEN val is null "); + } + } + else { + sqlAppender.append( "WHEN val = "); + sqlAstArguments.get( 1 ).accept( walker ); + } + sqlAppender.append( " THEN "); + sqlAstArguments.get( 2 ).accept( walker ); + sqlAppender.append( " ELSE val END) FROM unnest( "); + sqlAstArguments.get( 0 ).accept( walker ); + sqlAppender.append( ") AS val ), CAST(ARRAY[] AS VARCHAR[]) ) END AS result_array"); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArraySetFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArraySetFunction.java new file mode 100644 index 000000000000..057bdfb7a0e5 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/array/GaussDBArraySetFunction.java @@ -0,0 +1,69 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.array; + +import org.hibernate.dialect.function.array.ArrayAndElementArgumentTypeResolver; +import org.hibernate.dialect.function.array.ArrayAndElementArgumentValidator; +import org.hibernate.dialect.function.array.ArrayViaArgumentReturnTypeResolver; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; + +import java.util.List; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER; + +/** + * GaussDB array_set function. + * @author chenzhida + */ +public class GaussDBArraySetFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public GaussDBArraySetFunction() { + super( + "array_set", + StandardArgumentsValidators.composite( + new ArrayAndElementArgumentValidator( 0, 2 ), + new ArgumentTypesValidator( null, ANY, INTEGER, ANY ) + ), + ArrayViaArgumentReturnTypeResolver.DEFAULT_INSTANCE, + StandardFunctionArgumentTypeResolvers.composite( + StandardFunctionArgumentTypeResolvers.IMPLIED_RESULT_TYPE, + StandardFunctionArgumentTypeResolvers.invariant( ANY, INTEGER, ANY ), + new ArrayAndElementArgumentTypeResolver( 0, 2 ) + ) + ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final Expression indexExpression = (Expression) sqlAstArguments.get( 1 ); + final Expression elementExpression = (Expression) sqlAstArguments.get( 2 ); + + sqlAppender.append( "( SELECT array_agg( CASE WHEN idx_gen = "); + indexExpression.accept( walker ); + sqlAppender.append( " THEN "); + elementExpression.accept( walker ); + sqlAppender.append( " ELSE CASE WHEN idx_gen <= array_length(ewa1_0.the_array, 1) "); + sqlAppender.append( " THEN ewa1_0.the_array[idx_gen] ELSE NULL END END ORDER BY idx_gen ) "); + sqlAppender.append( " FROM generate_series(1, GREATEST(COALESCE(array_length( "); + arrayExpression.accept( walker ); + sqlAppender.append( " , 1), 0), "); + indexExpression.accept( walker ); + sqlAppender.append( " )) AS idx_gen ) AS result_array "); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/json/GaussDBJsonObjectFunction.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/json/GaussDBJsonObjectFunction.java new file mode 100644 index 000000000000..cc4f7ed332b1 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/function/json/GaussDBJsonObjectFunction.java @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.function.json; + +import org.hibernate.dialect.function.json.JsonObjectFunction; +import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JsonNullBehavior; +import org.hibernate.type.spi.TypeConfiguration; + +import java.util.List; + +/** + * GaussDB json_object function. + * @author chenzhida + * + * Notes: Original code of this class is based on PostgreSQLJsonObjectFunction. + */ +public class GaussDBJsonObjectFunction extends JsonObjectFunction { + + public GaussDBJsonObjectFunction(TypeConfiguration typeConfiguration) { + super( typeConfiguration, false ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + + sqlAppender.appendSql( "json_build_object" ); + char separator = '('; + if ( sqlAstArguments.isEmpty() ) { + sqlAppender.appendSql( separator ); + } + else { + final JsonNullBehavior nullBehavior; + final int argumentsCount; + if ( ( sqlAstArguments.size() & 1 ) == 1 ) { + nullBehavior = (JsonNullBehavior) sqlAstArguments.get( sqlAstArguments.size() - 1 ); + argumentsCount = sqlAstArguments.size() - 1; + } + else { + nullBehavior = JsonNullBehavior.NULL; + argumentsCount = sqlAstArguments.size(); + } + sqlAppender.appendSql('('); + separator = ' '; + for ( int i = 0; i < argumentsCount; i += 2 ) { + final SqlAstNode key = sqlAstArguments.get( i ); + Expression valueNode = (Expression) sqlAstArguments.get( i+1 ); + if ( nullBehavior == JsonNullBehavior.ABSENT && walker.getLiteralValue( valueNode ) == null) { + continue; + } + if (separator != ' ') { + sqlAppender.appendSql(separator); + } + else { + separator = ','; + } + key.accept( walker ); + sqlAppender.appendSql( ',' ); + valueNode.accept( walker ); + } + } + sqlAppender.appendSql( ')' ); + } + +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/identity/GaussDBIdentityColumnSupport.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/identity/GaussDBIdentityColumnSupport.java new file mode 100644 index 000000000000..e519964cfaa8 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/identity/GaussDBIdentityColumnSupport.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.identity; + +import org.hibernate.dialect.identity.IdentityColumnSupportImpl; + +import static org.hibernate.internal.util.StringHelper.unquote; + +/** + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLIdentityColumnSupport. + */ +public class GaussDBIdentityColumnSupport extends IdentityColumnSupportImpl { + + public static final GaussDBIdentityColumnSupport INSTANCE = new GaussDBIdentityColumnSupport(); + + @Override + public boolean supportsIdentityColumns() { + return true; + } + + @Override + public boolean hasDataTypeInIdentityColumn() { + return false; + } + + @Override + public String getIdentitySelectString(String table, String column, int type) { + return "select currval('" + unquote(table) + '_' + unquote(column) + "_seq')"; + } + + @Override + public String getIdentityColumnString(int type) { + return "bigserial"; + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/GaussDBSequenceSupport.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/GaussDBSequenceSupport.java new file mode 100644 index 000000000000..914388eff2f0 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/GaussDBSequenceSupport.java @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.community.dialect.sequence; + +import org.hibernate.MappingException; +import org.hibernate.community.dialect.GaussDBDialect; +import org.hibernate.dialect.sequence.SequenceSupport; + +/** + * Sequence support for {@link GaussDBDialect}. + * + * @author liubao + * + * Notes: Original code of this class is based on PostgreSQLAggregateSupport. + */ +public class GaussDBSequenceSupport implements SequenceSupport { + + public static final SequenceSupport INSTANCE = new GaussDBSequenceSupport(); + + @Override + public String getSelectSequenceNextValString(String sequenceName) { + return "nextval('" + sequenceName + "')"; + } + + @Override + public String getSelectSequencePreviousValString(String sequenceName) throws MappingException { + return "currval('" + sequenceName + "')"; + } + + @Override + public boolean sometimesNeedsStartingValue() { + return true; + } + + @Override + public String getDropSequenceString(String sequenceName) { + return "drop sequence if exists " + sequenceName; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 601d38bf336e..e43d0536be53 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -2042,6 +2042,43 @@ protected void visitOnDuplicateKeyConflictClause(ConflictClause conflictClause) clauseStack.pop(); } + protected void visitOnDuplicateKeyConflictClauseWithDoNothing(ConflictClause conflictClause) { + if ( conflictClause == null ) { + return; + } + // The duplicate key clause does not support specifying the constraint name or constraint column names, + // but to allow compatibility, we have to require the user to specify either one in the SQM conflict clause. + // To allow meaningful usage, we simply ignore the constraint column names in this emulation. + // A possible problem with this is when the constraint column names contain the primary key columns, + // but the insert fails due to a unique constraint violation. This emulation will not cause a failure to be + // propagated, but instead will run the respective conflict action. + final String constraintName = conflictClause.getConstraintName(); + if ( constraintName != null ) { + if ( conflictClause.isDoUpdate() ) { + throw new IllegalQueryOperationException( "Insert conflict 'do update' clause with constraint name is not supported" ); + } + else { + return; + } + } + clauseStack.push( Clause.CONFLICT ); + appendSql( " on duplicate key update" ); + final List assignments = conflictClause.getAssignments(); + if ( assignments.isEmpty() ) { + try { + clauseStack.push( Clause.SET ); + appendSql( " nothing " ); + } + finally { + clauseStack.pop(); + } + } + else { + renderPredicatedSetAssignments( assignments, conflictClause.getPredicate() ); + } + clauseStack.pop(); + } + private void renderPredicatedSetAssignments(List assignments, Predicate predicate) { char separator = ' '; try { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/ElementCollectionOfEmbeddableWithEntityWithEntityCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/ElementCollectionOfEmbeddableWithEntityWithEntityCollectionTest.java index 689e5de9a2c1..a3554c13560b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/ElementCollectionOfEmbeddableWithEntityWithEntityCollectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/ElementCollectionOfEmbeddableWithEntityWithEntityCollectionTest.java @@ -122,7 +122,8 @@ public void testInitializeCollection(SessionFactoryScope scope) { } @Entity(name = "Plan") - @Table(name = "PLAN_TABLE") + // add a table prefix to avoid conflict with system view + @Table(name = "PLAN_TEST_TABLE") public static class Plan { @Id public Integer id; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/EntityGraphAndJoinTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/EntityGraphAndJoinTest.java index d96296c4b93d..67c9bbcf5f32 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/EntityGraphAndJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/EntityGraphAndJoinTest.java @@ -112,7 +112,8 @@ private void executeQuery(SessionFactoryScope scope, boolean criteria, boolean l final EntityGraph entityGraph = session.getEntityGraph( "test-graph" ); final List resultList = query.setHint( HINT_SPEC_FETCH_GRAPH, entityGraph ).getResultList(); assertThat( resultList ).hasSize( 2 ); - assertThat( resultList.stream().map( p -> p.getAddress().getId() ) ).containsExactly( 1L, 2L ); + // No order by, there is no guarantee of the data order. + assertThat( resultList.stream().map( p -> p.getAddress().getId() ) ).containsExactlyInAnyOrder( 1L, 2L ); inspector.assertExecutedCount( 1 ); inspector.assertNumberOfOccurrenceInQuery( 0, "join", where ? 2 : 1 ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/basic/ExpressionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/basic/ExpressionsTest.java index 78914c85a8fa..1c9b7d3cd5a5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/basic/ExpressionsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/basic/ExpressionsTest.java @@ -33,6 +33,7 @@ import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.SkipForDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -304,6 +305,7 @@ public void testSumWithSubqueryPath() { @Test @SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "does not support extract(epoch)") @SkipForDialect(dialectClass = AltibaseDialect.class, reason = "datediff overflow limits") + @SkipForDialect(dialectClass = GaussDBDialect.class, reason = "type:resolved.date multi overflows") public void testDateTimeOperations() { HibernateCriteriaBuilder builder = (HibernateCriteriaBuilder) this.builder; doInJPA( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/LockTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/LockTest.java index 6fa1d4939902..ec004f531f9c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/LockTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/LockTest.java @@ -22,6 +22,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.community.dialect.AltibaseDialect; import org.hibernate.community.dialect.FirebirdDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.CockroachDialect; @@ -1189,6 +1190,7 @@ public void testLockTimeoutEMProps() throws Exception { @SkipForDialect(value = CockroachDialect.class, comment = "Cockroach supports the 'for no key update' syntax but it doesn't work") @SkipForDialect(value = FirebirdDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks") @SkipForDialect(value = AltibaseDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks") + @SkipForDialect(value = GaussDBDialect.class, comment = "The USTORE storage engine does not support For Key Share and For No Key Update") public void testLockInsertFkTarget() { Lock lock = new Lock(); lock.setName( "name" ); @@ -1228,6 +1230,7 @@ public void testLockInsertFkTarget() { @SkipForDialect(value = CockroachDialect.class, comment = "Cockroach supports the 'for no key update' syntax but it doesn't work") @SkipForDialect(value = FirebirdDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks") @SkipForDialect(value = AltibaseDialect.class, comment = "FK constraint checks are not compatible with exclusive locks") + @SkipForDialect(value = GaussDBDialect.class, comment = "The USTORE storage engine does not support For Key Share and For No Key Update") public void testLockUpdateFkTarget() { Lock lock1 = new Lock(); lock1.setName( "l1" ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryResultTypeAutoDiscoveryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryResultTypeAutoDiscoveryTest.java index b735005f4760..d86b0602ccaf 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryResultTypeAutoDiscoveryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryResultTypeAutoDiscoveryTest.java @@ -25,6 +25,7 @@ import org.hibernate.community.dialect.AltibaseDialect; import org.hibernate.community.dialect.FirebirdDialect; import org.hibernate.community.dialect.InformixDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.AbstractTransactSQLDialect; import org.hibernate.dialect.CockroachDialect; @@ -157,6 +158,7 @@ public void bitType() { @SkipForDialect(dialectClass = FirebirdDialect.class, reason = "No support for the tinyint datatype so we use smallint") @SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase maps tinyint to smallint") @SkipForDialect(dialectClass = InformixDialect.class, reason = "informix maps tinyint to smallint") + @SkipForDialect(dialectClass = GaussDBDialect.class, reason = "type:resolved.Turns tinyints into shorts in result sets and advertises the type as short in the metadata") public void tinyintType() { createEntityManagerFactory( TinyintEntity.class ); doTest( TinyintEntity.class, (byte)127 ); @@ -301,6 +303,7 @@ public void lobTypes() { @SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "EDB maps DATE and TIME to TIMESTAMP") @SkipForDialect(dialectClass = SybaseDialect.class, reason = "Sybase maps DATE and TIME to TIMESTAMP", matchSubTypes = true) @SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase maps DATE and TIME to TIMESTAMP") + @SkipForDialect(dialectClass = GaussDBDialect.class, reason = "type:resolved.GaussDB's Oracle model maps DATE and TIME to TIMESTAMP") public void dateTimeTypes() { createEntityManagerFactory( DateEntity.class, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryWithDatetimesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryWithDatetimesTest.java index b601baf28285..2cce06b26dc1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryWithDatetimesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryWithDatetimesTest.java @@ -8,6 +8,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgresPlusDialect; @@ -26,6 +27,7 @@ public class NativeQueryWithDatetimesTest { @SkipForDialect(dialectClass = PostgresPlusDialect.class) @SkipForDialect(dialectClass = OracleDialect.class) + @SkipForDialect(dialectClass = GaussDBDialect.class, reason = "type:resolved.gauss will map localdate to timestamp") @Test void test(EntityManagerFactoryScope scope) { scope.inTransaction(s -> s.persist(new Datetimes())); Object[] result = scope.fromTransaction(s -> (Object[]) s.createNativeQuery("select ctime, cdate, cdatetime from tdatetimes", Object[].class).getSingleResult()); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/QueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/QueryTest.java index 3a36f15fca91..9e9b7decb9ef 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/QueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/QueryTest.java @@ -29,6 +29,7 @@ import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.community.dialect.DerbyDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.PostgresPlusDialect; @@ -382,6 +383,7 @@ public Class getParameterType() { @Test @SkipForDialect(value = PostgreSQLDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") + @SkipForDialect(value = GaussDBDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = CockroachDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = InformixDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @@ -418,6 +420,7 @@ public void testNativeQueryNullPositionalParameter() throws Exception { @Test @JiraKey(value = "HHH-10161") @SkipForDialect(value = PostgreSQLDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") + @SkipForDialect(value = GaussDBDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = CockroachDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = InformixDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @@ -470,6 +473,7 @@ public Class getParameterType() { @Test @SkipForDialect(value = PostgreSQLDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") + @SkipForDialect(value = GaussDBDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = CockroachDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = InformixDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @@ -506,6 +510,7 @@ public void testNativeQueryNullNamedParameter() throws Exception { @Test @JiraKey(value = "HHH-10161") @SkipForDialect(value = PostgreSQLDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") + @SkipForDialect(value = GaussDBDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = PostgresPlusDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = CockroachDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") @SkipForDialect(value = InformixDialect.class, jiraKey = "HHH-10312", comment = "Cannot determine the parameter types and bind type is unknown because the value is null") diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/schemagen/SchemaDatabaseFileGenerationFailureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/schemagen/SchemaDatabaseFileGenerationFailureTest.java index 056673d79be7..20827faac075 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/schemagen/SchemaDatabaseFileGenerationFailureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/schemagen/SchemaDatabaseFileGenerationFailureTest.java @@ -19,6 +19,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.jpa.boot.spi.Bootstrap; import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; @@ -74,6 +75,7 @@ public void destroy() { @JiraKey(value = "HHH-12192") @SkipForDialect(dialectClass = PostgreSQLDialect.class, matchSubTypes = true, reason = "on postgres we send 'set client_min_messages = WARNING'") + @SkipForDialect( dialectClass = GaussDBDialect.class, reason = "on gauss we send 'set client_min_messages = WARNING'") public void testErrorMessageContainsTheFailingDDLCommand() { try { entityManagerFactoryBuilder.generateSchema(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/schemagen/SchemaScriptFileGenerationFailureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/schemagen/SchemaScriptFileGenerationFailureTest.java index 6e8f8ad4461d..d35715cfbd9c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/schemagen/SchemaScriptFileGenerationFailureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/schemagen/SchemaScriptFileGenerationFailureTest.java @@ -17,6 +17,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.jpa.boot.spi.Bootstrap; import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; @@ -65,6 +66,7 @@ public void destroy() { @JiraKey(value = "HHH-12192") @SkipForDialect(dialectClass = PostgreSQLDialect.class, matchSubTypes = true, reason = "on postgres we send 'set client_min_messages = WARNING'") + @SkipForDialect( dialectClass = GaussDBDialect.class, reason = "on gauss we send 'set client_min_messages = WARNING'") public void testErrorMessageContainsTheFailingDDLCommand() { try { entityManagerFactoryBuilder.generateSchema(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/array/ArrayOfArraysTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/array/ArrayOfArraysTest.java index 988b2e50b3aa..6fa83400534b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/array/ArrayOfArraysTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/array/ArrayOfArraysTest.java @@ -9,6 +9,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.dialect.CockroachDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.testing.orm.junit.SkipForDialect; import org.hibernate.type.SqlTypes; @@ -39,6 +40,7 @@ public class ArrayOfArraysTest { @ServiceRegistry( settings = @Setting( name = AvailableSettings.HBM2DDL_AUTO, value = "create-drop" ) ) @Test @SkipForDialect( dialectClass = CockroachDialect.class, reason = "Unable to find server array type for provided name bytes" ) + @SkipForDialect( dialectClass = GaussDBDialect.class, reason = "type:resolved.Method com.huawei.gaussdb.jdbc.jdbc.PgArray.getArrayImpl(long,int,Map) is not yet implemented.") public void testDoubleByteArrayWorks(SessionFactoryScope scope) { final Long id = scope.fromTransaction( session -> { final EntityWithDoubleByteArray entity = new EntityWithDoubleByteArray(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/XmlMappingTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/XmlMappingTests.java index 7d95f9d7e1aa..bed401be66ff 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/XmlMappingTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/XmlMappingTests.java @@ -13,6 +13,7 @@ import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.HANADialect; import org.hibernate.community.dialect.DerbyDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; @@ -96,6 +97,7 @@ public void tearDown(SessionFactoryScope scope) { } @Test + @SkipForDialect(dialectClass = GaussDBDialect.class, reason = "GaussDB don't support this xml feature") public void verifyMappings(SessionFactoryScope scope) { final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory() .getRuntimeMetamodels() @@ -120,6 +122,7 @@ public void verifyMappings(SessionFactoryScope scope) { } @Test + @SkipForDialect(dialectClass = GaussDBDialect.class, reason = "GaussDB don't support this xml feature") public void verifyReadWorks(SessionFactoryScope scope) { scope.inTransaction( (session) -> { @@ -138,6 +141,7 @@ public void verifyReadWorks(SessionFactoryScope scope) { @SkipForDialect(dialectClass = OracleDialect.class, matchSubTypes = true, reason = "Oracle doesn't support comparing JSON with the = operator") @SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase doesn't support comparing CLOBs with the = operator") @SkipForDialect(dialectClass = InformixDialect.class, reason = "Blobs are not allowed in this expression") + @SkipForDialect(dialectClass = GaussDBDialect.class, reason = "GaussDB doesn't support comparing CLOBs with the = operator") public void verifyComparisonWorks(SessionFactoryScope scope) { scope.inTransaction( (session) -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java index 53a6499b40e0..4cfee36b8c46 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java @@ -15,6 +15,7 @@ import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.community.dialect.DerbyDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.HSQLDialect; @@ -1183,6 +1184,7 @@ public void testCastFunctionWithLength(SessionFactoryScope scope) { @SkipForDialect(dialectClass = DB2Dialect.class, majorVersion = 10, minorVersion = 5, reason = "On this version the length of the cast to the parameter appears to be > 2") @SkipForDialect(dialectClass = HSQLDialect.class, reason = "HSQL interprets string as hex literal and produces error") @SkipForDialect(dialectClass = InformixDialect.class, reason = "No cast from varchar to byte") + @SkipForDialect(dialectClass = GaussDBDialect.class, reason = "GaussDB bytea doesn't have a length") public void testCastBinaryWithLength(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -1928,6 +1930,8 @@ public void testDurationSubtractionWithTimeLiterals(SessionFactoryScope scope) { reason = "numeric overflow") @SkipForDialect(dialectClass = InformixDialect.class, reason = "Overflow occurred on a datetime or interval operation") + @SkipForDialect(dialectClass = GaussDBDialect.class, + reason = "numeric overflow") public void testDurationSubtractionWithDatetimeLiterals(SessionFactoryScope scope) { scope.inTransaction( session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertConflictTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertConflictTests.java index 3a81fe3ba74f..dd0d0c42d02f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertConflictTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertConflictTests.java @@ -7,6 +7,7 @@ import java.time.LocalDate; import org.hibernate.community.dialect.InformixDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.SybaseASEDialect; import org.hibernate.query.criteria.HibernateCriteriaBuilder; @@ -129,6 +130,10 @@ else if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof Sy // Sybase seems to report all matched rows as affected and ignores additional predicates assertEquals( 1, updated ); } + else if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof GaussDBDialect ) { + // GaussDB seems to report all matched rows as affected and ignores additional predicates + assertEquals( 1, updated ); + } else { assertEquals( 0, updated ); } @@ -166,6 +171,10 @@ else if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof Sy // Sybase seems to report all matched rows as affected and ignores additional predicates assertEquals( 1, updated ); } + else if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof GaussDBDialect ) { + // GaussDB seems to report all matched rows as affected and ignores additional predicates + assertEquals( 1, updated ); + } else { assertEquals( 0, updated ); } @@ -251,6 +260,10 @@ else if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof Sy // Sybase seems to report all matched rows as affected and ignores additional predicates assertEquals( 1, updated ); } + else if ( scope.getSessionFactory().getJdbcServices().getDialect() instanceof GaussDBDialect ) { + // GaussDB seems to report all matched rows as affected and ignores additional predicates + assertEquals( 1, updated ); + } else { assertEquals( 0, updated ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/resource/ResultSetReleaseWithStatementDelegationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/resource/ResultSetReleaseWithStatementDelegationTest.java index ed7fb0f19173..cb2b7e41fe8d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/resource/ResultSetReleaseWithStatementDelegationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/resource/ResultSetReleaseWithStatementDelegationTest.java @@ -11,11 +11,11 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.logger.Triggerable; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -39,7 +39,7 @@ provider = ResultSetReleaseWithStatementDelegationTest.ConnectionProviderDelegateProvider.class) ) @SessionFactory -@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsIdentityColumns.class) @JiraKey( "HHH-19280" ) class ResultSetReleaseWithStatementDelegationTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/DateArrayTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/DateArrayTest.java index 251e65656330..135eec9d474f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/type/DateArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/DateArrayTest.java @@ -9,6 +9,7 @@ import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.MySQLDialect; @@ -126,6 +127,7 @@ public void testQueryById(SessionFactoryScope scope) { @SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "Seems that comparing date[] through JDBC is buggy. ERROR: operator does not exist: timestamp without time zone[] = date[]") @SkipForDialect(dialectClass = InformixDialect.class, reason = "The statement failed because binary large objects are not allowed in the Union, Intersect, or Minus ") + @SkipForDialect(dialectClass = GaussDBDialect.class, reason = "Seems that comparing date[] through JDBC is buggy. ERROR: operator does not exist: timestamp without time zone[] = date[]") public void testQuery(SessionFactoryScope scope) { scope.inSession( em -> { TypedQuery tq = em.createNamedQuery( "TableWithDateArrays.JPQL.getByData", TableWithDateArrays.class ); @@ -155,6 +157,7 @@ public void testNativeQueryById(SessionFactoryScope scope) { @SkipForDialect(dialectClass = MySQLDialect.class, matchSubTypes = true, reason = "MySQL supports distinct from through a special operator") @SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "Seems that comparing date[] through JDBC is buggy. ERROR: operator does not exist: timestamp without time zone[] = date[]") @SkipForDialect(dialectClass = InformixDialect.class, reason = "Informix can't compare LOBs") + @SkipForDialect( dialectClass = GaussDBDialect.class, reason = "type:resolved.Seems that comparing date[] through JDBC is buggy. ERROR: operator does not exist: timestamp without time zone[] = date[]") public void testNativeQuery(SessionFactoryScope scope) { scope.inSession( em -> { final Dialect dialect = em.getDialect(); @@ -173,6 +176,7 @@ public void testNativeQuery(SessionFactoryScope scope) { @Test @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTypedArrays.class) @SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "The 'date' type is a synonym for timestamp on Oracle and PostgresPlus, so untyped reading produces Timestamps") + @SkipForDialect( dialectClass = GaussDBDialect.class, reason = "type:resolved.The 'date' type is a synonym for timestamp on Oracle and PostgresPlus, so untyped reading produces Timestamps") public void testNativeQueryUntyped(SessionFactoryScope scope) { scope.inSession( em -> { Query q = em.createNamedQuery( "TableWithDateArrays.Native.getByIdUntyped" ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/typeoverride/TypeOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/typeoverride/TypeOverrideTest.java index 2884dd21b93e..c7ddbfd7558f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/typeoverride/TypeOverrideTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/typeoverride/TypeOverrideTest.java @@ -7,6 +7,7 @@ import java.sql.Types; import org.hibernate.boot.MetadataBuilder; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.HANADialect; @@ -63,6 +64,12 @@ else if ( PostgreSQLDialect.class.isInstance( dialect ) ) { jdbcTypeRegistry.getDescriptor( Types.BLOB ) ); } + else if ( GaussDBDialect.class.isInstance( dialect ) ) { + assertSame( + BlobJdbcType.BLOB_BINDING, + jdbcTypeRegistry.getDescriptor( Types.BLOB ) + ); + } else if ( SybaseDialect.class.isInstance( dialect ) ) { assertSame( BlobJdbcType.PRIMITIVE_ARRAY_BINDING, diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java index 49669c70d2a5..6d19b77ecb42 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariTransactionIsolationConfigTest.java @@ -5,6 +5,7 @@ package org.hibernate.test.hikaricp; import org.hibernate.community.dialect.AltibaseDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.community.dialect.TiDBDialect; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; @@ -19,6 +20,7 @@ @SkipForDialect(value = SybaseDialect.class, comment = "The jTDS driver doesn't implement Connection#getNetworkTimeout() so this fails") @SkipForDialect(value = TiDBDialect.class, comment = "Doesn't support SERIALIZABLE isolation") @SkipForDialect(value = AltibaseDialect.class, comment = "Altibase cannot change isolation level in autocommit mode") +@SkipForDialect(value = GaussDBDialect.class, comment = "GaussDB does not support SERIALIZABLE isolation") public class HikariTransactionIsolationConfigTest extends BaseTransactionIsolationConfigTest { @Override protected ConnectionProvider getConnectionProviderUnderTest() { diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java index 4e73777ac1cc..d3e89b711447 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java @@ -42,6 +42,7 @@ import org.hibernate.boot.spi.SecondPass; import org.hibernate.community.dialect.DerbyDialect; import org.hibernate.community.dialect.FirebirdDialect; +import org.hibernate.community.dialect.GaussDBDialect; import org.hibernate.community.dialect.InformixDialect; import org.hibernate.community.dialect.TiDBDialect; import org.hibernate.dialect.CockroachDialect; @@ -575,6 +576,7 @@ public boolean apply(Dialect dialect) { || dialect instanceof H2Dialect || dialect instanceof SQLServerDialect || dialect instanceof PostgreSQLDialect + || dialect instanceof GaussDBDialect || dialect instanceof DB2Dialect || dialect instanceof OracleDialect || dialect instanceof SybaseDialect diff --git a/local-build-plugins/src/main/groovy/local.databases.gradle b/local-build-plugins/src/main/groovy/local.databases.gradle index 38d66af05d7f..973b1c36338b 100644 --- a/local-build-plugins/src/main/groovy/local.databases.gradle +++ b/local-build-plugins/src/main/groovy/local.databases.gradle @@ -75,6 +75,17 @@ ext { // 'jdbc.datasource' : 'org.postgresql.ds.PGSimpleDataSource', 'connection.init_sql' : '' ], + gaussdb: [ + 'db.dialect' : 'org.hibernate.community.dialect.GaussDBDialect', + 'jdbc.driver' : 'com.huawei.gaussdb.jdbc.Driver', + 'jdbc.user' : 'hibernate_orm_test', + 'jdbc.pass' : 'Hibernate_orm_test@1234', + // Disable prepared statement caching to avoid issues with changing schemas + // Make batch verification work, see https://bbs.huaweicloud.com/forum/thread-02104174303512776081-1-1.html + 'jdbc.url' : 'jdbc:gaussdb://' + dbHost + '/hibernate_orm_test?currentSchema=test&preparedStatementCacheQueries=0&batchMode=off', + 'jdbc.datasource' : 'com.huawei.gaussdb.jdbc.Driver', + 'connection.init_sql': '' + ], edb_ci : [ 'db.dialect' : 'org.hibernate.dialect.PostgresPlusDialect', 'jdbc.driver': 'org.postgresql.Driver', @@ -375,4 +386,4 @@ ext { 'jdbc.datasource' : 'Altibase.jdbc.driver.AltibaseDriver' ], ] -} \ No newline at end of file +} diff --git a/local-build-plugins/src/main/groovy/local.java-module.gradle b/local-build-plugins/src/main/groovy/local.java-module.gradle index 477507fb36b5..47c225dd4ab8 100644 --- a/local-build-plugins/src/main/groovy/local.java-module.gradle +++ b/local-build-plugins/src/main/groovy/local.java-module.gradle @@ -80,6 +80,7 @@ dependencies { testRuntimeOnly jdbcLibs.mssql testRuntimeOnly jdbcLibs.informix testRuntimeOnly jdbcLibs.cockroachdb + testRuntimeOnly jdbcLibs.gaussdb testRuntimeOnly jdbcLibs.sybase testRuntimeOnly rootProject.fileTree(dir: 'drivers', include: '*.jar') diff --git a/settings.gradle b/settings.gradle index 83d980edee9c..ecbf1d2d7a08 100644 --- a/settings.gradle +++ b/settings.gradle @@ -231,6 +231,7 @@ dependencyResolutionManagement { def oracleVersion = version "oracle", "23.8.0.25.04" def oracleJacksonOsonExtension = version "oracleJacksonOsonExtension", "1.0.4" def pgsqlVersion = version "pgsql", "42.7.7" + def gaussdbVersion = version "gaussdb", "506.0.0.b058" def sybaseVersion = version "sybase", "1.3.1" def tidbVersion = version "tidb", mysqlVersion def altibaseVersion = version "altibase", "7.3.0.1.1" @@ -242,6 +243,7 @@ dependencyResolutionManagement { library( "derbyTools", "org.apache.derby", "derbytools" ).versionRef( derbyVersion ) library( "postgresql", "org.postgresql", "postgresql" ).versionRef( pgsqlVersion ) library( "cockroachdb", "org.postgresql", "postgresql" ).versionRef( pgsqlVersion ) + library( "gaussdb", "com.huaweicloud.gaussdb", "gaussdbjdbc" ).versionRef( gaussdbVersion ) library( "mysql", "com.mysql", "mysql-connector-j" ).versionRef( mysqlVersion ) library( "tidb", "com.mysql", "mysql-connector-j" ).versionRef( tidbVersion ) library( "mariadb", "org.mariadb.jdbc", "mariadb-java-client" ).versionRef( mariadbVersion )