From 4d4aaf3b4a96c46e1a0c3a91a8cb82aa081117ac Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Thu, 23 Jun 2022 18:45:58 +0200 Subject: [PATCH] HHH-15191 - Remove support for Derby versions older than 10.14.2.0 Signed-off-by: Jan Schatteman --- .../community/dialect/DerbyLegacyDialect.java | 957 ++++++++++++++++++ .../dialect/DerbyLegacySqlAstTranslator.java | 299 ++++++ .../dialect/DerbyLegacyDialectTestCase.java | 93 ++ ...alectSequenceInformationExtractorTest.java | 7 +- ...alectSequenceInformationExtractorTest.java | 7 +- ...alectSequenceInformationExtractorTest.java | 7 +- .../org/hibernate/dialect/DB2Dialect.java | 2 +- .../org/hibernate/dialect/DerbyDialect.java | 43 +- .../dialect/DerbySqlAstTranslator.java | 4 +- .../test/dialect/DerbyDialectTestCase.java | 23 +- 10 files changed, 1384 insertions(+), 58 deletions(-) create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacySqlAstTranslator.java create mode 100644 hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/DerbyLegacyDialectTestCase.java rename {hibernate-core/src/test/java/org/hibernate/orm/test => hibernate-community-dialects/src/test/java/org/hibernate/community}/dialect/unit/sequence/DerbyTenFiveDialectSequenceInformationExtractorTest.java (78%) rename {hibernate-core/src/test/java/org/hibernate/orm/test => hibernate-community-dialects/src/test/java/org/hibernate/community}/dialect/unit/sequence/DerbyTenSevenDialectSequenceInformationExtractorTest.java (81%) rename {hibernate-core/src/test/java/org/hibernate/orm/test => hibernate-community-dialects/src/test/java/org/hibernate/community}/dialect/unit/sequence/DerbyTenSixDialectSequenceInformationExtractorTest.java (81%) diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java new file mode 100644 index 0000000000..4f44309567 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacyDialect.java @@ -0,0 +1,957 @@ +/* + * 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.DatabaseMetaData; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.boot.model.TypeContributions; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.DatabaseVersion; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.NationalizationSupport; +import org.hibernate.dialect.RowLockStrategy; +import org.hibernate.dialect.function.CaseLeastGreatestEmulation; +import org.hibernate.dialect.function.CastingConcatFunction; +import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.function.CountFunction; +import org.hibernate.dialect.function.DerbyLpadEmulation; +import org.hibernate.dialect.function.DerbyRpadEmulation; +import org.hibernate.dialect.function.InsertSubstringOverlayEmulation; +import org.hibernate.dialect.identity.DB2IdentityColumnSupport; +import org.hibernate.dialect.identity.IdentityColumnSupport; +import org.hibernate.dialect.pagination.AbstractLimitHandler; +import org.hibernate.dialect.pagination.DerbyLimitHandler; +import org.hibernate.dialect.pagination.LimitHandler; +import org.hibernate.dialect.sequence.DerbySequenceSupport; +import org.hibernate.dialect.sequence.SequenceSupport; +import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableKind; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; +import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.exception.LockTimeoutException; +import org.hibernate.exception.spi.SQLExceptionConversionDelegate; +import org.hibernate.internal.util.JdbcExceptionHelper; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.CastType; +import org.hibernate.query.sqm.IntervalType; +import org.hibernate.query.sqm.TemporalUnit; +import org.hibernate.query.sqm.mutation.internal.temptable.BeforeUseAction; +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; +import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; +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.SqlAstNodeRenderingMode; +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.tool.schema.extract.internal.SequenceInformationExtractorDerbyDatabaseImpl; +import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl; +import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; +import org.hibernate.type.BasicType; +import org.hibernate.type.BasicTypeRegistry; +import org.hibernate.type.JavaObjectType; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.java.BigDecimalJavaType; +import org.hibernate.type.descriptor.jdbc.DecimalJdbcType; +import org.hibernate.type.descriptor.jdbc.ObjectNullResolvingJdbcType; +import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType; +import org.hibernate.type.descriptor.jdbc.TimestampJdbcType; +import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; +import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType; +import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; +import org.hibernate.type.spi.TypeConfiguration; + +import jakarta.persistence.TemporalType; + +import static org.hibernate.type.SqlTypes.BINARY; +import static org.hibernate.type.SqlTypes.BLOB; +import static org.hibernate.type.SqlTypes.BOOLEAN; +import static org.hibernate.type.SqlTypes.CHAR; +import static org.hibernate.type.SqlTypes.CLOB; +import static org.hibernate.type.SqlTypes.DECIMAL; +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.NUMERIC; +import static org.hibernate.type.SqlTypes.NVARCHAR; +import static org.hibernate.type.SqlTypes.TIMESTAMP; +import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; +import static org.hibernate.type.SqlTypes.TINYINT; +import static org.hibernate.type.SqlTypes.VARBINARY; +import static org.hibernate.type.SqlTypes.VARCHAR; + +/** + * A {@linkplain Dialect SQL dialect} for Apache Derby. + * + * @author Simon Johnston + * @author Gavin King + * + */ +public class DerbyLegacyDialect extends Dialect { + + // KNOWN LIMITATIONS: + + // no support for nationalized data (nchar, nvarchar, nclob) + // * limited set of fields for extract() + // (no 'day of xxxx', nor 'week of xxxx') + // * no support for format() + // * pad() can only pad with blanks + // * can't cast String to Binary + // * can't select a parameter unless wrapped + // in a cast or function call + + private final LimitHandler limitHandler = getVersion().isBefore( 10, 5 ) + ? AbstractLimitHandler.NO_LIMIT + : new DerbyLimitHandler( getVersion().isSameOrAfter( 10, 6 ) ); + + public DerbyLegacyDialect() { + this( DatabaseVersion.make( 10, 0 ) ); + } + + public DerbyLegacyDialect(DatabaseVersion version) { + super(version); + } + + public DerbyLegacyDialect(DialectResolutionInfo info) { + super(info); + } + + @Override + protected String columnType(int sqlTypeCode) { + switch ( sqlTypeCode ) { + case BOOLEAN: + return getVersion().isBefore( 10, 7 ) ? "smallint" : super.columnType( sqlTypeCode ); + case TINYINT: + //no tinyint + return "smallint"; + + case NUMERIC: + // HHH-12827: map them both to the same type to avoid problems with schema update + // Note that 31 is the maximum precision Derby supports + return columnType( DECIMAL ); + + case VARBINARY: + return "varchar($l) for bit data"; + case LONG32VARBINARY: + return "long varchar for bit data"; + case NCHAR: + return columnType( CHAR ); + case NVARCHAR: + return columnType( VARCHAR ); + case LONG32VARCHAR: + return "long varchar"; + case BLOB: + return "blob"; + case CLOB: + case NCLOB: + return "clob"; + case TIMESTAMP: + case TIMESTAMP_WITH_TIMEZONE: + return "timestamp"; + } + return super.columnType( sqlTypeCode ); + } + + @Override + protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + super.registerColumnTypes( typeContributions, serviceRegistry ); + final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry(); + + //long varchar is the right type to use for lengths between 32_672 and 32_700 + int maxLongVarcharLength = 32_700; + + ddlTypeRegistry.addDescriptor( + CapacityDependentDdlType.builder( VARBINARY, columnType( BLOB ), columnType( VARBINARY ), this ) + .withTypeCapacity( getMaxVarbinaryLength(), columnType( VARBINARY ) ) + .withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARBINARY ) ) + .build() + ); + ddlTypeRegistry.addDescriptor( + CapacityDependentDdlType.builder( VARCHAR, columnType( CLOB ), columnType( VARCHAR ), this ) + .withTypeCapacity( getMaxVarcharLength(), columnType( VARCHAR ) ) + .withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARCHAR ) ) + .build() + ); + ddlTypeRegistry.addDescriptor( + CapacityDependentDdlType.builder( NVARCHAR, columnType( CLOB ), columnType( NVARCHAR ), this ) + .withTypeCapacity( getMaxVarcharLength(), columnType( NVARCHAR ) ) + .withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARCHAR ) ) + .build() + ); + + ddlTypeRegistry.addDescriptor( + CapacityDependentDdlType.builder( BINARY, columnType( BLOB ), columnType( VARBINARY ), this ) + .withTypeCapacity( 254, "char($l) for bit data" ) + .withTypeCapacity( getMaxVarbinaryLength(), columnType( VARBINARY ) ) + .withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARBINARY ) ) + .build() + ); + + // This is the maximum size for the CHAR datatype on Derby + ddlTypeRegistry.addDescriptor( + CapacityDependentDdlType.builder( CHAR, columnType( CLOB ), columnType( CHAR ), this ) + .withTypeCapacity( 254, columnType( CHAR ) ) + .withTypeCapacity( getMaxVarcharLength(), columnType( VARCHAR ) ) + .withTypeCapacity( maxLongVarcharLength, columnType( LONG32VARCHAR ) ) + .build() + ); + ddlTypeRegistry.addDescriptor( + CapacityDependentDdlType.builder( NCHAR, columnType( CLOB ), columnType( NCHAR ), this ) + .withTypeCapacity( 254, columnType( NCHAR ) ) + .withTypeCapacity( getMaxVarcharLength(), columnType( NVARCHAR ) ) + .withTypeCapacity( maxLongVarcharLength, columnType( LONG32NVARCHAR ) ) + .build() + ); + } + + @Override + public int getMaxVarcharLength() { + return 32_672; + } + + @Override + public int getDefaultDecimalPrecision() { + //this is the maximum allowed in Derby + return 31; + } + + @Override + public int getPreferredSqlTypeCodeForBoolean() { + return getVersion().isBefore( 10, 7 ) + ? Types.SMALLINT + : Types.BOOLEAN; + } + + @Override + public NationalizationSupport getNationalizationSupport() { + return NationalizationSupport.IMPLICIT; + } + + @Override + public int getDefaultStatementBatchSize() { + return 15; + } + + @Override + public int getFloatPrecision() { + return 23; + } + + @Override + public int getDoublePrecision() { + return 52; + } + + @Override + public void initializeFunctionRegistry(QueryEngine queryEngine) { + super.initializeFunctionRegistry( queryEngine ); + + final BasicTypeRegistry basicTypeRegistry = queryEngine.getTypeConfiguration().getBasicTypeRegistry(); + final BasicType stringType = basicTypeRegistry.resolve( StandardBasicTypes.STRING ); + + CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine); + + // Derby needs an actual argument type for aggregates like SUM, AVG, MIN, MAX to determine the result type + functionFactory.aggregates( this, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); + queryEngine.getSqmFunctionRegistry().register( + "count", + new CountFunction( + this, + queryEngine.getTypeConfiguration(), + SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER, + "||", + queryEngine.getTypeConfiguration().getDdlTypeRegistry().getDescriptor( VARCHAR ) + .getCastTypeName( stringType, null, null, null ), + true + ) + ); + // AVG by default uses the input type, so we possibly need to cast the argument type, hence a special function + functionFactory.avg_castingNonDoubleArguments( this, SqlAstNodeRenderingMode.DEFAULT ); + + functionFactory.concat_pipeOperator(); + functionFactory.cot(); + functionFactory.chr_char(); + functionFactory.degrees(); + functionFactory.radians(); + functionFactory.log10(); + functionFactory.sinh(); + functionFactory.cosh(); + functionFactory.tanh(); + functionFactory.pi(); + functionFactory.rand(); + functionFactory.trim1(); + functionFactory.hourMinuteSecond(); + functionFactory.yearMonthDay(); + functionFactory.varPopSamp(); + functionFactory.stddevPopSamp(); + functionFactory.substring_substr(); + functionFactory.leftRight_substrLength(); + functionFactory.characterLength_length( SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); + functionFactory.power_expLn(); + functionFactory.round_floor(); + functionFactory.octetLength_pattern( "length(?1)" ); + functionFactory.bitLength_pattern( "length(?1)*8" ); + + queryEngine.getSqmFunctionRegistry().register( + "concat", + new CastingConcatFunction( + this, + "||", + true, + SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER, + queryEngine.getTypeConfiguration() + ) + ); + + //no way I can see to pad with anything other than spaces + queryEngine.getSqmFunctionRegistry().register( "lpad", new DerbyLpadEmulation( queryEngine.getTypeConfiguration() ) ); + queryEngine.getSqmFunctionRegistry().register( "rpad", new DerbyRpadEmulation( queryEngine.getTypeConfiguration() ) ); + queryEngine.getSqmFunctionRegistry().register( "least", new CaseLeastGreatestEmulation( true ) ); + queryEngine.getSqmFunctionRegistry().register( "greatest", new CaseLeastGreatestEmulation( false ) ); + queryEngine.getSqmFunctionRegistry().register( "overlay", new InsertSubstringOverlayEmulation( queryEngine.getTypeConfiguration(), true ) ); + } + + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new StandardSqlAstTranslatorFactory() { + @Override + protected SqlAstTranslator buildTranslator( + SessionFactoryImplementor sessionFactory, Statement statement) { + return new DerbyLegacySqlAstTranslator<>( sessionFactory, statement ); + } + }; + } + + /** + * Derby doesn't have an extract() function, and has + * no functions at all for calendaring, but we can + * emulate the most basic functionality of extract() + * using the functions it does have. + * + * The only supported {@link TemporalUnit}s are: + * {@link TemporalUnit#YEAR}, + * {@link TemporalUnit#MONTH} + * {@link TemporalUnit#DAY}, + * {@link TemporalUnit#HOUR}, + * {@link TemporalUnit#MINUTE}, + * {@link TemporalUnit#SECOND} (along with + * {@link TemporalUnit#NANOSECOND}, + * {@link TemporalUnit#DATE}, and + * {@link TemporalUnit#TIME}, which are desugared + * by the parser). + */ + @Override + public String extractPattern(TemporalUnit unit) { + switch (unit) { + case DAY_OF_MONTH: + return "day(?2)"; + case DAY_OF_YEAR: + return "({fn timestampdiff(sql_tsi_day,date(char(year(?2),4)||'-01-01'),?2)}+1)"; + case DAY_OF_WEEK: + // Use the approach as outlined here: https://stackoverflow.com/questions/36357013/day-of-week-from-seconds-since-epoch + return "(mod(mod({fn timestampdiff(sql_tsi_day,{d '1970-01-01'},?2)}+4,7)+7,7)+1)"; + case WEEK: + // Use the approach as outlined here: https://www.sqlservercentral.com/articles/a-simple-formula-to-calculate-the-iso-week-number + // In SQL Server terms this is (DATEPART(dy,DATEADD(dd,DATEDIFF(dd,'17530101',@SomeDate)/7*7,'17530104'))+6)/7 + return "(({fn timestampdiff(sql_tsi_day,date(char(year(?2),4)||'-01-01'),{fn timestampadd(sql_tsi_day,{fn timestampdiff(sql_tsi_day,{d '1753-01-01'},?2)}/7*7,{d '1753-01-04'})})}+7)/7)"; + case QUARTER: + return "((month(?2)+2)/3)"; + default: + return "?1(?2)"; + } + } + + @Override + public String translateExtractField(TemporalUnit unit) { + switch (unit) { + case WEEK: + case DAY_OF_YEAR: + case DAY_OF_WEEK: + throw new UnsupportedOperationException("field type not supported on Derby: " + unit); + case DAY_OF_MONTH: + return "day"; + default: + return super.translateExtractField(unit); + } + } + + /** + * Derby does have a real {@link Types#BOOLEAN} + * type, but it doesn't know how to cast to it. Worse, + * Derby makes us use the {@code double()} function to + * cast things to its floating point types. + */ + @Override + public String castPattern(CastType from, CastType to) { + switch ( to ) { + case FLOAT: + return "cast(double(?1) as real)"; + case DOUBLE: + return "double(?1)"; + case STRING: + // Derby madness http://db.apache.org/derby/docs/10.8/ref/rrefsqlj33562.html + // With a nice rant: https://blog.jooq.org/2011/10/29/derby-casting-madness-the-sequel/ + // See https://issues.apache.org/jira/browse/DERBY-2072 + + // Since numerics can't be cast to varchar directly, use char(254) i.e. with the maximum char capacity + // as an intermediate type before converting to varchar + switch ( from ) { + case FLOAT: + case DOUBLE: + // Derby can't cast to char directly, but needs to be cast to decimal first... + return "cast(trim(cast(cast(?1 as decimal(" + getDefaultDecimalPrecision() + "," + BigDecimalJavaType.INSTANCE.getDefaultSqlScale( this, null ) + ")) as char(254))) as ?2)"; + case INTEGER: + case LONG: + case FIXED: + return "cast(trim(cast(?1 as char(254))) as ?2)"; + case DATE: + // The maximum length of a date + return "cast(?1 as varchar(10))"; + case TIME: + // The maximum length of a time + return "cast(?1 as varchar(8))"; + case TIMESTAMP: + // The maximum length of a timestamp + return "cast(?1 as varchar(30))"; + } + break; + } + return super.castPattern( from, to ); + } + + @Override + public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { + switch (unit) { + case NANOSECOND: + case NATIVE: + return "{fn timestampadd(sql_tsi_frac_second,mod(bigint(?2),1000000000),{fn timestampadd(sql_tsi_second,bigint((?2)/1000000000),?3)})}"; + default: + return "{fn timestampadd(sql_tsi_?1,bigint(?2),?3)}"; + } + } + + @Override + public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { + switch (unit) { + case NANOSECOND: + case NATIVE: + return "{fn timestampdiff(sql_tsi_frac_second,?2,?3)}"; + default: + return "{fn timestampdiff(sql_tsi_?1,?2,?3)}"; + } + } + + @Override + public void appendBooleanValueString(SqlAppender appender, boolean bool) { + if ( getVersion().isBefore( 10, 7 ) ) { + appender.appendSql( bool ? '1' : '0' ); + } + else { + appender.appendSql( bool ); + } + } + + @Override + public SequenceSupport getSequenceSupport() { + return getVersion().isBefore( 10, 6 ) + ? super.getSequenceSupport() + : DerbySequenceSupport.INSTANCE; + } + + @Override + public String getQuerySequencesString() { + return getVersion().isBefore( 10, 6 ) + ? null + : "select sys.sysschemas.schemaname as sequence_schema,sys.syssequences.* from sys.syssequences left join sys.sysschemas on sys.syssequences.schemaid=sys.sysschemas.schemaid"; + } + + @Override + public SequenceInformationExtractor getSequenceInformationExtractor() { + return getVersion().isBefore( 10, 6 ) + ? SequenceInformationExtractorNoOpImpl.INSTANCE + : SequenceInformationExtractorDerbyDatabaseImpl.INSTANCE; + } + + @Override + public String[] getDropSchemaCommand(String schemaName) { + return new String[] {"drop schema " + schemaName + " restrict"}; + } + + @Override + public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) { + return DB2Dialect.selectNullString( sqlType ); + } + + @Override + public boolean supportsCommentOn() { + //HHH-4531 + return false; + } + + @Override + public RowLockStrategy getWriteRowLockStrategy() { + return RowLockStrategy.NONE; + } + + @Override + public RowLockStrategy getReadRowLockStrategy() { + return RowLockStrategy.NONE; + } + + @Override + public String getForUpdateString() { + return " for update with rs"; + } + + @Override + public String getWriteLockString(int timeout) { + return " for update with rs"; + } + + @Override + public String getReadLockString(int timeout) { + return " for read only with rs"; + } + + @Override + public boolean supportsOuterJoinForUpdate() { + //TODO: check this! + return false; + } + + @Override + public boolean supportsExistsInSelect() { + //TODO: check this! + return false; + } + + @Override + public boolean supportsLockTimeouts() { + // To enable the lock timeout, we need a dedicated call + // 'call SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.locks.waitTimeout', '3')' + return false; + } + + @Override + public boolean supportsCurrentTimestampSelection() { + return true; + } + + @Override + public String getCurrentTimestampSelectString() { + return "values current timestamp"; + } + + @Override + public boolean isCurrentTimestampSelectStringCallable() { + return false; + } + + @Override + public LimitHandler getLimitHandler() { + return limitHandler; + } + + @Override + public IdentityColumnSupport getIdentityColumnSupport() { + return new DB2IdentityColumnSupport(); + } + + @Override + public boolean doesReadCommittedCauseWritersToBlockReaders() { + //TODO: check this + return true; + } + + @Override + public boolean supportsParametersInInsertSelect() { + //TODO: check this + return true; + } + + @Override + public boolean supportsResultSetPositionQueryMethodsOnForwardOnlyCursor() { + return false; + } + + @Override + public boolean supportsTupleDistinctCounts() { + //checked on Derby 10.14 + return false; + } + + @Override + public boolean supportsOrderByInSubquery() { + // As of version 10.5 Derby supports OFFSET and FETCH as well as ORDER BY in subqueries + return getVersion().isSameOrAfter( 10, 5 ); + } + + @Override + public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + super.contributeTypes( typeContributions, serviceRegistry ); + final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() + .getJdbcTypeRegistry(); + if ( getVersion().isBefore( 10, 7 ) ) { + jdbcTypeRegistry.addDescriptor( Types.BOOLEAN, SmallIntJdbcType.INSTANCE ); + } + jdbcTypeRegistry.addDescriptor( Types.NUMERIC, DecimalJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptor( Types.TIMESTAMP_WITH_TIMEZONE, TimestampJdbcType.INSTANCE ); + + // Derby requires a custom binder for binding untyped nulls that resolves the type through the statement + typeContributions.contributeJdbcType( ObjectNullResolvingJdbcType.INSTANCE ); + + // Until we remove StandardBasicTypes, we have to keep this + typeContributions.contributeType( + new JavaObjectType( + ObjectNullResolvingJdbcType.INSTANCE, + typeContributions.getTypeConfiguration() + .getJavaTypeRegistry() + .getDescriptor( Object.class ) + ) + ); + } + + // Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public boolean supportsLobValueChangePropagation() { + return false; + } + + @Override + public boolean supportsUnboundedLobLocatorMaterialization() { + return false; + } + + @Override + public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() { + return (sqlException, message, sql) -> { + final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException ); +// final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException ); + + if ( "40XL1".equals( sqlState ) || "40XL2".equals( sqlState ) ) { + throw new LockTimeoutException( message, sqlException, sql ); + } + return null; + }; + } + + @Override + public void appendDatetimeFormat(SqlAppender appender, String format) { + throw new UnsupportedOperationException("format() function not supported on Derby"); + } + + @Override + protected void registerDefaultKeywords() { + super.registerDefaultKeywords(); + registerKeyword( "ADD" ); + registerKeyword( "ALL" ); + registerKeyword( "ALLOCATE" ); + registerKeyword( "ALTER" ); + registerKeyword( "AND" ); + registerKeyword( "ANY" ); + registerKeyword( "ARE" ); + registerKeyword( "AS" ); + registerKeyword( "ASC" ); + registerKeyword( "ASSERTION" ); + registerKeyword( "AT" ); + registerKeyword( "AUTHORIZATION" ); + registerKeyword( "AVG" ); + registerKeyword( "BEGIN" ); + registerKeyword( "BETWEEN" ); + registerKeyword( "BIT" ); + registerKeyword( "BOOLEAN" ); + registerKeyword( "BOTH" ); + registerKeyword( "BY" ); + registerKeyword( "CALL" ); + registerKeyword( "CASCADE" ); + registerKeyword( "CASCADED" ); + registerKeyword( "CASE" ); + registerKeyword( "CAST" ); + registerKeyword( "CHAR" ); + registerKeyword( "CHARACTER" ); + registerKeyword( "CHECK" ); + registerKeyword( "CLOSE" ); + registerKeyword( "COLLATE" ); + registerKeyword( "COLLATION" ); + registerKeyword( "COLUMN" ); + registerKeyword( "COMMIT" ); + registerKeyword( "CONNECT" ); + registerKeyword( "CONNECTION" ); + registerKeyword( "CONSTRAINT" ); + registerKeyword( "CONSTRAINTS" ); + registerKeyword( "CONTINUE" ); + registerKeyword( "CONVERT" ); + registerKeyword( "CORRESPONDING" ); + registerKeyword( "COUNT" ); + registerKeyword( "CREATE" ); + registerKeyword( "CURRENT" ); + registerKeyword( "CURRENT_DATE" ); + registerKeyword( "CURRENT_TIME" ); + registerKeyword( "CURRENT_TIMESTAMP" ); + registerKeyword( "CURRENT_USER" ); + registerKeyword( "CURSOR" ); + registerKeyword( "DEALLOCATE" ); + registerKeyword( "DEC" ); + registerKeyword( "DECIMAL" ); + registerKeyword( "DECLARE" ); + registerKeyword( "DEFERRABLE" ); + registerKeyword( "DEFERRED" ); + registerKeyword( "DELETE" ); + registerKeyword( "DESC" ); + registerKeyword( "DESCRIBE" ); + registerKeyword( "DIAGNOSTICS" ); + registerKeyword( "DISCONNECT" ); + registerKeyword( "DISTINCT" ); + registerKeyword( "DOUBLE" ); + registerKeyword( "DROP" ); + registerKeyword( "ELSE" ); + registerKeyword( "END" ); + registerKeyword( "ENDEXEC" ); + registerKeyword( "ESCAPE" ); + registerKeyword( "EXCEPT" ); + registerKeyword( "EXCEPTION" ); + registerKeyword( "EXEC" ); + registerKeyword( "EXECUTE" ); + registerKeyword( "EXISTS" ); + registerKeyword( "EXPLAIN" ); + registerKeyword( "EXTERNAL" ); + registerKeyword( "FALSE" ); + registerKeyword( "FETCH" ); + registerKeyword( "FIRST" ); + registerKeyword( "FLOAT" ); + registerKeyword( "FOR" ); + registerKeyword( "FOREIGN" ); + registerKeyword( "FOUND" ); + registerKeyword( "FROM" ); + registerKeyword( "FULL" ); + registerKeyword( "FUNCTION" ); + registerKeyword( "GET" ); + registerKeyword( "GET_CURRENT_CONNECTION" ); + registerKeyword( "GLOBAL" ); + registerKeyword( "GO" ); + registerKeyword( "GOTO" ); + registerKeyword( "GRANT" ); + registerKeyword( "GROUP" ); + registerKeyword( "HAVING" ); + registerKeyword( "HOUR" ); + registerKeyword( "IDENTITY" ); + registerKeyword( "IMMEDIATE" ); + registerKeyword( "IN" ); + registerKeyword( "INDICATOR" ); + registerKeyword( "INITIALLY" ); + registerKeyword( "INNER" ); + registerKeyword( "INOUT" ); + registerKeyword( "INPUT" ); + registerKeyword( "INSENSITIVE" ); + registerKeyword( "INSERT" ); + registerKeyword( "INT" ); + registerKeyword( "INTEGER" ); + registerKeyword( "INTERSECT" ); + registerKeyword( "INTO" ); + registerKeyword( "IS" ); + registerKeyword( "ISOLATION" ); + registerKeyword( "JOIN" ); + registerKeyword( "KEY" ); + registerKeyword( "LAST" ); + registerKeyword( "LEFT" ); + registerKeyword( "LIKE" ); + registerKeyword( "LONGINT" ); + registerKeyword( "LOWER" ); + registerKeyword( "LTRIM" ); + registerKeyword( "MATCH" ); + registerKeyword( "MAX" ); + registerKeyword( "MIN" ); + registerKeyword( "MINUTE" ); + registerKeyword( "NATIONAL" ); + registerKeyword( "NATURAL" ); + registerKeyword( "NCHAR" ); + registerKeyword( "NVARCHAR" ); + registerKeyword( "NEXT" ); + registerKeyword( "NO" ); + registerKeyword( "NOT" ); + registerKeyword( "NULL" ); + registerKeyword( "NULLIF" ); + registerKeyword( "NUMERIC" ); + registerKeyword( "OF" ); + registerKeyword( "ON" ); + registerKeyword( "ONLY" ); + registerKeyword( "OPEN" ); + registerKeyword( "OPTION" ); + registerKeyword( "OR" ); + registerKeyword( "ORDER" ); + registerKeyword( "OUT" ); + registerKeyword( "OUTER" ); + registerKeyword( "OUTPUT" ); + registerKeyword( "OVERLAPS" ); + registerKeyword( "PAD" ); + registerKeyword( "PARTIAL" ); + registerKeyword( "PREPARE" ); + registerKeyword( "PRESERVE" ); + registerKeyword( "PRIMARY" ); + registerKeyword( "PRIOR" ); + registerKeyword( "PRIVILEGES" ); + registerKeyword( "PROCEDURE" ); + registerKeyword( "PUBLIC" ); + registerKeyword( "READ" ); + registerKeyword( "REAL" ); + registerKeyword( "REFERENCES" ); + registerKeyword( "RELATIVE" ); + registerKeyword( "RESTRICT" ); + registerKeyword( "REVOKE" ); + registerKeyword( "RIGHT" ); + registerKeyword( "ROLLBACK" ); + registerKeyword( "ROWS" ); + registerKeyword( "RTRIM" ); + registerKeyword( "SCHEMA" ); + registerKeyword( "SCROLL" ); + registerKeyword( "SECOND" ); + registerKeyword( "SELECT" ); + registerKeyword( "SESSION_USER" ); + registerKeyword( "SET" ); + registerKeyword( "SMALLINT" ); + registerKeyword( "SOME" ); + registerKeyword( "SPACE" ); + registerKeyword( "SQL" ); + registerKeyword( "SQLCODE" ); + registerKeyword( "SQLERROR" ); + registerKeyword( "SQLSTATE" ); + registerKeyword( "SUBSTR" ); + registerKeyword( "SUBSTRING" ); + registerKeyword( "SUM" ); + registerKeyword( "SYSTEM_USER" ); + registerKeyword( "TABLE" ); + registerKeyword( "TEMPORARY" ); + registerKeyword( "TIMEZONE_HOUR" ); + registerKeyword( "TIMEZONE_MINUTE" ); + registerKeyword( "TO" ); + registerKeyword( "TRAILING" ); + registerKeyword( "TRANSACTION" ); + registerKeyword( "TRANSLATE" ); + registerKeyword( "TRANSLATION" ); + registerKeyword( "TRUE" ); + registerKeyword( "UNION" ); + registerKeyword( "UNIQUE" ); + registerKeyword( "UNKNOWN" ); + registerKeyword( "UPDATE" ); + registerKeyword( "UPPER" ); + registerKeyword( "USER" ); + registerKeyword( "USING" ); + registerKeyword( "VALUES" ); + registerKeyword( "VARCHAR" ); + registerKeyword( "VARYING" ); + registerKeyword( "VIEW" ); + registerKeyword( "WHENEVER" ); + registerKeyword( "WHERE" ); + registerKeyword( "WITH" ); + registerKeyword( "WORK" ); + registerKeyword( "WRITE" ); + registerKeyword( "XML" ); + registerKeyword( "XMLEXISTS" ); + registerKeyword( "XMLPARSE" ); + registerKeyword( "XMLSERIALIZE" ); + registerKeyword( "YEAR" ); + } + + /** + * {@inheritDoc} + *

+ * From Derby docs: + *

+	 *     The DECLARE GLOBAL TEMPORARY TABLE statement defines a temporary table for the current connection.
+	 * 
+ * + * {@link DB2Dialect} returns a {@link GlobalTemporaryTableMutationStrategy} that + * will make temporary tables created at startup and hence unavailable for subsequent connections.
+ * see HHH-10238. + */ + @Override + public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( + EntityMappingType rootEntityDescriptor, + RuntimeModelCreationContext runtimeModelCreationContext) { + return new LocalTemporaryTableMutationStrategy( + TemporaryTable.createIdTable( + rootEntityDescriptor, + basename -> "session." + TemporaryTable.ID_TABLE_PREFIX + basename, + this, + runtimeModelCreationContext + ), + runtimeModelCreationContext.getSessionFactory() + ); + } + + @Override + public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( + EntityMappingType rootEntityDescriptor, + RuntimeModelCreationContext runtimeModelCreationContext) { + return new LocalTemporaryTableInsertStrategy( + TemporaryTable.createEntityTable( + rootEntityDescriptor, + name -> "session." + TemporaryTable.ENTITY_TABLE_PREFIX + name, + this, + runtimeModelCreationContext + ), + runtimeModelCreationContext.getSessionFactory() + ); + } + + @Override + public TemporaryTableKind getSupportedTemporaryTableKind() { + return TemporaryTableKind.LOCAL; + } + + @Override + public String getTemporaryTableCreateOptions() { + return "not logged"; + } + + @Override + public boolean supportsTemporaryTablePrimaryKey() { + return false; + } + + @Override + public String getTemporaryTableCreateCommand() { + return "declare global temporary table"; + } + + @Override + public BeforeUseAction getTemporaryTableBeforeUseAction() { + return BeforeUseAction.CREATE; + } + + @Override + public boolean supportsPartitionBy() { + return false; + } + + @Override + public boolean supportsWindowFunctions() { + // It seems at least the row_number function is supported as of 10.4 + return getVersion().isSameOrAfter( 10, 4 ); + } + + @Override + public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData) + throws SQLException { + builder.setAutoQuoteInitialUnderscore(true); + return super.buildIdentifierHelper(builder, dbMetaData); + } +} diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacySqlAstTranslator.java new file mode 100644 index 0000000000..6f3de6e33e --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacySqlAstTranslator.java @@ -0,0 +1,299 @@ +/* + * 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 java.util.List; +import java.util.function.Consumer; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.sqm.BinaryArithmeticOperator; +import org.hibernate.query.sqm.ComparisonOperator; +import org.hibernate.sql.ast.SqlAstNodeRenderingMode; +import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteContainer; +import org.hibernate.sql.ast.tree.cte.CteStatement; +import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; +import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; +import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Literal; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.expression.Summarization; +import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate; +import org.hibernate.sql.ast.tree.predicate.InListPredicate; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.exec.spi.JdbcOperation; + +/** + * A SQL AST translator for Derby. + * + * @author Christian Beikov + */ +public class DerbyLegacySqlAstTranslator extends AbstractSqlAstTranslator { + + public DerbyLegacySqlAstTranslator(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 ); + } + } + + // Derby does not allow CASE expressions where all result arms contain plain parameters. + // At least one result arm must provide some type context for inference, + // so we cast the first result arm if we encounter this condition + + @Override + protected void visitAnsiCaseSearchedExpression( + CaseSearchedExpression caseSearchedExpression, + Consumer resultRenderer) { + if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSearchedExpression ) ) { + final List whenFragments = caseSearchedExpression.getWhenFragments(); + final Expression firstResult = whenFragments.get( 0 ).getResult(); + super.visitAnsiCaseSearchedExpression( + caseSearchedExpression, + e -> { + if ( e == firstResult ) { + renderCasted( e ); + } + else { + resultRenderer.accept( e ); + } + } + ); + } + else { + super.visitAnsiCaseSearchedExpression( caseSearchedExpression, resultRenderer ); + } + } + + @Override + protected void visitAnsiCaseSimpleExpression( + CaseSimpleExpression caseSimpleExpression, + Consumer resultRenderer) { + if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSimpleExpression ) ) { + final List whenFragments = caseSimpleExpression.getWhenFragments(); + final Expression firstResult = whenFragments.get( 0 ).getResult(); + super.visitAnsiCaseSimpleExpression( + caseSimpleExpression, + e -> { + if ( e == firstResult ) { + renderCasted( e ); + } + else { + resultRenderer.accept( e ); + } + } + ); + } + else { + super.visitAnsiCaseSimpleExpression( caseSimpleExpression, resultRenderer ); + } + } + + @Override + protected String getForUpdate() { + return " for update"; + } + + @Override + protected String getForShare(int timeoutMillis) { + return " for read only"; + } + + @Override + protected String getForUpdateWithClause() { + return " with rs"; + } + + @Override + public void visitCteContainer(CteContainer cteContainer) { + if ( cteContainer.isWithRecursive() ) { + throw new IllegalArgumentException( "Recursive CTEs can't be emulated" ); + } + super.visitCteContainer( cteContainer ); + } + + @Override + protected void renderSearchClause(CteStatement cte) { + // Derby does not support this, but it's just a hint anyway + } + + @Override + protected void renderCycleClause(CteStatement cte) { + // Derby does not support this, but it can be emulated + } + + @Override + public void visitOffsetFetchClause(QueryPart queryPart) { + // Derby only supports the OFFSET and FETCH clause with ROWS + assertRowsOnlyFetchClauseType( queryPart ); + if ( supportsOffsetFetchClause() ) { + renderOffsetFetchClause( queryPart, true ); + } + else if ( !getClauseStack().isEmpty() ) { + throw new IllegalArgumentException( "Can't render offset and fetch clause for subquery" ); + } + } + + @Override + protected void renderFetchExpression(Expression fetchExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderFetchExpression( fetchExpression ); + } + else { + renderExpressionAsLiteral( fetchExpression, getJdbcParameterBindings() ); + } + } + + @Override + protected void renderOffsetExpression(Expression offsetExpression) { + if ( supportsParameterOffsetFetchExpression() ) { + super.renderOffsetExpression( offsetExpression ); + } + else { + renderExpressionAsLiteral( offsetExpression, getJdbcParameterBindings() ); + } + } + + @Override + protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) { + renderComparisonEmulateIntersect( lhs, operator, rhs ); + } + + @Override + protected void renderSelectExpression(Expression expression) { + renderSelectExpressionWithCastedOrInlinedPlainParameters( expression ); + } + + @Override + protected void renderSelectTupleComparison( + List lhsExpressions, + SqlTuple tuple, + ComparisonOperator operator) { + emulateSelectTupleComparison( lhsExpressions, tuple.getExpressions(), operator, true ); + } + + @Override + protected void renderPartitionItem(Expression expression) { + if ( expression instanceof Literal ) { + appendSql( "'0'" ); + } + else if ( expression instanceof Summarization ) { + Summarization summarization = (Summarization) expression; + appendSql( summarization.getKind().sqlText() ); + appendSql( OPEN_PARENTHESIS ); + renderCommaSeparated( summarization.getGroupings() ); + appendSql( CLOSE_PARENTHESIS ); + } + else { + expression.accept( this ); + } + } + + @Override + public void visitInListPredicate(InListPredicate inListPredicate) { + final List listExpressions = inListPredicate.getListExpressions(); + if ( listExpressions.isEmpty() ) { + appendSql( "1=0" ); + return; + } + final Expression testExpression = inListPredicate.getTestExpression(); + if ( isParameter( testExpression ) ) { + renderCasted( testExpression ); + if ( inListPredicate.isNegated() ) { + appendSql( " not" ); + } + appendSql( " in(" ); + renderCommaSeparated( listExpressions ); + appendSql( CLOSE_PARENTHESIS ); + } + else { + super.visitInListPredicate( inListPredicate ); + } + } + + @Override + protected boolean supportsRowValueConstructorSyntax() { + return false; + } + + @Override + protected boolean supportsRowValueConstructorSyntaxInInList() { + return false; + } + + @Override + protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() { + return false; + } + + @Override + protected String getFromDual() { + return " from (values 0) dual"; + } + + @Override + protected String getFromDualForSelectOnly() { + return getFromDual(); + } + + @Override + protected boolean needsRowsToSkip() { + return !supportsOffsetFetchClause(); + } + + @Override + protected boolean needsMaxRows() { + return !supportsOffsetFetchClause(); + } + + private boolean supportsParameterOffsetFetchExpression() { + return getDialect().getVersion().isSameOrAfter( 10, 6 ); + } + + private boolean supportsOffsetFetchClause() { + // Before version 10.5 Derby didn't support OFFSET and FETCH + return getDialect().getVersion().isSameOrAfter( 10, 5 ); + } + + @Override + public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) { + final BinaryArithmeticOperator operator = arithmeticExpression.getOperator(); + if ( operator == BinaryArithmeticOperator.MODULO ) { + append( "mod" ); + appendSql( OPEN_PARENTHESIS ); + arithmeticExpression.getLeftHandOperand().accept( this ); + appendSql( ',' ); + arithmeticExpression.getRightHandOperand().accept( this ); + appendSql( CLOSE_PARENTHESIS ); + } + else { + appendSql( OPEN_PARENTHESIS ); + render( arithmeticExpression.getLeftHandOperand(), SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); + appendSql( arithmeticExpression.getOperator().getOperatorSqlTextString() ); + render( arithmeticExpression.getRightHandOperand(), SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); + appendSql( CLOSE_PARENTHESIS ); + } + } + +} diff --git a/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/DerbyLegacyDialectTestCase.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/DerbyLegacyDialectTestCase.java new file mode 100644 index 0000000000..f972717c5d --- /dev/null +++ b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/DerbyLegacyDialectTestCase.java @@ -0,0 +1,93 @@ +/* + * 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 org.hibernate.dialect.DatabaseVersion; +import org.hibernate.query.spi.Limit; + +import static org.junit.Assert.assertEquals; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +/** + * Testing of patched support for Derby limit and offset queries; see HHH-3972 + * + * @author Evan Leonard + */ +public class DerbyLegacyDialectTestCase extends BaseUnitTestCase { + + @Test + @TestForIssue( jiraKey = "HHH-3972" ) + public void testInsertLimitClause() { + final int limit = 50; + final String input = "select * from tablename t where t.cat = 5"; + final String expected = "select * from tablename t where t.cat = 5 fetch first " + limit + " rows only"; + + final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( 0, limit ) ); + assertEquals( expected, actual ); + } + + @Test + @TestForIssue( jiraKey = "HHH-3972" ) + public void testInsertLimitWithOffsetClause() { + final int limit = 50; + final int offset = 200; + final String input = "select * from tablename t where t.cat = 5"; + final String expected = "select * from tablename t where t.cat = 5 offset " + offset + " rows fetch next " + limit + " rows only"; + + final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); + assertEquals( expected, actual ); + } + + @Test + @TestForIssue( jiraKey = "HHH-3972" ) + public void testInsertLimitWithForUpdateClause() { + final int limit = 50; + final int offset = 200; + final String input = "select c11 as col1, c12 as col2, c13 as col13 from t1 for update of c11, c13"; + final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 offset " + offset + + " rows fetch next " + limit + " rows only for update of c11, c13"; + + final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); + assertEquals( expected, actual ); + } + + @Test + @TestForIssue( jiraKey = "HHH-3972" ) + public void testInsertLimitWithWithClause() { + final int limit = 50; + final int offset = 200; + final String input = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' with rr"; + final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' offset " + offset + + " rows fetch next " + limit + " rows only with rr"; + + final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); + assertEquals( expected, actual ); + } + + @Test + @TestForIssue( jiraKey = "HHH-3972" ) + public void testInsertLimitWithForUpdateAndWithClauses() { + final int limit = 50; + final int offset = 200; + final String input = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' for update of c11,c13 with rr"; + final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' offset " + offset + + " rows fetch next " + limit + " rows only for update of c11,c13 with rr"; + + final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); + assertEquals( expected, actual ); + } + + private Limit toRowSelection(int firstRow, int maxRows) { + Limit selection = new Limit(); + selection.setFirstRow( firstRow ); + selection.setMaxRows( maxRows ); + return selection; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/sequence/DerbyTenFiveDialectSequenceInformationExtractorTest.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/unit/sequence/DerbyTenFiveDialectSequenceInformationExtractorTest.java similarity index 78% rename from hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/sequence/DerbyTenFiveDialectSequenceInformationExtractorTest.java rename to hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/unit/sequence/DerbyTenFiveDialectSequenceInformationExtractorTest.java index 4c990889d1..63ff2b817d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/sequence/DerbyTenFiveDialectSequenceInformationExtractorTest.java +++ b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/unit/sequence/DerbyTenFiveDialectSequenceInformationExtractorTest.java @@ -4,11 +4,12 @@ * 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.unit.sequence; +package org.hibernate.community.dialect.unit.sequence; +import org.hibernate.community.dialect.DerbyLegacyDialect; import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; +import org.hibernate.orm.test.dialect.unit.sequence.AbstractSequenceInformationExtractorTest; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -22,7 +23,7 @@ public class DerbyTenFiveDialectSequenceInformationExtractorTest extends Abstrac @Override public Dialect getDialect() { - return new DerbyDialect( DatabaseVersion.make( 10, 5 ) ); + return new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/sequence/DerbyTenSevenDialectSequenceInformationExtractorTest.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/unit/sequence/DerbyTenSevenDialectSequenceInformationExtractorTest.java similarity index 81% rename from hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/sequence/DerbyTenSevenDialectSequenceInformationExtractorTest.java rename to hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/unit/sequence/DerbyTenSevenDialectSequenceInformationExtractorTest.java index f3cc1be1cc..0efea810bc 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/sequence/DerbyTenSevenDialectSequenceInformationExtractorTest.java +++ b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/unit/sequence/DerbyTenSevenDialectSequenceInformationExtractorTest.java @@ -4,11 +4,12 @@ * 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.unit.sequence; +package org.hibernate.community.dialect.unit.sequence; +import org.hibernate.community.dialect.DerbyLegacyDialect; import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; +import org.hibernate.orm.test.dialect.unit.sequence.AbstractSequenceInformationExtractorTest; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -21,7 +22,7 @@ import org.hibernate.testing.TestForIssue; public class DerbyTenSevenDialectSequenceInformationExtractorTest extends AbstractSequenceInformationExtractorTest { @Override public Dialect getDialect() { - return new DerbyDialect( DatabaseVersion.make( 10, 7 ) ); + return new DerbyLegacyDialect( DatabaseVersion.make( 10, 7 ) ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/sequence/DerbyTenSixDialectSequenceInformationExtractorTest.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/unit/sequence/DerbyTenSixDialectSequenceInformationExtractorTest.java similarity index 81% rename from hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/sequence/DerbyTenSixDialectSequenceInformationExtractorTest.java rename to hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/unit/sequence/DerbyTenSixDialectSequenceInformationExtractorTest.java index c2f146d7f9..5184a0412a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/sequence/DerbyTenSixDialectSequenceInformationExtractorTest.java +++ b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/unit/sequence/DerbyTenSixDialectSequenceInformationExtractorTest.java @@ -4,11 +4,12 @@ * 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.unit.sequence; +package org.hibernate.community.dialect.unit.sequence; +import org.hibernate.community.dialect.DerbyLegacyDialect; import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; +import org.hibernate.orm.test.dialect.unit.sequence.AbstractSequenceInformationExtractorTest; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -21,7 +22,7 @@ import org.hibernate.testing.TestForIssue; public class DerbyTenSixDialectSequenceInformationExtractorTest extends AbstractSequenceInformationExtractorTest { @Override public Dialect getDialect() { - return new DerbyDialect( DatabaseVersion.make( 10, 6 ) ); + return new DerbyLegacyDialect( DatabaseVersion.make( 10, 6 ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 8656dee52b..ed738decb3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -516,7 +516,7 @@ public class DB2Dialect extends Dialect { return selectNullString(sqlType); } - static String selectNullString(int sqlType) { + public static String selectNullString(int sqlType) { String literal; switch ( sqlType ) { case Types.VARCHAR: diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java index 51fce0aaf8..98e4387491 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -95,12 +95,12 @@ public class DerbyDialect extends Dialect { // * can't select a parameter unless wrapped // in a cast or function call - private final LimitHandler limitHandler = getVersion().isBefore( 10, 5 ) - ? AbstractLimitHandler.NO_LIMIT - : new DerbyLimitHandler( getVersion().isSameOrAfter( 10, 6 ) ); + private final static DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 10, 14, 2 ); + + private final LimitHandler limitHandler = new DerbyLimitHandler( true ); public DerbyDialect() { - this( DatabaseVersion.make( 10, 0 ) ); + this( MINIMUM_VERSION); } public DerbyDialect(DatabaseVersion version) { @@ -114,8 +114,6 @@ public class DerbyDialect extends Dialect { @Override protected String columnType(int sqlTypeCode) { switch ( sqlTypeCode ) { - case BOOLEAN: - return getVersion().isBefore( 10, 7 ) ? "smallint" : super.columnType( sqlTypeCode ); case TINYINT: //no tinyint return "smallint"; @@ -210,13 +208,6 @@ public class DerbyDialect extends Dialect { return 31; } - @Override - public int getPreferredSqlTypeCodeForBoolean() { - return getVersion().isBefore( 10, 7 ) - ? Types.SMALLINT - : Types.BOOLEAN; - } - @Override public NationalizationSupport getNationalizationSupport() { return NationalizationSupport.IMPLICIT; @@ -438,33 +429,22 @@ public class DerbyDialect extends Dialect { @Override public void appendBooleanValueString(SqlAppender appender, boolean bool) { - if ( getVersion().isBefore( 10, 7 ) ) { - appender.appendSql( bool ? '1' : '0' ); - } - else { - appender.appendSql( bool ); - } + appender.appendSql( bool ); } @Override public SequenceSupport getSequenceSupport() { - return getVersion().isBefore( 10, 6 ) - ? super.getSequenceSupport() - : DerbySequenceSupport.INSTANCE; + return DerbySequenceSupport.INSTANCE; } @Override public String getQuerySequencesString() { - return getVersion().isBefore( 10, 6 ) - ? null - : "select sys.sysschemas.schemaname as sequence_schema,sys.syssequences.* from sys.syssequences left join sys.sysschemas on sys.syssequences.schemaid=sys.sysschemas.schemaid"; + return "select sys.sysschemas.schemaname as sequence_schema,sys.syssequences.* from sys.syssequences left join sys.sysschemas on sys.syssequences.schemaid=sys.sysschemas.schemaid"; } @Override public SequenceInformationExtractor getSequenceInformationExtractor() { - return getVersion().isBefore( 10, 6 ) - ? SequenceInformationExtractorNoOpImpl.INSTANCE - : SequenceInformationExtractorDerbyDatabaseImpl.INSTANCE; + return SequenceInformationExtractorDerbyDatabaseImpl.INSTANCE; } @Override @@ -578,7 +558,7 @@ public class DerbyDialect extends Dialect { @Override public boolean supportsOrderByInSubquery() { // As of version 10.5 Derby supports OFFSET and FETCH as well as ORDER BY in subqueries - return getVersion().isSameOrAfter( 10, 5 ); + return true; } @Override @@ -586,9 +566,6 @@ public class DerbyDialect extends Dialect { super.contributeTypes( typeContributions, serviceRegistry ); final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() .getJdbcTypeRegistry(); - if ( getVersion().isBefore( 10, 7 ) ) { - jdbcTypeRegistry.addDescriptor( Types.BOOLEAN, SmallIntJdbcType.INSTANCE ); - } jdbcTypeRegistry.addDescriptor( Types.NUMERIC, DecimalJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( Types.TIMESTAMP_WITH_TIMEZONE, TimestampJdbcType.INSTANCE ); @@ -923,7 +900,7 @@ public class DerbyDialect extends Dialect { @Override public boolean supportsWindowFunctions() { // It seems at least the row_number function is supported as of 10.4 - return getVersion().isSameOrAfter( 10, 4 ); + return true; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java index 7d90b881eb..dfee3489d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java @@ -268,12 +268,12 @@ public class DerbySqlAstTranslator extends AbstractSqlA } private boolean supportsParameterOffsetFetchExpression() { - return getDialect().getVersion().isSameOrAfter( 10, 6 ); + return true; } private boolean supportsOffsetFetchClause() { // Before version 10.5 Derby didn't support OFFSET and FETCH - return getDialect().getVersion().isSameOrAfter( 10, 5 ); + return true; } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DerbyDialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DerbyDialectTestCase.java index ff2d4b3c09..0e8bf408d0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DerbyDialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DerbyDialectTestCase.java @@ -28,9 +28,9 @@ public class DerbyDialectTestCase extends BaseUnitTestCase { public void testInsertLimitClause() { final int limit = 50; final String input = "select * from tablename t where t.cat = 5"; - final String expected = "select * from tablename t where t.cat = 5 fetch first " + limit + " rows only"; + final String expected = "select * from tablename t where t.cat = 5 fetch first ? rows only"; - final String actual = new DerbyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( 0, limit ) ); + final String actual = new DerbyDialect().getLimitHandler().processSql( input, toRowSelection( 0, limit ) ); assertEquals( expected, actual ); } @@ -40,9 +40,9 @@ public class DerbyDialectTestCase extends BaseUnitTestCase { final int limit = 50; final int offset = 200; final String input = "select * from tablename t where t.cat = 5"; - final String expected = "select * from tablename t where t.cat = 5 offset " + offset + " rows fetch next " + limit + " rows only"; + final String expected = "select * from tablename t where t.cat = 5 offset ? rows fetch next ? rows only"; - final String actual = new DerbyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); + final String actual = new DerbyDialect().getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); assertEquals( expected, actual ); } @@ -52,10 +52,9 @@ public class DerbyDialectTestCase extends BaseUnitTestCase { final int limit = 50; final int offset = 200; final String input = "select c11 as col1, c12 as col2, c13 as col13 from t1 for update of c11, c13"; - final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 offset " + offset - + " rows fetch next " + limit + " rows only for update of c11, c13"; + final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 offset ? rows fetch next ? rows only for update of c11, c13"; - final String actual = new DerbyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); + final String actual = new DerbyDialect().getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); assertEquals( expected, actual ); } @@ -65,10 +64,9 @@ public class DerbyDialectTestCase extends BaseUnitTestCase { final int limit = 50; final int offset = 200; final String input = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' with rr"; - final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' offset " + offset - + " rows fetch next " + limit + " rows only with rr"; + final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' offset ? rows fetch next ? rows only with rr"; - final String actual = new DerbyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); + final String actual = new DerbyDialect().getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); assertEquals( expected, actual ); } @@ -78,10 +76,9 @@ public class DerbyDialectTestCase extends BaseUnitTestCase { final int limit = 50; final int offset = 200; final String input = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' for update of c11,c13 with rr"; - final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' offset " + offset - + " rows fetch next " + limit + " rows only for update of c11,c13 with rr"; + final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' offset ? rows fetch next ? rows only for update of c11,c13 with rr"; - final String actual = new DerbyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); + final String actual = new DerbyDialect().getLimitHandler().processSql( input, toRowSelection( offset, limit ) ); assertEquals( expected, actual ); }