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

org.hibernate.community.dialect.HANALegacyDialect 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.FilterReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Types;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.ScrollMode;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.HANAServerConfiguration;
import org.hibernate.dialect.HANASqlAstTranslator;
import org.hibernate.dialect.NullOrdering;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.RowLockStrategy;
import org.hibernate.dialect.aggregate.AggregateSupport;
import org.hibernate.dialect.aggregate.HANAAggregateSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.IntegralTimestampaddFunction;
import org.hibernate.dialect.identity.HANAIdentityColumnSupport;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitOffsetLimitHandler;
import org.hibernate.dialect.sequence.HANASequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.temptable.TemporaryTable;
import org.hibernate.dialect.temptable.TemporaryTableKind;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.BlobImplementer;
import org.hibernate.engine.jdbc.CharacterStream;
import org.hibernate.engine.jdbc.ClobImplementer;
import org.hibernate.engine.jdbc.NClobImplementer;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.SQLGrammarException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.procedure.internal.StandardCallableStatementSupport;
import org.hibernate.procedure.spi.CallableStatementSupport;
import org.hibernate.query.common.TemporalUnit;
import org.hibernate.query.sqm.CastType;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
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.exec.spi.JdbcOperation;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorHANADatabaseImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.tool.schema.internal.StandardTableExporter;
import org.hibernate.tool.schema.spi.Exporter;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.DataHelper;
import org.hibernate.type.descriptor.java.DoubleJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.DecimalJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.NCharJdbcType;
import org.hibernate.type.descriptor.jdbc.NClobJdbcType;
import org.hibernate.type.descriptor.jdbc.NVarcharJdbcType;
import org.hibernate.type.descriptor.jdbc.NumericJdbcType;
import org.hibernate.type.descriptor.jdbc.TinyIntAsSmallIntJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.internal.BasicTypeImpl;
import org.hibernate.type.spi.TypeConfiguration;

import jakarta.persistence.TemporalType;

import static org.hibernate.dialect.HANAServerConfiguration.MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY;
import static org.hibernate.type.SqlTypes.BINARY;
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.DOUBLE;
import static org.hibernate.type.SqlTypes.GEOMETRY;
import static org.hibernate.type.SqlTypes.LONG32NVARCHAR;
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.POINT;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_START_DATE;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_START_TIME;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_START_TIMESTAMP;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMicros;

