Skip to content

Commit

Permalink
HHH-15191 - Remove support for Derby versions older than 10.14.2.0
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Schatteman <jschatte@redhat.com>
  • Loading branch information
jrenaat authored and beikov committed Sep 5, 2022
1 parent 96f56db commit 4d4aaf3
Show file tree
Hide file tree
Showing 10 changed files with 1,384 additions and 58 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.community.dialect;

import java.util.List;
import java.util.function.Consumer;

import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteContainer;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InListPredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.exec.spi.JdbcOperation;

/**
* A SQL AST translator for Derby.
*
* @author Christian Beikov
*/
public class DerbyLegacySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {

public DerbyLegacySqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
super( sessionFactory, statement );
}

@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
}

@Override
public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanExpressionPredicate) {
final boolean isNegated = booleanExpressionPredicate.isNegated();
if ( isNegated ) {
appendSql( "not(" );
}
booleanExpressionPredicate.getExpression().accept( this );
if ( isNegated ) {
appendSql( CLOSE_PARENTHESIS );
}
}

// Derby does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition

@Override
protected void visitAnsiCaseSearchedExpression(
CaseSearchedExpression caseSearchedExpression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSearchedExpression ) ) {
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSearchedExpression(
caseSearchedExpression,
e -> {
if ( e == firstResult ) {
renderCasted( e );
}
else {
resultRenderer.accept( e );
}
}
);
}
else {
super.visitAnsiCaseSearchedExpression( caseSearchedExpression, resultRenderer );
}
}

@Override
protected void visitAnsiCaseSimpleExpression(
CaseSimpleExpression caseSimpleExpression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSimpleExpression ) ) {
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSimpleExpression(
caseSimpleExpression,
e -> {
if ( e == firstResult ) {
renderCasted( e );
}
else {
resultRenderer.accept( e );
}
}
);
}
else {
super.visitAnsiCaseSimpleExpression( caseSimpleExpression, resultRenderer );
}
}

@Override
protected String getForUpdate() {
return " for update";
}

@Override
protected String getForShare(int timeoutMillis) {
return " for read only";
}

@Override
protected String getForUpdateWithClause() {
return " with rs";
}

@Override
public void visitCteContainer(CteContainer cteContainer) {
if ( cteContainer.isWithRecursive() ) {
throw new IllegalArgumentException( "Recursive CTEs can't be emulated" );
}
super.visitCteContainer( cteContainer );
}

@Override
protected void renderSearchClause(CteStatement cte) {
// Derby does not support this, but it's just a hint anyway
}

@Override
protected void renderCycleClause(CteStatement cte) {
// Derby does not support this, but it can be emulated
}

@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
// Derby only supports the OFFSET and FETCH clause with ROWS
assertRowsOnlyFetchClauseType( queryPart );
if ( supportsOffsetFetchClause() ) {
renderOffsetFetchClause( queryPart, true );
}
else if ( !getClauseStack().isEmpty() ) {
throw new IllegalArgumentException( "Can't render offset and fetch clause for subquery" );
}
}

@Override
protected void renderFetchExpression(Expression fetchExpression) {
if ( supportsParameterOffsetFetchExpression() ) {
super.renderFetchExpression( fetchExpression );
}
else {
renderExpressionAsLiteral( fetchExpression, getJdbcParameterBindings() );
}
}

@Override
protected void renderOffsetExpression(Expression offsetExpression) {
if ( supportsParameterOffsetFetchExpression() ) {
super.renderOffsetExpression( offsetExpression );
}
else {
renderExpressionAsLiteral( offsetExpression, getJdbcParameterBindings() );
}
}

@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );
}

@Override
protected void renderSelectExpression(Expression expression) {
renderSelectExpressionWithCastedOrInlinedPlainParameters( expression );
}

@Override
protected void renderSelectTupleComparison(
List<SqlSelection> lhsExpressions,
SqlTuple tuple,
ComparisonOperator operator) {
emulateSelectTupleComparison( lhsExpressions, tuple.getExpressions(), operator, true );
}

@Override
protected void renderPartitionItem(Expression expression) {
if ( expression instanceof Literal ) {
appendSql( "'0'" );
}
else if ( expression instanceof Summarization ) {
Summarization summarization = (Summarization) expression;
appendSql( summarization.getKind().sqlText() );
appendSql( OPEN_PARENTHESIS );
renderCommaSeparated( summarization.getGroupings() );
appendSql( CLOSE_PARENTHESIS );
}
else {
expression.accept( this );
}
}

