All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.community.dialect.HSQLLegacyDialect Maven / Gradle / Ivy

The newest version!
/*
 * SPDX-License-Identifier: LGPL-2.1-or-later
 * Copyright Red Hat Inc. and Hibernate Authors
 */
package org.hibernate.community.dialect;

import java.lang.invoke.MethodHandles;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Types;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.JDBCException;
import org.hibernate.LockMode;
import org.hibernate.StaleObjectStateException;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.dialect.*;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.TrimFunction;
import org.hibernate.dialect.identity.HSQLIdentityColumnSupport;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.lock.LockingStrategy;
import org.hibernate.dialect.lock.OptimisticForceIncrementLockingStrategy;
import org.hibernate.dialect.lock.OptimisticLockingStrategy;
import org.hibernate.dialect.lock.PessimisticForceIncrementLockingStrategy;
import org.hibernate.dialect.lock.PessimisticReadSelectLockingStrategy;
import org.hibernate.dialect.lock.PessimisticWriteSelectLockingStrategy;
import org.hibernate.dialect.lock.SelectLockingStrategy;
import org.hibernate.dialect.pagination.LegacyHSQLLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitOffsetLimitHandler;
import org.hibernate.dialect.pagination.OffsetFetchLimitHandler;
import org.hibernate.dialect.sequence.HSQLSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.temptable.TemporaryTable;
import org.hibernate.dialect.temptable.TemporaryTableKind;
import org.hibernate.dialect.unique.CreateTableUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.sqm.CastType;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.dialect.NullOrdering;
import org.hibernate.query.common.TemporalUnit;
import org.hibernate.query.sqm.mutation.spi.AfterUseAction;
import org.hibernate.query.sqm.mutation.spi.BeforeUseAction;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy;
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.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.SequenceInformationExtractorHSQLDBDatabaseImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.spi.TypeConfiguration;

import org.jboss.logging.Logger;

import jakarta.persistence.TemporalType;

import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.CLOB;
import static org.hibernate.type.SqlTypes.DOUBLE;
import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.NUMERIC;

/**
 * A {@linkplain Dialect SQL dialect} for HSQLDB (HyperSQL) 1.8 up to (but not including) 2.6.1.
 *
 * @author Christoph Sturm
 * @author Phillip Baird
 * @author Fred Toussi
 */
public class HSQLLegacyDialect extends Dialect {
	private static final CoreMessageLogger LOG = Logger.getMessageLogger(
			MethodHandles.lookup(),
			CoreMessageLogger.class,
			org.hibernate.community.dialect.HSQLLegacyDialect.class.getName()
	);
	private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate( this );
	private final HSQLIdentityColumnSupport identityColumnSupport;

	public HSQLLegacyDialect(DialectResolutionInfo info) {
		super( info );
		this.identityColumnSupport = new HSQLIdentityColumnSupport( getVersion() );
	}

	public HSQLLegacyDialect() {
		this( DatabaseVersion.make( 1, 8 ) );
	}

	public HSQLLegacyDialect(DatabaseVersion version) {
		super( version.isSame( 1, 8 ) ? reflectedVersion( version ) : version );
		this.identityColumnSupport = new HSQLIdentityColumnSupport( getVersion() );
	}

	@Override
	protected void registerDefaultKeywords() {
		super.registerDefaultKeywords();
		if ( getVersion().isSameOrAfter( 2, 5 ) ) {
			registerKeyword( "period" );
		}
	}

