Skip to content

Commit

Permalink
HHH-18643 - Remove support for SAP HANA versions older than 2.0 SPS 0…
Browse files Browse the repository at this point in the history
…5, create a legacy HANA dialect in the community dialects module

Signed-off-by: Jan Schatteman <[email protected]>
  • Loading branch information
jrenaat committed Sep 20, 2024
1 parent 2be3000 commit 5f2274d
Show file tree
Hide file tree
Showing 8 changed files with 2,417 additions and 21 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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 );
}
}
Original file line number Diff line number Diff line change
@@ -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";
}
}
Loading

0 comments on commit 5f2274d

Please sign in to comment.