HHH-18643 - Remove support for SAP HANA versions older than 2.0 SPS 05, create a legacy HANA dialect in the community dialects module

Signed-off-by: Jan Schatteman <jschatte@redhat.com>
This commit is contained in:
Jan Schatteman 2024-09-20 22:27:13 +02:00 committed by Jan Schatteman
parent f8e4e6e49f
commit 58ee919feb
8 changed files with 2417 additions and 21 deletions

View File

@ -0,0 +1,102 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.community.dialect;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import static org.hibernate.cfg.DialectSpecificSettings.HANA_MAX_LOB_PREFETCH_SIZE;
/**
* Utility class that extracts some initial configuration from the database for {@link HANALegacyDialect}.
*/
public class HANALegacyServerConfiguration {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( HANALegacyServerConfiguration.class );
public static final int MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE = 1024;
private final DatabaseVersion fullVersion;
private final int maxLobPrefetchSize;
public HANALegacyServerConfiguration(DatabaseVersion fullVersion) {
this( fullVersion, MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE );
}
public HANALegacyServerConfiguration(DatabaseVersion fullVersion, int maxLobPrefetchSize) {
this.fullVersion = fullVersion;
this.maxLobPrefetchSize = maxLobPrefetchSize;
}
public DatabaseVersion getFullVersion() {
return fullVersion;
}
public int getMaxLobPrefetchSize() {
return maxLobPrefetchSize;
}
public static HANALegacyServerConfiguration fromDialectResolutionInfo(DialectResolutionInfo info) {
Integer maxLobPrefetchSize = null;
final DatabaseMetaData databaseMetaData = info.getDatabaseMetadata();
if ( databaseMetaData != null ) {
try (final Statement statement = databaseMetaData.getConnection().createStatement()) {
try ( ResultSet rs = statement.executeQuery(
"SELECT TOP 1 VALUE,MAP(LAYER_NAME,'DEFAULT',1,'SYSTEM',2,'DATABASE',3,4) AS LAYER FROM SYS.M_INIFILE_CONTENTS WHERE FILE_NAME='indexserver.ini' AND SECTION='session' AND KEY='max_lob_prefetch_size' ORDER BY LAYER DESC" ) ) {
// This only works if the current user has the privilege INIFILE ADMIN
if ( rs.next() ) {
maxLobPrefetchSize = rs.getInt( 1 );
}
}
}
catch (SQLException e) {
// Ignore
LOG.debug(
"An error occurred while trying to determine the value of the HANA parameter indexserver.ini / session / max_lob_prefetch_size.",
e );
}
}
// default to the dialect-specific configuration settings
if ( maxLobPrefetchSize == null ) {
maxLobPrefetchSize = ConfigurationHelper.getInt(
HANA_MAX_LOB_PREFETCH_SIZE,
info.getConfigurationValues(),
MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE
);
}
return new HANALegacyServerConfiguration( staticDetermineDatabaseVersion( info ), maxLobPrefetchSize );
}
static DatabaseVersion staticDetermineDatabaseVersion(DialectResolutionInfo info) {
// Parse the version according to https://answers.sap.com/questions/9760991/hana-sps-version-check.html
final String versionString = info.getDatabaseVersion();
int majorVersion = 1;
int minorVersion = 0;
int patchLevel = 0;
if ( versionString == null ) {
return HANALegacyDialect.DEFAULT_VERSION;
}
final String[] components = StringHelper.split( ".", versionString );
if ( components.length >= 3 ) {
try {
majorVersion = Integer.parseInt( components[0] );
minorVersion = Integer.parseInt( components[1] );
patchLevel = Integer.parseInt( components[2] );
}
catch (NumberFormatException ex) {
// Ignore
}
}
return DatabaseVersion.make( majorVersion, minorVersion, patchLevel );
}
}

View File