	@Override
	protected String columnType(int sqlTypeCode) {
		// Note that all floating point types are synonyms for 'double'

		// Note that the HSQL type 'longvarchar' and 'longvarbinary' are
		// synonyms for 'varchar(16M)' and 'varbinary(16M)' respectively.
		// But using these types results in schema validation issue as
		// described in HHH-9693.

		switch ( sqlTypeCode ) {
			//HSQL has no 'nclob' type, but 'clob' is Unicode (See HHH-10364)
			case NCLOB:
				return "clob";
		}
		if ( getVersion().isBefore( 2 ) ) {
			switch ( sqlTypeCode ) {
				case NUMERIC:
					// Older versions of HSQL did not accept
					// precision for the 'numeric' type
					return "numeric";

				// Older versions of HSQL had no lob support
				case BLOB:
					return "longvarbinary";
				case CLOB:
					return "longvarchar";

			}
		}

		return super.columnType( sqlTypeCode );
	}

	@Override
	protected Integer resolveSqlTypeCode(String typeName, String baseTypeName, TypeConfiguration typeConfiguration) {
		switch ( baseTypeName ) {
			case "DOUBLE":
				return DOUBLE;
		}
		return super.resolveSqlTypeCode( typeName, baseTypeName, typeConfiguration );
	}

	@Override
	public int getDefaultStatementBatchSize() {
		return 15;
	}

	private static DatabaseVersion reflectedVersion(DatabaseVersion version) {
		try {
			final Class props = ReflectHelper.classForName( "org.hsqldb.persist.HsqlDatabaseProperties");
			final String versionString = (String) props.getDeclaredField("THIS_VERSION").get( null );

			return new SimpleDatabaseVersion(
					Integer.parseInt( versionString.substring( 0, 1 ) ),
					Integer.parseInt( versionString.substring( 2, 3 ) ),
					Integer.parseInt( versionString.substring( 4, 5 ) )
			);
		}
		catch (Throwable e) {
			// might be a very old version, or not accessible in class path
			return version;
		}
	}

	@Override
	public void initializeFunctionRegistry(FunctionContributions functionContributions) {
		super.initializeFunctionRegistry(functionContributions);

		CommonFunctionFactory functionFactory = new CommonFunctionFactory(functionContributions);

		// 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.cot();
		functionFactory.radians();
		functionFactory.degrees();
		functionFactory.log10();
		functionFactory.rand();
		functionFactory.trunc_dateTrunc_trunc();
		functionFactory.pi();
		functionFactory.soundex();
		functionFactory.reverse();
		functionFactory.space();
		functionFactory.repeat();
		functionFactory.translate();
		functionFactory.bitand();
		functionFactory.bitor();
		functionFactory.bitxor();
		functionFactory.bitnot();
		functionFactory.yearMonthDay();
		functionFactory.hourMinuteSecond();
		functionFactory.dayofweekmonthyear();
		functionFactory.weekQuarter();
		functionFactory.daynameMonthname();
		functionFactory.lastDay();
		functionFactory.trim1();
		functionFactory.toCharNumberDateTimestamp();
		functionFactory.concat_pipeOperator();
		functionFactory.localtimeLocaltimestamp();
		functionFactory.bitLength();
		functionFactory.octetLength();
		functionFactory.ascii();
		functionFactory.chr_char();
		functionFactory.instr();
		functionFactory.substr();
		//also natively supports ANSI-style substring()
		functionFactory.position();
		functionFactory.nowCurdateCurtime();
		functionFactory.insert();
		functionFactory.overlay();
		functionFactory.median();
		functionFactory.stddevPopSamp();
		functionFactory.varPopSamp();
		functionFactory.addMonths();
		functionFactory.monthsBetween();
		functionFactory.collate_quoted();

		if ( getVersion().isSameOrAfter( 2 ) ) {
			//SYSDATE is similar to LOCALTIMESTAMP but it returns the timestamp when it is called
			functionFactory.sysdate();
		}

		// from v. 2.2.0 ROWNUM() is supported in all modes as the equivalent of Oracle ROWNUM
		if ( getVersion().isSameOrAfter( 2, 2 ) ) {
			functionFactory.rownum();
		}
		functionFactory.listagg_groupConcat();
		functionFactory.array_hsql();
		functionFactory.arrayAggregate();
		functionFactory.arrayPosition_hsql();
		functionFactory.arrayPositions_hsql();
		functionFactory.arrayLength_cardinality();
		functionFactory.arrayConcat_operator();
		functionFactory.arrayPrepend_operator();
		functionFactory.arrayAppend_operator();
		functionFactory.arrayContains_hsql();
		functionFactory.arrayIntersects_hsql();
		functionFactory.arrayGet_unnest();
		functionFactory.arraySet_hsql();
		functionFactory.arrayRemove_hsql();
		functionFactory.arrayRemoveIndex_unnest( false );
		functionFactory.arraySlice_unnest();
		functionFactory.arrayReplace_unnest();
		functionFactory.arrayTrim_trim_array();
		functionFactory.arrayFill_hsql();
		functionFactory.arrayToString_hsql();

		if ( getVersion().isSameOrAfter( 2, 7 ) ) {
			functionFactory.jsonObject_hsqldb();
			functionFactory.jsonArray_hsqldb();
			functionFactory.jsonArrayAgg_hsqldb();
			functionFactory.jsonObjectAgg_h2();
		}

		functionFactory.unnest( "c1", "c2" );
		functionFactory.generateSeries_recursive( getMaximumSeriesSize(), true, false );

		//trim() requires parameters to be cast when used as trim character
		functionContributions.getFunctionRegistry().register( "trim", new TrimFunction(
				this,
				functionContributions.getTypeConfiguration(),
				SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER
		) );
	}

