Skip to content

Commit 58ee919

Browse files
committed
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>
1 parent f8e4e6e commit 58ee919

File tree

8 files changed

+2417
-21
lines changed

8 files changed

+2417
-21
lines changed

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HANALegacyDialect.java

Lines changed: 2022 additions & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.community.dialect;
6+
7+
import java.sql.DatabaseMetaData;
8+
import java.sql.ResultSet;
9+
import java.sql.SQLException;
10+
import java.sql.Statement;
11+
12+
import org.hibernate.dialect.DatabaseVersion;
13+
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
14+
import org.hibernate.internal.CoreLogging;
15+
import org.hibernate.internal.CoreMessageLogger;
16+
import org.hibernate.internal.util.StringHelper;
17+
import org.hibernate.internal.util.config.ConfigurationHelper;
18+
19+
import static org.hibernate.cfg.DialectSpecificSettings.HANA_MAX_LOB_PREFETCH_SIZE;
20+
21+
/**
22+
* Utility class that extracts some initial configuration from the database for {@link HANALegacyDialect}.
23+
*/
24+
public class HANALegacyServerConfiguration {
25+
26+
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( HANALegacyServerConfiguration.class );
27+
public static final int MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE = 1024;
28+
29+
private final DatabaseVersion fullVersion;
30+
private final int maxLobPrefetchSize;
31+
32+
public HANALegacyServerConfiguration(DatabaseVersion fullVersion) {
33+
this( fullVersion, MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE );
34+
}
35+
36+
public HANALegacyServerConfiguration(DatabaseVersion fullVersion, int maxLobPrefetchSize) {
37+
this.fullVersion = fullVersion;
38+
this.maxLobPrefetchSize = maxLobPrefetchSize;
39+
}
40+
41+
public DatabaseVersion getFullVersion() {
42+
return fullVersion;
43+
}
44+
45+
public int getMaxLobPrefetchSize() {
46+
return maxLobPrefetchSize;
47+
}
48+
49+
public static HANALegacyServerConfiguration fromDialectResolutionInfo(DialectResolutionInfo info) {
50+
Integer maxLobPrefetchSize = null;
51+
final DatabaseMetaData databaseMetaData = info.getDatabaseMetadata();
52+
if ( databaseMetaData != null ) {
53+
try (final Statement statement = databaseMetaData.getConnection().createStatement()) {
54+
try ( ResultSet rs = statement.executeQuery(
55+
"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" ) ) {
56+
// This only works if the current user has the privilege INIFILE ADMIN
57+
if ( rs.next() ) {
58+
maxLobPrefetchSize = rs.getInt( 1 );
59+
}
60+
}
61+
}
62+
catch (SQLException e) {
63+
// Ignore
64+
LOG.debug(
65+
"An error occurred while trying to determine the value of the HANA parameter indexserver.ini / session / max_lob_prefetch_size.",
66+
e );
67+
}
68+
}
69+
// default to the dialect-specific configuration settings
70+
if ( maxLobPrefetchSize == null ) {
71+
maxLobPrefetchSize = ConfigurationHelper.getInt(
72+
HANA_MAX_LOB_PREFETCH_SIZE,
73+
info.getConfigurationValues(),
74+
MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE
75+
);
76+
}
77+
return new HANALegacyServerConfiguration( staticDetermineDatabaseVersion( info ), maxLobPrefetchSize );
78+
}
79+
80+
static DatabaseVersion staticDetermineDatabaseVersion(DialectResolutionInfo info) {
81+
// Parse the version according to https://answers.sap.com/questions/9760991/hana-sps-version-check.html
82+
final String versionString = info.getDatabaseVersion();
83+
int majorVersion = 1;
84+
int minorVersion = 0;
85+
int patchLevel = 0;
86+
if ( versionString == null ) {
87+
return HANALegacyDialect.DEFAULT_VERSION;
88+
}
89+
final String[] components = StringHelper.split( ".", versionString );
90+
if ( components.length >= 3 ) {
91+
try {
92+
majorVersion = Integer.parseInt( components[0] );
93+
minorVersion = Integer.parseInt( components[1] );
94+
patchLevel = Integer.parseInt( components[2] );
95+
}
96+
catch (NumberFormatException ex) {
97+
// Ignore
98+
}
99+
}
100+
return DatabaseVersion.make( majorVersion, minorVersion, patchLevel );
101+
}
102+
}
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.community.dialect;
6+
7+
import java.util.List;
8+
9+
import org.hibernate.MappingException;
10+
import org.hibernate.engine.spi.SessionFactoryImplementor;
11+
import org.hibernate.internal.util.collections.Stack;
12+
import org.hibernate.query.IllegalQueryOperationException;
13+
import org.hibernate.query.sqm.ComparisonOperator;
14+
import org.hibernate.sql.ast.Clause;
15+
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
16+
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
17+
import org.hibernate.sql.ast.tree.Statement;
18+
import org.hibernate.sql.ast.tree.cte.CteStatement;
19+
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
20+
import org.hibernate.sql.ast.tree.expression.Expression;
21+
import org.hibernate.sql.ast.tree.expression.Literal;
22+
import org.hibernate.sql.ast.tree.expression.Summarization;
23+
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
24+
import org.hibernate.sql.ast.tree.from.NamedTableReference;
25+
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
26+
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
27+
import org.hibernate.sql.ast.tree.insert.ConflictClause;
28+
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
29+
import org.hibernate.sql.ast.tree.insert.Values;
30+
import org.hibernate.sql.ast.tree.select.QueryGroup;
31+
import org.hibernate.sql.ast.tree.select.QueryPart;
32+
import org.hibernate.sql.ast.tree.select.QuerySpec;
33+
import org.hibernate.sql.ast.tree.update.UpdateStatement;
34+
import org.hibernate.sql.exec.spi.JdbcOperation;
35+
import org.hibernate.sql.model.internal.TableInsertStandard;
36+
37+
/**
38+
* An SQL AST translator for the Legacy HANA dialect.
39+
*/
40+
public class HANALegacySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
41+
42+
private boolean inLateral;
43+
44+
public HANALegacySqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
45+
super( sessionFactory, statement );
46+
}
47+
48+
@Override
49+
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
50+
if ( isIntegerDivisionEmulationRequired( arithmeticExpression ) ) {
51+
appendSql( "cast(" );
52+
visitArithmeticOperand( arithmeticExpression.getLeftHandOperand() );
53+
appendSql( arithmeticExpression.getOperator().getOperatorSqlTextString() );
54+
visitArithmeticOperand( arithmeticExpression.getRightHandOperand() );
55+
appendSql( " as int)" );
56+
}
57+
else {
58+
super.visitBinaryArithmeticExpression( arithmeticExpression );
59+
}
60+
}
61+
62+
@Override
63+
protected void visitArithmeticOperand(Expression expression) {
64+
render( expression, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
65+
}
66+
67+
private boolean isHanaCloud() {
68+
return ( (HANALegacyDialect) getDialect() ).isCloud();
69+
}
70+
71+
@Override
72+
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
73+
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
74+
// Render plain insert statement and possibly run into unique constraint violation
75+
super.visitInsertStatementOnly( statement );
76+
}
77+
else {
78+
visitInsertStatementEmulateMerge( statement );
79+
}
80+
}
81+
82+
@Override
83+
protected void visitUpdateStatementOnly(UpdateStatement statement) {
84+
// HANA Cloud does not support the FROM clause in UPDATE statements
85+
if ( isHanaCloud() && hasNonTrivialFromClause( statement.getFromClause() ) ) {
86+
visitUpdateStatementEmulateMerge( statement );
87+
}
88+
else {
89+
super.visitUpdateStatementOnly( statement );
90+
}
91+
}
92+
93+
@Override
94+
protected void renderUpdateClause(UpdateStatement updateStatement) {
95+
// HANA Cloud does not support the FROM clause in UPDATE statements
96+
if ( isHanaCloud() ) {
97+
super.renderUpdateClause( updateStatement );
98+
}
99+
else {
100+
appendSql( "update" );
101+
final Stack<Clause> clauseStack = getClauseStack();
102+
try {
103+
clauseStack.push( Clause.UPDATE );
104+
renderTableReferenceIdentificationVariable( updateStatement.getTargetTable() );
105+
}
106+
finally {
107+
clauseStack.pop();
108+
}
109+
}
110+
}
111+
112+
@Override
113+
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
114+
// HANA Cloud does not support the FROM clause in UPDATE statements
115+
if ( !isHanaCloud() ) {
116+
if ( statement.getFromClause().getRoots().isEmpty() ) {
117+
appendSql( " from " );
118+
renderDmlTargetTableExpression( statement.getTargetTable() );
119+
}
120+
else {
121+
visitFromClause( statement.getFromClause() );
122+
}
123+
}
124+
}
125+
126+
@Override
127+
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
128+
super.renderDmlTargetTableExpression( tableReference );
129+
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
130+
renderTableReferenceIdentificationVariable( tableReference );
131+
}
132+
}
133+
134+
@Override
135+
protected void visitConflictClause(ConflictClause conflictClause) {
136+
if ( conflictClause != null ) {
137+
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
138+
throw new IllegalQueryOperationException( "Insert conflict 'do update' clause with constraint name is not supported" );
139+
}
140+
}
141+
}
142+
143+
protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
144+
// HANA only supports the LIMIT + OFFSET syntax but also window functions
145+
// Check if current query part is already row numbering to avoid infinite recursion
146+
return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart
147+
&& !isRowsOnlyFetchClauseType( queryPart );
148+
}
149+
150+
@Override
151+
protected boolean supportsWithClauseInSubquery() {
152+
// HANA doesn't seem to support correlation, so we just report false here for simplicity
153+
return false;
154+
}
155+
156+
@Override
157+
protected boolean isCorrelated(CteStatement cteStatement) {
158+
// Report false here, because apparently HANA does not need the "lateral" keyword to correlate a from clause subquery in a subquery
159+
return false;
160+
}
161+
162+
@Override
163+
public void visitQueryGroup(QueryGroup queryGroup) {
164+
if ( shouldEmulateFetchClause( queryGroup ) ) {
165+
emulateFetchOffsetWithWindowFunctions( queryGroup, true );
166+
}
167+
else {
168+
super.visitQueryGroup( queryGroup );
169+
}
170+
}
171+
172+
@Override
173+
public void visitQuerySpec(QuerySpec querySpec) {
174+
if ( shouldEmulateFetchClause( querySpec ) ) {
175+
emulateFetchOffsetWithWindowFunctions( querySpec, true );
176+
}
177+
else {
178+
super.visitQuerySpec( querySpec );
179+
}
180+
}
181+
182+
@Override
183+
public void visitQueryPartTableReference(QueryPartTableReference tableReference) {
184+
if ( tableReference.isLateral() && !inLateral ) {
185+
inLateral = true;
186+
emulateQueryPartTableReferenceColumnAliasing( tableReference );
187+
inLateral = false;
188+
}
189+
else {
190+
emulateQueryPartTableReferenceColumnAliasing( tableReference );
191+
}
192+
}
193+
194+
@Override
195+
protected SqlAstNodeRenderingMode getParameterRenderingMode() {
196+
// HANA does not support parameters in lateral subqueries for some reason, so inline all the parameters in this case
197+
return inLateral ? SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS : super.getParameterRenderingMode();
198+
}
199+
200+
@Override
201+
public void visitFunctionTableReference(FunctionTableReference tableReference) {
202+
tableReference.getFunctionExpression().accept( this );
203+
renderTableReferenceIdentificationVariable( tableReference );
204+
}
205+
206+
@Override
207+
public void visitOffsetFetchClause(QueryPart queryPart) {
208+
if ( !isRowNumberingCurrentQueryPart() ) {
209+
renderLimitOffsetClause( queryPart );
210+
}
211+
}
212+
213+
@Override
214+
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
215+
if ( operator == ComparisonOperator.DISTINCT_FROM || operator == ComparisonOperator.NOT_DISTINCT_FROM ) {
216+
// HANA does not support plain parameters in the select clause of the intersect emulation
217+
withParameterRenderingMode(
218+
SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER,
219+
() -> renderComparisonEmulateIntersect( lhs, operator, rhs )
220+
);
221+
}
222+
else {
223+
renderComparisonEmulateIntersect( lhs, operator, rhs );
224+
}
225+
}
226+
227+
@Override
228+
protected void renderPartitionItem(Expression expression) {
229+
if ( expression instanceof Literal ) {
230+
appendSql( "grouping sets (())" );
231+
}
232+
else if ( expression instanceof Summarization ) {
233+
throw new UnsupportedOperationException( "Summarization is not supported by DBMS" );
234+
}
235+
else {
236+
expression.accept( this );
237+
}
238+
}
239+
240+
@Override
241+
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
242+
return false;
243+
}
244+
245+
@Override
246+
protected boolean supportsRowValueConstructorGtLtSyntax() {
247+
return false;
248+
}
249+
250+
@Override
251+
protected void renderInsertIntoNoColumns(TableInsertStandard tableInsert) {
252+
throw new MappingException(
253+
String.format(
254+
"The INSERT statement for table [%s] contains no column, and this is not supported by [%s]",
255+
tableInsert.getMutatingTable().getTableId(),
256+
getDialect()
257+
)
258+
);
259+
}
260+
261+
@Override
262+
protected void visitValuesList(List<Values> valuesList) {
263+
visitValuesListEmulateSelectUnion( valuesList );
264+
}
265+
266+
@Override
267+
public void visitValuesTableReference(ValuesTableReference tableReference) {
268+
emulateValuesTableReferenceColumnAliasing( tableReference );
269+
}
270+
271+
@Override
272+
protected String getSkipLocked() {
273+
return " ignore locked";
274+
}
275+
}

0 commit comments

Comments
 (0)