HHH-15190 - Remove support for H2 versions older than 1.4.197

Signed-off-by: Jan Schatteman <jschatte@redhat.com>
This commit is contained in:
Jan Schatteman 2022-06-21 23:56:01 +02:00 committed by Christian Beikov
parent 89f04d2274
commit f33d3ed308
4 changed files with 947 additions and 36 deletions

View File

@ -0,0 +1,711 @@
/*
* 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.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import org.hibernate.PessimisticLockException;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2DurationIntervalSecondJdbcType;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.Replacer;
import org.hibernate.dialect.SelectItemReferenceStrategy;
import org.hibernate.dialect.SimpleDatabaseVersion;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.hint.IndexQueryHintHandler;
import org.hibernate.dialect.identity.H2IdentityColumnSupport;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitOffsetLimitHandler;
import org.hibernate.dialect.pagination.OffsetFetchLimitHandler;
import org.hibernate.dialect.sequence.H2V1SequenceSupport;
import org.hibernate.dialect.sequence.H2V2SequenceSupport;
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.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
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.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
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.FetchClauseType;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.NullOrdering;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.mutation.internal.temptable.BeforeUseAction;
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.SequenceInformationExtractorH2DatabaseImpl;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
import static org.hibernate.query.sqm.TemporalUnit.SECOND;
import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.CHAR;
import static org.hibernate.type.SqlTypes.DECIMAL;
import static org.hibernate.type.SqlTypes.DOUBLE;
import static org.hibernate.type.SqlTypes.FLOAT;
import static org.hibernate.type.SqlTypes.GEOMETRY;
import static org.hibernate.type.SqlTypes.INTERVAL_SECOND;
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.NUMERIC;
import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR;
/**
* A legacy {@linkplain Dialect SQL dialect} for H2.
*
* @author Thomas Mueller
*/
public class H2LegacyDialect extends Dialect {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( H2LegacyDialect.class );
private final LimitHandler limitHandler;
private final boolean ansiSequence;
private final boolean cascadeConstraints;
private final boolean useLocalTime;
private final SequenceInformationExtractor sequenceInformationExtractor;
private final String querySequenceString;
public H2LegacyDialect(DialectResolutionInfo info) {
this( parseVersion( info ) );
registerKeywords( info );
}
public H2LegacyDialect() {
this( SimpleDatabaseVersion.ZERO_VERSION );
}
public H2LegacyDialect(DatabaseVersion version) {
super(version);
// https://github.com/h2database/h2database/commit/b2cdf84e0b84eb8a482fa7dccdccc1ab95241440
limitHandler = version.isSameOrAfter( 1, 4, 195 )
? OffsetFetchLimitHandler.INSTANCE
: LimitOffsetLimitHandler.INSTANCE;
if ( version.isBefore( 1, 2, 139 ) ) {
LOG.unsupportedMultiTableBulkHqlJpaql( version.getMajor(), version.getMinor(), version.getMicro() );
}
// supportsTuplesInSubqueries = version.isSameOrAfter( 1, 4, 198 );
// Prior to 1.4.200 there was no support for 'current value for sequence_name'
// After 2.0.202 there is no support for 'sequence_name.nextval' and 'sequence_name.currval'
ansiSequence = version.isSameOrAfter( 1, 4, 200 );
// Prior to 1.4.200 the 'cascade' in 'drop table' was implicit
cascadeConstraints = version.isSameOrAfter( 1, 4, 200 );
// 1.4.200 introduced changes in current_time and current_timestamp
useLocalTime = version.isSameOrAfter( 1, 4, 200 );
if ( version.isSameOrAfter( 1, 4, 32 ) ) {
this.sequenceInformationExtractor = version.isSameOrAfter( 1, 4, 201 )
? SequenceInformationExtractorLegacyImpl.INSTANCE
: SequenceInformationExtractorH2DatabaseImpl.INSTANCE;
this.querySequenceString = "select * from INFORMATION_SCHEMA.SEQUENCES";
}
else {
this.sequenceInformationExtractor = SequenceInformationExtractorNoOpImpl.INSTANCE;
this.querySequenceString = null;
}
}
private static DatabaseVersion parseVersion(DialectResolutionInfo info) {
return DatabaseVersion.make( info.getMajor(), info.getMinor(), parseBuildId( info ) );
}
private static int parseBuildId(DialectResolutionInfo info) {
final String databaseVersion = info.getDatabaseVersion();
if ( databaseVersion == null ) {
return 0;
}
final String[] bits = databaseVersion.split("[. ]");
return bits.length > 2 ? Integer.parseInt( bits[2] ) : 0;
}
@Override
public boolean getDefaultNonContextualLobCreation() {
// http://code.google.com/p/h2database/issues/detail?id=235
return true;
}
@Override
public boolean supportsStandardArrays() {
return getVersion().isSameOrAfter( 2 );
}
@Override
protected String columnType(int sqlTypeCode) {
switch ( sqlTypeCode ) {
// prior to version 2.0, H2 reported NUMERIC columns as DECIMAL,
// which caused problems for schema update tool
case NUMERIC:
return getVersion().isBefore( 2 ) ? columnType( DECIMAL ) : super.columnType( sqlTypeCode );
case NCHAR:
return columnType( CHAR );
case NVARCHAR:
return columnType( VARCHAR );
}
return super.columnType( sqlTypeCode );
}
@Override
protected String castType(int sqlTypeCode) {
switch ( sqlTypeCode ) {
case CHAR:
case NCHAR:
return "char";
case VARCHAR:
case NVARCHAR:
case LONG32VARCHAR:
case LONG32NVARCHAR:
return "varchar";
case BINARY:
case VARBINARY:
case LONG32VARBINARY:
return "varbinary";
}
return super.castType( sqlTypeCode );
}
@Override
protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
if ( getVersion().isBefore( 2 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( ARRAY, "array", this ) );
}
if ( getVersion().isSameOrAfter( 1, 4, 197 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) );
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) );
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INTERVAL_SECOND, "interval second($p,$s)", this ) );
}
}
}
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantJdbcType.INSTANCE );
if ( getVersion().isSameOrAfter( 1, 4, 197 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
}
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
}
}
@Override
public int getDefaultStatementBatchSize() {
return 15;
}
public boolean hasOddDstBehavior() {
// H2 1.4.200 has a bug: https://github.com/h2database/h2database/issues/3184
return getVersion().isSameOrAfter( 1, 4, 200 );
}
@Override
public void initializeFunctionRegistry(QueryEngine queryEngine) {
super.initializeFunctionRegistry( queryEngine );
CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine);
// H2 needs an actual argument type for aggregates like SUM, AVG, MIN, MAX to determine the result type
functionFactory.aggregates( this, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
// 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.NO_PLAIN_PARAMETER );
functionFactory.pi();
functionFactory.cot();
functionFactory.radians();
functionFactory.degrees();
functionFactory.log10();
functionFactory.rand();
functionFactory.truncate();
functionFactory.soundex();
functionFactory.translate();
functionFactory.bitand();
functionFactory.bitor();
functionFactory.bitxor();
functionFactory.bitAndOr();
functionFactory.yearMonthDay();
functionFactory.hourMinuteSecond();
functionFactory.dayOfWeekMonthYear();
functionFactory.weekQuarter();
functionFactory.daynameMonthname();
if ( useLocalTime ) {
functionFactory.localtimeLocaltimestamp();
}
functionFactory.bitLength();
functionFactory.octetLength();
functionFactory.ascii();
functionFactory.octetLength();
functionFactory.space();
functionFactory.repeat();
functionFactory.chr_char();
functionFactory.instr();
functionFactory.substr();
//also natively supports ANSI-style substring()
functionFactory.position();
functionFactory.trim1();
functionFactory.concat_pipeOperator();
functionFactory.nowCurdateCurtime();
functionFactory.sysdate();
functionFactory.insert();
// functionFactory.everyAny(); //this would work too
functionFactory.everyAny_boolAndOr();
functionFactory.median();
functionFactory.stddevPopSamp();
functionFactory.varPopSamp();
if ( getVersion().isSame( 1, 4, 200 ) ) {
// See https://github.com/h2database/h2database/issues/2518
functionFactory.format_toChar();
}
else {
functionFactory.format_formatdatetime();
}
functionFactory.rownum();
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
functionFactory.windowFunctions();
if ( getVersion().isSameOrAfter( 2 ) ) {
functionFactory.listagg( null );
functionFactory.inverseDistributionOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates();
}
else {
// Use group_concat until 2.x as listagg was buggy
functionFactory.listagg_groupConcat();
}
}
else {
functionFactory.listagg_groupConcat();
}
}
@Override
public void augmentPhysicalTableTypes(List<String> tableTypesList) {
if ( getVersion().isSameOrAfter( 2 ) ) {
tableTypesList.add( "BASE TABLE" );
}
}
@Override
protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration typeConfiguration) {
switch ( columnTypeName ) {
case "FLOAT(24)":
// Use REAL instead of FLOAT to get Float as recommended Java type
return Types.REAL;
}
return super.resolveSqlTypeCode( columnTypeName, typeConfiguration );
}
@Override
public JdbcType resolveSqlTypeDescriptor(
String columnTypeName,
int jdbcTypeCode,
int precision,
int scale,
JdbcTypeRegistry jdbcTypeRegistry) {
// As of H2 2.0 we get a FLOAT type code even though it is a DOUBLE
if ( jdbcTypeCode == FLOAT && "DOUBLE PRECISION".equals( columnTypeName ) ) {
return jdbcTypeRegistry.getDescriptor( DOUBLE );
}
return super.resolveSqlTypeDescriptor( columnTypeName, jdbcTypeCode, precision, scale, jdbcTypeRegistry );
}
@Override
protected Integer resolveSqlTypeCode(String typeName, String baseTypeName, TypeConfiguration typeConfiguration) {
switch ( baseTypeName ) {
case "CHARACTER VARYING":
return VARCHAR;
}
return super.resolveSqlTypeCode( typeName, baseTypeName, typeConfiguration );
}
@Override
public int getMaxVarcharLength() {
return 1_048_576;
}
@Override
public String currentTime() {
return useLocalTime ? "localtime" : super.currentTime();
}
@Override
public String currentTimestamp() {
return useLocalTime ? "localtimestamp" : super.currentTimestamp();
}
@Override
public String currentTimestampWithTimeZone() {
return "current_timestamp";
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() {
@Override
protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
SessionFactoryImplementor sessionFactory, Statement statement) {
return new H2LegacySqlAstTranslator<>( sessionFactory, statement );
}
};
}
/**
* In H2, the extract() function does not return
* fractional seconds for the field
* {@link TemporalUnit#SECOND}. We work around
* this here with two calls to extract().
*/
@Override
public String extractPattern(TemporalUnit unit) {
return unit == SECOND
? "(" + super.extractPattern(unit) + "+extract(nanosecond from ?2)/1e9)"
: super.extractPattern(unit);
}
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
if ( intervalType != null ) {
return "(?2+?3)";
}
return "dateadd(?1,?2,?3)";
}
@Override
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
if ( unit == null ) {
return "(?3-?2)";
}
return "datediff(?1,?2,?3)";
}
@Override
public boolean supportsTemporalLiteralOffset() {
return true;
}
@Override
public TimeZoneSupport getTimeZoneSupport() {
return TimeZoneSupport.NATIVE;
}
@Override
public void appendBooleanValueString(SqlAppender appender, boolean bool) {
appender.appendSql( bool );
}
@Override
public LimitHandler getLimitHandler() {
return limitHandler;
}
@Override
public boolean supportsDistinctFromPredicate() {
return true;
}
@Override
public boolean supportsIfExistsAfterTableName() {
return !supportsIfExistsBeforeTableName();
}
@Override
public boolean supportsIfExistsBeforeTableName() {
return cascadeConstraints;
}
@Override
public boolean supportsIfExistsAfterAlterTable() {
return cascadeConstraints;
}
@Override
public boolean supportsIfExistsBeforeConstraintName() {
return true;
}
@Override
public String getCascadeConstraintsString() {
return cascadeConstraints ? " cascade "
: super.getCascadeConstraintsString();
}
@Override
public boolean supportsCommentOn() {
return true;
}
@Override
public boolean dropConstraints() {
return false;
}
@Override
public SequenceSupport getSequenceSupport() {
return ansiSequence ? H2V2SequenceSupport.INSTANCE: H2V1SequenceSupport.INSTANCE;
}
@Override
public String getQuerySequencesString() {
return querySequenceString;
}
@Override
public SequenceInformationExtractor getSequenceInformationExtractor() {
return sequenceInformationExtractor;
}
@Override
public NullOrdering getNullOrdering() {
return NullOrdering.SMALLEST;
}
@Override
public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(
EntityMappingType entityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new LocalTemporaryTableMutationStrategy(
TemporaryTable.createIdTable(
entityDescriptor,
basename -> TemporaryTable.ID_TABLE_PREFIX + basename,
this,
runtimeModelCreationContext
),
runtimeModelCreationContext.getSessionFactory()
);
}
@Override
public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy(
EntityMappingType entityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new LocalTemporaryTableInsertStrategy(
TemporaryTable.createEntityTable(
entityDescriptor,
name -> TemporaryTable.ENTITY_TABLE_PREFIX + name,
this,
runtimeModelCreationContext
),
runtimeModelCreationContext.getSessionFactory()
);
}
@Override
public TemporaryTableKind getSupportedTemporaryTableKind() {
return TemporaryTableKind.LOCAL;
}
@Override
public BeforeUseAction getTemporaryTableBeforeUseAction() {
return BeforeUseAction.CREATE;
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return EXTRACTOR;
}
private static final ViolatedConstraintNameExtractor EXTRACTOR =
new TemplatedViolatedConstraintNameExtractor( sqle -> {
// 23000: Check constraint violation: {0}
// 23001: Unique index or primary key violation: {0}
if ( sqle.getSQLState().startsWith( "23" ) ) {
final String message = sqle.getMessage();
final int idx = message.indexOf( "violation: " );
if ( idx > 0 ) {
String constraintName = message.substring( idx + "violation: ".length() );
if ( sqle.getSQLState().equals( "23506" ) ) {
constraintName = constraintName.substring( 1, constraintName.indexOf( ":" ) );
}
return constraintName;
}
}
return null;
} );
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
switch (errorCode) {
case 40001:
// DEADLOCK DETECTED
return new LockAcquisitionException(message, sqlException, sql);
case 50200:
// LOCK NOT AVAILABLE
return new PessimisticLockException(message, sqlException, sql);
case 90006:
// NULL not allowed for column [90006-145]
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
return new ConstraintViolationException(message, sqlException, sql, constraintName);
}
return null;
};
}
@Override
public boolean supportsCurrentTimestampSelection() {
return true;
}
@Override
public boolean isCurrentTimestampSelectStringCallable() {
return false;
}
@Override
public String getCurrentTimestampSelectString() {
return "call current_timestamp()";
}
// Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public boolean supportsLobValueChangePropagation() {
return false;
}
@Override
public boolean supportsTupleCounts() {
return true;
}
@Override
public boolean requiresParensForTupleDistinctCounts() {
return true;
}
@Override
public boolean doesReadCommittedCauseWritersToBlockReaders() {
// see http://groups.google.com/group/h2-database/browse_thread/thread/562d8a49e2dabe99?hl=en
return true;
}
@Override
public SelectItemReferenceStrategy getGroupBySelectItemReferenceStrategy() {
return SelectItemReferenceStrategy.ALIAS;
}
@Override
public boolean supportsOffsetInSubquery() {
return true;
}
@Override
public boolean supportsWindowFunctions() {
return getVersion().isSameOrAfter( 1, 4, 200 );
}
@Override
public boolean supportsFetchClause(FetchClauseType type) {
return getVersion().isSameOrAfter( 1, 4, 198 );
}
@Override
public IdentityColumnSupport getIdentityColumnSupport() {
return new H2IdentityColumnSupport();
}
@Override
public int registerResultSetOutParameter(CallableStatement statement, int position) throws SQLException {
return position;
}
@Override
public String getQueryHintString(String query, String hints) {
return IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints );
}
@Override
public void appendDatetimeFormat(SqlAppender appender, String format) {
if ( getVersion().isSame( 1, 4, 200 ) ) {
// See https://github.com/h2database/h2database/issues/2518
appender.appendSql( OracleDialect.datetimeFormat( format, true, true ).result() );
}
else {
appender.appendSql(
new Replacer( format, "'", "''" )
.replace("e", "u")
.replace( "xxx", "XXX" )
.replace( "xx", "XX" )
.replace( "x", "X" )
.result()
);
}
}
public String translateExtractField(TemporalUnit unit) {
switch ( unit ) {
case DAY_OF_MONTH: return "day";
case WEEK: return "iso_week";
default: return unit.toString();
}
}
@Override
public String generatedAs(String generatedAs) {
return " generated always as (" + generatedAs + ")";
}
}