	/**
	 * HSQLDB doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs,
	 * so it has to be emulated with a top level recursive CTE which requires an upper bound on the amount
	 * of elements that the series can return.
	 */
	protected int getMaximumSeriesSize() {
		// The maximum recursion depth of HSQLDB
		return 258;
	}

	@Override
	public @Nullable String getDefaultOrdinalityColumnName() {
		return "c2";
	}

	@Override
	public String currentTime() {
		return "localtime";
	}

	@Override
	public String currentTimestamp() {
		return "localtimestamp";
	}

	@Override
	public String currentTimestampWithTimeZone() {
		return "current_timestamp";
	}

	@Override
	public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
		return new StandardSqlAstTranslatorFactory() {
			@Override
			protected  SqlAstTranslator buildTranslator(
					SessionFactoryImplementor sessionFactory, Statement statement) {
				return new HSQLLegacySqlAstTranslator<>( sessionFactory, statement );
			}
		};
	}
	@Override
	public String castPattern(CastType from, CastType to) {
		String result;
		switch ( to ) {
			case INTEGER:
			case LONG:
				result = BooleanDecoder.toInteger( from );
				if ( result != null ) {
					return result;
				}
				break;
			case BOOLEAN:
				result = from == CastType.STRING
						? buildStringToBooleanCastDecode( "true", "false" )
						: BooleanDecoder.toBoolean( from );
				if ( result != null ) {
					return result;
				}
				break;
			case INTEGER_BOOLEAN:
				result = from == CastType.STRING
						? buildStringToBooleanCastDecode( "1", "0" )
						: BooleanDecoder.toIntegerBoolean( from );
				if ( result != null ) {
					return result;
				}
				break;
			case YN_BOOLEAN:
				result = from == CastType.STRING
						? buildStringToBooleanCastDecode( "'Y'", "'N'" )
						: BooleanDecoder.toYesNoBoolean( from );
				if ( result != null ) {
					return result;
				}
				break;
			case TF_BOOLEAN:
				result = from == CastType.STRING
						? buildStringToBooleanCastDecode( "'T'", "'F'" )
						: BooleanDecoder.toTrueFalseBoolean( from );
				if ( result != null ) {
					return result;
				}
				break;
			case STRING:
				result = BooleanDecoder.toString( from );
				if ( result != null ) {
					return "trim(" + result + ')';
				}
				break;
		}
		return super.castPattern( from, to );
	}

	@Override
	public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
		StringBuilder pattern = new StringBuilder();
		boolean castTo = temporalType != TemporalType.TIMESTAMP && !unit.isDateUnit();
		switch (unit) {
			case NANOSECOND:
			case NATIVE:
				pattern.append("timestampadd(sql_tsi_frac_second,?2,"); //nanos
				break;
			case WEEK:
				pattern.append("dateadd('day',?2*7,");
				break;
			default:
				pattern.append("dateadd('?1',?2,");
		}
		if (castTo) {
			pattern.append("cast(?3 as timestamp)");
		}
		else {
			pattern.append("?3");
		}
		pattern.append(")");
		return pattern.toString();
	}

	@Override
	public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
		StringBuilder pattern = new StringBuilder();
		boolean castFrom = fromTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit();
		boolean castTo = toTemporalType != TemporalType.TIMESTAMP && !unit.isDateUnit();
		switch (unit) {
			case NANOSECOND:
			case NATIVE:
				pattern.append("timestampdiff(sql_tsi_frac_second"); //nanos
				break;
			case WEEK:
				pattern.append("(datediff('day'");
			default:
				pattern.append("datediff('?1'");
		}
		pattern.append(',');
		if (castFrom) {
			pattern.append("cast(?2 as timestamp)");
		}
		else {
			pattern.append("?2");
		}
		pattern.append(',');
		if (castTo) {
			pattern.append("cast(?3 as timestamp)");
		}
		else {
			pattern.append("?3");
		}
		pattern.append(")");
		if ( unit == TemporalUnit.WEEK ) {
			pattern.append( "/7)" );
		}
		return pattern.toString();
	}

	@Override
	public boolean supportsDistinctFromPredicate() {
		return true;
	}

	@Override
	public boolean supportsLockTimeouts() {
		return false;
	}

	@Override
	public String getForUpdateString() {
		if ( getVersion().isSameOrAfter( 2 ) ) {
			return " for update";
		}
		else {
			return "";
		}
	}

	@Override
	public LimitHandler getLimitHandler() {
		return getVersion().isBefore( 2 ) ? LegacyHSQLLimitHandler.INSTANCE
				: getVersion().isBefore( 2, 5 ) ? LimitOffsetLimitHandler.OFFSET_ONLY_INSTANCE
				: OffsetFetchLimitHandler.INSTANCE;
	}

	// Note : HSQLDB actually supports [IF EXISTS] before AND after the 
	// But as CASCADE has to be AFTER IF EXISTS in case it's after the tablename,
	// We put the IF EXISTS before the tablename to be able to add CASCADE after.
	@Override
	public boolean supportsIfExistsAfterTableName() {
		return false;
	}

	@Override
	public boolean supportsIfExistsBeforeTableName() {
		return true;
	}

	@Override
	public boolean supportsColumnCheck() {
		return getVersion().isSameOrAfter( 2 );
	}

	@Override
	public SequenceSupport getSequenceSupport() {
		return HSQLSequenceSupport.INSTANCE;
	}

	@Override
	public String getQuerySequencesString() {
		// this assumes schema support, which is present in 1.8.0 and later...
		return "select * from information_schema.sequences";
	}

	@Override
	public SequenceInformationExtractor getSequenceInformationExtractor() {
		return SequenceInformationExtractorHSQLDBDatabaseImpl.INSTANCE;
	}

	@Override
	public boolean supportsStandardArrays() {
		return true;
	}

	@Override
	public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
		return getVersion().isBefore( 2 ) ? EXTRACTOR_18 : EXTRACTOR_20;
	}

	private static final ViolatedConstraintNameExtractor EXTRACTOR_18 =
			new TemplatedViolatedConstraintNameExtractor( sqle -> {
				switch ( JdbcExceptionHelper.extractErrorCode( sqle ) ) {
					case -8:
						return extractUsingTemplate(
								"Integrity constraint violation ", " table:",
								sqle.getMessage()
						);
					case -9:
						return extractUsingTemplate(
								"Violation of unique index: ", " in statement [",
								sqle.getMessage()
						);
					case -104:
						return extractUsingTemplate(
								"Unique constraint violation: ", " in statement [",
								sqle.getMessage()
						);
					case -177:
						return extractUsingTemplate(
								"Integrity constraint violation - no parent ", " table:",
								sqle.getMessage()
						);
				}
				return null;
			} );

	@Override
	public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
		return (sqlException, message, sql) -> {
			final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
			final String constraintName;

			switch ( errorCode ) {
				case -104:
					// Unique constraint violation
					constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
					return new ConstraintViolationException(
							message,
							sqlException,
							sql,
							ConstraintViolationException.ConstraintKind.UNIQUE,
							constraintName
					);
			}

			return null;
		};
	}

	/**
	 * HSQLDB 2.0 messages have changed
	 * messages may be localized - therefore use the common, non-locale element " table: "
	 */
	private static final ViolatedConstraintNameExtractor EXTRACTOR_20 =
			new TemplatedViolatedConstraintNameExtractor( sqle -> {
				switch ( JdbcExceptionHelper.extractErrorCode( sqle ) ) {
					case -8:
					case -9:
					case -104:
					case -177:
						return extractUsingTemplate(
								"; ", " table: ",
								sqle.getMessage()
						);
				}
				return null;
			} );

	@Override
	public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) {
		String literal;
		switch ( sqlType ) {
			case Types.LONGVARCHAR:
			case Types.VARCHAR:
			case Types.CHAR:
				literal = "cast(null as varchar(100))";
				break;
			case Types.LONGVARBINARY:
			case Types.VARBINARY:
			case Types.BINARY:
				literal = "cast(null as varbinary(100))";
				break;
			case Types.CLOB:
				literal = "cast(null as clob)";
				break;
			case Types.BLOB:
				literal = "cast(null as blob)";
				break;
			case Types.DATE:
				literal = "cast(null as date)";
				break;
			case Types.TIMESTAMP:
			case Types.TIMESTAMP_WITH_TIMEZONE:
				literal = "cast(null as timestamp)";
				break;
			case Types.BOOLEAN:
				literal = "cast(null as boolean)";
				break;
			case Types.BIT:
				literal = "cast(null as bit)";
				break;
			case Types.TIME:
				literal = "cast(null as time)";
				break;
			default:
				literal = "cast(null as int)";
		}
		return literal;
	}

	@Override
	public NullOrdering getNullOrdering() {
		return NullOrdering.FIRST;
	}

	@Override
	public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(
			EntityMappingType rootEntityDescriptor,
			RuntimeModelCreationContext runtimeModelCreationContext) {

		// Hibernate uses this information for temporary tables that it uses for its own operations
		// therefore the appropriate strategy is taken with different versions of HSQLDB

		// All versions of HSQLDB support GLOBAL TEMPORARY tables where the table
		// definition is shared by all users but data is private to the session
		// HSQLDB 2.0 also supports session-based LOCAL TEMPORARY tables where
		// the definition and data is private to the session and table declaration
		// can happen in the middle of a transaction

		if ( getVersion().isBefore( 2 ) ) {
			return new GlobalTemporaryTableMutationStrategy(
					TemporaryTable.createIdTable(
							rootEntityDescriptor,
							basename -> TemporaryTable.ID_TABLE_PREFIX + basename,
							this,
							runtimeModelCreationContext
					),
					runtimeModelCreationContext.getSessionFactory()
			);
		}
		else {
			return new LocalTemporaryTableMutationStrategy(
					// With HSQLDB 2.0, the table name is qualified with MODULE to assist the drop
					// statement (in-case there is a global name beginning with HT_)
					TemporaryTable.createIdTable(
							rootEntityDescriptor,
							basename -> "MODULE." + TemporaryTable.ID_TABLE_PREFIX + basename,
							this,
							runtimeModelCreationContext
					),
					runtimeModelCreationContext.getSessionFactory()
			);
		}
	}

	@Override
	public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy(
			EntityMappingType rootEntityDescriptor,
			RuntimeModelCreationContext runtimeModelCreationContext) {

		// Hibernate uses this information for temporary tables that it uses for its own operations
		// therefore the appropriate strategy is taken with different versions of HSQLDB

		// All versions of HSQLDB support GLOBAL TEMPORARY tables where the table
		// definition is shared by all users but data is private to the session
		// HSQLDB 2.0 also supports session-based LOCAL TEMPORARY tables where
		// the definition and data is private to the session and table declaration
		// can happen in the middle of a transaction

		if ( getVersion().isBefore( 2 ) ) {
			return new GlobalTemporaryTableInsertStrategy(
					TemporaryTable.createEntityTable(
							rootEntityDescriptor,
							name -> TemporaryTable.ENTITY_TABLE_PREFIX + name,
							this,
							runtimeModelCreationContext
					),
					runtimeModelCreationContext.getSessionFactory()
			);
		}
		else {
			return new LocalTemporaryTableInsertStrategy(
					// With HSQLDB 2.0, the table name is qualified with MODULE to assist the drop
					// statement (in-case there is a global name beginning with HT_)
					TemporaryTable.createEntityTable(
							rootEntityDescriptor,
							name -> "MODULE." + TemporaryTable.ENTITY_TABLE_PREFIX + name,
							this,
							runtimeModelCreationContext
					),
					runtimeModelCreationContext.getSessionFactory()
			);
		}
	}

	@Override
	public TemporaryTableKind getSupportedTemporaryTableKind() {
		return getVersion().isBefore( 2 ) ? TemporaryTableKind.GLOBAL : TemporaryTableKind.LOCAL;
	}

	@Override
	public String getTemporaryTableCreateCommand() {
		return getVersion().isBefore( 2 ) ? super.getTemporaryTableCreateCommand() : "declare local temporary table";
	}

	@Override
	public AfterUseAction getTemporaryTableAfterUseAction() {
		// Version 1.8 GLOBAL TEMPORARY table definitions persist beyond the end
		// of the session (by default, data is cleared at commit).
		return getVersion().isBefore( 2 ) ? AfterUseAction.CLEAN : AfterUseAction.DROP;
	}

	@Override
	public BeforeUseAction getTemporaryTableBeforeUseAction() {
		return getVersion().isBefore( 2 ) ? BeforeUseAction.NONE : BeforeUseAction.CREATE;
	}

	// current timestamp support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	/**
	 * HSQLDB 1.8.x requires CALL CURRENT_TIMESTAMP but this should not
	 * be treated as a callable statement. It is equivalent to
	 * "select current_timestamp from dual" in some databases.
	 * HSQLDB 2.0 also supports VALUES CURRENT_TIMESTAMP
	 * 