@ -0,0 +1,275 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.community.dialect;
import java.util.List;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.IllegalQueryOperationException;
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.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.Summarization;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.TableInsertStandard;
/**
* An SQL AST translator for the Legacy HANA dialect.
*/
public class HANALegacySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
private boolean inLateral;
public HANALegacySqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
super( sessionFactory, statement );
}
@Override
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
if ( isIntegerDivisionEmulationRequired( arithmeticExpression ) ) {
appendSql( "cast(" );
visitArithmeticOperand( arithmeticExpression.getLeftHandOperand() );
appendSql( arithmeticExpression.getOperator().getOperatorSqlTextString() );
visitArithmeticOperand( arithmeticExpression.getRightHandOperand() );
appendSql( " as int)" );
}
else {
super.visitBinaryArithmeticExpression( arithmeticExpression );
}
}
@Override
protected void visitArithmeticOperand(Expression expression) {
render( expression, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
}
private boolean isHanaCloud() {
return ( (HANALegacyDialect) getDialect() ).isCloud();
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void visitUpdateStatementOnly(UpdateStatement statement) {
// HANA Cloud does not support the FROM clause in UPDATE statements
if ( isHanaCloud() && hasNonTrivialFromClause( statement.getFromClause() ) ) {
visitUpdateStatementEmulateMerge( statement );
}
else {
super.visitUpdateStatementOnly( statement );
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
// HANA Cloud does not support the FROM clause in UPDATE statements
if ( isHanaCloud() ) {
super.renderUpdateClause( updateStatement );
}
else {
appendSql( "update" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.UPDATE );
renderTableReferenceIdentificationVariable( updateStatement.getTargetTable() );
}
finally {
clauseStack.pop();
}
}
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
// HANA Cloud does not support the FROM clause in UPDATE statements
if ( !isHanaCloud() ) {
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict 'do update' clause with constraint name is not supported" );
}
}
}
protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
// HANA only supports the LIMIT + OFFSET syntax but also window functions
// Check if current query part is already row numbering to avoid infinite recursion
return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart
&& !isRowsOnlyFetchClauseType( queryPart );
}
@Override
protected boolean supportsWithClauseInSubquery() {
// HANA doesn't seem to support correlation, so we just report false here for simplicity
return false;
}
@Override
protected boolean isCorrelated(CteStatement cteStatement) {
// Report false here, because apparently HANA does not need the "lateral" keyword to correlate a from clause subquery in a subquery
return false;
}
@Override
public void visitQueryGroup(QueryGroup queryGroup) {
if ( shouldEmulateFetchClause( queryGroup ) ) {
emulateFetchOffsetWithWindowFunctions( queryGroup, true );
}
else {
super.visitQueryGroup( queryGroup );
}
}
@Override
public void visitQuerySpec(QuerySpec querySpec) {
if ( shouldEmulateFetchClause( querySpec ) ) {
emulateFetchOffsetWithWindowFunctions( querySpec, true );
}
else {
super.visitQuerySpec( querySpec );
}
}
@Override
public void visitQueryPartTableReference(QueryPartTableReference tableReference) {
if ( tableReference.isLateral() && !inLateral ) {
inLateral = true;
emulateQueryPartTableReferenceColumnAliasing( tableReference );
inLateral = false;
}
else {
emulateQueryPartTableReferenceColumnAliasing( tableReference );
}
}
@Override
protected SqlAstNodeRenderingMode getParameterRenderingMode() {
// HANA does not support parameters in lateral subqueries for some reason, so inline all the parameters in this case
return inLateral ? SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS : super.getParameterRenderingMode();
}
@Override
public void visitFunctionTableReference(FunctionTableReference tableReference) {
tableReference.getFunctionExpression().accept( this );
renderTableReferenceIdentificationVariable( tableReference );
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
if ( !isRowNumberingCurrentQueryPart() ) {
renderLimitOffsetClause( queryPart );
}
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
if ( operator == ComparisonOperator.DISTINCT_FROM || operator == ComparisonOperator.NOT_DISTINCT_FROM ) {
// HANA does not support plain parameters in the select clause of the intersect emulation
withParameterRenderingMode(
SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER,
() -> renderComparisonEmulateIntersect( lhs, operator, rhs )
);
}
else {
renderComparisonEmulateIntersect( lhs, operator, rhs );
}
}
@Override
protected void renderPartitionItem(Expression expression) {
if ( expression instanceof Literal ) {
appendSql( "grouping sets (())" );
}
else if ( expression instanceof Summarization ) {
throw new UnsupportedOperationException( "Summarization is not supported by DBMS" );
}
else {
expression.accept( this );
}
}
@Override
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
return false;
}
@Override
protected boolean supportsRowValueConstructorGtLtSyntax() {
return false;
}
@Override
protected void renderInsertIntoNoColumns(TableInsertStandard tableInsert) {
throw new MappingException(
String.format(
"The INSERT statement for table [%s] contains no column, and this is not supported by [%s]",
tableInsert.getMutatingTable().getTableId(),
getDialect()
)
);
}
@Override
protected void visitValuesList(List<Values> valuesList) {
visitValuesListEmulateSelectUnion( valuesList );
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
emulateValuesTableReferenceColumnAliasing( tableReference );
}
@Override
protected String getSkipLocked() {
return " ignore locked";
}
}