@Override
public void visitInListPredicate(InListPredicate inListPredicate) {
final List<Expression> listExpressions = inListPredicate.getListExpressions();
if ( listExpressions.isEmpty() ) {
appendSql( "1=0" );
return;
}
final Expression testExpression = inListPredicate.getTestExpression();
if ( isParameter( testExpression ) ) {
renderCasted( testExpression );
if ( inListPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in(" );
renderCommaSeparated( listExpressions );
appendSql( CLOSE_PARENTHESIS );
}
else {
super.visitInListPredicate( inListPredicate );
}
}

@Override
protected boolean supportsRowValueConstructorSyntax() {
return false;
}

@Override
protected boolean supportsRowValueConstructorSyntaxInInList() {
return false;
}

@Override
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
return false;
}

@Override
protected String getFromDual() {
return " from (values 0) dual";
}

@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
}

@Override
protected boolean needsRowsToSkip() {
return !supportsOffsetFetchClause();
}

@Override
protected boolean needsMaxRows() {
return !supportsOffsetFetchClause();
}

private boolean supportsParameterOffsetFetchExpression() {
return getDialect().getVersion().isSameOrAfter( 10, 6 );
}

private boolean supportsOffsetFetchClause() {
// Before version 10.5 Derby didn't support OFFSET and FETCH
return getDialect().getVersion().isSameOrAfter( 10, 5 );
}

@Override
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
final BinaryArithmeticOperator operator = arithmeticExpression.getOperator();
if ( operator == BinaryArithmeticOperator.MODULO ) {
append( "mod" );
appendSql( OPEN_PARENTHESIS );
arithmeticExpression.getLeftHandOperand().accept( this );
appendSql( ',' );
arithmeticExpression.getRightHandOperand().accept( this );
appendSql( CLOSE_PARENTHESIS );
}
else {
appendSql( OPEN_PARENTHESIS );
render( arithmeticExpression.getLeftHandOperand(), SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
appendSql( arithmeticExpression.getOperator().getOperatorSqlTextString() );
render( arithmeticExpression.getRightHandOperand(), SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
appendSql( CLOSE_PARENTHESIS );
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.community.dialect;

import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.query.spi.Limit;

import static org.junit.Assert.assertEquals;

import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;

/**
* Testing of patched support for Derby limit and offset queries; see HHH-3972
*
* @author Evan Leonard
*/
public class DerbyLegacyDialectTestCase extends BaseUnitTestCase {

@Test
@TestForIssue( jiraKey = "HHH-3972" )
public void testInsertLimitClause() {
final int limit = 50;
final String input = "select * from tablename t where t.cat = 5";
final String expected = "select * from tablename t where t.cat = 5 fetch first " + limit + " rows only";

final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( 0, limit ) );
assertEquals( expected, actual );
}

@Test
@TestForIssue( jiraKey = "HHH-3972" )
public void testInsertLimitWithOffsetClause() {
final int limit = 50;
final int offset = 200;
final String input = "select * from tablename t where t.cat = 5";
final String expected = "select * from tablename t where t.cat = 5 offset " + offset + " rows fetch next " + limit + " rows only";

final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) );
assertEquals( expected, actual );
}

@Test
@TestForIssue( jiraKey = "HHH-3972" )
public void testInsertLimitWithForUpdateClause() {
final int limit = 50;
final int offset = 200;
final String input = "select c11 as col1, c12 as col2, c13 as col13 from t1 for update of c11, c13";
final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 offset " + offset
+ " rows fetch next " + limit + " rows only for update of c11, c13";

final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) );
assertEquals( expected, actual );
}

@Test
@TestForIssue( jiraKey = "HHH-3972" )
public void testInsertLimitWithWithClause() {
final int limit = 50;
final int offset = 200;
final String input = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' with rr";
final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' offset " + offset
+ " rows fetch next " + limit + " rows only with rr";

final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) );
assertEquals( expected, actual );
}

@Test
@TestForIssue( jiraKey = "HHH-3972" )
public void testInsertLimitWithForUpdateAndWithClauses() {
final int limit = 50;
final int offset = 200;
final String input = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' for update of c11,c13 with rr";
final String expected = "select c11 as col1, c12 as col2, c13 as col13 from t1 where flight_id between 'AA1111' and 'AA1112' offset " + offset
+ " rows fetch next " + limit + " rows only for update of c11,c13 with rr";

final String actual = new DerbyLegacyDialect( DatabaseVersion.make( 10, 5 ) ).getLimitHandler().processSql( input, toRowSelection( offset, limit ) );
assertEquals( expected, actual );
}

private Limit toRowSelection(int firstRow, int maxRows) {
Limit selection = new Limit();
selection.setFirstRow( firstRow );
selection.setMaxRows( maxRows );
return selection;
}
}

0 comments on commit 4d4aaf3

Please sign in to comment.