/**
 * An SQL dialect for legacy versions of the SAP HANA Platform up tu and including 2.0 SPS 04.
 * 

* For more information on SAP HANA Platform, refer to the * SAP HANA Platform SQL Reference Guide. *

* Column tables are created by this dialect by default when using the auto-ddl feature. */ public class HANALegacyDialect extends Dialect { static final DatabaseVersion DEFAULT_VERSION = DatabaseVersion.make( 1, 0, 120 ); public HANALegacyDialect(DialectResolutionInfo info) { this( HANAServerConfiguration.fromDialectResolutionInfo( info ), true ); registerKeywords( info ); } public HANALegacyDialect() { this( DEFAULT_VERSION ); } public HANALegacyDialect(DatabaseVersion version) { this( new HANAServerConfiguration( version ), true ); } public HANALegacyDialect(DatabaseVersion version, boolean defaultTableTypeColumn) { this( new HANAServerConfiguration( version ), defaultTableTypeColumn ); } public HANALegacyDialect(HANAServerConfiguration configuration, boolean defaultTableTypeColumn) { super( configuration.getFullVersion() ); this.defaultTableTypeColumn = defaultTableTypeColumn; this.maxLobPrefetchSize = configuration.getMaxLobPrefetchSize(); this.useUnicodeStringTypes = useUnicodeStringTypesDefault(); } @Override public DatabaseVersion determineDatabaseVersion(DialectResolutionInfo info) { return HANALegacyServerConfiguration.staticDetermineDatabaseVersion( info ); } // Use column or row tables by default public static final String USE_DEFAULT_TABLE_TYPE_COLUMN = "hibernate.dialect.hana.use_default_table_type_column"; // Use TINYINT instead of the native BOOLEAN type private static final String USE_LEGACY_BOOLEAN_TYPE_PARAMETER_NAME = "hibernate.dialect.hana.use_legacy_boolean_type"; // Use unicode (NVARCHAR, NCLOB, etc.) instead of non-unicode (VARCHAR, CLOB) string types private static final String USE_UNICODE_STRING_TYPES_PARAMETER_NAME = "hibernate.dialect.hana.use_unicode_string_types"; // Read and write double-typed fields as BigDecimal instead of Double to get around precision issues of the HANA // JDBC driver (https://service.sap.com/sap/support/notes/2590160) private static final String TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_PARAMETER_NAME = "hibernate.dialect.hana.treat_double_typed_fields_as_decimal"; private static final Boolean USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE = Boolean.FALSE; private static final Boolean TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_DEFAULT_VALUE = Boolean.FALSE; private static final String SQL_IGNORE_LOCKED = " ignore locked"; private final int maxLobPrefetchSize; private boolean defaultTableTypeColumn; private boolean useLegacyBooleanType = USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE; private boolean useUnicodeStringTypes; private boolean treatDoubleTypedFieldsAsDecimal; /* * Tables named "TYPE" need to be quoted */ private final StandardTableExporter hanaTableExporter = new StandardTableExporter( this ) { @Override public String[] getSqlCreateStrings(Table table, Metadata metadata, SqlStringGenerationContext context) { String[] sqlCreateStrings = super.getSqlCreateStrings( table, metadata, context ); return quoteTypeIfNecessary( table, sqlCreateStrings, getCreateTableString() ); } @Override public String[] getSqlDropStrings(Table table, Metadata metadata, SqlStringGenerationContext context) { String[] sqlDropStrings = super.getSqlDropStrings( table, metadata, context ); return quoteTypeIfNecessary( table, sqlDropStrings, "drop table" ); } private String[] quoteTypeIfNecessary(Table table, String[] strings, String prefix) { if ( table.getNameIdentifier() == null || table.getNameIdentifier().isQuoted() || !"type".equalsIgnoreCase( table.getNameIdentifier().getText() ) ) { return strings; } Pattern createTableTypePattern = Pattern.compile( "(" + prefix + "\\s+)(" + table.getNameIdentifier().getText() + ")(.+)" ); Pattern commentOnTableTypePattern = Pattern.compile( "(comment\\s+on\\s+table\\s+)(" + table.getNameIdentifier().getText() + ")(.+)" ); for ( int i = 0; i < strings.length; i++ ) { Matcher createTableTypeMatcher = createTableTypePattern.matcher( strings[i] ); Matcher commentOnTableTypeMatcher = commentOnTableTypePattern.matcher( strings[i] ); if ( createTableTypeMatcher.matches() ) { strings[i] = createTableTypeMatcher.group( 1 ) + "\"TYPE\"" + createTableTypeMatcher.group( 3 ); } if ( commentOnTableTypeMatcher.matches() ) { strings[i] = commentOnTableTypeMatcher.group( 1 ) + "\"TYPE\"" + commentOnTableTypeMatcher.group( 3 ); } } return strings; } }; @Override public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { // This is the best hook for consuming dialect configuration that we have for now, // since this method is called very early in the bootstrap process final ConfigurationService configurationService = serviceRegistry.requireService( ConfigurationService.class ); this.defaultTableTypeColumn = configurationService.getSetting( USE_DEFAULT_TABLE_TYPE_COLUMN, StandardConverters.BOOLEAN, this.defaultTableTypeColumn ); if ( supportsAsciiStringTypes() ) { this.useUnicodeStringTypes = configurationService.getSetting( USE_UNICODE_STRING_TYPES_PARAMETER_NAME, StandardConverters.BOOLEAN, useUnicodeStringTypesDefault() ); } this.useLegacyBooleanType = configurationService.getSetting( USE_LEGACY_BOOLEAN_TYPE_PARAMETER_NAME, StandardConverters.BOOLEAN, USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE ); this.treatDoubleTypedFieldsAsDecimal = configurationService.getSetting( TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_PARAMETER_NAME, StandardConverters.BOOLEAN, TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_DEFAULT_VALUE ); super.contribute( typeContributions, serviceRegistry ); } protected boolean isDefaultTableTypeColumn() { return defaultTableTypeColumn; } protected boolean isCloud() { return getVersion().isSameOrAfter( 4 ); } @Override protected String columnType(int sqlTypeCode) { switch ( sqlTypeCode ) { case BOOLEAN: return useLegacyBooleanType ? "tinyint" : super.columnType( sqlTypeCode ); case NUMERIC: //there is no 'numeric' type in HANA return columnType( DECIMAL ); //'double precision' syntax not supported case DOUBLE: return "double"; //no explicit precision case TIME: case TIME_WITH_TIMEZONE: return "time"; case TIMESTAMP: case TIMESTAMP_WITH_TIMEZONE: return "timestamp"; //there is no 'char' or 'nchar' type in HANA case CHAR: case VARCHAR: return isUseUnicodeStringTypes() ? columnType( NVARCHAR ) : super.columnType( VARCHAR ); case NCHAR: return columnType( NVARCHAR ); case LONG32VARCHAR: return isUseUnicodeStringTypes() ? columnType( LONG32NVARCHAR ) : super.columnType( LONG32VARCHAR ); case CLOB: return isUseUnicodeStringTypes() ? columnType( NCLOB ) : super.columnType( CLOB ); // map tinyint to smallint since tinyint is unsigned on HANA case TINYINT: return "smallint"; default: return super.columnType( sqlTypeCode ); } } @Override protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { super.registerColumnTypes( typeContributions, serviceRegistry ); final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry(); // varbinary max length 5000 ddlTypeRegistry.addDescriptor( CapacityDependentDdlType.builder( BINARY, CapacityDependentDdlType.LobKind.BIGGEST_LOB, "blob", this ) .withTypeCapacity( getMaxVarbinaryLength(), "varbinary($l)" ) .build() ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "st_geometry", this ) ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( POINT, "st_point", this ) ); } @Override public boolean getDefaultNonContextualLobCreation() { // createBlob() and createClob() are not supported by the HANA JDBC driver return true; } @Override public boolean getDefaultUseGetGeneratedKeys() { // getGeneratedKeys() is not supported by the HANA JDBC driver return false; } @Override public String castPattern(CastType from, CastType to) { if ( to == CastType.BOOLEAN ) { switch ( from ) { case INTEGER_BOOLEAN: case INTEGER: case LONG: return "case ?1 when 1 then true when 0 then false else null end"; case YN_BOOLEAN: return "case ?1 when 'Y' then true when 'N' then false else null end"; case TF_BOOLEAN: return "case ?1 when 'T' then true when 'F' then false else null end"; } } return super.castPattern( from, to ); } @Override public int getDefaultTimestampPrecision() { return 7; } @Override public int getDefaultDecimalPrecision() { //the maximum on HANA return 34; } @Override public int getMaxVarcharLength() { return 5000; } @Override public int getMaxNVarcharLength() { return 5000; } @Override public int getMaxVarbinaryLength() { return 5000; } @Override public void initializeFunctionRegistry(FunctionContributions functionContributions) { super.initializeFunctionRegistry(functionContributions); final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration(); functionContributions.getFunctionRegistry().registerBinaryTernaryPattern( "locate", typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.INTEGER ), "locate(?2,?1)", "locate(?2,?1,?3)", FunctionParameterType.STRING, FunctionParameterType.STRING, FunctionParameterType.INTEGER, typeConfiguration ).setArgumentListSignature("(pattern, string[, start])"); CommonFunctionFactory functionFactory = new CommonFunctionFactory(functionContributions); functionFactory.ceiling_ceil(); functionFactory.concat_pipeOperator(); functionFactory.trim2(); functionFactory.cot(); functionFactory.cosh(); functionFactory.sinh(); functionFactory.tanh(); functionFactory.trunc_roundMode(); functionFactory.log10_log(); functionFactory.log(); functionFactory.bitand(); functionFactory.bitor(); functionFactory.bitxor(); functionFactory.bitnot(); functionFactory.hourMinuteSecond(); functionFactory.yearMonthDay(); functionFactory.dayofweekmonthyear(); functionFactory.weekQuarter(); functionFactory.daynameMonthname(); functionFactory.lastDay(); functionFactory.characterLength_length( SqlAstNodeRenderingMode.DEFAULT ); functionFactory.ascii(); functionFactory.chr_char(); functionFactory.addYearsMonthsDaysHoursMinutesSeconds(); functionFactory.daysBetween(); functionFactory.secondsBetween(); functionFactory.format_toVarchar(); functionFactory.currentUtcdatetimetimestamp(); functionFactory.everyAny_minMaxCase(); functionFactory.octetLength_pattern( "length(to_binary(?1))" ); functionFactory.bitLength_pattern( "length(to_binary(?1))*8" ); functionFactory.repeat_rpad(); functionFactory.median(); functionFactory.windowFunctions(); functionFactory.listagg_stringAgg( "varchar" ); functionFactory.inverseDistributionOrderedSetAggregates(); functionFactory.hypotheticalOrderedSetAggregates_windowEmulation(); functionFactory.radians_acos(); functionFactory.degrees_acos(); functionContributions.getFunctionRegistry().register( "timestampadd", new IntegralTimestampaddFunction( this, typeConfiguration ) ); // full-text search functions functionContributions.getFunctionRegistry().registerNamed( "score", typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE ) ); functionContributions.getFunctionRegistry().registerNamed( "snippets" ); functionContributions.getFunctionRegistry().registerNamed( "highlighted" ); functionContributions.getFunctionRegistry().registerBinaryTernaryPattern( "contains", typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.BOOLEAN ), "contains(?1,?2)", "contains(?1,?2,?3)", ANY, ANY, ANY, typeConfiguration ); if ( getVersion().isSameOrAfter( 2, 0 ) ) { // Introduced in 2.0 SPS 00 functionFactory.jsonValue_no_passing(); functionFactory.jsonQuery_no_passing(); functionFactory.jsonExists_hana(); functionFactory.unnest_hana(); functionFactory.jsonTable_hana(); functionFactory.generateSeries_hana( getMaximumSeriesSize() ); if ( getVersion().isSameOrAfter(2, 0, 20 ) ) { if ( getVersion().isSameOrAfter( 2, 0, 40 ) ) { // Introduced in 2.0 SPS 04 functionFactory.jsonObject_hana(); functionFactory.jsonArray_hana(); functionFactory.jsonArrayAgg_hana(); functionFactory.jsonObjectAgg_hana(); } functionFactory.xmltable_hana(); } // functionFactory.xmlextract(); } } /** * HANA doesn't support the {@code generate_series} function or {@code lateral} recursive CTEs, * so it has to be emulated with the {@code xmltable} and {@code lpad} functions. */ protected int getMaximumSeriesSize() { return 10000; } @Override public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { return new StandardSqlAstTranslatorFactory() { @Override protected SqlAstTranslator buildTranslator( SessionFactoryImplementor sessionFactory, org.hibernate.sql.ast.tree.Statement statement) { return new HANASqlAstTranslator<>( sessionFactory, statement ); } }; } @Override public AggregateSupport getAggregateSupport() { return HANAAggregateSupport.valueOf( this ); } /** * HANA has no extract() function, but we can emulate * it using the appropriate named functions instead of * extract(). * * The supported fields are * {@link TemporalUnit#YEAR}, * {@link TemporalUnit#MONTH} * {@link TemporalUnit#DAY}, * {@link TemporalUnit#HOUR}, * {@link TemporalUnit#MINUTE}, * {@link TemporalUnit#SECOND} * {@link TemporalUnit#WEEK}, * {@link TemporalUnit#DAY_OF_WEEK}, * {@link TemporalUnit#DAY_OF_MONTH}, * {@link TemporalUnit#DAY_OF_YEAR}. */ @Override public String extractPattern(TemporalUnit unit) { switch (unit) { case DAY_OF_WEEK: return "(mod(weekday(?2)+1,7)+1)"; case DAY: case DAY_OF_MONTH: return "dayofmonth(?2)"; case DAY_OF_YEAR: return "dayofyear(?2)"; case QUARTER: return "((month(?2)+2)/3)"; case EPOCH: return "seconds_between('1970-01-01', ?2)"; default: //I think week() returns the ISO week number return "?1(?2)"; } } @Override public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() { return (sqlException, message, sql) -> { final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException ); if ( errorCode == 131 ) { // 131 - Transaction rolled back by lock wait timeout return new LockTimeoutException( message, sqlException, sql ); } if ( errorCode == 146 ) { // 146 - Resource busy and acquire with NOWAIT specified return new LockTimeoutException( message, sqlException, sql ); } if ( errorCode == 132 ) { // 132 - Transaction rolled back due to unavailable resource return new LockAcquisitionException( message, sqlException, sql ); } if ( errorCode == 133 ) { // 133 - Transaction rolled back by detected deadlock return new LockAcquisitionException( message, sqlException, sql ); } // 259 - Invalid table name // 260 - Invalid column name // 261 - Invalid index name // 262 - Invalid query name // 263 - Invalid alias name if ( errorCode == 257 || ( errorCode >= 259 && errorCode <= 263 ) ) { return new SQLGrammarException( message, sqlException, sql ); } // 257 - Cannot insert NULL or update to NULL // 301 - Unique constraint violated // 461 - foreign key constraint violation // 462 - failed on update or delete by foreign key constraint violation if ( errorCode == 287 || errorCode == 301 || errorCode == 461 || errorCode == 462 ) { final String constraintName = getViolatedConstraintNameExtractor() .extractConstraintName( sqlException ); return new ConstraintViolationException( message, sqlException, sql, errorCode == 301 ? ConstraintViolationException.ConstraintKind.UNIQUE : ConstraintViolationException.ConstraintKind.OTHER, constraintName ); } return null; }; } @Override public RowLockStrategy getWriteRowLockStrategy() { return RowLockStrategy.COLUMN; } @Override public String getCreateTableString() { return isDefaultTableTypeColumn() ? "create column table" : "create row table"; } @Override public String getAddColumnString() { return "add ("; } @Override public String getAddColumnSuffixString() { return ")"; } @Override public String getCascadeConstraintsString() { return " cascade"; } @Override public String getCurrentTimestampSelectString() { return "select current_timestamp from sys.dummy"; } @Override public String getForUpdateString(final String aliases) { return getForUpdateString() + " of " + aliases; } @Override public String getForUpdateString(final String aliases, final LockOptions lockOptions) { LockMode lockMode = lockOptions.findGreatestLockMode(); lockOptions.setLockMode( lockMode ); // not sure why this is sometimes empty if ( aliases == null || aliases.isEmpty() ) { return getForUpdateString( lockOptions ); } return getForUpdateString( aliases, lockMode, lockOptions.getTimeOut() ); } @SuppressWarnings({ "deprecation" }) private String getForUpdateString(String aliases, LockMode lockMode, int timeout) { switch ( lockMode ) { case PESSIMISTIC_READ: { return getReadLockString( aliases, timeout ); } case PESSIMISTIC_WRITE: { return getWriteLockString( aliases, timeout ); } case UPGRADE_NOWAIT: case PESSIMISTIC_FORCE_INCREMENT: { return getForUpdateNowaitString( aliases ); } case UPGRADE_SKIPLOCKED: { return getForUpdateSkipLockedString( aliases ); } default: { return ""; } } } @Override public String getForUpdateNowaitString() { return getForUpdateString() + " nowait"; } @Override public String getQuerySequencesString() { return "select * from sys.sequences"; } @Override public SequenceInformationExtractor getSequenceInformationExtractor() { return SequenceInformationExtractorHANADatabaseImpl.INSTANCE; } @Override public boolean isCurrentTimestampSelectStringCallable() { return false; } @Override protected void registerDefaultKeywords() { super.registerDefaultKeywords(); // https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/28bcd6af3eb6437892719f7c27a8a285.html?locale=en-US registerKeyword( "all" ); registerKeyword( "alter" ); registerKeyword( "as" ); registerKeyword( "before" ); registerKeyword( "begin" ); registerKeyword( "both" ); registerKeyword( "case" ); registerKeyword( "char" ); registerKeyword( "condition" ); registerKeyword( "connect" ); registerKeyword( "cross" ); registerKeyword( "cube" ); registerKeyword( "current_connection" ); registerKeyword( "current_date" ); registerKeyword( "current_schema" ); registerKeyword( "current_time" ); registerKeyword( "current_timestamp" ); registerKeyword( "current_transaction_isolation_level" ); registerKeyword( "current_user" ); registerKeyword( "current_utcdate" ); registerKeyword( "current_utctime" ); registerKeyword( "current_utctimestamp" ); registerKeyword( "currval" ); registerKeyword( "cursor" ); registerKeyword( "declare" ); registerKeyword( "deferred" ); registerKeyword( "distinct" ); registerKeyword( "else" ); registerKeyword( "elseif" ); registerKeyword( "end" ); registerKeyword( "except" ); registerKeyword( "exception" ); registerKeyword( "exec" ); registerKeyword( "false" ); registerKeyword( "for" ); registerKeyword( "from" ); registerKeyword( "full" ); registerKeyword( "group" ); registerKeyword( "having" ); registerKeyword( "if" ); registerKeyword( "in" ); registerKeyword( "inner" ); registerKeyword( "inout" ); registerKeyword( "intersect" ); registerKeyword( "into" ); registerKeyword( "is" ); registerKeyword( "join" ); registerKeyword( "lateral" ); registerKeyword( "leading" ); registerKeyword( "left" ); registerKeyword( "limit" ); registerKeyword( "loop" ); registerKeyword( "minus" ); registerKeyword( "natural" ); registerKeyword( "nchar" ); registerKeyword( "nextval" ); registerKeyword( "null" ); registerKeyword( "on" ); registerKeyword( "order" ); registerKeyword( "out" ); registerKeyword( "prior" ); registerKeyword( "return" ); registerKeyword( "returns" ); registerKeyword( "reverse" ); registerKeyword( "right" ); registerKeyword( "rollup" ); registerKeyword( "rowid" ); registerKeyword( "select" ); registerKeyword( "session_user" ); registerKeyword( "set" ); registerKeyword( "sql" ); registerKeyword( "start" ); registerKeyword( "sysuuid" ); registerKeyword( "tablesample" ); registerKeyword( "top" ); registerKeyword( "trailing" ); registerKeyword( "true" ); registerKeyword( "union" ); registerKeyword( "unknown" ); registerKeyword( "using" ); registerKeyword( "utctimestamp" ); registerKeyword( "values" ); registerKeyword( "when" ); registerKeyword( "where" ); registerKeyword( "while" ); registerKeyword( "with" ); if ( isCloud() ) { // https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/reserved-words registerKeyword( "array" ); registerKeyword( "at" ); registerKeyword( "authorization" ); registerKeyword( "between" ); registerKeyword( "by" ); registerKeyword( "collate" ); registerKeyword( "empty" ); registerKeyword( "filter" ); registerKeyword( "grouping" ); registerKeyword( "no" ); registerKeyword( "not" ); registerKeyword( "of" ); registerKeyword( "over" ); registerKeyword( "recursive" ); registerKeyword( "row" ); registerKeyword( "table" ); registerKeyword( "to" ); registerKeyword( "unnest" ); registerKeyword( "window" ); registerKeyword( "within" ); } } @Override public ScrollMode defaultScrollMode() { return ScrollMode.FORWARD_ONLY; } /** * HANA currently does not support check constraints. */ @Override public boolean supportsColumnCheck() { return false; } @Override public boolean supportsCurrentTimestampSelection() { return true; } @Override public boolean doesRoundTemporalOnOverflow() { // HANA does truncation return false; } @Override public boolean supportsExistsInSelect() { return false; } @Override public boolean supportsExpectedLobUsagePattern() { // http://scn.sap.com/thread/3221812 return false; } @Override public boolean supportsUnboundedLobLocatorMaterialization() { return false; } @Override public SequenceSupport getSequenceSupport() { return HANASequenceSupport.INSTANCE; } @Override public boolean supportsTableCheck() { return true; } @Override public boolean supportsTupleDistinctCounts() { return true; } @Override public boolean dropConstraints() { return false; } @Override public int getMaxAliasLength() { return 128; } @Override public int getMaxIdentifierLength() { return 127; } @Override public LimitHandler getLimitHandler() { return LimitOffsetLimitHandler.INSTANCE; } @Override public String getSelectGUIDString() { return "select sysuuid from sys.dummy"; } @Override public NameQualifierSupport getNameQualifierSupport() { return NameQualifierSupport.SCHEMA; } @Override public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData) throws SQLException { /* * HANA-specific extensions */ builder.setQuotedCaseStrategy( IdentifierCaseStrategy.MIXED ); builder.setUnquotedCaseStrategy( IdentifierCaseStrategy.UPPER ); final IdentifierHelper identifierHelper = super.buildIdentifierHelper( builder, dbMetaData ); return new IdentifierHelper() { private final IdentifierHelper helper = identifierHelper; @Override public String toMetaDataSchemaName(Identifier schemaIdentifier) { return this.helper.toMetaDataSchemaName( schemaIdentifier ); } @Override public String toMetaDataObjectName(Identifier identifier) { return this.helper.toMetaDataObjectName( identifier ); } @Override public String toMetaDataCatalogName(Identifier catalogIdentifier) { return this.helper.toMetaDataCatalogName( catalogIdentifier ); } @Override public Identifier toIdentifier(String text) { return normalizeQuoting( Identifier.toIdentifier( text ) ); } @Override public Identifier toIdentifier(String text, boolean quoted) { return normalizeQuoting( Identifier.toIdentifier( text, quoted ) ); } @Override public Identifier normalizeQuoting(Identifier identifier) { Identifier normalizedIdentifier = this.helper.normalizeQuoting( identifier ); if ( normalizedIdentifier == null ) { return null; } // need to quote names containing special characters like ':' if ( !normalizedIdentifier.isQuoted() && !normalizedIdentifier.getText().matches( "\\w+" ) ) { normalizedIdentifier = Identifier.quote( normalizedIdentifier ); } return normalizedIdentifier; } @Override public boolean isReservedWord(String word) { return this.helper.isReservedWord( word ); } @Override public Identifier applyGlobalQuoting(String text) { return this.helper.applyGlobalQuoting( text ); } }; } @Override public String getCurrentSchemaCommand() { return "select current_schema from sys.dummy"; } @Override public String getForUpdateNowaitString(String aliases) { return getForUpdateString( aliases ) + " nowait"; } @Override public String getReadLockString(int timeout) { return getWriteLockString( timeout ); } @Override public String getReadLockString(String aliases, int timeout) { return getWriteLockString( aliases, timeout ); } @Override public String getWriteLockString(int timeout) { if ( timeout > 0 ) { return getForUpdateString() + " wait " + getTimeoutInSeconds( timeout ); } else if ( timeout == 0 ) { return getForUpdateNowaitString(); } else { return getForUpdateString(); } } @Override public String getWriteLockString(String aliases, int timeout) { if ( timeout > 0 ) { return getForUpdateString( aliases ) + " wait " + getTimeoutInSeconds( timeout ); } else if ( timeout == 0 ) { return getForUpdateNowaitString( aliases ); } else { return getForUpdateString( aliases ); } } @Override public String getQueryHintString(String query, List hints) { return query + " with hint (" + String.join( ",", hints ) + ")"; } @Override public String getTableComment(String comment) { return " comment '" + comment + "'"; } @Override public String getColumnComment(String comment) { return " comment '" + comment + "'"; } @Override public boolean supportsCommentOn() { return true; } @Override public boolean supportsPartitionBy() { return true; } @Override public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { super.contributeTypes( typeContributions, serviceRegistry ); final TypeConfiguration typeConfiguration = typeContributions.getTypeConfiguration(); final JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry(); if ( treatDoubleTypedFieldsAsDecimal ) { typeConfiguration.getBasicTypeRegistry() .register( new BasicTypeImpl<>( DoubleJavaType.INSTANCE, NumericJdbcType.INSTANCE ), Double.class.getName() ); final Map> jdbcToHibernateTypeContributionMap = typeConfiguration.getJdbcToHibernateTypeContributionMap(); jdbcToHibernateTypeContributionMap.computeIfAbsent( Types.FLOAT, code -> new HashSet<>() ).clear(); jdbcToHibernateTypeContributionMap.computeIfAbsent( Types.REAL, code -> new HashSet<>() ).clear(); jdbcToHibernateTypeContributionMap.computeIfAbsent( Types.DOUBLE, code -> new HashSet<>() ).clear(); jdbcToHibernateTypeContributionMap.get( Types.FLOAT ).add( StandardBasicTypes.BIG_DECIMAL.getName() ); jdbcToHibernateTypeContributionMap.get( Types.REAL ).add( StandardBasicTypes.BIG_DECIMAL.getName() ); jdbcToHibernateTypeContributionMap.get( Types.DOUBLE ).add( StandardBasicTypes.BIG_DECIMAL.getName() ); jdbcTypeRegistry.addDescriptor( Types.FLOAT, NumericJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( Types.REAL, NumericJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( Types.DOUBLE, NumericJdbcType.INSTANCE ); } jdbcTypeRegistry.addDescriptor( Types.CLOB, new HANAClobJdbcType( maxLobPrefetchSize, useUnicodeStringTypes ) ); jdbcTypeRegistry.addDescriptor( Types.NCLOB, new HANANClobJdbcType( maxLobPrefetchSize ) ); jdbcTypeRegistry.addDescriptor( Types.BLOB, new HANABlobType( maxLobPrefetchSize ) ); // tinyint is unsigned on HANA jdbcTypeRegistry.addDescriptor( Types.TINYINT, TinyIntAsSmallIntJdbcType.INSTANCE ); if ( isUseUnicodeStringTypes() ) { jdbcTypeRegistry.addDescriptor( Types.VARCHAR, NVarcharJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptor( Types.CHAR, NCharJdbcType.INSTANCE ); } if ( treatDoubleTypedFieldsAsDecimal ) { jdbcTypeRegistry.addDescriptor( Types.DOUBLE, DecimalJdbcType.INSTANCE ); } } @Override public void appendBooleanValueString(SqlAppender appender, boolean bool) { if ( this.useLegacyBooleanType ) { appender.appendSql( bool ? '1' : '0' ); } else { appender.appendSql( bool ); } } @Override public IdentityColumnSupport getIdentityColumnSupport() { return HANAIdentityColumnSupport.INSTANCE; } @Override public Exporter

getTableExporter() { return this.hanaTableExporter; } /* * HANA doesn't really support REF_CURSOR returns from a procedure, but REF_CURSOR support can be emulated by using * procedures or functions with an OUT parameter of type TABLE. The results will be returned as result sets on the * callable statement. */ @Override public CallableStatementSupport getCallableStatementSupport() { return StandardCallableStatementSupport.REF_CURSOR_INSTANCE; } @Override public int registerResultSetOutParameter(CallableStatement statement, int position) throws SQLException { // Result set (TABLE) OUT parameters don't need to be registered return position; } @Override public int registerResultSetOutParameter(CallableStatement statement, String name) throws SQLException { // Result set (TABLE) OUT parameters don't need to be registered return 0; } @Override public boolean supportsOffsetInSubquery() { return true; } @Override public boolean supportsWindowFunctions() { return true; } @Override public boolean supportsLateral() { return getVersion().isSameOrAfter( 2, 0, 40 ); } @Override public boolean supportsNoWait() { return true; } @Override public boolean supportsJdbcConnectionLobCreation(DatabaseMetaData databaseMetaData) { return false; } @Override public boolean supportsNoColumnsInsert() { return false; } @Override public boolean supportsOrderByInSubquery() { // Seems to work, though I don't know as of which version return true; } @Override public NullOrdering getNullOrdering() { return NullOrdering.SMALLEST; } @Override public void appendDatetimeFormat(SqlAppender appender, String format) { //I don't think HANA needs FM appender.appendSql( OracleDialect.datetimeFormat( format, false, false ).result() ); } @Override public boolean supportsFractionalTimestampArithmetic() { return false; } @Override public long getFractionalSecondPrecisionInNanos() { return 100; } @Override public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { switch (unit) { case NANOSECOND: if ( temporalType == TemporalType.TIME ) { return "cast(add_nano100(cast('1970-01-01 '||(?3) as timestamp),?2/100) as time)"; } else { return "add_nano100(?3,?2/100)"; } case NATIVE: if ( temporalType == TemporalType.TIME ) { return "cast(add_nano100(cast('1970-01-01 '||(?3) as timestamp),?2) as time)"; } else { return "add_nano100(?3,?2)"; } case QUARTER: return "add_months(?3,3*?2)"; case WEEK: return "add_days(?3,7*?2)"; case MINUTE: if ( temporalType == TemporalType.TIME ) { return "cast(add_seconds(cast('1970-01-01 '||(?3) as timestamp),60*?2) as time)"; } else { return "add_seconds(?3,60*?2)"; } case HOUR: if ( temporalType == TemporalType.TIME ) { return "cast(add_seconds(cast('1970-01-01 '||(?3) as timestamp),3600*?2) as time)"; } else { return "add_seconds(?3,3600*?2)"; } case SECOND: if ( temporalType == TemporalType.TIME ) { return "cast(add_seconds(cast('1970-01-01 '||(?3) as timestamp),?2) as time)"; } // Fall through on purpose default: return "add_?1s(?3,?2)"; } } @Override public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { switch (unit) { case NANOSECOND: if ( fromTemporalType == TemporalType.TIME && toTemporalType == TemporalType.TIME ) { return "seconds_between(?2,?3)*1000000000"; } else { return "nano100_between(?2,?3)*100"; } case NATIVE: if ( fromTemporalType == TemporalType.TIME && toTemporalType == TemporalType.TIME ) { return "seconds_between(?2,?3)*10000000"; } else { return "nano100_between(?2,?3)"; } case QUARTER: return "months_between(?2,?3)/3"; case WEEK: return "days_between(?2,?3)/7"; case MINUTE: return "seconds_between(?2,?3)/60"; case HOUR: return "seconds_between(?2,?3)/3600"; default: return "?1s_between(?2,?3)"; } } @Override public void appendDateTimeLiteral( SqlAppender appender, TemporalAccessor temporalAccessor, TemporalType precision, TimeZone jdbcTimeZone) { switch ( precision ) { case DATE: appender.appendSql( JDBC_ESCAPE_START_DATE ); appendAsDate( appender, temporalAccessor ); appender.appendSql( JDBC_ESCAPE_END ); break; case TIME: appender.appendSql( JDBC_ESCAPE_START_TIME ); appendAsTime( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone ); appender.appendSql( JDBC_ESCAPE_END ); break; case TIMESTAMP: appender.appendSql( JDBC_ESCAPE_START_TIMESTAMP ); appendAsTimestampWithMicros( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone ); appender.appendSql( JDBC_ESCAPE_END ); break; default: throw new IllegalArgumentException(); } } @Override public void appendDateTimeLiteral(SqlAppender appender, Date date, TemporalType precision, TimeZone jdbcTimeZone) { switch ( precision ) { case DATE: appender.appendSql( JDBC_ESCAPE_START_DATE ); appendAsDate( appender, date ); appender.appendSql( JDBC_ESCAPE_END ); break; case TIME: appender.appendSql( JDBC_ESCAPE_START_TIME ); appendAsTime( appender, date ); appender.appendSql( JDBC_ESCAPE_END ); break; case TIMESTAMP: appender.appendSql( JDBC_ESCAPE_START_TIMESTAMP ); appendAsTimestampWithMicros( appender, date, jdbcTimeZone ); appender.appendSql( JDBC_ESCAPE_END ); break; default: throw new IllegalArgumentException(); } } @Override public String generatedAs(String generatedAs) { return " generated always as (" + generatedAs + ")"; } public boolean isUseUnicodeStringTypes() { return this.useUnicodeStringTypes || isDefaultTableTypeColumn() && isCloud(); } protected boolean supportsAsciiStringTypes() { return !isDefaultTableTypeColumn() || !isCloud(); } protected Boolean useUnicodeStringTypesDefault() { return isDefaultTableTypeColumn() ? isCloud() : Boolean.FALSE; } private static class CloseSuppressingReader extends FilterReader { protected CloseSuppressingReader(final Reader in) { super( in ); } @Override public void close() { // do not close } } private static class CloseSuppressingInputStream extends FilterInputStream { protected CloseSuppressingInputStream(final InputStream in) { super( in ); } @Override public void close() { // do not close } } private static class MaterializedBlob implements Blob { private byte[] bytes = null; public MaterializedBlob(byte[] bytes) { this.setBytes( bytes ); } @Override public long length() throws SQLException { return this.getBytes().length; } @Override public byte[] getBytes(long pos, int length) throws SQLException { return Arrays.copyOfRange( this.bytes, (int) ( pos - 1 ), (int) ( pos - 1 + length ) ); } @Override public InputStream getBinaryStream() throws SQLException { return new ByteArrayInputStream( this.getBytes() ); } @Override public long position(byte[] pattern, long start) throws SQLException { throw new SQLFeatureNotSupportedException(); } @Override public long position(Blob pattern, long start) throws SQLException { throw new SQLFeatureNotSupportedException(); } @Override public int setBytes(long pos, byte[] bytes) throws SQLException { int bytesSet = 0; if ( this.bytes.length < pos - 1 + bytes.length ) { this.bytes = Arrays.copyOf( this.bytes, (int) ( pos - 1 + bytes.length ) ); } for ( int i = 0; i < bytes.length && i < this.bytes.length; i++, bytesSet++ ) { this.bytes[(int) ( i + pos - 1 )] = bytes[i]; } return bytesSet; } @Override public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException { int bytesSet = 0; if ( this.bytes.length < pos - 1 + len ) { this.bytes = Arrays.copyOf( this.bytes, (int) ( pos - 1 + len ) ); } for ( int i = offset; i < len && i < this.bytes.length; i++, bytesSet++ ) { this.bytes[(int) ( i + pos - 1 )] = bytes[i]; } return bytesSet; } @Override public OutputStream setBinaryStream(long pos) { return new ByteArrayOutputStream() { { this.buf = getBytes(); } }; } @Override public void truncate(long len) throws SQLException { this.setBytes( Arrays.copyOf( this.getBytes(), (int) len ) ); } @Override public void free() throws SQLException { this.setBytes( null ); } @Override public InputStream getBinaryStream(long pos, long length) throws SQLException { return new ByteArrayInputStream( this.getBytes(), (int) ( pos - 1 ), (int) length ); } byte[] getBytes() { return this.bytes; } void setBytes(byte[] bytes) { this.bytes = bytes; } } private static class MaterializedNClob implements NClob { private String data; public MaterializedNClob(String data) { this.data = data; } @Override public void truncate(long len) throws SQLException { this.data = ""; } @Override public int setString(long pos, String str, int offset, int len) throws SQLException { this.data = this.data.substring( 0, (int) ( pos - 1 ) ) + str.substring( offset, offset + len ) + this.data.substring( (int) ( pos - 1 + len ) ); return len; } @Override public int setString(long pos, String str) throws SQLException { this.data = this.data.substring( 0, (int) ( pos - 1 ) ) + str + this.data.substring( (int) ( pos - 1 + str.length() ) ); return str.length(); } @Override public Writer setCharacterStream(long pos) throws SQLException { throw new SQLFeatureNotSupportedException(); } @Override public OutputStream setAsciiStream(long pos) throws SQLException { throw new SQLFeatureNotSupportedException(); } @Override public long position(Clob searchstr, long start) throws SQLException { return this.data.indexOf( DataHelper.extractString( searchstr ), (int) ( start - 1 ) ); } @Override public long position(String searchstr, long start) throws SQLException { return this.data.indexOf( searchstr, (int) ( start - 1 ) ); } @Override public long length() throws SQLException { return this.data.length(); } @Override public String getSubString(long pos, int length) throws SQLException { return this.data.substring( (int) ( pos - 1 ), (int) ( pos - 1 + length ) ); } @Override public Reader getCharacterStream(long pos, long length) throws SQLException { return new StringReader( this.data.substring( (int) ( pos - 1 ), (int) ( pos - 1 + length ) ) ); } @Override public Reader getCharacterStream() throws SQLException { return new StringReader( this.data ); } @Override public InputStream getAsciiStream() { return new ByteArrayInputStream( this.data.getBytes( StandardCharsets.ISO_8859_1 ) ); } @Override public void free() throws SQLException { this.data = null; } } private static class HANAStreamBlobType implements JdbcType { private static final long serialVersionUID = -2476600722093442047L; final int maxLobPrefetchSize; public HANAStreamBlobType(int maxLobPrefetchSize) { this.maxLobPrefetchSize = maxLobPrefetchSize; } @Override public String getFriendlyName() { return "BLOB (hana-stream)"; } @Override public String toString() { return "HANAStreamBlobType"; } @Override public int getJdbcTypeCode() { return Types.BLOB; } @Override public ValueBinder getBinder(JavaType javaType) { return new BasicBinder<>( javaType, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { final BinaryStream binaryStream = javaType.unwrap( value, BinaryStream.class, options ); if ( value instanceof BlobImplementer) { try ( InputStream is = new CloseSuppressingInputStream( binaryStream.getInputStream() ) ) { st.setBinaryStream( index, is, binaryStream.getLength() ); } catch (IOException e) { // can't happen => ignore } } else { st.setBinaryStream( index, binaryStream.getInputStream(), binaryStream.getLength() ); } } @Override protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { final BinaryStream binaryStream = javaType.unwrap( value, BinaryStream.class, options ); if ( value instanceof BlobImplementer ) { try ( InputStream is = new CloseSuppressingInputStream( binaryStream.getInputStream() ) ) { st.setBinaryStream( name, is, binaryStream.getLength() ); } catch (IOException e) { // can't happen => ignore } } else { st.setBinaryStream( name, binaryStream.getInputStream(), binaryStream.getLength() ); } } }; } @Override public ValueExtractor getExtractor(JavaType javaType) { return new BasicExtractor<>( javaType, this ) { private X extract(Blob blob, WrapperOptions options) throws SQLException { if ( blob == null ) { return null; } if ( blob.length() < HANALegacyDialect.HANAStreamBlobType.this.maxLobPrefetchSize ) { X result = javaType.wrap( blob, options ); blob.free(); return result; } Blob materializedBlob = new MaterializedBlob( DataHelper.extractBytes( blob.getBinaryStream() ) ); blob.free(); return javaType.wrap( materializedBlob, options ); } @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { return extract( rs.getBlob( paramIndex ), options ); } @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { return extract( statement.getBlob( index ), options ); } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { return extract( statement.getBlob( name ), options ); } }; } } // the ClobTypeDescriptor and NClobTypeDescriptor for HANA are slightly // changed from the standard ones. The HANA JDBC driver currently closes any // stream passed in via // PreparedStatement.setCharacterStream(int,Reader,long) // after the stream has been processed. this causes problems later if we are // using non-contextual lob creation and HANA then closes our StringReader. // see test case LobLocatorTest private static class HANAClobJdbcType extends ClobJdbcType { @Override public String toString() { return "HANAClobTypeDescriptor"; } /** serial version uid. */ private static final long serialVersionUID = -379042275442752102L; final int maxLobPrefetchSize; final boolean useUnicodeStringTypes; public HANAClobJdbcType(int maxLobPrefetchSize, boolean useUnicodeStringTypes) { this.maxLobPrefetchSize = maxLobPrefetchSize; this.useUnicodeStringTypes = useUnicodeStringTypes; } @Override public BasicBinder getClobBinder(final JavaType javaType) { return new BasicBinder<>( javaType, this ) { @Override protected void doBind(final PreparedStatement st, final X value, final int index, final WrapperOptions options) throws SQLException { final CharacterStream characterStream = javaType.unwrap( value, CharacterStream.class, options ); if ( value instanceof ClobImplementer) { try ( Reader r = new CloseSuppressingReader( characterStream.asReader() ) ) { st.setCharacterStream( index, r, characterStream.getLength() ); } catch (IOException e) { // can't happen => ignore } } else { st.setCharacterStream( index, characterStream.asReader(), characterStream.getLength() ); } } @Override protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { final CharacterStream characterStream = javaType.unwrap( value, CharacterStream.class, options ); if ( value instanceof ClobImplementer ) { try ( Reader r = new CloseSuppressingReader( characterStream.asReader() ) ) { st.setCharacterStream( name, r, characterStream.getLength() ); } catch (IOException e) { // can't happen => ignore } } else { st.setCharacterStream( name, characterStream.asReader(), characterStream.getLength() ); } } }; } @Override public ValueExtractor getExtractor(JavaType javaType) { return new BasicExtractor<>( javaType, this ) { private X extract(Clob clob, WrapperOptions options) throws SQLException { if ( clob == null ) { return null; } if ( clob.length() < HANALegacyDialect.HANAClobJdbcType.this.maxLobPrefetchSize ) { X retVal = javaType.wrap(clob, options); clob.free(); return retVal; } NClob materializedNClob = new MaterializedNClob( DataHelper.extractString( clob ) ); clob.free(); return javaType.wrap( materializedNClob, options ); } @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { Clob rsClob; if ( HANALegacyDialect.HANAClobJdbcType.this.useUnicodeStringTypes ) { rsClob = rs.getNClob( paramIndex ); } else { rsClob = rs.getClob( paramIndex ); } return extract( rsClob, options ); } @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { Clob rsClob; if ( HANALegacyDialect.HANAClobJdbcType.this.useUnicodeStringTypes ) { rsClob = statement.getNClob( index ); } else { rsClob = statement.getClob( index ); } return extract( rsClob, options ); } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { Clob rsClob; if ( HANALegacyDialect.HANAClobJdbcType.this.useUnicodeStringTypes ) { rsClob = statement.getNClob( name ); } else { rsClob = statement.getClob( name ); } return extract( rsClob, options ); } }; } public int getMaxLobPrefetchSize() { return this.maxLobPrefetchSize; } public boolean isUseUnicodeStringTypes() { return this.useUnicodeStringTypes; } } private static class HANANClobJdbcType extends NClobJdbcType { /** serial version uid. */ private static final long serialVersionUID = 5651116091681647859L; final int maxLobPrefetchSize; public HANANClobJdbcType(int maxLobPrefetchSize) { this.maxLobPrefetchSize = maxLobPrefetchSize; } @Override public String toString() { return "HANANClobTypeDescriptor"; } @Override public BasicBinder getNClobBinder(final JavaType javaType) { return new BasicBinder<>( javaType, this ) { @Override protected void doBind(final PreparedStatement st, final X value, final int index, final WrapperOptions options) throws SQLException { final CharacterStream characterStream = javaType.unwrap( value, CharacterStream.class, options ); if ( value instanceof NClobImplementer) { try ( Reader r = new CloseSuppressingReader( characterStream.asReader() ) ) { st.setCharacterStream( index, r, characterStream.getLength() ); } catch (IOException e) { // can't happen => ignore } } else { st.setCharacterStream( index, characterStream.asReader(), characterStream.getLength() ); } } @Override protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { final CharacterStream characterStream = javaType.unwrap( value, CharacterStream.class, options ); if ( value instanceof NClobImplementer ) { try ( Reader r = new CloseSuppressingReader( characterStream.asReader() ) ) { st.setCharacterStream( name, r, characterStream.getLength() ); } catch (IOException e) { // can't happen => ignore } } else { st.setCharacterStream( name, characterStream.asReader(), characterStream.getLength() ); } } }; } @Override public ValueExtractor getExtractor(JavaType javaType) { return new BasicExtractor<>( javaType, this ) { private X extract(NClob nclob, WrapperOptions options) throws SQLException { if ( nclob == null ) { return null; } if ( nclob.length() < maxLobPrefetchSize ) { X retVal = javaType.wrap(nclob, options); nclob.free(); return retVal; } NClob materializedNClob = new MaterializedNClob( DataHelper.extractString( nclob ) ); nclob.free(); return javaType.wrap( materializedNClob, options ); } @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { return extract( rs.getNClob( paramIndex ), options ); } @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { return extract( statement.getNClob( index ), options ); } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { return extract( statement.getNClob( name ), options ); } }; } public int getMaxLobPrefetchSize() { return maxLobPrefetchSize; } } public static class HANABlobType implements JdbcType { private static final long serialVersionUID = 5874441715643764323L; public static final JdbcType INSTANCE = new HANABlobType( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE ); final int maxLobPrefetchSize; final HANAStreamBlobType hanaStreamBlobTypeDescriptor; public HANABlobType(int maxLobPrefetchSize) { this.maxLobPrefetchSize = maxLobPrefetchSize; this.hanaStreamBlobTypeDescriptor = new HANAStreamBlobType( maxLobPrefetchSize ); } @Override public int getJdbcTypeCode() { return Types.BLOB; } @Override public String getFriendlyName() { return "BLOB (HANA)"; } @Override public String toString() { return "HANABlobType"; } @Override public ValueExtractor getExtractor(final JavaType javaType) { return new BasicExtractor<>( javaType, this ) { private X extract(Blob blob, WrapperOptions options) throws SQLException { if ( blob == null ) { return null; } if ( blob.length() < maxLobPrefetchSize ) { X retVal = javaType.wrap(blob, options); blob.free(); return retVal; } Blob materializedBlob = new MaterializedBlob( DataHelper.extractBytes( blob.getBinaryStream() ) ); blob.free(); return javaType.wrap( materializedBlob, options ); } @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { return extract( rs.getBlob( paramIndex ) , options ); } @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { return extract( statement.getBlob( index ), options ); } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { return extract( statement.getBlob( name ), options ); } }; } @Override public BasicBinder getBinder(final JavaType javaType) { return new BasicBinder<>( javaType, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { JdbcType descriptor = BlobJdbcType.BLOB_BINDING; if ( value instanceof byte[] ) { // performance shortcut for binding BLOB data in byte[] format descriptor = BlobJdbcType.PRIMITIVE_ARRAY_BINDING; } else if ( options.useStreamForLobBinding() ) { descriptor = hanaStreamBlobTypeDescriptor; } descriptor.getBinder( javaType ).bind( st, value, index, options ); } @Override protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { JdbcType descriptor = BlobJdbcType.BLOB_BINDING; if ( value instanceof byte[] ) { // performance shortcut for binding BLOB data in byte[] format descriptor = BlobJdbcType.PRIMITIVE_ARRAY_BINDING; } else if ( options.useStreamForLobBinding() ) { descriptor = hanaStreamBlobTypeDescriptor; } descriptor.getBinder( javaType ).bind( st, value, name, options ); } }; } public int getMaxLobPrefetchSize() { return maxLobPrefetchSize; } } @Override public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new GlobalTemporaryTableMutationStrategy( TemporaryTable.createIdTable( entityDescriptor, basename -> TemporaryTable.ID_TABLE_PREFIX + basename, this, runtimeModelCreationContext ), runtimeModelCreationContext.getSessionFactory() ); } @Override public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy( EntityMappingType entityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new GlobalTemporaryTableInsertStrategy( TemporaryTable.createEntityTable( entityDescriptor, name -> TemporaryTable.ENTITY_TABLE_PREFIX + name, this, runtimeModelCreationContext ), runtimeModelCreationContext.getSessionFactory() ); } @Override public TemporaryTableKind getSupportedTemporaryTableKind() { return TemporaryTableKind.GLOBAL; } @Override public String getTemporaryTableCreateOptions() { return "on commit delete rows"; } @Override public String getTemporaryTableCreateCommand() { return "create global temporary row table"; } @Override public String getTemporaryTableTruncateCommand() { return "truncate table"; } @Override public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() { return DmlTargetColumnQualifierSupport.TABLE_ALIAS; } @Override public boolean supportsSkipLocked() { // HANA supports IGNORE LOCKED since HANA 2.0 SPS3 (2.0.030) return getVersion().isSameOrAfter(2, 0, 30); } @Override public String getForUpdateSkipLockedString() { return supportsSkipLocked() ? getForUpdateString() + SQL_IGNORE_LOCKED : getForUpdateString(); } @Override public String getForUpdateSkipLockedString(String aliases) { return supportsSkipLocked() ? getForUpdateString(aliases) + SQL_IGNORE_LOCKED : getForUpdateString(aliases); } @Override public String getForUpdateString(LockMode lockMode) { return super.getForUpdateString(lockMode); } @Override public String getDual() { return "sys.dummy"; } @Override public String getFromDualForSelectOnly() { return " from " + getDual(); } }