-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HHH-18643 - Remove support for SAP HANA versions older than 2.0 SPS 0…
…5, create a legacy HANA dialect in the community dialects module Signed-off-by: Jan Schatteman <[email protected]>
- Loading branch information
Showing
8 changed files
with
2,417 additions
and
21 deletions.
There are no files selected for viewing
2,022 changes: 2,022 additions & 0 deletions
2,022
...e-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java
Large diffs are not rendered by default.
Oops, something went wrong.
102 changes: 102 additions & 0 deletions
102
...dialects/src/main/java/org/hibernate/community/dialect/HANALegacyServerConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ); | ||
} | ||
} |
275 changes: 275 additions & 0 deletions
275
...ty-dialects/src/main/java/org/hibernate/community/dialect/HANALegacySqlAstTranslator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
} |
Oops, something went wrong.