From bb5aa629277820bea0c847bcb915ae49a6ec4f71 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Wed, 14 Sep 2022 23:55:50 +0200 Subject: [PATCH] HHH-15487 - Remove support for PostgreSQL versions older than 10 Signed-off-by: Jan Schatteman --- .../SingleTableDiscriminatorFormulaTest.java | 2 +- .../PostgreSQLSchemaGenerationTest.java | 2 +- .../dialect/PostgreSQLLegacyDialect.java | 1217 +++++++++++++++++ .../PostgreSQLLegacySqlAstTranslator.java | 191 +++ .../PostgreSQLLegacySequenceSupport.java | 48 + .../hibernate/dialect/PostgreSQLDialect.java | 91 +- .../sequence/PostgreSQLSequenceSupport.java | 7 - .../dialect/PostgreSQL92DialectTestCase.java | 37 - ...se.java => PostgreSQLDialectTestCase.java} | 41 +- .../PostgreSQLLockTimeoutTest.java | 2 +- ...greSQLSequenceGeneratorWithSerialTest.java | 2 +- .../PostgreSQLIdentitySequenceTest.java | 2 +- .../PostgreSQLUUIDGeneratedValueTest.java | 2 +- .../UUIDBasedIdInterpretationTest.java | 2 +- ...istChildEntitiesWithDiscriminatorTest.java | 8 +- .../NativeQueryOrdinalParametersTest.java | 3 +- .../nationalized/StringNationalizedTest.java | 8 +- .../AlterTableQuoteDefaultSchemaTest.java | 2 +- .../AlterTableQuoteSpecifiedSchemaTest.java | 2 +- .../JdbcTimestampUTCTimeZoneTest.java | 2 +- ...bcTimestampWithDefaultUTCTimeZoneTest.java | 2 +- .../JdbcTimestampWithoutUTCTimeZoneTest.java | 2 +- .../orm/junit/DialectFeatureChecks.java | 4 +- 23 files changed, 1543 insertions(+), 136 deletions(-) create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacySqlAstTranslator.java create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/PostgreSQLLegacySequenceSupport.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQL92DialectTestCase.java rename hibernate-core/src/test/java/org/hibernate/orm/test/dialect/{PostgreSQL81DialectTestCase.java => PostgreSQLDialectTestCase.java} (77%) diff --git a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java index d78f535177..b78262d1f1 100644 --- a/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/inheritance/SingleTableDiscriminatorFormulaTest.java @@ -36,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class SingleTableDiscriminatorFormulaTest { @Test - @RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8, minorVersion = 1) + @RequiresDialect(value = PostgreSQLDialect.class) public void test(EntityManagerFactoryScope scope) { scope.inTransaction(entityManager -> { DebitAccount debitAccount = new DebitAccount("123-debit"); diff --git a/documentation/src/test/java/org/hibernate/userguide/schema/PostgreSQLSchemaGenerationTest.java b/documentation/src/test/java/org/hibernate/userguide/schema/PostgreSQLSchemaGenerationTest.java index d01464d5e6..f6ed8e4b52 100644 --- a/documentation/src/test/java/org/hibernate/userguide/schema/PostgreSQLSchemaGenerationTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/schema/PostgreSQLSchemaGenerationTest.java @@ -14,7 +14,7 @@ import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.Setting; import org.junit.jupiter.api.Test; -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8) +@RequiresDialect(value = PostgreSQLDialect.class) @Jpa( annotatedClasses = { BaseSchemaGeneratorTest.Person.class, diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java new file mode 100644 index 0000000000..8ba307fb3c --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java @@ -0,0 +1,1217 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.community.dialect; + +import java.sql.CallableStatement; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.time.temporal.TemporalAccessor; +import java.util.Calendar; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.PessimisticLockException; +import org.hibernate.QueryTimeoutException; +import org.hibernate.boot.model.TypeContributions; +import org.hibernate.community.dialect.sequence.PostgreSQLLegacySequenceSupport; +import org.hibernate.dialect.DatabaseVersion; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.NationalizationSupport; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQLDriverKind; +import org.hibernate.dialect.PostgreSQLInetJdbcType; +import org.hibernate.dialect.PostgreSQLIntervalSecondJdbcType; +import org.hibernate.dialect.PostgreSQLJsonbJdbcType; +import org.hibernate.dialect.PostgreSQLPGObjectJdbcType; +import org.hibernate.dialect.Replacer; +import org.hibernate.dialect.RowLockStrategy; +import org.hibernate.dialect.SelectItemReferenceStrategy; +import org.hibernate.dialect.TimeZoneSupport; +import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.identity.IdentityColumnSupport; +import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport; +import org.hibernate.dialect.pagination.LimitHandler; +import org.hibernate.dialect.pagination.LimitOffsetLimitHandler; +import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; +import org.hibernate.dialect.sequence.PostgreSQLSequenceSupport; +import org.hibernate.dialect.sequence.SequenceSupport; +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.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.exception.LockAcquisitionException; +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.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.procedure.internal.PostgresCallableStatementSupport; +import org.hibernate.procedure.spi.CallableStatementSupport; +import org.hibernate.query.SemanticException; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.FetchClauseType; +import org.hibernate.query.sqm.IntervalType; +import org.hibernate.query.sqm.TemporalUnit; +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.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.type.JavaObjectType; +import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; +import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; +import org.hibernate.type.descriptor.jdbc.BlobJdbcType; +import org.hibernate.type.descriptor.jdbc.ClobJdbcType; +import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType; +import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; +import org.hibernate.type.descriptor.jdbc.XmlJdbcType; +import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; +import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType; +import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; +import org.hibernate.type.descriptor.sql.internal.Scale6IntervalSecondDdlType; +import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; +import org.hibernate.type.spi.TypeConfiguration; + +import jakarta.persistence.TemporalType; + +import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; +import static org.hibernate.query.sqm.TemporalUnit.DAY; +import static org.hibernate.query.sqm.TemporalUnit.EPOCH; +import static org.hibernate.query.sqm.TemporalUnit.MONTH; +import static org.hibernate.query.sqm.TemporalUnit.QUARTER; +import static org.hibernate.query.sqm.TemporalUnit.YEAR; +import static org.hibernate.type.SqlTypes.ARRAY; +import static org.hibernate.type.SqlTypes.BINARY; +import static org.hibernate.type.SqlTypes.BLOB; +import static org.hibernate.type.SqlTypes.CHAR; +import static org.hibernate.type.SqlTypes.CLOB; +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.TIMESTAMP_UTC; +import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; +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.appendAsTime; +import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMicros; + +/** + * A {@linkplain Dialect SQL dialect} for PostgreSQL 8 and above. + * + * @author Gavin King + */ +public class PostgreSQLLegacyDialect extends Dialect { + + private static final PostgreSQLIdentityColumnSupport IDENTITY_COLUMN_SUPPORT = new PostgreSQLIdentityColumnSupport(); + + private final PostgreSQLDriverKind driverKind; + + public PostgreSQLLegacyDialect() { + this( DatabaseVersion.make( 8, 0 ) ); + } + + public PostgreSQLLegacyDialect(DialectResolutionInfo info) { + super(info); + driverKind = PostgreSQLDriverKind.determineKind( info ); + } + + public PostgreSQLLegacyDialect(DatabaseVersion version) { + super(version); + driverKind = PostgreSQLDriverKind.PG_JDBC; + } + + public PostgreSQLLegacyDialect(DatabaseVersion version, PostgreSQLDriverKind driverKind) { + super(version); + this.driverKind = driverKind; + } + + @Override + public boolean getDefaultNonContextualLobCreation() { + return true; + } + + @Override + protected String columnType(int sqlTypeCode) { + switch ( sqlTypeCode ) { + case TINYINT: + // no tinyint, not even in Postgres 11 + return "smallint"; + // there are no nchar/nvarchar types in Postgres + case NCHAR: + return columnType( CHAR ); + case NVARCHAR: + return columnType( VARCHAR ); + // since there's no real difference between TEXT and VARCHAR, + // except for the length limit, we can just use 'text' for the + // "long" string types + case LONG32VARCHAR: + case LONG32NVARCHAR: + return "text"; + case BLOB: + case CLOB: + case NCLOB: + // use oid as the blob/clob type on Postgres because + // the JDBC driver doesn't allow using bytea/text through LOB APIs + return "oid"; + // use bytea as the "long" binary type (that there is no + // real VARBINARY type in Postgres, so we always use this) + case BINARY: + case VARBINARY: + case LONG32VARBINARY: + return "bytea"; + + case TIMESTAMP_UTC: + return columnType( TIMESTAMP_WITH_TIMEZONE ); + } + return super.columnType( sqlTypeCode ); + } + + @Override + protected String castType(int sqlTypeCode) { + switch ( sqlTypeCode ) { + case CHAR: + case NCHAR: + case VARCHAR: + case NVARCHAR: + case LONG32VARCHAR: + case LONG32NVARCHAR: + return "text"; + case BINARY: + case VARBINARY: + case LONG32VARBINARY: + return "bytea"; + } + return super.castType( sqlTypeCode ); + } + + @Override + protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + super.registerColumnTypes( typeContributions, serviceRegistry ); + final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry(); + + // 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 ) ); + if ( getVersion().isSameOrAfter( 8, 2 ) ) { + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) ); + } + if ( PostgreSQLPGObjectJdbcType.isUsable() ) { + // The following DDL types require that the PGobject class is usable/visible + 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 ) ); + + if ( getVersion().isSameOrAfter( 9, 2 ) ) { + // Prefer jsonb if possible + if ( getVersion().isSameOrAfter( 9, 4 ) ) { + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); + } + else { + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) ); + } + } + } + } + + @Override + public int getMaxVarcharLength() { + return 10_485_760; + } + + @Override + public int getMaxVarbinaryLength() { + //postgres has no varbinary-like type + return Integer.MAX_VALUE; + } + + @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 ARRAY: + final JdbcType jdbcType = jdbcTypeRegistry.getDescriptor( jdbcTypeCode ); + // PostgreSQL names array types by prepending an underscore to the base name + if ( jdbcType instanceof ArrayJdbcType && columnTypeName.charAt( 0 ) == '_' ) { + final String componentTypeName = columnTypeName.substring( 1 ); + final Integer sqlTypeCode = resolveSqlTypeCode( componentTypeName, jdbcTypeRegistry.getTypeConfiguration() ); + if ( sqlTypeCode != null ) { + return ( (ArrayJdbcType) jdbcType ).resolveType( + jdbcTypeRegistry.getTypeConfiguration(), + jdbcTypeRegistry.getTypeConfiguration().getServiceRegistry() + .getService( JdbcServices.class ) + .getDialect(), + jdbcTypeRegistry.getDescriptor( sqlTypeCode ), + null + ); + } + } + return jdbcType; + } + return jdbcTypeRegistry.getDescriptor( jdbcTypeCode ); + } + + @Override + protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration typeConfiguration) { + switch ( columnTypeName ) { + case "bool": + return Types.BOOLEAN; + case "float4": + // Use REAL instead of FLOAT to get Float as recommended Java type + return Types.REAL; + case "float8": + return Types.DOUBLE; + case "int2": + return Types.SMALLINT; + case "int4": + return Types.INTEGER; + case "int8": + return Types.BIGINT; + } + return super.resolveSqlTypeCode( columnTypeName, typeConfiguration ); + } + + @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) { + switch ( unit ) { + case DAY_OF_WEEK: + return "(" + super.extractPattern(unit) + "+1)"; + default: + return super.extractPattern(unit); + } + } + + /** + * {@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 + public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { + if ( intervalType != null ) { + return "(?2+?3)"; + } + switch ( unit ) { + case NANOSECOND: + return "(?3+(?2)/1e3*interval '1 microsecond')"; + case NATIVE: + return "(?3+(?2)*interval '1 second')"; + case QUARTER: //quarter is not supported in interval literals + return "(?3+(?2)*interval '3 month')"; + case WEEK: //week is not supported in interval literals + return "(?3+(?2)*interval '7 day')"; + default: + return "(?3+(?2)*interval '1 ?1')"; + } + } + + @Override + public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { + if ( unit == null ) { + return "(?3-?2)"; + } + if ( toTemporalType != TemporalType.TIMESTAMP && fromTemporalType != TemporalType.TIMESTAMP && unit == DAY ) { + // special case: subtraction of two dates + // results in an integer number of days + // instead of an INTERVAL + return "(?3-?2)"; + } + else { + StringBuilder pattern = new StringBuilder(); + switch ( unit ) { + case YEAR: + extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit ); + break; + case QUARTER: + pattern.append( "(" ); + extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit ); + pattern.append( "+" ); + extractField( pattern, QUARTER, fromTemporalType, toTemporalType, unit ); + pattern.append( ")" ); + break; + case MONTH: + pattern.append( "(" ); + extractField( pattern, YEAR, fromTemporalType, toTemporalType, unit ); + pattern.append( "+" ); + extractField( pattern, MONTH, fromTemporalType, toTemporalType, unit ); + pattern.append( ")" ); + break; + case WEEK: //week is not supported by extract() when the argument is a duration + case DAY: + extractField( pattern, DAY, fromTemporalType, toTemporalType, unit ); + break; + //in order to avoid multiple calls to extract(), + //we use extract(epoch from x - y) * factor for + //all the following units: + case HOUR: + case MINUTE: + case SECOND: + case NANOSECOND: + case NATIVE: + extractField( pattern, EPOCH, fromTemporalType, toTemporalType, unit ); + break; + default: + throw new SemanticException( "unrecognized field: " + unit ); + } + return pattern.toString(); + } + } + + protected void extractField( + StringBuilder pattern, + TemporalUnit unit, + TemporalType fromTimestamp, + TemporalType toTimestamp, + TemporalUnit toUnit) { + pattern.append( "extract(" ); + pattern.append( translateDurationField( unit ) ); + pattern.append( " from " ); + if ( toTimestamp != TemporalType.TIMESTAMP && fromTimestamp != TemporalType.TIMESTAMP ) { + // special case subtraction of two + // dates results in an integer not + // an Interval + pattern.append( "age(?3,?2)" ); + } + else { + switch ( unit ) { + case YEAR: + case MONTH: + case QUARTER: + pattern.append( "age(?3,?2)" ); + break; + case DAY: + case HOUR: + case MINUTE: + case SECOND: + case EPOCH: + pattern.append( "?3-?2" ); + break; + default: + throw new SemanticException( unit + " is not a legal field" ); + } + } + pattern.append( ")" ).append( unit.conversionFactor( toUnit, this ) ); + } + + @Override + public TimeZoneSupport getTimeZoneSupport() { + return TimeZoneSupport.NORMALIZE; + } + + @Override + public void initializeFunctionRegistry(QueryEngine queryEngine) { + super.initializeFunctionRegistry( queryEngine ); + + CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine); + + functionFactory.round_floor(); //Postgres round(x,n) does not accept double + functionFactory.cot(); + functionFactory.radians(); + functionFactory.degrees(); + functionFactory.trunc(); + functionFactory.log(); + if ( getVersion().isSameOrAfter(12) ) { + functionFactory.log10(); + } + else { + queryEngine.getSqmFunctionRegistry().registerAlternateKey( "log10", "log" ); + } + functionFactory.cbrt(); + functionFactory.trim2(); + functionFactory.repeat(); + functionFactory.md5(); + functionFactory.initcap(); + functionFactory.substr(); + functionFactory.substring_substr(); + //also natively supports ANSI-style substring() + functionFactory.translate(); + functionFactory.toCharNumberDateTimestamp(); + functionFactory.concat_pipeOperator( "convert_from(lo_get(?1),pg_client_encoding())" ); + functionFactory.localtimeLocaltimestamp(); + functionFactory.dateTrunc(); + functionFactory.length_characterLength_pattern( "length(lo_get(?1),pg_client_encoding())" ); + functionFactory.bitLength_pattern( "bit_length(?1)", "length(lo_get(?1))*8" ); + functionFactory.octetLength_pattern( "octet_length(?1)", "length(lo_get(?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 in Postgres 9 apparently + + functionFactory.locate_positionSubstring(); + functionFactory.windowFunctions(); + functionFactory.listagg_stringAgg( "varchar" ); + + if ( getVersion().isSameOrAfter( 9, 4 ) ) { + functionFactory.makeDateTimeTimestamp(); + // Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions + functionFactory.inverseDistributionOrderedSetAggregates(); + functionFactory.hypotheticalOrderedSetAggregates(); + } + } + + @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 getVersion().isSameOrAfter( 8, 2 ); + } + + @Override + public boolean supportsIfExistsBeforeConstraintName() { + return getVersion().isSameOrAfter( 9 ); + } + + @Override + public boolean supportsIfExistsAfterAlterTable() { + return getVersion().isSameOrAfter( 9, 2 ); + } + + @Override + public boolean supportsValuesList() { + return getVersion().isSameOrAfter( 8, 2 ); + } + + @Override + public boolean supportsPartitionBy() { + return getVersion().isSameOrAfter( 9, 1 ); + } + + @Override + public boolean supportsNonQueryWithCTE() { + return getVersion().isSameOrAfter( 9, 1 ); + } + + @Override + public SequenceSupport getSequenceSupport() { + return getVersion().isBefore( 8, 2 ) + ? PostgreSQLLegacySequenceSupport.LEGACY_INSTANCE + : PostgreSQLSequenceSupport.INSTANCE; + } + + @Override + public String getCascadeConstraintsString() { + return " cascade"; + } + + @Override + public String getQuerySequencesString() { + return "select * from information_schema.sequences"; + } + + @Override + public LimitHandler getLimitHandler() { + return getVersion().isBefore( 8, 4 ) + ? LimitOffsetLimitHandler.INSTANCE + : OffsetFetchLimitHandler.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(); + final Iterator> itr = lockOptions.getAliasLockIterator(); + while ( itr.hasNext() ) { + // seek the highest lock mode + final Map.Entry entry = itr.next(); + final LockMode lm = entry.getValue(); + if ( lm.greaterThan( lockMode ) ) { + aliases = entry.getKey(); + } + } + } + LockMode lockMode = lockOptions.getAliasSpecificLockMode( aliases ); + if (lockMode == null ) { + lockMode = lockOptions.getLockMode(); + } + switch ( lockMode ) { + case PESSIMISTIC_READ: { + return getReadLockString( aliases, lockOptions.getTimeOut() ); + } + case PESSIMISTIC_WRITE: { + return getWriteLockString( aliases, lockOptions.getTimeOut() ); + } + case UPGRADE_NOWAIT: + case PESSIMISTIC_FORCE_INCREMENT: { + return getForUpdateNowaitString(aliases); + } + case UPGRADE_SKIPLOCKED: { + return getForUpdateSkipLockedString(aliases); + } + default: { + return ""; + } + } + } + + @Override + public String getNoColumnsInsertString() { + return "default values"; + } + + @Override + public String getCaseInsensitiveLike(){ + return "ilike"; + } + + @Override + public boolean supportsCaseInsensitiveLike() { + return true; + } + + @Override + public String getNativeIdentifierGeneratorStrategy() { + return "sequence"; + } + + @Override + public boolean supportsOuterJoinForUpdate() { + return false; + } + + @Override + public boolean useInputStreamToInsertBlob() { + return false; + } + + @Override + public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) { + // Workaround for postgres bug #1453 + return "null::" + typeConfiguration.getDdlTypeRegistry().getDescriptor( sqlType ).getRawTypeName(); + } + + @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 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 PostgreSQLLegacySqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + + @Override + public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() { + return EXTRACTOR; + } + + /** + * Constraint-name extractor for Postgres constraint violation exceptions. + * Orginally contributed by Denny Bartelt. + */ + private static final ViolatedConstraintNameExtractor EXTRACTOR = + new TemplatedViolatedConstraintNameExtractor( sqle -> { + switch ( Integer.parseInt( JdbcExceptionHelper.extractSqlState( sqle ) ) ) { + // 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; + // ALL OTHER + default: + return null; + } + } ); + + @Override + public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() { + return (sqlException, message, sql) -> { + switch ( JdbcExceptionHelper.extractSqlState( sqlException ) ) { + case "40P01": + // DEADLOCK DETECTED + return new LockAcquisitionException(message, sqlException, sql); + case "55P03": + // LOCK NOT AVAILABLE + return new PessimisticLockException(message, sqlException, sql); + case "57014": + return new QueryTimeoutException( message, sqlException, sql ); + default: + // returning null allows other delegates to operate + return null; + } + }; + } + + @Override + public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException { + // Register the type of the out param - PostgreSQL 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 PostgresCallableStatementSupport.INSTANCE; + } + + @Override + public ResultSet getResultSet(CallableStatement statement, int position) throws SQLException { + if ( position != 1 ) { + throw new UnsupportedOperationException( "PostgreSQL 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( "PostgreSQL only supports accessing REF_CURSOR parameters by position" ); + } + + @Override + public boolean qualifyIndexName() { + return false; + } + + @Override + public IdentityColumnSupport getIdentityColumnSupport() { + return IDENTITY_COLUMN_SUPPORT; + } + + @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 void appendDatetimeFormat(SqlAppender appender, String format) { + appender.appendSql( datetimeFormat( format ).result() ); + } + + public Replacer datetimeFormat(String format) { + return OracleDialect.datetimeFormat( format, true, false ) + .replace("SSSSSS", "US") + .replace("SSSSS", "US") + .replace("SSSS", "US") + .replace("SSS", "MS") + .replace("SS", "MS") + .replace("S", "MS") + //use ISO day in week, as per DateTimeFormatter + .replace("ee", "ID") + .replace("e", "fmID") + //TZR is TZ in Postgres + .replace("zzz", "TZ") + .replace("zz", "TZ") + .replace("z", "TZ") + .replace("xxx", "OF") + .replace("xx", "OF") + .replace("x", "OF"); + } + + @Override + public String translateExtractField(TemporalUnit unit) { + switch ( unit ) { + //WEEK means the ISO week number on Postgres + case DAY_OF_MONTH: return "day"; + case DAY_OF_YEAR: return "doy"; + case DAY_OF_WEEK: return "dow"; + default: return super.translateExtractField( unit ); + } + } + + @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, + TemporalType precision, + TimeZone jdbcTimeZone) { + switch ( precision ) { + case DATE: + appender.appendSql( "date '" ); + appendAsDate( appender, temporalAccessor ); + appender.appendSql( '\'' ); + break; + case TIME: + appender.appendSql( "time '" ); + appendAsTime( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone ); + appender.appendSql( '\'' ); + break; + case TIMESTAMP: + appender.appendSql( "timestamp with time zone '" ); + appendAsTimestampWithMicros( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone ); + appender.appendSql( '\'' ); + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + public void appendDateTimeLiteral(SqlAppender appender, Date date, TemporalType precision, TimeZone jdbcTimeZone) { + switch ( precision ) { + case DATE: + appender.appendSql( "date '" ); + appendAsDate( appender, date ); + appender.appendSql( '\'' ); + break; + case TIME: + appender.appendSql( "time '" ); + appendAsTime( appender, date ); + 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, + TemporalType precision, + TimeZone jdbcTimeZone) { + switch ( precision ) { + case DATE: + appender.appendSql( "date '" ); + appendAsDate( appender, calendar ); + appender.appendSql( '\'' ); + break; + case TIME: + appender.appendSql( "time '" ); + appendAsTime( appender, calendar ); + appender.appendSql( '\'' ); + break; + case TIMESTAMP: + appender.appendSql( "timestamp with time zone '" ); + appendAsTimestampWithMicros( appender, calendar, jdbcTimeZone ); + appender.appendSql( '\'' ); + break; + default: + throw new IllegalArgumentException(); + } + } + + private String withTimeout(String lockString, int timeout) { + switch (timeout) { + case LockOptions.NO_WAIT: + return supportsNoWait() ? lockString + " nowait" : lockString; + case LockOptions.SKIP_LOCKED: + return supportsSkipLocked() ? lockString + " skip locked" : lockString; + default: + return 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 getVersion().isSameOrAfter( 8, 1 ); + } + + @Override + public boolean supportsWait() { + return false; + } + + @Override + public boolean supportsSkipLocked() { + return getVersion().isSameOrAfter( 9, 5 ); + } + + @Override + public boolean supportsOffsetInSubquery() { + return true; + } + + @Override + public boolean supportsWindowFunctions() { + return true; + } + + @Override + public boolean supportsLateral() { + return getVersion().isSameOrAfter( 9, 3 ); + } + + @Override + public boolean supportsFetchClause(FetchClauseType type) { + switch ( type ) { + case ROWS_ONLY: + return getVersion().isSameOrAfter( 8, 4 ); + case PERCENT_ONLY: + case PERCENT_WITH_TIES: + return false; + case ROWS_WITH_TIES: + return getVersion().isSameOrAfter( 13 ); + } + return false; + } + + @Override + public RowLockStrategy getWriteRowLockStrategy() { + return RowLockStrategy.TABLE; + } + + @Override + public void augmentRecognizedTableTypes(List tableTypesList) { + super.augmentRecognizedTableTypes( tableTypesList ); + if ( getVersion().isSameOrAfter( 9, 3 ) ) { + tableTypesList.add( "MATERIALIZED VIEW" ); + + /* + PostgreSQL 10 and later adds support for Partition table. + */ + if ( getVersion().isSameOrAfter( 10 ) ) { + tableTypesList.add( "PARTITIONED TABLE" ); + } + } + } + + @Override + public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + super.contributeTypes(typeContributions, serviceRegistry); + + final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() + .getJdbcTypeRegistry(); + // For discussion of BLOB support in Postgres, as of 8.4, have a peek at + // http://jdbc.postgresql.org/documentation/84/binary-data.html. + // For the effects in regards to Hibernate see http://in.relation.to/15492.lace + + // Force BLOB binding. Otherwise, byte[] fields annotated + // with @Lob will attempt to use + // BlobTypeDescriptor.PRIMITIVE_ARRAY_BINDING. Since the + // dialect uses oid for Blobs, byte arrays cannot be used. + jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.BLOB_BINDING ); + jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING ); + jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE ); + + if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) { + if ( PostgreSQLPGObjectJdbcType.isUsable() ) { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLInetJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLIntervalSecondJdbcType.INSTANCE ); + } + + if ( getVersion().isSameOrAfter( 8, 2 ) ) { + // HHH-9562 + jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); + if ( getVersion().isSameOrAfter( 9, 2 ) ) { + if ( PostgreSQLPGObjectJdbcType.isUsable() ) { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLJsonbJdbcType.INSTANCE ); + } + } + } + } + + // PostgreSQL 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 ) + ) + ); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacySqlAstTranslator.java new file mode 100644 index 0000000000..1e07bffa17 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacySqlAstTranslator.java @@ -0,0 +1,191 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.community.dialect; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.sqm.FetchClauseType; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +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.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.sql.ast.tree.expression.Summarization; +import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate; +import org.hibernate.sql.ast.tree.predicate.LikePredicate; +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.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for PostgreSQL. + * + * @author Christian Beikov + */ +public class PostgreSQLLegacySqlAstTranslator extends AbstractSqlAstTranslator { + + public PostgreSQLLegacySqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected void renderExpressionAsClauseItem(Expression expression) { + expression.accept( this ); + } + + @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 + protected void renderMaterializationHint(CteMaterialization materialization) { + if ( getDialect().getVersion().isSameOrAfter( 12 ) ) { + if ( materialization == CteMaterialization.NOT_MATERIALIZED ) { + appendSql( "not " ); + } + appendSql( "materialized " ); + } + } + + @Override + public boolean supportsFilterClause() { + return getDialect().getVersion().isSameOrAfter( 9, 4 ); + } + + @Override + protected String getForUpdate() { + return getDialect().getVersion().isSameOrAfter( 9, 3 ) ? " for no key update" : " for update"; + } + + @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 renderSearchClause(CteStatement cte) { + // PostgreSQL does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // PostgreSQL does not support this, but it can be emulated + } + + @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 ) { + if ( getDialect().getVersion().isSameOrAfter( 9, 5 ) ) { + appendSql( "()" ); + } + else { + appendSql( "(select 1" ); + appendSql( getFromDualForSelectOnly() ); + appendSql( ')' ); + } + } + else if ( expression instanceof Summarization ) { + Summarization summarization = (Summarization) expression; + if ( getDialect().getVersion().isSameOrAfter( 9, 5 ) ) { + appendSql( summarization.getKind().sqlText() ); + appendSql( OPEN_PARENTHESIS ); + renderCommaSeparated( summarization.getGroupings() ); + appendSql( CLOSE_PARENTHESIS ); + } + else { + // This could theoretically be emulated by rendering all grouping variations of the query and + // connect them via union all but that's probably pretty inefficient and would have to happen + // on the query spec level + throw new UnsupportedOperationException( "Summarization is not supported by DBMS" ); + } + } + else { + expression.accept( this ); + } + } + + @Override + public void visitLikePredicate(LikePredicate likePredicate) { + // We need a custom implementation here because PostgreSQL + // 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 ''" ); + } + } + +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/PostgreSQLLegacySequenceSupport.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/PostgreSQLLegacySequenceSupport.java new file mode 100644 index 0000000000..f5a6503093 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/PostgreSQLLegacySequenceSupport.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.community.dialect.sequence; + +import org.hibernate.MappingException; +import org.hibernate.dialect.sequence.SequenceSupport; + +/** + * Sequence support for {@link org.hibernate.dialect.PostgreSQLDialect}. + * + * @author Gavin King + */ +public class PostgreSQLLegacySequenceSupport implements SequenceSupport { + + public static final SequenceSupport INSTANCE = new PostgreSQLLegacySequenceSupport(); + + public static final SequenceSupport LEGACY_INSTANCE = new PostgreSQLLegacySequenceSupport() { + @Override + public String getDropSequenceString(String sequenceName) { + return "drop sequence " + sequenceName; + } + }; + + @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/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 3806416bf0..3e8c5bafe5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -28,7 +28,6 @@ import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; -import org.hibernate.dialect.pagination.LimitOffsetLimitHandler; import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; import org.hibernate.dialect.sequence.PostgreSQLSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; @@ -121,13 +120,13 @@ import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithM * @author Gavin King */ public class PostgreSQLDialect extends Dialect { - + private final static DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 10 ); private static final PostgreSQLIdentityColumnSupport IDENTITY_COLUMN_SUPPORT = new PostgreSQLIdentityColumnSupport(); private final PostgreSQLDriverKind driverKind; public PostgreSQLDialect() { - this( DatabaseVersion.make( 8, 0 ) ); + this( MINIMUM_VERSION ); } public PostgreSQLDialect(DialectResolutionInfo info) { @@ -145,6 +144,11 @@ public class PostgreSQLDialect extends Dialect { this.driverKind = driverKind; } + @Override + protected DatabaseVersion getMinimumSupportedVersion() { + return MINIMUM_VERSION; + } + @Override public boolean getDefaultNonContextualLobCreation() { return true; @@ -220,9 +224,7 @@ public class PostgreSQLDialect extends Dialect { ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "xml", this ) ); - if ( getVersion().isSameOrAfter( 8, 2 ) ) { - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) ); - } + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) ); if ( PostgreSQLPGObjectJdbcType.isUsable() ) { // The following DDL types require that the PGobject class is usable/visible ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) ); @@ -230,15 +232,8 @@ public class PostgreSQLDialect extends Dialect { ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) ); ddlTypeRegistry.addDescriptor( new Scale6IntervalSecondDdlType( this ) ); - if ( getVersion().isSameOrAfter( 9, 2 ) ) { - // Prefer jsonb if possible - if ( getVersion().isSameOrAfter( 9, 4 ) ) { - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); - } - else { - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) ); - } - } + // Prefer jsonb if possible + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); } } @@ -543,12 +538,10 @@ public class PostgreSQLDialect extends Dialect { functionFactory.windowFunctions(); functionFactory.listagg_stringAgg( "varchar" ); - if ( getVersion().isSameOrAfter( 9, 4 ) ) { - functionFactory.makeDateTimeTimestamp(); - // Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions - functionFactory.inverseDistributionOrderedSetAggregates(); - functionFactory.hypotheticalOrderedSetAggregates(); - } + functionFactory.makeDateTimeTimestamp(); + // Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions + functionFactory.inverseDistributionOrderedSetAggregates(); + functionFactory.hypotheticalOrderedSetAggregates(); } @Override @@ -570,39 +563,37 @@ public class PostgreSQLDialect extends Dialect { @Override public boolean supportsIfExistsBeforeTableName() { - return getVersion().isSameOrAfter( 8, 2 ); + return true; } @Override public boolean supportsIfExistsBeforeConstraintName() { - return getVersion().isSameOrAfter( 9 ); + return true; } @Override public boolean supportsIfExistsAfterAlterTable() { - return getVersion().isSameOrAfter( 9, 2 ); + return true; } @Override public boolean supportsValuesList() { - return getVersion().isSameOrAfter( 8, 2 ); + return true; } @Override public boolean supportsPartitionBy() { - return getVersion().isSameOrAfter( 9, 1 ); + return true; } @Override public boolean supportsNonQueryWithCTE() { - return getVersion().isSameOrAfter( 9, 1 ); + return true; } @Override public SequenceSupport getSequenceSupport() { - return getVersion().isBefore( 8, 2 ) - ? PostgreSQLSequenceSupport.LEGACY_INSTANCE - : PostgreSQLSequenceSupport.INSTANCE; + return PostgreSQLSequenceSupport.INSTANCE; } @Override @@ -617,9 +608,7 @@ public class PostgreSQLDialect extends Dialect { @Override public LimitHandler getLimitHandler() { - return getVersion().isBefore( 8, 4 ) - ? LimitOffsetLimitHandler.INSTANCE - : OffsetFetchLimitHandler.INSTANCE; + return OffsetFetchLimitHandler.INSTANCE; } @Override @@ -780,7 +769,7 @@ public class PostgreSQLDialect extends Dialect { /** * Constraint-name extractor for Postgres constraint violation exceptions. - * Orginally contributed by Denny Bartelt. + * Originally contributed by Denny Bartelt. */ private static final ViolatedConstraintNameExtractor EXTRACTOR = new TemplatedViolatedConstraintNameExtractor( sqle -> { @@ -1084,7 +1073,7 @@ public class PostgreSQLDialect extends Dialect { @Override public boolean supportsNoWait() { - return getVersion().isSameOrAfter( 8, 1 ); + return true; } @Override @@ -1094,7 +1083,7 @@ public class PostgreSQLDialect extends Dialect { @Override public boolean supportsSkipLocked() { - return getVersion().isSameOrAfter( 9, 5 ); + return true; } @Override @@ -1109,14 +1098,14 @@ public class PostgreSQLDialect extends Dialect { @Override public boolean supportsLateral() { - return getVersion().isSameOrAfter( 9, 3 ); + return true; } @Override public boolean supportsFetchClause(FetchClauseType type) { switch ( type ) { case ROWS_ONLY: - return getVersion().isSameOrAfter( 8, 4 ); + return true; case PERCENT_ONLY: case PERCENT_WITH_TIES: return false; @@ -1134,16 +1123,12 @@ public class PostgreSQLDialect extends Dialect { @Override public void augmentRecognizedTableTypes(List tableTypesList) { super.augmentRecognizedTableTypes( tableTypesList ); - if ( getVersion().isSameOrAfter( 9, 3 ) ) { - tableTypesList.add( "MATERIALIZED VIEW" ); + tableTypesList.add( "MATERIALIZED VIEW" ); - /* - PostgreSQL 10 and later adds support for Partition table. - */ - if ( getVersion().isSameOrAfter( 10 ) ) { - tableTypesList.add( "PARTITIONED TABLE" ); - } - } + /* + PostgreSQL 10 and later adds support for Partition table. + */ + tableTypesList.add( "PARTITIONED TABLE" ); } @Override @@ -1171,14 +1156,10 @@ public class PostgreSQLDialect extends Dialect { jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLIntervalSecondJdbcType.INSTANCE ); } - if ( getVersion().isSameOrAfter( 8, 2 ) ) { - // HHH-9562 - jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); - if ( getVersion().isSameOrAfter( 9, 2 ) ) { - if ( PostgreSQLPGObjectJdbcType.isUsable() ) { - jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLJsonbJdbcType.INSTANCE ); - } - } + // HHH-9562 + jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); + if ( PostgreSQLPGObjectJdbcType.isUsable() ) { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLJsonbJdbcType.INSTANCE ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/sequence/PostgreSQLSequenceSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/sequence/PostgreSQLSequenceSupport.java index ac8d36f838..76f985d386 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/sequence/PostgreSQLSequenceSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/sequence/PostgreSQLSequenceSupport.java @@ -17,13 +17,6 @@ public class PostgreSQLSequenceSupport implements SequenceSupport { public static final SequenceSupport INSTANCE = new PostgreSQLSequenceSupport(); - public static final SequenceSupport LEGACY_INSTANCE = new PostgreSQLSequenceSupport() { - @Override - public String getDropSequenceString(String sequenceName) { - return "drop sequence " + sequenceName; - } - }; - @Override public String getSelectSequenceNextValString(String sequenceName) { return "nextval('" + sequenceName + "')"; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQL92DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQL92DialectTestCase.java deleted file mode 100644 index 09d5439089..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQL92DialectTestCase.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.orm.test.dialect; - -import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.PostgreSQLDialect; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseUnitTestCase; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * Test case for PostgreSQL 9.2 specific things. - * - * @author Christoph Dreis - */ -@TestForIssue( jiraKey = "HHH-11647") -public class PostgreSQL92DialectTestCase extends BaseUnitTestCase { - - /** - * Tests that getAlterTableString() will make use of IF EXISTS syntax - */ - @Test - @TestForIssue( jiraKey = "HHH-11647" ) - public void testGetAlterTableString() { - PostgreSQLDialect dialect = new PostgreSQLDialect( DatabaseVersion.make( 9, 2 ) ); - - assertEquals("alter table if exists table_name", dialect.getAlterTableString( "table_name" )); - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQL81DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQLDialectTestCase.java similarity index 77% rename from hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQL81DialectTestCase.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQLDialectTestCase.java index f169141b35..ec09836199 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQL81DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/PostgreSQLDialectTestCase.java @@ -14,7 +14,7 @@ import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.PessimisticLockException; -import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.QueryTimeoutException; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; @@ -25,23 +25,24 @@ import org.junit.Test; import org.mockito.Mockito; -import static junit.framework.TestCase.assertEquals; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** - * Testing of patched support for PostgreSQL Lock error detection. HHH-7251 - * + * Test case for PostgreSQL specific things. * @author Bryan Varner + * @author Christoph Dreis */ -public class PostgreSQL81DialectTestCase extends BaseUnitTestCase { +public class PostgreSQLDialectTestCase extends BaseUnitTestCase { @Test + @TestForIssue( jiraKey = "HHH-7251") public void testDeadlockException() { - PostgreSQL81Dialect dialect = new PostgreSQL81Dialect(); + PostgreSQLDialect dialect = new PostgreSQLDialect(); SQLExceptionConversionDelegate delegate = dialect.buildSQLExceptionConversionDelegate(); assertNotNull(delegate); @@ -50,8 +51,9 @@ public class PostgreSQL81DialectTestCase extends BaseUnitTestCase { } @Test + @TestForIssue( jiraKey = "HHH-7251") public void testTimeoutException() { - PostgreSQL81Dialect dialect = new PostgreSQL81Dialect(); + PostgreSQLDialect dialect = new PostgreSQLDialect(); SQLExceptionConversionDelegate delegate = dialect.buildSQLExceptionConversionDelegate(); assertNotNull(delegate); @@ -62,7 +64,7 @@ public class PostgreSQL81DialectTestCase extends BaseUnitTestCase { @Test @TestForIssue( jiraKey = "HHH-13661") public void testQueryTimeoutException() { - final PostgreSQL81Dialect dialect = new PostgreSQL81Dialect(); + final PostgreSQLDialect dialect = new PostgreSQLDialect(); final SQLExceptionConversionDelegate delegate = dialect.buildSQLExceptionConversionDelegate(); assertNotNull( delegate ); @@ -76,21 +78,21 @@ public class PostgreSQL81DialectTestCase extends BaseUnitTestCase { */ @TestForIssue( jiraKey = "HHH-5654" ) public void testGetForUpdateStringWithAliasesAndLockOptions() { - PostgreSQL81Dialect dialect = new PostgreSQL81Dialect(); + PostgreSQLDialect dialect = new PostgreSQLDialect(); LockOptions lockOptions = new LockOptions(); lockOptions.setAliasSpecificLockMode("tableAlias1", LockMode.PESSIMISTIC_WRITE); String forUpdateClause = dialect.getForUpdateString("tableAlias1", lockOptions); - assertTrue("for update of tableAlias1".equals(forUpdateClause)); + assertEquals( "for update of tableAlias1", forUpdateClause ); lockOptions.setAliasSpecificLockMode("tableAlias2", LockMode.PESSIMISTIC_WRITE); forUpdateClause = dialect.getForUpdateString("tableAlias1,tableAlias2", lockOptions); - assertTrue("for update of tableAlias1,tableAlias2".equals(forUpdateClause)); + assertEquals("for update of tableAlias1,tableAlias2", forUpdateClause); } @Test public void testExtractConstraintName() { - PostgreSQL81Dialect dialect = new PostgreSQL81Dialect(); + PostgreSQLDialect dialect = new PostgreSQLDialect(); SQLException psqlException = new SQLException("ERROR: duplicate key value violates unique constraint \"uk_4bm1x2ultdmq63y3h5r3eg0ej\" Detail: Key (username, server_config)=(user, 1) already exists.", "23505"); BatchUpdateException batchUpdateException = new BatchUpdateException("Concurrent Error", "23505", null); batchUpdateException.setNextException(psqlException); @@ -100,8 +102,8 @@ public class PostgreSQL81DialectTestCase extends BaseUnitTestCase { @Test @TestForIssue(jiraKey = "HHH-8687") - public void testMessageException() throws SQLException { - PostgreSQL81Dialect dialect = new PostgreSQL81Dialect(); + public void testMessageException() { + PostgreSQLDialect dialect = new PostgreSQLDialect(); try { dialect.getResultSet( Mockito.mock( CallableStatement.class), "abc" ); fail( "Expected UnsupportedOperationException" ); @@ -111,4 +113,15 @@ public class PostgreSQL81DialectTestCase extends BaseUnitTestCase { assertEquals( "PostgreSQL only supports accessing REF_CURSOR parameters by position", e.getMessage() ); } } + + /** + * Tests that getAlterTableString() will make use of IF EXISTS syntax + */ + @Test + @TestForIssue( jiraKey = "HHH-11647" ) + public void testGetAlterTableString() { + PostgreSQLDialect dialect = new PostgreSQLDialect(); + + assertEquals("alter table if exists table_name", dialect.getAlterTableString( "table_name" )); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/PostgreSQLLockTimeoutTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/PostgreSQLLockTimeoutTest.java index 69e3d830df..e16658db1f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/PostgreSQLLockTimeoutTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/PostgreSQLLockTimeoutTest.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertEquals; */ public class PostgreSQLLockTimeoutTest extends BaseUnitTestCase { - private final Dialect dialect = new PostgreSQLDialect( DatabaseVersion.make( 9, 5 ) ); + private final Dialect dialect = new PostgreSQLDialect(); @Test public void testLockTimeoutNoAliasNoTimeout() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/PostgreSQLSequenceGeneratorWithSerialTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/PostgreSQLSequenceGeneratorWithSerialTest.java index cce231800d..32fc70e01f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/PostgreSQLSequenceGeneratorWithSerialTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/PostgreSQLSequenceGeneratorWithSerialTest.java @@ -46,7 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; * @author Vlad Mihalcea */ @TestForIssue(jiraKey = "HHH-12973") -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8, minorVersion = 2) +@RequiresDialect(value = PostgreSQLDialect.class) public class PostgreSQLSequenceGeneratorWithSerialTest extends EntityManagerFactoryBasedFunctionalTest { @Rule diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/sequence/PostgreSQLIdentitySequenceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/sequence/PostgreSQLIdentitySequenceTest.java index 6777a97640..f69b95354d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/sequence/PostgreSQLIdentitySequenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/sequence/PostgreSQLIdentitySequenceTest.java @@ -36,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.fail; * @author Vlad Mhalcea */ @TestForIssue(jiraKey = "HHH-13106") -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 10) +@RequiresDialect(value = PostgreSQLDialect.class) @Jpa( annotatedClasses = PostgreSQLIdentitySequenceTest.Role.class ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/PostgreSQLUUIDGeneratedValueTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/PostgreSQLUUIDGeneratedValueTest.java index 12157f2edc..893bd861bc 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/PostgreSQLUUIDGeneratedValueTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/PostgreSQLUUIDGeneratedValueTest.java @@ -30,7 +30,7 @@ import static org.hamcrest.Matchers.notNullValue; /** * @author Vlad Mihalcea */ -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 9, minorVersion = 4) +@RequiresDialect(value = PostgreSQLDialect.class) @DomainModel(annotatedClasses = { PostgreSQLUUIDGeneratedValueTest.Book.class }) @SessionFactory public class PostgreSQLUUIDGeneratedValueTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/interpretation/UUIDBasedIdInterpretationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/interpretation/UUIDBasedIdInterpretationTest.java index d2b5b42f2b..15524e7773 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/interpretation/UUIDBasedIdInterpretationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/interpretation/UUIDBasedIdInterpretationTest.java @@ -66,7 +66,7 @@ public class UUIDBasedIdInterpretationTest { @Test @JiraKey( "HHH-10564" ) - @RequiresDialect( value = PostgreSQLDialect.class, majorVersion = 9, minorVersion = 4 ) + @RequiresDialect( value = PostgreSQLDialect.class ) public void testPostgreSQL(DomainModelScope scope) { checkUuidTypeUsed( scope, UUIDJdbcType.class ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/PersistChildEntitiesWithDiscriminatorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/PersistChildEntitiesWithDiscriminatorTest.java index 3297ee808d..0b3ba9693d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/PersistChildEntitiesWithDiscriminatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/PersistChildEntitiesWithDiscriminatorTest.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; * @author Pawel Stawicki */ @SessionFactory -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8) +@RequiresDialect(value = PostgreSQLDialect.class) @DomainModel(annotatedClasses = { ParentEntity.class, InheritingEntity.class }) @@ -34,14 +34,14 @@ public class PersistChildEntitiesWithDiscriminatorTest { session -> { // we need the 2 inserts so that the id is incremented on the second get-generated-keys-result set, since // on the first insert both the pk and the discriminator values are 1 - session.save( new InheritingEntity( "yabba" ) ); - session.save( new InheritingEntity( "dabba" ) ); + session.persist( new InheritingEntity( "yabba" ) ); + session.persist( new InheritingEntity( "dabba" ) ); } ); scope.inTransaction( session -> { - session.createQuery( "delete ParentEntity" ).executeUpdate(); + session.createQuery( "delete ParentEntity", null ).executeUpdate(); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryOrdinalParametersTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryOrdinalParametersTest.java index e62c329253..01aad24050 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryOrdinalParametersTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NativeQueryOrdinalParametersTest.java @@ -7,6 +7,7 @@ package org.hibernate.orm.test.jpa.query; import java.util.List; + import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -123,7 +124,7 @@ public class NativeQueryOrdinalParametersTest { @Test @TestForIssue(jiraKey = "HHH-12532") // Add RequiresDialect be Cockroach version 201 - @RequiresDialect( value = PostgreSQLDialect.class, majorVersion = 8, minorVersion = 2 ) + @RequiresDialect( value = PostgreSQLDialect.class ) @RequiresDialect( value = CockroachDialect.class, majorVersion = 20, minorVersion = 1 ) public void testCteNativeQueryOrdinalParameter(EntityManagerFactoryScope scope) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/nationalized/StringNationalizedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/nationalized/StringNationalizedTest.java index deef4ea5a3..f7f1e2e920 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/nationalized/StringNationalizedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/nationalized/StringNationalizedTest.java @@ -36,7 +36,7 @@ import static org.hamcrest.core.Is.is; @RequiresDialects( value = { @RequiresDialect(value = OracleDialect.class), - @RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8, minorVersion = 1) + @RequiresDialect(value = PostgreSQLDialect.class) }) @DomainModel( annotatedClasses = StringNationalizedTest.NationalizedEntity.class @@ -48,7 +48,7 @@ public class StringNationalizedTest { public void tearDown(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery( "delete from NationalizedEntity" ).executeUpdate(); + session.createQuery( "delete from NationalizedEntity", null ).executeUpdate(); } ); } @@ -59,13 +59,13 @@ public class StringNationalizedTest { session -> { NationalizedEntity ne = new NationalizedEntity(); ne.name = "Hello"; - session.save( ne ); + session.persist( ne ); } ); scope.inSession( session -> { - final Query query = session.createQuery( "from NationalizedEntity where name = :name" ); + final Query query = session.createQuery( "from NationalizedEntity where name = :name", null ); query.setParameter( "name", "Hello" ); final List list = query.list(); assertThat( list.size(), is( 1 ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java index 2faa64d912..de8351fe13 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/AlterTableQuoteDefaultSchemaTest.java @@ -46,7 +46,7 @@ import static org.junit.jupiter.api.Assertions.fail; */ @TestForIssue(jiraKey = "HHH-12939") @RequiresDialect(value = H2Dialect.class) -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8, minorVersion = 2) +@RequiresDialect(value = PostgreSQLDialect.class) @RequiresDialect(value = SQLServerDialect.class, majorVersion = 11) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportSchemaCreation.class) public class AlterTableQuoteDefaultSchemaTest extends AbstractAlterTableQuoteSchemaTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java index c3f38c3d5c..c9b9022eda 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/AlterTableQuoteSpecifiedSchemaTest.java @@ -45,7 +45,7 @@ import static org.junit.jupiter.api.Assertions.fail; */ @TestForIssue(jiraKey = "HHH-12939") @RequiresDialect(value = H2Dialect.class) -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8, minorVersion = 2) +@RequiresDialect(value = PostgreSQLDialect.class) @RequiresDialect(value = SQLServerDialect.class, majorVersion = 11) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportSchemaCreation.class) public class AlterTableQuoteSpecifiedSchemaTest extends AbstractAlterTableQuoteSchemaTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampUTCTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampUTCTimeZoneTest.java index 6c8a4af00b..5164e6da8f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampUTCTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampUTCTimeZoneTest.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.AfterAll; /** * @author Vlad Mihalcea */ -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8, minorVersion = 2) +@RequiresDialect(value = PostgreSQLDialect.class) public class JdbcTimestampUTCTimeZoneTest extends JdbcTimestampWithoutUTCTimeZoneTest { private TimeZoneConnectionProvider connectionProvider; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampWithDefaultUTCTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampWithDefaultUTCTimeZoneTest.java index e7ed1690d7..d39f78050b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampWithDefaultUTCTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampWithDefaultUTCTimeZoneTest.java @@ -18,7 +18,7 @@ import org.junit.jupiter.api.AfterAll; /** * @author Vlad Mihalcea */ -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8, minorVersion = 2) +@RequiresDialect(value = PostgreSQLDialect.class) public class JdbcTimestampWithDefaultUTCTimeZoneTest extends JdbcTimestampWithoutUTCTimeZoneTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampWithoutUTCTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampWithoutUTCTimeZoneTest.java index 0ad8c90b3a..58efae18e2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampWithoutUTCTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampWithoutUTCTimeZoneTest.java @@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Vlad Mihalcea */ -@RequiresDialect(value = PostgreSQLDialect.class, majorVersion = 8, minorVersion = 2) +@RequiresDialect(value = PostgreSQLDialect.class) public class JdbcTimestampWithoutUTCTimeZoneTest extends BaseSessionFactoryFunctionalTest { private TimeZoneConnectionProvider connectionProvider; 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 e3ff313d7f..9b3705b5bb 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 @@ -259,7 +259,7 @@ abstract public class DialectFeatureChecks { public boolean apply(Dialect dialect) { return dialect instanceof DB2Dialect || dialect instanceof OracleDialect - || dialect instanceof PostgreSQLDialect && dialect.getVersion().isSameOrAfter( 9, 5 ) + || dialect instanceof PostgreSQLDialect || dialect instanceof SQLServerDialect || dialect instanceof DerbyDialect || dialect instanceof MySQLDialect && !(dialect instanceof TiDBDialect) @@ -271,7 +271,7 @@ abstract public class DialectFeatureChecks { public boolean apply(Dialect dialect) { return dialect instanceof DB2Dialect || dialect instanceof OracleDialect - || dialect instanceof PostgreSQLDialect && dialect.getVersion().isSameOrAfter( 9, 5 ) + || dialect instanceof PostgreSQLDialect || dialect instanceof SQLServerDialect; } }