View File

@ -0,0 +1,220 @@
/*
* 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 org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
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.CteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.exec.spi.JdbcOperation;
/**
* A legacy SQL AST translator for H2.
*
* @author Christian Beikov
*/
public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
private boolean renderAsArray;
public H2LegacySqlAstTranslator(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
public void visitOffsetFetchClause(QueryPart queryPart) {
if ( isRowsOnlyFetchClauseType( queryPart ) ) {
if ( supportsOffsetFetchClause() ) {
renderOffsetFetchClause( queryPart, true );
}
else {
renderLimitOffsetClause( queryPart );
}
}
else {
if ( supportsOffsetFetchClausePercentWithTies() ) {
renderOffsetFetchClause( queryPart, true );
}
else {
// FETCH PERCENT and WITH TIES were introduced along with window functions
throw new IllegalArgumentException( "Can't emulate fetch clause type: " + queryPart.getFetchClauseType() );
}
}
}
@Override
protected void renderSearchClause(CteStatement cte) {
// H2 does not support this, but it's just a hint anyway
}
@Override
protected void renderCycleClause(CteStatement cte) {
// H2 does not support this, but it can be emulated
}
@Override
protected void renderSelectTupleComparison(
List<SqlSelection> lhsExpressions,
SqlTuple tuple,
ComparisonOperator operator) {
emulateSelectTupleComparison( lhsExpressions, tuple.getExpressions(), operator, true );
}
@Override
public void visitInSubQueryPredicate(InSubQueryPredicate inSubQueryPredicate) {
final SqlTuple lhsTuple;
// As of 1.4.200 this is supported
if ( getDialect().getVersion().isBefore( 1, 4, 200 )
&& ( lhsTuple = SqlTupleContainer.getSqlTuple( inSubQueryPredicate.getTestExpression() ) ) != null
&& lhsTuple.getExpressions().size() != 1 ) {
inSubQueryPredicate.getTestExpression().accept( this );
if ( inSubQueryPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in" );
final boolean renderAsArray = this.renderAsArray;
this.renderAsArray = true;
inSubQueryPredicate.getSubQuery().accept( this );
this.renderAsArray = renderAsArray;
}
else {
super.visitInSubQueryPredicate( inSubQueryPredicate );
}
}
@Override
protected void visitSqlSelections(SelectClause selectClause) {
final boolean renderAsArray = this.renderAsArray;
this.renderAsArray = false;
if ( renderAsArray ) {
append( OPEN_PARENTHESIS );
}
super.visitSqlSelections( selectClause );
if ( renderAsArray ) {
append( CLOSE_PARENTHESIS );
}
}
@Override
protected void renderPartitionItem(Expression expression) {
if ( expression instanceof Literal ) {
appendSql( "'0' || '0'" );
}
else if ( expression instanceof Summarization ) {
// 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 visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
appendSql( OPEN_PARENTHESIS );
render( arithmeticExpression.getLeftHandOperand(), SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
appendSql( arithmeticExpression.getOperator().getOperatorSqlTextString() );
render( arithmeticExpression.getRightHandOperand(), SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
appendSql( CLOSE_PARENTHESIS );
}
@Override
protected boolean renderPrimaryTableReference(TableGroup tableGroup, LockMode lockMode) {
final TableReference tableRef = tableGroup.getPrimaryTableReference();
// The H2 parser can't handle a sub-query as first element in a nested join
// i.e. `join ( (select ...) alias join ... )`, so we have to introduce a dummy table reference
if ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) {
final boolean realTableGroup = tableGroup.isRealTableGroup()
&& ( CollectionHelper.isNotEmpty( tableGroup.getTableReferenceJoins() )
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
if ( realTableGroup ) {
appendSql( "dual cross join " );
}
}
return super.renderPrimaryTableReference( tableGroup, lockMode );
}
@Override
protected boolean supportsRowValueConstructorSyntax() {
// Just a guess
return getDialect().getVersion().isSameOrAfter( 1, 4, 197 );
}
@Override
protected boolean supportsRowValueConstructorSyntaxInInList() {
// Just a guess
return getDialect().getVersion().isSameOrAfter( 1, 4, 197 );
}
@Override
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
// Just a guess
return getDialect().getVersion().isSameOrAfter( 1, 4, 197 );
}
@Override
protected boolean supportsNullPrecedence() {
// Support for nulls clause in listagg was added in 2.0
return getClauseStack().getCurrent() != Clause.WITHIN_GROUP || getDialect().getVersion().isSameOrAfter( 2 );
}
@Override
protected String getFromDual() {
return " from dual";
}
private boolean supportsOffsetFetchClause() {
return getDialect().getVersion().isSameOrAfter( 1, 4, 195 );
}
private boolean supportsOffsetFetchClausePercentWithTies() {
// Introduction of TIES clause https://github.com/h2database/h2database/commit/876e9fbe7baf11d01675bfe871aac2cf1b6104ce
// Introduction of PERCENT support https://github.com/h2database/h2database/commit/f45913302e5f6ad149155a73763c0c59d8205849
return getDialect().getVersion().isSameOrAfter( 1, 4, 198 );
}
}

View File

@ -96,8 +96,7 @@ import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
*/
public class H2Dialect extends Dialect {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( H2Dialect.class );
private final LimitHandler limitHandler;
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 1, 4, 197 );
private final boolean ansiSequence;
private final boolean cascadeConstraints;
@ -112,21 +111,12 @@ public class H2Dialect extends Dialect {
}
public H2Dialect() {
this( SimpleDatabaseVersion.ZERO_VERSION );
this( MINIMUM_VERSION );
}
public H2Dialect(DatabaseVersion version) {
super(version);
// https://github.com/h2database/h2database/commit/b2cdf84e0b84eb8a482fa7dccdccc1ab95241440
limitHandler = version.isSameOrAfter( 1, 4, 195 )
? OffsetFetchLimitHandler.INSTANCE
: LimitOffsetLimitHandler.INSTANCE;
if ( version.isBefore( 1, 2, 139 ) ) {
LOG.unsupportedMultiTableBulkHqlJpaql( version.getMajor(), version.getMinor(), version.getMicro() );
}
// supportsTuplesInSubqueries = version.isSameOrAfter( 1, 4, 198 );
// Prior to 1.4.200 there was no support for 'current value for sequence_name'
@ -138,17 +128,11 @@ public class H2Dialect extends Dialect {
// 1.4.200 introduced changes in current_time and current_timestamp
useLocalTime = version.isSameOrAfter( 1, 4, 200 );
if ( version.isSameOrAfter( 1, 4, 32 ) ) {
this.sequenceInformationExtractor = version.isSameOrAfter( 1, 4, 201 )
? SequenceInformationExtractorLegacyImpl.INSTANCE
: SequenceInformationExtractorH2DatabaseImpl.INSTANCE;
this.querySequenceString = "select * from INFORMATION_SCHEMA.SEQUENCES";
}
else {
this.sequenceInformationExtractor = SequenceInformationExtractorNoOpImpl.INSTANCE;
this.querySequenceString = null;
}
}
private static DatabaseVersion parseVersion(DialectResolutionInfo info) {
return DatabaseVersion.make( info.getMajor(), info.getMinor(), parseBuildId( info ) );
@ -217,14 +201,12 @@ public class H2Dialect extends Dialect {
if ( getVersion().isBefore( 2 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( ARRAY, "array", this ) );
}
if ( getVersion().isSameOrAfter( 1, 4, 197 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) );
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) );
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INTERVAL_SECOND, "interval second($p,$s)", this ) );
}
}
}
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
@ -234,9 +216,7 @@ public class H2Dialect extends Dialect {
.getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantJdbcType.INSTANCE );
if ( getVersion().isSameOrAfter( 1, 4, 197 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE );
}
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
}
@ -447,7 +427,7 @@ public class H2Dialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
return limitHandler;
return OffsetFetchLimitHandler.INSTANCE;
}
@Override

View File

@ -182,19 +182,19 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstT
@Override
protected boolean supportsRowValueConstructorSyntax() {
// Just a guess
return getDialect().getVersion().isSameOrAfter( 1, 4, 197 );
return true;
}
@Override
protected boolean supportsRowValueConstructorSyntaxInInList() {
// Just a guess
return getDialect().getVersion().isSameOrAfter( 1, 4, 197 );
return true;
}
@Override
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
// Just a guess
return getDialect().getVersion().isSameOrAfter( 1, 4, 197 );
return true;
}
@Override
@ -209,7 +209,7 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstT
}
private boolean supportsOffsetFetchClause() {
return getDialect().getVersion().isSameOrAfter( 1, 4, 195 );
return true;
}
private boolean supportsOffsetFetchClausePercentWithTies() {