View File

@ -168,7 +168,7 @@ import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithM
*/
public class HANADialect extends Dialect {
static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 1, 0, 120 );
static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 2, 0, 50 );
public HANADialect(DialectResolutionInfo info) {
this( HANAServerConfiguration.fromDialectResolutionInfo( info ), true );
@ -176,7 +176,7 @@ public class HANADialect extends Dialect {
}
public HANADialect() {
// SAP HANA 1.0 SPS12 R0 is the default
// SAP HANA 2.0 SPS 05 is the default
this( MINIMUM_VERSION );
}
@ -392,6 +392,7 @@ public class HANADialect extends Dialect {
return 7;
}
@Override
public int getDefaultDecimalPrecision() {
//the maximum on HANA
return 34;
@ -489,20 +490,16 @@ public class HANADialect extends Dialect {
typeConfiguration
);
if ( getVersion().isSameOrAfter(2, 0, 20) ) {
// Introduced in 2.0 SPS 02
functionFactory.jsonValue_no_passing();
functionFactory.jsonQuery_no_passing();
functionFactory.jsonExists_hana();
if ( getVersion().isSameOrAfter(2, 0, 40) ) {
// Introduced in 2.0 SPS 04
functionFactory.jsonObject_hana();
functionFactory.jsonArray_hana();
functionFactory.jsonArrayAgg_hana();
functionFactory.jsonObjectAgg_hana();
}
}
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
@ -1130,7 +1127,7 @@ public class HANADialect extends Dialect {
@Override
public boolean supportsLateral() {
return getVersion().isSameOrAfter( 2, 0, 40 );
return true;
}
@Override
@ -1992,7 +1989,7 @@ public class HANADialect extends Dialect {
@Override
public boolean supportsSkipLocked() {
// HANA supports IGNORE LOCKED since HANA 2.0 SPS3 (2.0.030)
return getVersion().isSameOrAfter(2, 0, 30);
return true;
}
@Override

View File

@ -18,7 +18,7 @@ import org.hibernate.internal.util.config.ConfigurationHelper;
import static org.hibernate.cfg.DialectSpecificSettings.HANA_MAX_LOB_PREFETCH_SIZE;
/**
* Utility class that extract some initial configuration from the database for {@link HANADialect}.
* Utility class that extracts some initial configuration from the database for {@link HANADialect}.
*/
public class HANAServerConfiguration {

View File

@ -35,7 +35,7 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.TableInsertStandard;
/**
* A SQL AST translator for HANA.
* An SQL AST translator for HANA.
*
* @author Christian Beikov
*/

View File

@ -21,7 +21,7 @@ import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.spi.TypeConfiguration;
/**
* SQL Server json_arrayagg function.
* SAP HANA json_arrayagg function.
*/
public class HANAJsonArrayAggFunction extends JsonArrayAggFunction {

View File

@ -17,7 +17,7 @@ import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.type.spi.TypeConfiguration;
/**
* HANA json_objectagg function.
* SAP HANA json_objectagg function.
*/
public class HANAJsonObjectAggFunction extends JsonObjectAggFunction {