* {@inheritDoc} */ @Override public boolean supportsCurrentTimestampSelection() { return true; } @Override public boolean isCurrentTimestampSelectStringCallable() { return false; } @Override public String getCurrentTimestampSelectString() { return "call current_timestamp"; } @Override public boolean doesRoundTemporalOnOverflow() { // HSQLDB does truncation return false; } /** * For HSQLDB 2.0, this is a copy of the base class implementation. * For HSQLDB 1.8, only READ_UNCOMMITTED is supported. *

* {@inheritDoc} */ @Override public LockingStrategy getLockingStrategy(EntityPersister lockable, LockMode lockMode) { switch (lockMode) { case PESSIMISTIC_FORCE_INCREMENT: return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); case PESSIMISTIC_WRITE: return new PessimisticWriteSelectLockingStrategy( lockable, lockMode); case PESSIMISTIC_READ: return new PessimisticReadSelectLockingStrategy( lockable, lockMode); case OPTIMISTIC: return new OptimisticLockingStrategy( lockable, lockMode); case OPTIMISTIC_FORCE_INCREMENT: return new OptimisticForceIncrementLockingStrategy( lockable, lockMode); } if ( getVersion().isBefore( 2 ) ) { return new ReadUncommittedLockingStrategy( lockable, lockMode ); } else { return new SelectLockingStrategy( lockable, lockMode ); } } private static class ReadUncommittedLockingStrategy extends SelectLockingStrategy { private ReadUncommittedLockingStrategy(EntityPersister lockable, LockMode lockMode) { super( lockable, lockMode ); } @Override public void lock(Object id, Object version, Object object, int timeout, EventSource session) throws StaleObjectStateException, JDBCException { if ( getLockMode().greaterThan( LockMode.READ ) ) { LOG.hsqldbSupportsOnlyReadCommittedIsolation(); } super.lock( id, version, object, timeout, session ); } } @Override public boolean supportsCommentOn() { return getVersion().isSameOrAfter( 2 ); } // Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override public boolean doesReadCommittedCauseWritersToBlockReaders() { return getVersion().isSameOrAfter( 2 ); } @Override public boolean doesRepeatableReadCauseReadersToBlockWriters() { return getVersion().isSameOrAfter( 2 ); } @Override public boolean supportsLobValueChangePropagation() { return false; } @Override public void appendBooleanValueString(SqlAppender appender, boolean bool) { appender.appendSql( bool ); } @Override public boolean supportsTupleCounts() { return true; } @Override public boolean supportsTupleDistinctCounts() { // from v. 2.2.9 is added support for COUNT(DISTINCT ...) with multiple arguments return getVersion().isSameOrAfter( 2, 2, 9 ); } @Override public boolean supportsOffsetInSubquery() { return true; } @Override public boolean supportsLateral() { return getVersion().isSameOrAfter( 2, 6, 1 ); } @Override public boolean requiresFloatCastingOfIntegerDivision() { return true; } @Override public boolean supportsValuesList() { return true; } @Override public IdentityColumnSupport getIdentityColumnSupport() { return identityColumnSupport; } @Override public NameQualifierSupport getNameQualifierSupport() { return NameQualifierSupport.SCHEMA; } @Override public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) { return false; } @Override public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { return FunctionalDependencyAnalysisSupportImpl.TABLE_REFERENCE; } // Do not drop constraints explicitly, just do this by cascading instead. @Override public boolean dropConstraints() { return false; } @Override public String getCascadeConstraintsString() { return " cascade "; } @Override public void appendDatetimeFormat(SqlAppender appender, String format) { appender.appendSql( OracleDialect.datetimeFormat( format, false, false ) // HSQL is case sensitive i.e. requires MONTH and DAY instead of Month and Day .replace("MMMM", "MONTH") .replace("EEEE", "DAY") .replace("SSSSSS", "FF") .replace("SSSSS", "FF") .replace("SSSS", "FF") .replace("SSS", "FF") .replace("SS", "FF") .replace("S", "FF") .result() ); } @Override public String translateExtractField(TemporalUnit unit) { //TODO: does not support MICROSECOND, but on the // other hand it doesn't support microsecond // precision in timestamps either so who cares? switch (unit) { case WEEK: return "week_of_year"; //this is the ISO week number, I believe default: return unit.toString(); } } @Override public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData) throws SQLException { builder.setAutoQuoteInitialUnderscore(true); return super.buildIdentifierHelper(builder, dbMetaData); } @Override public UniqueDelegate getUniqueDelegate() { return uniqueDelegate; } @Override public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() { return DmlTargetColumnQualifierSupport.TABLE_ALIAS; } @Override public String getFromDualForSelectOnly() { return " from " + getDual(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy