HHH-16433 Fix forced follow on locking with order by
This commit is contained in:
parent
79d2e208a6
commit
6c8bb03c93
|
@ -6,11 +6,14 @@
|
|||
*/
|
||||
package org.hibernate.community.dialect;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.sqm.ComparisonOperator;
|
||||
|
@ -24,7 +27,6 @@ import org.hibernate.sql.ast.tree.Statement;
|
|||
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
|
||||
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.AggregateColumnWriteExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.FunctionExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.Literal;
|
||||
|
@ -32,13 +34,16 @@ import org.hibernate.sql.ast.tree.expression.Over;
|
|||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
|
||||
import org.hibernate.sql.ast.tree.expression.Summarization;
|
||||
import org.hibernate.sql.ast.tree.from.FromClause;
|
||||
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.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.UnionTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
|
||||
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
|
||||
import org.hibernate.sql.ast.tree.insert.Values;
|
||||
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.QueryGroup;
|
||||
import org.hibernate.sql.ast.tree.select.QueryPart;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
|
@ -46,6 +51,7 @@ import org.hibernate.sql.ast.tree.select.SelectClause;
|
|||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
import org.hibernate.sql.exec.spi.JdbcOperation;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
/**
|
||||
|
@ -97,12 +103,6 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
|
|||
Boolean followOnLocking) {
|
||||
LockStrategy strategy = super.determineLockingStrategy( querySpec, forUpdateClause, followOnLocking );
|
||||
final boolean followOnLockingDisabled = Boolean.FALSE.equals( followOnLocking );
|
||||
if ( strategy != LockStrategy.FOLLOW_ON && querySpec.hasSortSpecifications() ) {
|
||||
if ( followOnLockingDisabled ) {
|
||||
throw new IllegalQueryOperationException( "Locking with ORDER BY is not supported" );
|
||||
}
|
||||
strategy = LockStrategy.FOLLOW_ON;
|
||||
}
|
||||
// Oracle also doesn't support locks with set operators
|
||||
// See https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_10002.htm#i2066346
|
||||
if ( strategy != LockStrategy.FOLLOW_ON && isPartOfQueryGroup() ) {
|
||||
|
@ -117,29 +117,12 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
|
|||
}
|
||||
strategy = LockStrategy.FOLLOW_ON;
|
||||
}
|
||||
if ( strategy != LockStrategy.FOLLOW_ON && useOffsetFetchClause( querySpec ) && !isRowsOnlyFetchClauseType( querySpec ) ) {
|
||||
if ( strategy != LockStrategy.FOLLOW_ON && needsLockingWrapper( querySpec ) && !canApplyLockingWrapper( querySpec ) ) {
|
||||
if ( followOnLockingDisabled ) {
|
||||
throw new IllegalQueryOperationException( "Locking with FETCH is not supported" );
|
||||
throw new IllegalQueryOperationException( "Locking with OFFSET/FETCH is not supported" );
|
||||
}
|
||||
strategy = LockStrategy.FOLLOW_ON;
|
||||
}
|
||||
if ( strategy != LockStrategy.FOLLOW_ON ) {
|
||||
final boolean hasOffset;
|
||||
if ( querySpec.isRoot() && hasLimit() && getLimit().getFirstRow() != null ) {
|
||||
hasOffset = true;
|
||||
// We must record that the generated SQL depends on the fact that there is an offset
|
||||
addAppliedParameterBinding( getOffsetParameter(), null );
|
||||
}
|
||||
else {
|
||||
hasOffset = querySpec.getOffsetClauseExpression() != null;
|
||||
}
|
||||
if ( hasOffset ) {
|
||||
if ( followOnLockingDisabled ) {
|
||||
throw new IllegalQueryOperationException( "Locking with OFFSET is not supported" );
|
||||
}
|
||||
strategy = LockStrategy.FOLLOW_ON;
|
||||
}
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
|
||||
|
@ -166,7 +149,7 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
|
|||
|
||||
protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
|
||||
// Check if current query part is already row numbering to avoid infinite recursion
|
||||
if (getQueryPartForRowNumbering() == queryPart) {
|
||||
if ( getQueryPartForRowNumbering() == queryPart ) {
|
||||
return false;
|
||||
}
|
||||
final boolean hasLimit = queryPart.isRoot() && hasLimit() || queryPart.getFetchClauseExpression() != null
|
||||
|
@ -176,77 +159,12 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
|
|||
}
|
||||
// Even if Oracle supports the OFFSET/FETCH clause, there are conditions where we still want to use the ROWNUM pagination
|
||||
if ( supportsOffsetFetchClause() ) {
|
||||
// When the query has no sort specifications and offset, we want to use the ROWNUM pagination as that is a special locking case
|
||||
return !queryPart.hasSortSpecifications() && !hasOffset( queryPart )
|
||||
// Workaround an Oracle bug, segmentation fault for insert queries with a plain query group and fetch clause
|
||||
|| queryPart instanceof QueryGroup && getClauseStack().isEmpty() && getStatement() instanceof InsertSelectStatement;
|
||||
// Workaround an Oracle bug, segmentation fault for insert queries with a plain query group and fetch clause
|
||||
return queryPart instanceof QueryGroup && getClauseStack().isEmpty() && getStatement() instanceof InsertSelectStatement;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FetchClauseType getFetchClauseTypeForRowNumbering(QueryPart queryPart) {
|
||||
final FetchClauseType fetchClauseType = super.getFetchClauseTypeForRowNumbering( queryPart );
|
||||
final boolean hasOffset;
|
||||
if ( queryPart.isRoot() && hasLimit() ) {
|
||||
hasOffset = getLimit().getFirstRow() != null;
|
||||
}
|
||||
else {
|
||||
hasOffset = queryPart.getOffsetClauseExpression() != null;
|
||||
}
|
||||
if ( queryPart instanceof QuerySpec && !hasOffset && fetchClauseType == FetchClauseType.ROWS_ONLY ) {
|
||||
// We return null here, because in this particular case, we render a special rownum query
|
||||
// which can be seen in #emulateFetchOffsetWithWindowFunctions
|
||||
// Note that we also build upon this in #visitOrderBy
|
||||
return null;
|
||||
}
|
||||
return fetchClauseType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emulateFetchOffsetWithWindowFunctions(
|
||||
QueryPart queryPart,
|
||||
Expression offsetExpression,
|
||||
Expression fetchExpression,
|
||||
FetchClauseType fetchClauseType,
|
||||
boolean emulateFetchClause) {
|
||||
if ( queryPart instanceof QuerySpec && offsetExpression == null && fetchClauseType == FetchClauseType.ROWS_ONLY ) {
|
||||
// Special case for Oracle to support locking along with simple max results paging
|
||||
final QuerySpec querySpec = (QuerySpec) queryPart;
|
||||
withRowNumbering(
|
||||
querySpec,
|
||||
true, // we need select aliases to avoid ORA-00918: column ambiguously defined
|
||||
() -> {
|
||||
appendSql( "select * from " );
|
||||
emulateFetchOffsetWithWindowFunctionsVisitQueryPart( querySpec );
|
||||
appendSql( " where rownum<=" );
|
||||
final Stack<Clause> clauseStack = getClauseStack();
|
||||
clauseStack.push( Clause.WHERE );
|
||||
try {
|
||||
fetchExpression.accept( this );
|
||||
|
||||
// We render the FOR UPDATE clause in the outer query
|
||||
clauseStack.pop();
|
||||
clauseStack.push( Clause.FOR_UPDATE );
|
||||
visitForUpdateClause( querySpec );
|
||||
}
|
||||
finally {
|
||||
clauseStack.pop();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
super.emulateFetchOffsetWithWindowFunctions(
|
||||
queryPart,
|
||||
offsetExpression,
|
||||
fetchExpression,
|
||||
fetchClauseType,
|
||||
emulateFetchClause
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visitOrderBy(List<SortSpecification> sortSpecifications) {
|
||||
// If we have a query part for row numbering, there is no need to render the order by clause
|
||||
|
@ -262,13 +180,49 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
|
|||
final QuerySpec querySpec = (QuerySpec) queryPartForRowNumbering;
|
||||
if ( querySpec.getOffsetClauseExpression() == null
|
||||
&& ( !querySpec.isRoot() || getOffsetParameter() == null ) ) {
|
||||
// When rendering `rownum` for Oracle, we need to render the order by clause still
|
||||
renderOrderBy( true, sortSpecifications );
|
||||
// When we enter here, we need to handle the special ROWNUM pagination
|
||||
if ( hasGroupingOrDistinct( querySpec ) || querySpec.getFromClause().hasJoins() ) {
|
||||
// When the query spec has joins, a group by, having or distinct clause,
|
||||
// we just need to render the order by clause, because the query is wrapped
|
||||
renderOrderBy( true, sortSpecifications );
|
||||
}
|
||||
else {
|
||||
// Otherwise we need to render the ROWNUM pagination predicate in here
|
||||
final Predicate whereClauseRestrictions = querySpec.getWhereClauseRestrictions();
|
||||
if ( whereClauseRestrictions != null && !whereClauseRestrictions.isEmpty() ) {
|
||||
appendSql( " and " );
|
||||
}
|
||||
else {
|
||||
appendSql( " where " );
|
||||
}
|
||||
appendSql( "rownum<=" );
|
||||
final Stack<Clause> clauseStack = getClauseStack();
|
||||
clauseStack.push( Clause.WHERE );
|
||||
try {
|
||||
if ( querySpec.isRoot() && hasLimit() ) {
|
||||
getLimitParameter().accept( this );
|
||||
}
|
||||
else {
|
||||
querySpec.getFetchClauseExpression().accept( this );
|
||||
}
|
||||
}
|
||||
finally {
|
||||
clauseStack.pop();
|
||||
}
|
||||
renderOrderBy( true, sortSpecifications );
|
||||
visitForUpdateClause( querySpec );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasGroupingOrDistinct(QuerySpec querySpec) {
|
||||
return querySpec.getSelectClause().isDistinct()
|
||||
|| !querySpec.getGroupByClauseExpressions().isEmpty()
|
||||
|| querySpec.getHavingClauseRestrictions() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visitValuesList(List<Values> valuesList) {
|
||||
if ( valuesList.size() < 2 ) {
|
||||
|
@ -323,12 +277,142 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
|
|||
|
||||
@Override
|
||||
public void visitQuerySpec(QuerySpec querySpec) {
|
||||
if ( shouldEmulateFetchClause( querySpec ) ) {
|
||||
emulateFetchOffsetWithWindowFunctions( querySpec, true );
|
||||
final EntityIdentifierMapping identifierMappingForLockingWrapper = identifierMappingForLockingWrapper( querySpec );
|
||||
final Expression offsetExpression;
|
||||
final Expression fetchExpression;
|
||||
final FetchClauseType fetchClauseType;
|
||||
if ( querySpec.isRoot() && hasLimit() ) {
|
||||
prepareLimitOffsetParameters();
|
||||
offsetExpression = getOffsetParameter();
|
||||
fetchExpression = getLimitParameter();
|
||||
fetchClauseType = FetchClauseType.ROWS_ONLY;
|
||||
}
|
||||
else {
|
||||
super.visitQuerySpec( querySpec );
|
||||
offsetExpression = querySpec.getOffsetClauseExpression();
|
||||
fetchExpression = querySpec.getFetchClauseExpression();
|
||||
fetchClauseType = querySpec.getFetchClauseType();
|
||||
}
|
||||
if ( shouldEmulateFetchClause( querySpec ) ) {
|
||||
if ( identifierMappingForLockingWrapper == null ) {
|
||||
emulateFetchOffsetWithWindowFunctions(
|
||||
querySpec,
|
||||
offsetExpression,
|
||||
fetchExpression,
|
||||
fetchClauseType,
|
||||
true
|
||||
);
|
||||
}
|
||||
else {
|
||||
super.visitQuerySpec(
|
||||
createLockingWrapper(
|
||||
querySpec,
|
||||
offsetExpression,
|
||||
fetchExpression,
|
||||
fetchClauseType,
|
||||
identifierMappingForLockingWrapper
|
||||
)
|
||||
);
|
||||
// Render the for update clause for the original query spec, because the locking wrapper is marked as non-root
|
||||
visitForUpdateClause( querySpec );
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( identifierMappingForLockingWrapper == null ) {
|
||||
super.visitQuerySpec( querySpec );
|
||||
}
|
||||
else {
|
||||
super.visitQuerySpec(
|
||||
createLockingWrapper(
|
||||
querySpec,
|
||||
offsetExpression,
|
||||
fetchExpression,
|
||||
fetchClauseType,
|
||||
identifierMappingForLockingWrapper
|
||||
)
|
||||
);
|
||||
// Render the for update clause for the original query spec, because the locking wrapper is marked as non-root
|
||||
visitForUpdateClause( querySpec );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private QuerySpec createLockingWrapper(
|
||||
QuerySpec querySpec,
|
||||
Expression offsetExpression,
|
||||
Expression fetchExpression,
|
||||
FetchClauseType fetchClauseType,
|
||||
EntityIdentifierMapping identifierMappingForLockingWrapper) {
|
||||
|
||||
final TableGroup rootTableGroup = querySpec.getFromClause().getRoots().get( 0 );
|
||||
final List<ColumnReference> idColumnReferences = new ArrayList<>( identifierMappingForLockingWrapper.getJdbcTypeCount() );
|
||||
identifierMappingForLockingWrapper.forEachSelectable(
|
||||
0,
|
||||
(selectionIndex, selectableMapping) -> {
|
||||
idColumnReferences.add( new ColumnReference( rootTableGroup.getPrimaryTableReference(), selectableMapping ) );
|
||||
}
|
||||
);
|
||||
final Expression idExpression;
|
||||
if ( identifierMappingForLockingWrapper instanceof EmbeddableValuedModelPart ) {
|
||||
idExpression = new SqlTuple( idColumnReferences, identifierMappingForLockingWrapper );
|
||||
}
|
||||
else {
|
||||
idExpression = idColumnReferences.get( 0 );
|
||||
}
|
||||
final QuerySpec subquery = new QuerySpec( false, 1 );
|
||||
for ( ColumnReference idColumnReference : idColumnReferences ) {
|
||||
subquery.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, -1, idColumnReference ) );
|
||||
}
|
||||
subquery.getFromClause().addRoot( rootTableGroup );
|
||||
subquery.applyPredicate( querySpec.getWhereClauseRestrictions() );
|
||||
if ( querySpec.hasSortSpecifications() ) {
|
||||
for ( SortSpecification sortSpecification : querySpec.getSortSpecifications() ) {
|
||||
subquery.addSortSpecification( sortSpecification );
|
||||
}
|
||||
}
|
||||
subquery.setOffsetClauseExpression( offsetExpression );
|
||||
subquery.setFetchClauseExpression( fetchExpression, fetchClauseType );
|
||||
|
||||
// Mark the query spec as non-root even if it might be the root, to avoid applying the pagination there
|
||||
final QuerySpec lockingWrapper = new QuerySpec( false, 1 );
|
||||
lockingWrapper.getFromClause().addRoot( rootTableGroup );
|
||||
for ( SqlSelection sqlSelection : querySpec.getSelectClause().getSqlSelections() ) {
|
||||
lockingWrapper.getSelectClause().addSqlSelection( sqlSelection );
|
||||
}
|
||||
lockingWrapper.applyPredicate( new InSubQueryPredicate( idExpression, subquery, false ) );
|
||||
return lockingWrapper;
|
||||
}
|
||||
|
||||
private EntityIdentifierMapping identifierMappingForLockingWrapper(QuerySpec querySpec) {
|
||||
// We only need a locking wrapper for very simple queries
|
||||
if ( canApplyLockingWrapper( querySpec )
|
||||
// There must be the need for locking in this query
|
||||
&& needsLocking( querySpec )
|
||||
// The query uses some sort of pagination which makes the wrapper necessary
|
||||
&& needsLockingWrapper( querySpec )
|
||||
// The query may not have a group by, having and distinct clause, or use aggregate functions,
|
||||
// as these features will force the use of follow-on locking
|
||||
&& querySpec.getGroupByClauseExpressions().isEmpty()
|
||||
&& querySpec.getHavingClauseRestrictions() == null
|
||||
&& !querySpec.getSelectClause().isDistinct()
|
||||
&& !hasAggregateFunctions( querySpec ) ) {
|
||||
return ( (EntityMappingType) querySpec.getFromClause().getRoots().get( 0 ).getModelPart() ).getIdentifierMapping();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean canApplyLockingWrapper(QuerySpec querySpec) {
|
||||
final FromClause fromClause;
|
||||
return querySpec.isRoot()
|
||||
// Must have a single root with no joins for an entity type
|
||||
&& ( fromClause = querySpec.getFromClause() ).getRoots().size() == 1
|
||||
&& !fromClause.hasJoins()
|
||||
&& fromClause.getRoots().get( 0 ).getModelPart() instanceof EntityMappingType;
|
||||
}
|
||||
|
||||
private boolean needsLockingWrapper(QuerySpec querySpec) {
|
||||
return querySpec.getFetchClauseType() != FetchClauseType.ROWS_ONLY
|
||||
|| hasOffset( querySpec )
|
||||
|| hasLimit( querySpec );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -6,10 +6,14 @@
|
|||
*/
|
||||
package org.hibernate.dialect;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.sqm.ComparisonOperator;
|
||||
|
@ -28,12 +32,15 @@ import org.hibernate.sql.ast.tree.expression.Over;
|
|||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
|
||||
import org.hibernate.sql.ast.tree.expression.Summarization;
|
||||
import org.hibernate.sql.ast.tree.from.FromClause;
|
||||
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.UnionTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
|
||||
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
|
||||
import org.hibernate.sql.ast.tree.insert.Values;
|
||||
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
||||
import org.hibernate.sql.ast.tree.select.QueryGroup;
|
||||
import org.hibernate.sql.ast.tree.select.QueryPart;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
|
@ -43,6 +50,7 @@ import org.hibernate.sql.ast.tree.update.Assignment;
|
|||
import org.hibernate.sql.exec.spi.JdbcOperation;
|
||||
import org.hibernate.sql.model.ast.ColumnValueBinding;
|
||||
import org.hibernate.sql.model.internal.OptionalTableUpdate;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
/**
|
||||
|
@ -94,12 +102,6 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
|
|||
Boolean followOnLocking) {
|
||||
LockStrategy strategy = super.determineLockingStrategy( querySpec, forUpdateClause, followOnLocking );
|
||||
final boolean followOnLockingDisabled = Boolean.FALSE.equals( followOnLocking );
|
||||
if ( strategy != LockStrategy.FOLLOW_ON && querySpec.hasSortSpecifications() ) {
|
||||
if ( followOnLockingDisabled ) {
|
||||
throw new IllegalQueryOperationException( "Locking with ORDER BY is not supported" );
|
||||
}
|
||||
strategy = LockStrategy.FOLLOW_ON;
|
||||
}
|
||||
// Oracle also doesn't support locks with set operators
|
||||
// See https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_10002.htm#i2066346
|
||||
if ( strategy != LockStrategy.FOLLOW_ON && isPartOfQueryGroup() ) {
|
||||
|
@ -114,29 +116,12 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
|
|||
}
|
||||
strategy = LockStrategy.FOLLOW_ON;
|
||||
}
|
||||
if ( strategy != LockStrategy.FOLLOW_ON && useOffsetFetchClause( querySpec ) && !isRowsOnlyFetchClauseType( querySpec ) ) {
|
||||
if ( strategy != LockStrategy.FOLLOW_ON && needsLockingWrapper( querySpec ) && !canApplyLockingWrapper( querySpec ) ) {
|
||||
if ( followOnLockingDisabled ) {
|
||||
throw new IllegalQueryOperationException( "Locking with FETCH is not supported" );
|
||||
throw new IllegalQueryOperationException( "Locking with OFFSET/FETCH is not supported" );
|
||||
}
|
||||
strategy = LockStrategy.FOLLOW_ON;
|
||||
}
|
||||
if ( strategy != LockStrategy.FOLLOW_ON ) {
|
||||
final boolean hasOffset;
|
||||
if ( querySpec.isRoot() && hasLimit() && getLimit().getFirstRow() != null ) {
|
||||
hasOffset = true;
|
||||
// We must record that the generated SQL depends on the fact that there is an offset
|
||||
addAppliedParameterBinding( getOffsetParameter(), null );
|
||||
}
|
||||
else {
|
||||
hasOffset = querySpec.getOffsetClauseExpression() != null;
|
||||
}
|
||||
if ( hasOffset ) {
|
||||
if ( followOnLockingDisabled ) {
|
||||
throw new IllegalQueryOperationException( "Locking with OFFSET is not supported" );
|
||||
}
|
||||
strategy = LockStrategy.FOLLOW_ON;
|
||||
}
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
|
||||
|
@ -167,7 +152,7 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
|
|||
|
||||
protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
|
||||
// Check if current query part is already row numbering to avoid infinite recursion
|
||||
if (getQueryPartForRowNumbering() == queryPart) {
|
||||
if ( getQueryPartForRowNumbering() == queryPart ) {
|
||||
return false;
|
||||
}
|
||||
final boolean hasLimit = queryPart.isRoot() && hasLimit() || queryPart.getFetchClauseExpression() != null
|
||||
|
@ -177,99 +162,12 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
|
|||
}
|
||||
// Even if Oracle supports the OFFSET/FETCH clause, there are conditions where we still want to use the ROWNUM pagination
|
||||
if ( supportsOffsetFetchClause() ) {
|
||||
// When the query has no sort specifications and offset, we want to use the ROWNUM pagination as that is a special locking case
|
||||
return !queryPart.hasSortSpecifications() && !hasOffset( queryPart )
|
||||
// Workaround an Oracle bug, segmentation fault for insert queries with a plain query group and fetch clause
|
||||
|| queryPart instanceof QueryGroup && getClauseStack().isEmpty() && getStatement() instanceof InsertSelectStatement;
|
||||
// Workaround an Oracle bug, segmentation fault for insert queries with a plain query group and fetch clause
|
||||
return queryPart instanceof QueryGroup && getClauseStack().isEmpty() && getStatement() instanceof InsertSelectStatement;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FetchClauseType getFetchClauseTypeForRowNumbering(QueryPart queryPart) {
|
||||
final FetchClauseType fetchClauseType = super.getFetchClauseTypeForRowNumbering( queryPart );
|
||||
final boolean hasOffset;
|
||||
if ( queryPart.isRoot() && hasLimit() ) {
|
||||
hasOffset = getLimit().getFirstRow() != null;
|
||||
}
|
||||
else {
|
||||
hasOffset = queryPart.getOffsetClauseExpression() != null;
|
||||
}
|
||||
if ( queryPart instanceof QuerySpec && !hasOffset && fetchClauseType == FetchClauseType.ROWS_ONLY ) {
|
||||
// We return null here, because in this particular case, we render a special rownum query
|
||||
// which can be seen in #emulateFetchOffsetWithWindowFunctions
|
||||
// Note that we also build upon this in #visitOrderBy
|
||||
return null;
|
||||
}
|
||||
return fetchClauseType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emulateFetchOffsetWithWindowFunctions(
|
||||
QueryPart queryPart,
|
||||
Expression offsetExpression,
|
||||
Expression fetchExpression,
|
||||
FetchClauseType fetchClauseType,
|
||||
boolean emulateFetchClause) {
|
||||
if ( queryPart instanceof QuerySpec && offsetExpression == null && fetchClauseType == FetchClauseType.ROWS_ONLY ) {
|
||||
// Special case for Oracle to support locking along with simple max results paging
|
||||
final QuerySpec querySpec = (QuerySpec) queryPart;
|
||||
withRowNumbering(
|
||||
querySpec,
|
||||
true, // we need select aliases to avoid ORA-00918: column ambiguously defined
|
||||
() -> {
|
||||
appendSql( "select * from " );
|
||||
emulateFetchOffsetWithWindowFunctionsVisitQueryPart( querySpec );
|
||||
appendSql( " where rownum<=" );
|
||||
final Stack<Clause> clauseStack = getClauseStack();
|
||||
clauseStack.push( Clause.WHERE );
|
||||
try {
|
||||
fetchExpression.accept( this );
|
||||
|
||||
// We render the FOR UPDATE clause in the outer query
|
||||
clauseStack.pop();
|
||||
clauseStack.push( Clause.FOR_UPDATE );
|
||||
visitForUpdateClause( querySpec );
|
||||
}
|
||||
finally {
|
||||
clauseStack.pop();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
super.emulateFetchOffsetWithWindowFunctions(
|
||||
queryPart,
|
||||
offsetExpression,
|
||||
fetchExpression,
|
||||
fetchClauseType,
|
||||
emulateFetchClause
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visitOrderBy(List<SortSpecification> sortSpecifications) {
|
||||
// If we have a query part for row numbering, there is no need to render the order by clause
|
||||
// as that is part of the row numbering window function already, by which we then order by in the outer query
|
||||
final QueryPart queryPartForRowNumbering = getQueryPartForRowNumbering();
|
||||
if ( queryPartForRowNumbering == null ) {
|
||||
renderOrderBy( true, sortSpecifications );
|
||||
}
|
||||
else {
|
||||
// This logic is tightly coupled to #emulateFetchOffsetWithWindowFunctions and #getFetchClauseTypeForRowNumbering
|
||||
// so that this is rendered when we end up in the special case for Oracle that renders a rownum filter
|
||||
if ( getFetchClauseTypeForRowNumbering( queryPartForRowNumbering ) == null ) {
|
||||
final QuerySpec querySpec = (QuerySpec) queryPartForRowNumbering;
|
||||
if ( querySpec.getOffsetClauseExpression() == null
|
||||
&& ( !querySpec.isRoot() || getOffsetParameter() == null ) ) {
|
||||
// When rendering `rownum` for Oracle, we need to render the order by clause still
|
||||
renderOrderBy( true, sortSpecifications );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visitValuesList(List<Values> valuesList) {
|
||||
if ( valuesList.size() < 2 ) {
|
||||
|
@ -324,12 +222,142 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
|
|||
|
||||
@Override
|
||||
public void visitQuerySpec(QuerySpec querySpec) {
|
||||
if ( shouldEmulateFetchClause( querySpec ) ) {
|
||||
emulateFetchOffsetWithWindowFunctions( querySpec, true );
|
||||
final EntityIdentifierMapping identifierMappingForLockingWrapper = identifierMappingForLockingWrapper( querySpec );
|
||||
final Expression offsetExpression;
|
||||
final Expression fetchExpression;
|
||||
final FetchClauseType fetchClauseType;
|
||||
if ( querySpec.isRoot() && hasLimit() ) {
|
||||
prepareLimitOffsetParameters();
|
||||
offsetExpression = getOffsetParameter();
|
||||
fetchExpression = getLimitParameter();
|
||||
fetchClauseType = FetchClauseType.ROWS_ONLY;
|
||||
}
|
||||
else {
|
||||
super.visitQuerySpec( querySpec );
|
||||
offsetExpression = querySpec.getOffsetClauseExpression();
|
||||
fetchExpression = querySpec.getFetchClauseExpression();
|
||||
fetchClauseType = querySpec.getFetchClauseType();
|
||||
}
|
||||
if ( shouldEmulateFetchClause( querySpec ) ) {
|
||||
if ( identifierMappingForLockingWrapper == null ) {
|
||||
emulateFetchOffsetWithWindowFunctions(
|
||||
querySpec,
|
||||
offsetExpression,
|
||||
fetchExpression,
|
||||
fetchClauseType,
|
||||
true
|
||||
);
|
||||
}
|
||||
else {
|
||||
super.visitQuerySpec(
|
||||
createLockingWrapper(
|
||||
querySpec,
|
||||
offsetExpression,
|
||||
fetchExpression,
|
||||
fetchClauseType,
|
||||
identifierMappingForLockingWrapper
|
||||
)
|
||||
);
|
||||
// Render the for update clause for the original query spec, because the locking wrapper is marked as non-root
|
||||
visitForUpdateClause( querySpec );
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( identifierMappingForLockingWrapper == null ) {
|
||||
super.visitQuerySpec( querySpec );
|
||||
}
|
||||
else {
|
||||
super.visitQuerySpec(
|
||||
createLockingWrapper(
|
||||
querySpec,
|
||||
offsetExpression,
|
||||
fetchExpression,
|
||||
fetchClauseType,
|
||||
identifierMappingForLockingWrapper
|
||||
)
|
||||
);
|
||||
// Render the for update clause for the original query spec, because the locking wrapper is marked as non-root
|
||||
visitForUpdateClause( querySpec );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private QuerySpec createLockingWrapper(
|
||||
QuerySpec querySpec,
|
||||
Expression offsetExpression,
|
||||
Expression fetchExpression,
|
||||
FetchClauseType fetchClauseType,
|
||||
EntityIdentifierMapping identifierMappingForLockingWrapper) {
|
||||
|
||||
final TableGroup rootTableGroup = querySpec.getFromClause().getRoots().get( 0 );
|
||||
final List<ColumnReference> idColumnReferences = new ArrayList<>( identifierMappingForLockingWrapper.getJdbcTypeCount() );
|
||||
identifierMappingForLockingWrapper.forEachSelectable(
|
||||
0,
|
||||
(selectionIndex, selectableMapping) -> {
|
||||
idColumnReferences.add( new ColumnReference( rootTableGroup.getPrimaryTableReference(), selectableMapping ) );
|
||||
}
|
||||
);
|
||||
final Expression idExpression;
|
||||
if ( identifierMappingForLockingWrapper instanceof EmbeddableValuedModelPart ) {
|
||||
idExpression = new SqlTuple( idColumnReferences, identifierMappingForLockingWrapper );
|
||||
}
|
||||
else {
|
||||
idExpression = idColumnReferences.get( 0 );
|
||||
}
|
||||
final QuerySpec subquery = new QuerySpec( false, 1 );
|
||||
for ( ColumnReference idColumnReference : idColumnReferences ) {
|
||||
subquery.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, -1, idColumnReference ) );
|
||||
}
|
||||
subquery.getFromClause().addRoot( rootTableGroup );
|
||||
subquery.applyPredicate( querySpec.getWhereClauseRestrictions() );
|
||||
if ( querySpec.hasSortSpecifications() ) {
|
||||
for ( SortSpecification sortSpecification : querySpec.getSortSpecifications() ) {
|
||||
subquery.addSortSpecification( sortSpecification );
|
||||
}
|
||||
}
|
||||
subquery.setOffsetClauseExpression( offsetExpression );
|
||||
subquery.setFetchClauseExpression( fetchExpression, fetchClauseType );
|
||||
|
||||
// Mark the query spec as non-root even if it might be the root, to avoid applying the pagination there
|
||||
final QuerySpec lockingWrapper = new QuerySpec( false, 1 );
|
||||
lockingWrapper.getFromClause().addRoot( rootTableGroup );
|
||||
for ( SqlSelection sqlSelection : querySpec.getSelectClause().getSqlSelections() ) {
|
||||
lockingWrapper.getSelectClause().addSqlSelection( sqlSelection );
|
||||
}
|
||||
lockingWrapper.applyPredicate( new InSubQueryPredicate( idExpression, subquery, false ) );
|
||||
return lockingWrapper;
|
||||
}
|
||||
|
||||
private EntityIdentifierMapping identifierMappingForLockingWrapper(QuerySpec querySpec) {
|
||||
// We only need a locking wrapper for very simple queries
|
||||
if ( canApplyLockingWrapper( querySpec )
|
||||
// There must be the need for locking in this query
|
||||
&& needsLocking( querySpec )
|
||||
// The query uses some sort of pagination which makes the wrapper necessary
|
||||
&& needsLockingWrapper( querySpec )
|
||||
// The query may not have a group by, having and distinct clause, or use aggregate functions,
|
||||
// as these features will force the use of follow-on locking
|
||||
&& querySpec.getGroupByClauseExpressions().isEmpty()
|
||||
&& querySpec.getHavingClauseRestrictions() == null
|
||||
&& !querySpec.getSelectClause().isDistinct()
|
||||
&& !hasAggregateFunctions( querySpec ) ) {
|
||||
return ( (EntityMappingType) querySpec.getFromClause().getRoots().get( 0 ).getModelPart() ).getIdentifierMapping();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean canApplyLockingWrapper(QuerySpec querySpec) {
|
||||
final FromClause fromClause;
|
||||
return querySpec.isRoot()
|
||||
// Must have a single root with no joins for an entity type
|
||||
&& ( fromClause = querySpec.getFromClause() ).getRoots().size() == 1
|
||||
&& !fromClause.hasJoins()
|
||||
&& fromClause.getRoots().get( 0 ).getModelPart() instanceof EntityMappingType;
|
||||
}
|
||||
|
||||
private boolean needsLockingWrapper(QuerySpec querySpec) {
|
||||
return querySpec.getFetchClauseType() != FetchClauseType.ROWS_ONLY
|
||||
|| hasOffset( querySpec )
|
||||
|| hasLimit( querySpec );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -582,6 +610,7 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
|
|||
appendSql( " s" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderMergeSource(OptionalTableUpdate optionalTableUpdate) {
|
||||
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
||||
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
|
||||
|
|
|
@ -55,7 +55,6 @@ public enum Clause {
|
|||
ORDER,
|
||||
OFFSET,
|
||||
FETCH,
|
||||
FOR_UPDATE,
|
||||
OVER,
|
||||
/**
|
||||
* The clause containing CTEs
|
||||
|
|
|
@ -593,6 +593,15 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
return limit != null && !limit.isEmpty();
|
||||
}
|
||||
|
||||
protected boolean hasLimit(QueryPart queryPart) {
|
||||
if ( queryPart.isRoot() && hasLimit() && limit.getMaxRows() != null ) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return queryPart.getFetchClauseExpression() != null;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasOffset(QueryPart queryPart) {
|
||||
if ( queryPart.isRoot() && hasLimit() && limit.getFirstRow() != null ) {
|
||||
return true;
|
||||
|
@ -1350,12 +1359,15 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
|
||||
protected LockMode getEffectiveLockMode(String alias) {
|
||||
return getEffectiveLockMode( alias, getQueryPartStack().getCurrent().isRoot() );
|
||||
}
|
||||
|
||||
protected LockMode getEffectiveLockMode(String alias, boolean isRoot) {
|
||||
if ( getLockOptions() == null ) {
|
||||
return LockMode.NONE;
|
||||
}
|
||||
final QueryPart currentQueryPart = getQueryPartStack().getCurrent();
|
||||
LockMode lockMode = getLockOptions().getAliasSpecificLockMode( alias );
|
||||
if ( currentQueryPart.isRoot() && lockMode == null ) {
|
||||
if ( isRoot && lockMode == null ) {
|
||||
lockMode = getLockOptions().getLockMode();
|
||||
}
|
||||
return lockMode == null ? LockMode.NONE : lockMode;
|
||||
|
@ -4487,8 +4499,6 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
|
||||
// We render the FOR UPDATE clause in the outer query
|
||||
if ( queryPart instanceof QuerySpec ) {
|
||||
clauseStack.pop();
|
||||
clauseStack.push( Clause.FOR_UPDATE );
|
||||
visitForUpdateClause( (QuerySpec) queryPart );
|
||||
}
|
||||
}
|
||||
|
@ -5254,6 +5264,17 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean needsLocking(QuerySpec querySpec) {
|
||||
return querySpec.getFromClause().queryTableGroups(
|
||||
tableGroup -> {
|
||||
if ( LockMode.READ.lessThan( getEffectiveLockMode( tableGroup.getSourceAlias(), querySpec.isRoot() ) ) ) {
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
) != null;
|
||||
}
|
||||
|
||||
protected boolean hasNestedTableGroupsToRender(List<TableGroupJoin> nestedTableGroupJoins) {
|
||||
for ( TableGroupJoin nestedTableGroupJoin : nestedTableGroupJoins ) {
|
||||
final TableGroup joinedGroup = nestedTableGroupJoin.getJoinedGroup();
|
||||
|
|
|
@ -244,4 +244,38 @@ public class FromClause implements SqlAstNode {
|
|||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
sqlTreeWalker.visitFromClause( this );
|
||||
}
|
||||
|
||||
public boolean hasJoins() {
|
||||
for ( int i = 0; i < roots.size(); i++ ) {
|
||||
final TableGroup tableGroup = roots.get( i );
|
||||
if ( !tableGroup.getTableReferenceJoins().isEmpty() ) {
|
||||
return true;
|
||||
}
|
||||
if ( hasJoins( tableGroup.getTableGroupJoins() ) ) {
|
||||
return true;
|
||||
}
|
||||
if ( hasJoins( tableGroup.getNestedTableGroupJoins() ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasJoins(List<TableGroupJoin> tableGroupJoins) {
|
||||
for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) {
|
||||
final TableGroup joinedGroup = tableGroupJoin.getJoinedGroup();
|
||||
if ( joinedGroup instanceof VirtualTableGroup ) {
|
||||
if ( hasJoins( joinedGroup.getTableGroupJoins() ) ) {
|
||||
return true;
|
||||
}
|
||||
if ( hasJoins( joinedGroup.getNestedTableGroupJoins() ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ( joinedGroup.isInitialized() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,14 +24,20 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.LockModeType;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.NamedQuery;
|
||||
import jakarta.persistence.QueryHint;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
@ -111,7 +117,7 @@ public class OracleFollowOnLockingTest extends
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithFirstResultsThenFollowOnLocking() {
|
||||
public void testPessimisticLockWithFirstResultThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
@ -126,6 +132,29 @@ public class OracleFollowOnLockingTest extends
|
|||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithFirstResultAndJoinThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p left join p.vehicle v on v.id is null", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ) )
|
||||
.setFirstResult( 40 )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 11, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
|
@ -173,7 +202,29 @@ public class OracleFollowOnLockingTest extends
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithFirstResultsWhileExplicitlyDisablingFollowOnLockingThenFails() {
|
||||
public void testPessimisticLockWithFirstResultWhileExplicitlyDisablingFollowOnLockingThenFails() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products = session.createQuery( "select p from Product p", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( false ) )
|
||||
.setFirstResult( 40 )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithFirstResultAndJoinWhileExplicitlyDisablingFollowOnLockingThenFails() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
@ -183,7 +234,7 @@ public class OracleFollowOnLockingTest extends
|
|||
try {
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p", Product.class )
|
||||
"select p from Product p left join p.vehicle v on v.id is null", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( false ) )
|
||||
.setFirstResult( 40 )
|
||||
|
@ -196,10 +247,9 @@ public class OracleFollowOnLockingTest extends
|
|||
IllegalQueryOperationException.class,
|
||||
expected.getCause().getClass()
|
||||
);
|
||||
assertTrue(
|
||||
expected.getCause().getMessage().contains(
|
||||
"Locking with OFFSET is not supported"
|
||||
)
|
||||
assertThat(
|
||||
expected.getCause().getMessage(),
|
||||
containsString( "Locking with OFFSET/FETCH is not supported" )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +280,29 @@ public class OracleFollowOnLockingTest extends
|
|||
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithMaxResultsAndOrderByThenFollowOnLocking() {
|
||||
@TestForIssue(jiraKey = "HHH-16433")
|
||||
public void testPessimisticLockWithOrderByThenNoFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p order by p.id", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ) )
|
||||
.getResultList();
|
||||
|
||||
assertTrue( products.size() > 1 );
|
||||
assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithMaxResultsAndOrderByThenNoFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
@ -245,43 +317,31 @@ public class OracleFollowOnLockingTest extends
|
|||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 11, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithMaxResultsAndOrderByWhileExplicitlyDisablingFollowOnLockingThenFails() {
|
||||
public void testPessimisticLockWithMaxResultsAndOrderByWhileExplicitlyDisablingFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
try {
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p order by p.id",
|
||||
Product.class
|
||||
)
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( false ) )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
fail( "Should throw exception since Oracle does not support ORDER BY if follow on locking is disabled" );
|
||||
}
|
||||
catch ( IllegalStateException expected ) {
|
||||
assertEquals(
|
||||
IllegalQueryOperationException.class,
|
||||
expected.getCause().getClass()
|
||||
);
|
||||
assertTrue(
|
||||
expected.getCause().getMessage().contains(
|
||||
"Locking with ORDER BY is not supported"
|
||||
)
|
||||
);
|
||||
}
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p order by p.id",
|
||||
Product.class
|
||||
)
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( false ) )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -397,7 +457,7 @@ public class OracleFollowOnLockingTest extends
|
|||
session.createQuery(
|
||||
"select count(p), p " +
|
||||
"from Product p " +
|
||||
"group by p.id, p.name " )
|
||||
"group by p.id, p.name, p.vehicle.id " )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ) )
|
||||
.getResultList();
|
||||
|
||||
|
@ -421,7 +481,7 @@ public class OracleFollowOnLockingTest extends
|
|||
session.createQuery(
|
||||
"select count(p), p " +
|
||||
"from Product p " +
|
||||
"group by p.id, p.name " )
|
||||
"group by p.id, p.name, p.vehicle.id " )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( false ) )
|
||||
.getResultList();
|
||||
|
@ -452,7 +512,7 @@ public class OracleFollowOnLockingTest extends
|
|||
session.createQuery(
|
||||
"select count(p), p " +
|
||||
"from Product p " +
|
||||
"group by p.id, p.name " )
|
||||
"group by p.id, p.name, p.vehicle.id " )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( true ) )
|
||||
.getResultList();
|
||||
|
@ -525,6 +585,9 @@ public class OracleFollowOnLockingTest extends
|
|||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Vehicle vehicle;
|
||||
}
|
||||
|
||||
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
|
||||
|
|
|
@ -132,8 +132,7 @@ public class OraclePaginationWithLocksTest {
|
|||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ).setFollowOnLocking( false ) )
|
||||
.getResultList();
|
||||
assertEquals( 10, people.size() );
|
||||
assertFalse( mostRecentStatementInspector.sqlContains( "fetch" ) );
|
||||
assertTrue( mostRecentStatementInspector.sqlContains( "rownum" ) );
|
||||
assertSqlContainsFetch( session );
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -146,7 +145,7 @@ public class OraclePaginationWithLocksTest {
|
|||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
assertEquals( 10, people.size() );
|
||||
assertTrue( mostRecentStatementInspector.sqlContains( "rownum" ) );
|
||||
assertSqlContainsFetch( session );
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -176,8 +175,7 @@ public class OraclePaginationWithLocksTest {
|
|||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ).setFollowOnLocking( false ) )
|
||||
.getResultList();
|
||||
assertEquals( 10, people.size() );
|
||||
assertFalse( mostRecentStatementInspector.sqlContains( "fetch" ) );
|
||||
assertTrue( mostRecentStatementInspector.sqlContains( "rownum" ) );
|
||||
assertSqlContainsFetch( session );
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -188,7 +186,7 @@ public class OraclePaginationWithLocksTest {
|
|||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
assertEquals( 10, people.size() );
|
||||
assertTrue( mostRecentStatementInspector.sqlContains( "rownum" ) );
|
||||
assertSqlContainsFetch( session );
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -213,8 +211,7 @@ public class OraclePaginationWithLocksTest {
|
|||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ).setFollowOnLocking( false ) )
|
||||
.getResultList();
|
||||
assertEquals( 1, people.size() );
|
||||
assertFalse( mostRecentStatementInspector.sqlContains( "fetch" ) );
|
||||
assertTrue( mostRecentStatementInspector.sqlContains( "rownum" ) );
|
||||
assertSqlContainsFetch( session );
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -225,7 +222,7 @@ public class OraclePaginationWithLocksTest {
|
|||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
assertEquals( 1, people.size() );
|
||||
assertTrue( mostRecentStatementInspector.sqlContains( "rownum" ) );
|
||||
assertSqlContainsFetch( session );
|
||||
}
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue