HHH-7440, HHH-7368, HHH-7369, HHH-7370 - Redesign dialect-specific LIMIT clause appliance

This commit is contained in:
Lukasz Antoniak 2012-07-10 21:19:32 +02:00
parent 65d1724433
commit c46daa4cf0
14 changed files with 907 additions and 377 deletions

View File

@ -39,6 +39,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.hibernate.engine.spi.RowSelection;
import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
@ -58,6 +59,8 @@ import org.hibernate.dialect.lock.PessimisticForceIncrementLockingStrategy;
import org.hibernate.dialect.lock.PessimisticReadSelectLockingStrategy;
import org.hibernate.dialect.lock.PessimisticWriteSelectLockingStrategy;
import org.hibernate.dialect.lock.SelectLockingStrategy;
import org.hibernate.dialect.pagination.LegacyLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.exception.spi.ConversionContext;
@ -935,7 +938,9 @@ public abstract class Dialect implements ConversionContext {
* via a SQL clause?
*
* @return True if this dialect supports some form of LIMIT.
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
public boolean supportsLimit() {
return false;
}
@ -945,7 +950,9 @@ public abstract class Dialect implements ConversionContext {
* support specifying an offset?
*
* @return True if the dialect supports an offset within the limit support.
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
public boolean supportsLimitOffset() {
return supportsLimit();
}
@ -955,7 +962,9 @@ public abstract class Dialect implements ConversionContext {
* parameters) for its limit/offset?
*
* @return True if bind variables can be used; false otherwise.
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
public boolean supportsVariableLimit() {
return supportsLimit();
}
@ -965,7 +974,9 @@ public abstract class Dialect implements ConversionContext {
* Does this dialect require us to bind the parameters in reverse order?
*
* @return true if the correct order is limit, offset
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
public boolean bindLimitParametersInReverseOrder() {
return false;
}
@ -975,7 +986,9 @@ public abstract class Dialect implements ConversionContext {
* <tt>SELECT</tt> statement, rather than at the end?
*
* @return true if limit parameters should come before other parameters
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
public boolean bindLimitParametersFirst() {
return false;
}
@ -995,7 +1008,9 @@ public abstract class Dialect implements ConversionContext {
* So essentially, is limit relative from offset? Or is limit absolute?
*
* @return True if limit is relative from offset; false otherwise.
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
public boolean useMaxForLimit() {
return false;
}
@ -1005,7 +1020,9 @@ public abstract class Dialect implements ConversionContext {
* to the SQL query. This option forces that the limit be written to the SQL query.
*
* @return True to force limit into SQL query even if none specified in Hibernate query; false otherwise.
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
public boolean forceLimitUsage() {
return false;
}
@ -1017,7 +1034,9 @@ public abstract class Dialect implements ConversionContext {
* @param offset The offset of the limit
* @param limit The limit of the limit ;)
* @return The modified query statement with the limit applied.
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
public String getLimitString(String query, int offset, int limit) {
return getLimitString( query, ( offset > 0 || forceLimitUsage() ) );
}
@ -1038,7 +1057,9 @@ public abstract class Dialect implements ConversionContext {
* @param query The query to which to apply the limit.
* @param hasOffset Is the query requesting an offset?
* @return the modified SQL
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
protected String getLimitString(String query, boolean hasOffset) {
throw new UnsupportedOperationException( "Paged queries not supported by " + getClass().getName());
}
@ -1052,16 +1073,27 @@ public abstract class Dialect implements ConversionContext {
* to injecting the limit values into the SQL string.
*
* @param zeroBasedFirstResult The user-supplied, zero-based first row offset.
*
* @return The corresponding db/dialect specific offset.
*
* @see org.hibernate.Query#setFirstResult
* @see org.hibernate.Criteria#setFirstResult
* @deprecated {@link #buildLimitHandler(String, RowSelection)} should be overridden instead.
*/
@Deprecated
public int convertToFirstRowValue(int zeroBasedFirstResult) {
return zeroBasedFirstResult;
}
/**
* Build delegate managing LIMIT clause.
*
* @param sql SQL query.
* @param selection Selection criteria. {@code null} in case of unlimited number of rows.
* @return LIMIT clause delegate.
*/
public LimitHandler buildLimitHandler(String sql, RowSelection selection) {
return new LegacyLimitHandler( this, sql, selection );
}
// lock acquisition support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -25,14 +25,15 @@ package org.hibernate.dialect;
import java.sql.SQLException;
import java.sql.Types;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.JDBCException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.QueryTimeoutException;
import org.hibernate.dialect.function.NoArgSQLFunction;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.SQLServer2005LimitHandler;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.internal.util.JdbcExceptionHelper;
@ -42,19 +43,11 @@ import org.hibernate.type.StandardBasicTypes;
* A dialect for Microsoft SQL 2005. (HHH-3936 fix)
*
* @author Yoryos Valotasios
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class SQLServer2005Dialect extends SQLServerDialect {
private static final String SELECT = "select";
private static final String FROM = "from";
private static final String DISTINCT = "distinct";
private static final String ORDER_BY = "order by";
private static final int MAX_LENGTH = 8000;
/**
* Regular expression for stripping alias
*/
private static final Pattern ALIAS_PATTERN = Pattern.compile( "\\sas\\s[^,]+(,?)" );
public SQLServer2005Dialect() {
// HHH-3965 fix
// As per http://www.sql-server-helper.com/faq/sql-server-2005-varchar-max-p01.aspx
@ -78,139 +71,8 @@ public class SQLServer2005Dialect extends SQLServerDialect {
}
@Override
public boolean supportsLimitOffset() {
return true;
}
@Override
public boolean bindLimitParametersFirst() {
return false;
}
@Override
public boolean supportsVariableLimit() {
return true;
}
@Override
public int convertToFirstRowValue(int zeroBasedFirstResult) {
// Our dialect paginated results aren't zero based. The first row should get the number 1 and so on
return zeroBasedFirstResult + 1;
}
@Override
public String getLimitString(String query, int offset, int limit) {
// We transform the query to one with an offset and limit if we have an offset and limit to bind
if ( offset > 1 || limit > 1 ) {
return getLimitString( query, true );
}
return query;
}
/**
* Add a LIMIT clause to the given SQL SELECT (HHH-2655: ROW_NUMBER for Paging)
*
* The LIMIT SQL will look like:
*
* <pre>
* WITH query AS (
* original_select_clause_without_distinct_and_order_by,
* ROW_NUMBER() OVER ([ORDER BY CURRENT_TIMESTAMP | original_order_by_clause]) as __hibernate_row_nr__
* original_from_clause
* original_where_clause
* group_by_if_originally_select_distinct
* )
* SELECT * FROM query WHERE __hibernate_row_nr__ >= offset AND __hibernate_row_nr__ < offset + last
* </pre>
*
* @param querySqlString The SQL statement to base the limit query off of.
* @param hasOffset Is the query requesting an offset?
*
* @return A new SQL statement with the LIMIT clause applied.
*/
@Override
public String getLimitString(String querySqlString, boolean hasOffset) {
StringBuilder sb = new StringBuilder( querySqlString.trim() );
int orderByIndex = shallowIndexOfWord( sb, ORDER_BY, 0 );
CharSequence orderby = orderByIndex > 0 ? sb.subSequence( orderByIndex, sb.length() )
: "ORDER BY CURRENT_TIMESTAMP";
// Delete the order by clause at the end of the query
if ( orderByIndex > 0 ) {
sb.delete( orderByIndex, orderByIndex + orderby.length() );
}
// HHH-5715 bug fix
replaceDistinctWithGroupBy( sb );
insertRowNumberFunction( sb, orderby );
// Wrap the query within a with statement:
sb.insert( 0, "WITH query AS (" ).append( ") SELECT * FROM query " );
sb.append( "WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?" );
return sb.toString();
}
/**
* Utility method that checks if the given sql query is a select distinct one and if so replaces the distinct select
* with an equivalent simple select with a group by clause.
*
* @param sql an sql query
*/
protected static void replaceDistinctWithGroupBy(StringBuilder sql) {
int distinctIndex = shallowIndexOfWord( sql, DISTINCT, 0 );
int selectEndIndex = shallowIndexOfWord( sql, FROM, 0 );
if (distinctIndex > 0 && distinctIndex < selectEndIndex) {
sql.delete( distinctIndex, distinctIndex + DISTINCT.length() + " ".length());
sql.append( " group by" ).append( getSelectFieldsWithoutAliases( sql ) );
}
}
public static final String SELECT_WITH_SPACE = SELECT + ' ';
/**
* This utility method searches the given sql query for the fields of the select statement and returns them without
* the aliases.
*
* @param sql sql query
*
* @return the fields of the select statement without their alias
*/
protected static CharSequence getSelectFieldsWithoutAliases(StringBuilder sql) {
final int selectStartPos = shallowIndexOf( sql, SELECT_WITH_SPACE, 0 );
final int fromStartPos = shallowIndexOfWord( sql, FROM, selectStartPos );
String select = sql.substring( selectStartPos + SELECT.length(), fromStartPos );
// Strip the as clauses
return stripAliases( select );
}
/**
* Utility method that strips the aliases.
*
* @param str string to replace the as statements
*
* @return a string without the as statements
*/
protected static String stripAliases(String str) {
Matcher matcher = ALIAS_PATTERN.matcher( str );
return matcher.replaceAll( "$1" );
}
/**
* We must place the row_number function at the end of select clause.
*
* @param sql the initial sql query without the order by clause
* @param orderby the order by clause of the query
*/
protected void insertRowNumberFunction(StringBuilder sql, CharSequence orderby) {
// Find the end of the select clause
int selectEndIndex = shallowIndexOfWord( sql, FROM, 0 );
// Insert after the select clause the row_number() function:
sql.insert( selectEndIndex - 1, ", ROW_NUMBER() OVER (" + orderby + ") as __hibernate_row_nr__" );
public LimitHandler buildLimitHandler(String sql, RowSelection selection) {
return new SQLServer2005LimitHandler( sql, selection );
}
@Override // since SQLServer2005 the nowait hint is supported
@ -235,50 +97,6 @@ public class SQLServer2005Dialect extends SQLServerDialect {
}
}
/**
* Returns index of the first case-insensitive match of search term surrounded by spaces
* that is not enclosed in parentheses.
*
* @param sb String to search.
* @param search Search term.
* @param fromIndex The index from which to start the search.
* @return Position of the first match, or {@literal -1} if not found.
*/
private static int shallowIndexOfWord(final StringBuilder sb, final String search, int fromIndex) {
final int index = shallowIndexOf( sb, ' ' + search + ' ', fromIndex );
return index != -1 ? ( index + 1 ) : -1; // In case of match adding one because of space placed in front of search term.
}
/**
* Returns index of the first case-insensitive match of search term that is not enclosed in parentheses.
*
* @param sb String to search.
* @param search Search term.
* @param fromIndex The index from which to start the search.
* @return Position of the first match, or {@literal -1} if not found.
*/
private static int shallowIndexOf(StringBuilder sb, String search, int fromIndex) {
final String lowercase = sb.toString().toLowerCase(); // case-insensitive match
final int len = lowercase.length();
final int searchlen = search.length();
int pos = -1, depth = 0, cur = fromIndex;
do {
pos = lowercase.indexOf( search, cur );
if ( pos != -1 ) {
for ( int iter = cur; iter < pos; iter++ ) {
char c = sb.charAt( iter );
if ( c == '(' ) {
depth = depth + 1;
}
else if ( c == ')' ) {
depth = depth - 1;
}
}
cur = pos + searchlen;
}
} while ( cur < len && depth != 0 && pos != -1 );
return depth == 0 ? pos : -1;
}
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return new SQLExceptionConversionDelegate() {
@ -286,7 +104,7 @@ public class SQLServer2005Dialect extends SQLServerDialect {
public JDBCException convert(SQLException sqlException, String message, String sql) {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException );
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
if( "HY008".equals( sqlState )){
if ( "HY008".equals( sqlState ) ) {
throw new QueryTimeoutException( message, sqlException, sql );
}
if (1222 == errorCode ) {
@ -296,7 +114,4 @@ public class SQLServer2005Dialect extends SQLServerDialect {
}
};
}
}

View File

@ -0,0 +1,168 @@
package org.hibernate.dialect.pagination;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.hibernate.engine.spi.RowSelection;
/**
* Default implementation of {@link LimitHandler} interface.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public abstract class AbstractLimitHandler implements LimitHandler {
protected final String sql;
protected final RowSelection selection;
/**
* Default constructor. SQL query and selection criteria required to allow LIMIT clause pre-processing.
*
* @param sql SQL query.
* @param selection Selection criteria. {@code null} in case of unlimited number of rows.
*/
public AbstractLimitHandler(String sql, RowSelection selection) {
this.sql = sql;
this.selection = selection;
}
public boolean supportsLimit() {
return false;
}
public boolean supportsLimitOffset() {
return supportsLimit();
}
/**
* Does this handler support bind variables (i.e., prepared statement
* parameters) for its limit/offset?
*
* @return True if bind variables can be used; false otherwise.
*/
public boolean supportsVariableLimit() {
return supportsLimit();
}
/**
* ANSI SQL defines the LIMIT clause to be in the form LIMIT offset, limit.
* Does this dialect require us to bind the parameters in reverse order?
*
* @return true if the correct order is limit, offset
*/
public boolean bindLimitParametersInReverseOrder() {
return false;
}
/**
* Does the <tt>LIMIT</tt> clause come at the start of the
* <tt>SELECT</tt> statement, rather than at the end?
*
* @return true if limit parameters should come before other parameters
*/
public boolean bindLimitParametersFirst() {
return false;
}
/**
* Does the <tt>LIMIT</tt> clause take a "maximum" row number instead
* of a total number of returned rows?
* <p/>
* This is easiest understood via an example. Consider you have a table
* with 20 rows, but you only want to retrieve rows number 11 through 20.
* Generally, a limit with offset would say that the offset = 11 and the
* limit = 10 (we only want 10 rows at a time); this is specifying the
* total number of returned rows. Some dialects require that we instead
* specify offset = 11 and limit = 20, where 20 is the "last" row we want
* relative to offset (i.e. total number of rows = 20 - 11 = 9)
* <p/>
* So essentially, is limit relative from offset? Or is limit absolute?
*
* @return True if limit is relative from offset; false otherwise.
*/
public boolean useMaxForLimit() {
return false;
}
/**
* Generally, if there is no limit applied to a Hibernate query we do not apply any limits
* to the SQL query. This option forces that the limit be written to the SQL query.
*
* @return True to force limit into SQL query even if none specified in Hibernate query; false otherwise.
*/
public boolean forceLimitUsage() {
return false;
}
/**
* Hibernate APIs explicitly state that setFirstResult() should be a zero-based offset. Here we allow the
* Dialect a chance to convert that value based on what the underlying db or driver will expect.
* <p/>
* NOTE: what gets passed into {@link #AbstractLimitHandler(String, RowSelection)} is the zero-based offset.
* Dialects which do not {@link #supportsVariableLimit} should take care to perform any needed first-row-conversion
* calls prior to injecting the limit values into the SQL string.
*
* @param zeroBasedFirstResult The user-supplied, zero-based first row offset.
*
* @return The corresponding db/dialect specific offset.
*
* @see org.hibernate.Query#setFirstResult
* @see org.hibernate.Criteria#setFirstResult
*/
public int convertToFirstRowValue(int zeroBasedFirstResult) {
return zeroBasedFirstResult;
}
public String getProcessedSql() {
throw new UnsupportedOperationException( "Paged queries not supported by " + getClass().getName() );
}
public int bindLimitParametersAtStartOfQuery(PreparedStatement statement, int index)
throws SQLException {
return bindLimitParametersFirst() ? bindLimitParameters( statement, index ) : 0;
}
public int bindLimitParametersAtEndOfQuery(PreparedStatement statement, int index)
throws SQLException {
return !bindLimitParametersFirst() ? bindLimitParameters( statement, index ) : 0;
}
public void setMaxRows(PreparedStatement statement) throws SQLException {
}
/**
* Default implementation of binding parameter values needed by the LIMIT clause.
*
* @param statement Statement to which to bind limit parameter values.
* @param index Index from which to start binding.
* @return The number of parameter values bound.
* @throws SQLException Indicates problems binding parameter values.
*/
protected int bindLimitParameters(PreparedStatement statement, int index)
throws SQLException {
if ( !supportsVariableLimit() || !LimitHelper.hasMaxRows( selection ) ) {
return 0;
}
int firstRow = convertToFirstRowValue( LimitHelper.getFirstRow( selection ) );
int lastRow = getMaxOrLimit();
boolean hasFirstRow = supportsLimitOffset() && ( firstRow > 0 || forceLimitUsage() );
boolean reverse = bindLimitParametersInReverseOrder();
if ( hasFirstRow ) {
statement.setInt( index + ( reverse ? 1 : 0 ), firstRow );
}
statement.setInt( index + ( reverse || !hasFirstRow ? 0 : 1 ), lastRow );
return hasFirstRow ? 2 : 1;
}
/**
* Some dialect-specific LIMIT clauses require the maximum last row number
* (aka, first_row_number + total_row_count), while others require the maximum
* returned row count (the total maximum number of rows to return).
*
* @return The appropriate value to bind into the limit clause.
*/
protected int getMaxOrLimit() {
final int firstRow = convertToFirstRowValue( LimitHelper.getFirstRow( selection ) );
final int lastRow = selection.getMaxRows();
return useMaxForLimit() ? lastRow + firstRow : lastRow;
}
}

View File

@ -0,0 +1,58 @@
package org.hibernate.dialect.pagination;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.RowSelection;
/**
* Limit handler that delegates all operations to the underlying dialect.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class LegacyLimitHandler extends AbstractLimitHandler {
private final Dialect dialect;
public LegacyLimitHandler(Dialect dialect, String sql, RowSelection selection) {
super( sql, selection );
this.dialect = dialect;
}
public boolean supportsLimit() {
return dialect.supportsLimit();
}
public boolean supportsLimitOffset() {
return dialect.supportsLimitOffset();
}
public boolean supportsVariableLimit() {
return dialect.supportsVariableLimit();
}
public boolean bindLimitParametersInReverseOrder() {
return dialect.bindLimitParametersInReverseOrder();
}
public boolean bindLimitParametersFirst() {
return dialect.bindLimitParametersFirst();
}
public boolean useMaxForLimit() {
return dialect.useMaxForLimit();
}
public boolean forceLimitUsage() {
return dialect.forceLimitUsage();
}
public int convertToFirstRowValue(int zeroBasedFirstResult) {
return dialect.convertToFirstRowValue( zeroBasedFirstResult );
}
public String getProcessedSql() {
boolean useLimitOffset = supportsLimit() && supportsLimitOffset()
&& LimitHelper.hasFirstRow( selection ) && LimitHelper.hasMaxRows( selection );
return dialect.getLimitString(
sql, useLimitOffset ? LimitHelper.getFirstRow( selection ) : 0, getMaxOrLimit()
);
}
}

View File

@ -0,0 +1,66 @@
package org.hibernate.dialect.pagination;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.hibernate.engine.spi.RowSelection;
/**
* Contract defining dialect-specific LIMIT clause handling. Typically implementers might consider extending
* {@link AbstractLimitHandler} class.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public interface LimitHandler {
/**
* Does this handler support some form of limiting query results
* via a SQL clause?
*
* @return True if this handler supports some form of LIMIT.
*/
public boolean supportsLimit();
/**
* Does this handler's LIMIT support (if any) additionally
* support specifying an offset?
*
* @return True if the handler supports an offset within the limit support.
*/
public boolean supportsLimitOffset();
/**
* Return processed SQL query.
*
* @return Query statement with LIMIT clause applied.
*/
public String getProcessedSql();
/**
* Bind parameter values needed by the LIMIT clause before original SELECT statement.
*
* @param statement Statement to which to bind limit parameter values.
* @param index Index from which to start binding.
* @return The number of parameter values bound.
* @throws SQLException Indicates problems binding parameter values.
*/
public int bindLimitParametersAtStartOfQuery(PreparedStatement statement, int index) throws SQLException;
/**
* Bind parameter values needed by the LIMIT clause after original SELECT statement.
*
* @param statement Statement to which to bind limit parameter values.
* @param index Index from which to start binding.
* @return The number of parameter values bound.
* @throws SQLException Indicates problems binding parameter values.
*/
public int bindLimitParametersAtEndOfQuery(PreparedStatement statement, int index) throws SQLException;
/**
* Use JDBC API to limit the number of rows returned by the SQL query. Typically handlers that do not
* support LIMIT clause should implement this method.
*
* @param statement Statement which number of returned rows shall be limited.
* @throws SQLException Indicates problems while limiting maximum rows returned.
*/
public void setMaxRows(PreparedStatement statement) throws SQLException;
}

View File

@ -0,0 +1,24 @@
package org.hibernate.dialect.pagination;
import org.hibernate.engine.spi.RowSelection;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class LimitHelper {
public static boolean useLimit(LimitHandler limitHandler, RowSelection selection) {
return limitHandler.supportsLimit() && hasMaxRows( selection );
}
public static boolean hasFirstRow(RowSelection selection) {
return getFirstRow( selection ) > 0;
}
public static int getFirstRow(RowSelection selection) {
return ( selection == null || selection.getFirstRow() == null ) ? 0 : selection.getFirstRow();
}
public static boolean hasMaxRows(RowSelection selection) {
return selection != null && selection.getMaxRows() != null && selection.getMaxRows() > 0;
}
}

View File

@ -0,0 +1,35 @@
package org.hibernate.dialect.pagination;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.hibernate.engine.spi.RowSelection;
/**
* Handler not supporting query LIMIT clause. JDBC API is used to set maximum number of returned rows.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class NoopLimitHandler extends AbstractLimitHandler {
public NoopLimitHandler(String sql, RowSelection selection) {
super( sql, selection );
}
public String getProcessedSql() {
return sql;
}
public int bindLimitParametersAtStartOfQuery(PreparedStatement statement, int index) {
return 0;
}
public int bindLimitParametersAtEndOfQuery(PreparedStatement statement, int index) {
return 0;
}
public void setMaxRows(PreparedStatement statement) throws SQLException {
if ( LimitHelper.hasMaxRows( selection ) ) {
statement.setMaxRows( selection.getMaxRows() + convertToFirstRowValue( LimitHelper.getFirstRow( selection ) ) );
}
}
}

View File

@ -0,0 +1,263 @@
package org.hibernate.dialect.pagination;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.internal.util.StringHelper;
/**
* LIMIT clause handler compatible with SQL Server 2005 and later.
*
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class SQLServer2005LimitHandler extends AbstractLimitHandler {
private static final String SELECT = "select";
private static final String SELECT_WITH_SPACE = SELECT + ' ';
private static final String FROM = "from";
private static final String DISTINCT = "distinct";
private static final String ORDER_BY = "order by";
private static final Pattern ALIAS_PATTERN = Pattern.compile( "(?i)\\sas\\s(.)+$" );
private boolean topAdded = false; // Flag indicating whether TOP(?) expression has been added to the original query.
private boolean hasOffset = true; // True if offset greater than 0.
public SQLServer2005LimitHandler(String sql, RowSelection selection) {
super( sql, selection );
}
@Override
public boolean supportsLimit() {
return true;
}
@Override
public boolean useMaxForLimit() {
return true;
}
@Override
public boolean supportsLimitOffset() {
return true;
}
@Override
public boolean supportsVariableLimit() {
return true;
}
@Override
public int convertToFirstRowValue(int zeroBasedFirstResult) {
// Our dialect paginated results aren't zero based. The first row should get the number 1 and so on
return zeroBasedFirstResult + 1;
}
/**
* Add a LIMIT clause to the given SQL SELECT (HHH-2655: ROW_NUMBER for Paging)
*
* The LIMIT SQL will look like:
*
* <pre>
* WITH query AS (
* SELECT inner_query.*
* , ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__
* FROM ( original_query_with_top_if_order_by_present_and_all_aliased_columns ) inner_query
* )
* SELECT alias_list FROM query WHERE __hibernate_row_nr__ >= offset AND __hibernate_row_nr__ < offset + last
* </pre>
*
* When offset equals {@literal 0}, only {@literal TOP(?)} expression is added to the original query.
*
* @return A new SQL statement with the LIMIT clause applied.
*/
@Override
public String getProcessedSql() {
StringBuilder sb = new StringBuilder( sql );
if ( sb.charAt( sb.length() - 1 ) == ';' ) {
sb.setLength( sb.length() - 1 );
}
if ( LimitHelper.hasFirstRow( selection ) ) {
final String selectClause = fillAliasInSelectClause( sb );
int orderByIndex = shallowIndexOfWord( sb, ORDER_BY, 0 );
if ( orderByIndex > 0 ) {
// ORDER BY requires using TOP.
addTopExpression( sb );
}
encloseWithOuterQuery( sb );
// Wrap the query within a with statement:
sb.insert( 0, "WITH query AS (" ).append( ") SELECT " ).append( selectClause ).append( " FROM query " );
sb.append( "WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?" );
}
else {
hasOffset = false;
addTopExpression( sb );
}
return sb.toString();
}
@Override
public int bindLimitParametersAtStartOfQuery(PreparedStatement statement, int index) throws SQLException {
if ( topAdded ) {
statement.setInt( index, getMaxOrLimit() - 1 ); // Binding TOP(?).
return 1;
}
return 0;
}
@Override
public int bindLimitParametersAtEndOfQuery(PreparedStatement statement, int index) throws SQLException {
return hasOffset ? super.bindLimitParametersAtEndOfQuery( statement, index ) : 0;
}
/**
* Adds missing aliases in provided SELECT clause and returns coma-separated list of them.
*
* @param sb SQL query.
*
* @return List of aliases separated with comas.
*/
protected String fillAliasInSelectClause(StringBuilder sb) {
final List<String> aliases = new LinkedList<String>();
final int startPos = shallowIndexOf( sb, SELECT_WITH_SPACE, 0 );
int endPos = shallowIndexOfWord( sb, FROM, startPos );
int nextComa = startPos;
int prevComa = startPos;
int unique = 0;
while ( nextComa != -1 ) {
prevComa = nextComa;
nextComa = shallowIndexOf( sb, ",", nextComa );
if ( nextComa > endPos ) {
break;
}
if ( nextComa != -1 ) {
String expression = sb.substring( prevComa, nextComa );
String alias = getAlias( expression );
if ( alias == null ) {
// Inserting alias. It is unlikely that we would have to add alias, but just in case.
alias = StringHelper.generateAlias( "page", unique );
sb.insert( nextComa, " as " + alias );
++unique;
nextComa += ( " as " + alias ).length();
}
aliases.add( alias );
++nextComa;
}
}
// Processing last column.
endPos = shallowIndexOfWord( sb, FROM, startPos ); // Refreshing end position, because we might have inserted new alias.
String expression = sb.substring( prevComa, endPos );
String alias = getAlias( expression );
if ( alias == null ) {
// Inserting alias. It is unlikely that we would have to add alias, but just in case.
alias = StringHelper.generateAlias( "page", unique );
sb.insert( endPos - 1, " as " + alias );
}
aliases.add( alias );
return StringHelper.join( ", ", aliases.iterator() );
}
/**
* Returns alias of provided single column selection or {@code null} if not found.
* Alias should be preceded with {@code AS} keyword.
*
* @param expression Single column select expression.
*
* @return Column alias.
*/
private String getAlias(String expression) {
Matcher matcher = ALIAS_PATTERN.matcher( expression );
if ( matcher.find() ) {
return matcher.group( 0 ).replaceFirst( "(?i)\\sas\\s", "" ).trim();
}
return null;
}
/**
* Encloses original SQL statement with outer query that provides {@literal __hibernate_row_nr__} column.
*
* @param sql SQL query.
*/
protected void encloseWithOuterQuery(StringBuilder sql) {
sql.insert( 0, "SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " );
sql.append( " ) inner_query " );
}
/**
* Adds {@code TOP} expression. Parameter value is bind in
* {@link #bindLimitParametersAtStartOfQuery(PreparedStatement, int)} method.
*
* @param sql SQL query.
*/
protected void addTopExpression(StringBuilder sql) {
final int distinctStartPos = shallowIndexOfWord( sql, DISTINCT, 0 );
if ( distinctStartPos > 0 ) {
// Place TOP after DISTINCT.
sql.insert( distinctStartPos + DISTINCT.length(), " TOP(?)" );
}
else {
final int selectStartPos = shallowIndexOf( sql, SELECT_WITH_SPACE, 0 );
// Place TOP after SELECT.
sql.insert( selectStartPos + SELECT.length(), " TOP(?)" );
}
topAdded = true;
}
/**
* Returns index of the first case-insensitive match of search term surrounded by spaces
* that is not enclosed in parentheses.
*
* @param sb String to search.
* @param search Search term.
* @param fromIndex The index from which to start the search.
*
* @return Position of the first match, or {@literal -1} if not found.
*/
private static int shallowIndexOfWord(final StringBuilder sb, final String search, int fromIndex) {
final int index = shallowIndexOf( sb, ' ' + search + ' ', fromIndex );
return index != -1 ? ( index + 1 ) : -1; // In case of match adding one because of space placed in front of search term.
}
/**
* Returns index of the first case-insensitive match of search term that is not enclosed in parentheses.
*
* @param sb String to search.
* @param search Search term.
* @param fromIndex The index from which to start the search.
*
* @return Position of the first match, or {@literal -1} if not found.
*/
private static int shallowIndexOf(StringBuilder sb, String search, int fromIndex) {
final String lowercase = sb.toString().toLowerCase(); // case-insensitive match
final int len = lowercase.length();
final int searchlen = search.length();
int pos = -1, depth = 0, cur = fromIndex;
do {
pos = lowercase.indexOf( search, cur );
if ( pos != -1 ) {
for ( int iter = cur; iter < pos; iter++ ) {
char c = sb.charAt( iter );
if ( c == '(' ) {
depth = depth + 1;
}
else if ( c == ')' ) {
depth = depth - 1;
}
}
cur = pos + searchlen;
}
} while ( cur < len && depth != 0 && pos != -1 );
return depth == 0 ? pos : -1;
}
}

View File

@ -953,9 +953,8 @@ public class QueryTranslatorImpl extends BasicLoader implements FilterTranslator
if ( stats ) startTime = System.currentTimeMillis();
try {
PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), false, queryParameters.getRowSelection(), session );
final ResultSet rs = executeQueryStatement( queryParameters, false, session );
final PreparedStatement st = (PreparedStatement) rs.getStatement();
HolderInstantiator hi = HolderInstantiator.createClassicHolderInstantiator(holderConstructor, queryParameters.getResultTransformer());
Iterator result = new IteratorImpl( rs, st, session, queryParameters.isReadOnly( session ), returnTypes, getColumnNames(), hi );

View File

@ -53,6 +53,9 @@ import org.hibernate.cache.spi.QueryCache;
import org.hibernate.cache.spi.QueryKey;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitHelper;
import org.hibernate.dialect.pagination.NoopLimitHandler;
import org.hibernate.engine.internal.TwoPhaseLoad;
import org.hibernate.engine.jdbc.ColumnNameCache;
import org.hibernate.engine.spi.EntityKey;
@ -818,15 +821,15 @@ public abstract class Loader {
final ResultTransformer forcedResultTransformer) throws SQLException, HibernateException {
final RowSelection selection = queryParameters.getRowSelection();
final int maxRows = hasMaxRows( selection ) ?
final int maxRows = LimitHelper.hasMaxRows( selection ) ?
selection.getMaxRows() :
Integer.MAX_VALUE;
final int entitySpan = getEntityPersisters().length;
final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 );
final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );
final ResultSet rs = executeQueryStatement( queryParameters, false, session );
final PreparedStatement st = (PreparedStatement) rs.getStatement();
// would be great to move all this below here into another method that could also be used
// from the new scrolling stuff.
@ -1633,7 +1636,7 @@ public abstract class Loader {
private void advance(final ResultSet rs, final RowSelection selection)
throws SQLException {
final int firstRow = getFirstRow( selection );
final int firstRow = LimitHelper.getFirstRow( selection );
if ( firstRow != 0 ) {
if ( getFactory().getSettings().isScrollableResultSetsEnabled() ) {
// we can go straight to the first required row
@ -1646,24 +1649,17 @@ public abstract class Loader {
}
}
private static boolean hasMaxRows(RowSelection selection) {
return selection != null && selection.getMaxRows() != null && selection.getMaxRows() > 0;
}
private static int getFirstRow(RowSelection selection) {
return ( selection == null || selection.getFirstRow() == null ) ? 0 : selection.getFirstRow();
}
private int interpretFirstRow(int zeroBasedFirstResult) {
return getFactory().getDialect().convertToFirstRowValue( zeroBasedFirstResult );
}
/**
* Should we pre-process the SQL string, adding a dialect-specific
* LIMIT clause.
* Build LIMIT clause handler applicable for given selection criteria. Returns {@link NoopLimitHandler} delegate
* if dialect does not support LIMIT expression or processed query does not use pagination.
*
* @param sql Query string.
* @param selection Selection criteria.
* @return LIMIT clause delegate.
*/
private static boolean useLimit(final RowSelection selection, final Dialect dialect) {
return dialect.supportsLimit() && hasMaxRows( selection );
protected LimitHandler getLimitHandler(String sql, RowSelection selection) {
final LimitHandler limitHandler = getFactory().getDialect().buildLimitHandler( sql, selection );
return LimitHelper.useLimit( limitHandler, selection ) ? limitHandler : new NoopLimitHandler( sql, selection );
}
private ScrollMode getScrollMode(boolean scroll, boolean hasFirstRow, boolean useLimitOffSet, QueryParameters queryParameters) {
@ -1678,36 +1674,48 @@ public abstract class Loader {
}
return null;
}
/**
* Process query string by applying filters, LIMIT clause, locks and comments if necessary.
* Finally execute SQL statement and advance to the first row.
*/
protected ResultSet executeQueryStatement(
final QueryParameters queryParameters,
final boolean scroll,
final SessionImplementor session) throws SQLException {
// Processing query filters.
queryParameters.processFilters( getSQLString(), session );
// Applying LIMIT clause.
final LimitHandler limitHandler = getLimitHandler( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() );
String sql = limitHandler.getProcessedSql();
// Adding locks and comments.
sql = preprocessSQL( sql, queryParameters, getFactory().getDialect() );
final PreparedStatement st = prepareQueryStatement( sql, queryParameters, limitHandler, scroll, session );
return getResultSet( st, queryParameters.getRowSelection(), limitHandler, queryParameters.hasAutoDiscoverScalarTypes(), session );
}
/**
* Obtain a <tt>PreparedStatement</tt> with all parameters pre-bound.
* Bind JDBC-style <tt>?</tt> parameters, named parameters, and
* limit parameters.
*/
protected final PreparedStatement prepareQueryStatement(
final String sql,
final QueryParameters queryParameters,
final LimitHandler limitHandler,
final boolean scroll,
final SessionImplementor session) throws SQLException, HibernateException {
queryParameters.processFilters( getSQLString(), session );
String sql = queryParameters.getFilteredSQL();
final Dialect dialect = getFactory().getDialect();
final RowSelection selection = queryParameters.getRowSelection();
boolean useLimit = useLimit( selection, dialect );
boolean hasFirstRow = getFirstRow( selection ) > 0;
boolean useLimitOffset = hasFirstRow && useLimit && dialect.supportsLimitOffset();
boolean useLimit = LimitHelper.useLimit( limitHandler, selection );
boolean hasFirstRow = LimitHelper.hasFirstRow( selection );
boolean useLimitOffset = hasFirstRow && useLimit && limitHandler.supportsLimitOffset();
boolean callable = queryParameters.isCallable();
final ScrollMode scrollMode = getScrollMode( scroll, hasFirstRow, useLimitOffset, queryParameters );
if ( useLimit ) {
sql = dialect.getLimitString(
sql.trim(), //use of trim() here is ugly?
useLimitOffset ? getFirstRow(selection) : 0,
getMaxOrLimit(selection, dialect)
);
}
sql = preprocessSQL( sql, queryParameters, dialect );
PreparedStatement st = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareQueryStatement(
sql,
callable,
@ -1718,22 +1726,17 @@ public abstract class Loader {
int col = 1;
//TODO: can we limit stored procedures ?!
if ( useLimit && dialect.bindLimitParametersFirst() ) {
col += bindLimitParameters( st, col, selection );
}
col += limitHandler.bindLimitParametersAtStartOfQuery( st, col );
if (callable) {
col = dialect.registerResultSetOutParameter( (CallableStatement)st, col );
}
col += bindParameterValues( st, queryParameters, col, session );
if ( useLimit && !dialect.bindLimitParametersFirst() ) {
col += bindLimitParameters( st, col, selection );
}
col += limitHandler.bindLimitParametersAtEndOfQuery( st, col );
if ( !useLimit ) {
setMaxRows( st, selection );
}
limitHandler.setMaxRows( st );
if ( selection != null ) {
if ( selection.getTimeout() != null ) {
@ -1776,63 +1779,6 @@ public abstract class Loader {
return st;
}
/**
* Some dialect-specific LIMIT clauses require the maximum last row number
* (aka, first_row_number + total_row_count), while others require the maximum
* returned row count (the total maximum number of rows to return).
*
* @param selection The selection criteria
* @param dialect The dialect
* @return The appropriate value to bind into the limit clause.
*/
private static int getMaxOrLimit(final RowSelection selection, final Dialect dialect) {
final int firstRow = dialect.convertToFirstRowValue( getFirstRow( selection ) );
final int lastRow = selection.getMaxRows();
return dialect.useMaxForLimit() ? lastRow + firstRow : lastRow;
}
/**
* Bind parameter values needed by the dialect-specific LIMIT clause.
*
* @param statement The statement to which to bind limit param values.
* @param index The bind position from which to start binding
* @param selection The selection object containing the limit information.
* @return The number of parameter values bound.
* @throws java.sql.SQLException Indicates problems binding parameter values.
*/
private int bindLimitParameters(
final PreparedStatement statement,
final int index,
final RowSelection selection) throws SQLException {
Dialect dialect = getFactory().getDialect();
if ( !dialect.supportsVariableLimit() ) {
return 0;
}
if ( !hasMaxRows( selection ) ) {
throw new AssertionFailure( "no max results set" );
}
int firstRow = interpretFirstRow( getFirstRow( selection ) );
int lastRow = getMaxOrLimit( selection, dialect );
boolean hasFirstRow = dialect.supportsLimitOffset() && ( firstRow > 0 || dialect.forceLimitUsage() );
boolean reverse = dialect.bindLimitParametersInReverseOrder();
if ( hasFirstRow ) {
statement.setInt( index + ( reverse ? 1 : 0 ), firstRow );
}
statement.setInt( index + ( reverse || !hasFirstRow ? 0 : 1 ), lastRow );
return hasFirstRow ? 2 : 1;
}
/**
* Use JDBC API to limit the number of rows returned by the SQL query if necessary
*/
private void setMaxRows(
final PreparedStatement st,
final RowSelection selection) throws SQLException {
if ( hasMaxRows( selection ) ) {
st.setMaxRows( selection.getMaxRows() + interpretFirstRow( getFirstRow( selection ) ) );
}
}
/**
* Bind all parameter values into the prepared statement in preparation
* for execution.
@ -1936,24 +1882,21 @@ public abstract class Loader {
}
/**
* Fetch a <tt>PreparedStatement</tt>, call <tt>setMaxRows</tt> and then execute it,
* advance to the first result and return an SQL <tt>ResultSet</tt>
* Execute given <tt>PreparedStatement</tt>, advance to the first result and return SQL <tt>ResultSet</tt>.
*/
protected final ResultSet getResultSet(
final PreparedStatement st,
final boolean autodiscovertypes,
final boolean callable,
final RowSelection selection,
final LimitHandler limitHandler,
final boolean autodiscovertypes,
final SessionImplementor session)
throws SQLException, HibernateException {
ResultSet rs = null;
try {
Dialect dialect = getFactory().getDialect();
rs = st.executeQuery();
ResultSet rs = st.executeQuery();
rs = wrapResultSetIfEnabled( rs , session );
if ( !dialect.supportsLimitOffset() || !useLimit( selection, dialect ) ) {
if ( !limitHandler.supportsLimitOffset() || !LimitHelper.useLimit( limitHandler, selection ) ) {
advance( rs, selection );
}
@ -2505,9 +2448,8 @@ public abstract class Loader {
if ( stats ) startTime = System.currentTimeMillis();
try {
PreparedStatement st = prepareQueryStatement( queryParameters, true, session );
ResultSet rs = getResultSet(st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), queryParameters.getRowSelection(), session);
final ResultSet rs = executeQueryStatement( queryParameters, true, session );
final PreparedStatement st = (PreparedStatement) rs.getStatement();
if ( stats ) {
getFactory().getStatisticsImplementor().queryExecuted(

View File

@ -487,11 +487,11 @@ public class QueryLoader extends BasicLoader {
}
try {
final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
if ( queryParameters.isCallable() ) {
throw new QueryException("iterate() not supported for callable statements");
}
final ResultSet rs = getResultSet(st, queryParameters.hasAutoDiscoverScalarTypes(), false, queryParameters.getRowSelection(), session);
final ResultSet rs = executeQueryStatement( queryParameters, false, session );
final PreparedStatement st = (PreparedStatement) rs.getStatement();
final Iterator result = new IteratorImpl(
rs,
st,

View File

@ -27,6 +27,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
@ -51,34 +52,16 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase {
dialect = null;
}
@Test
public void testStripAliases() {
String input = "some_field1 as f1, some_fild2 as f2, _field3 as f3 ";
assertEquals( "some_field1, some_fild2, _field3", SQLServer2005Dialect.stripAliases(input) );
}
@Test
public void testGetSelectFieldsWithoutAliases() {
StringBuilder input = new StringBuilder( "select some_field1 as f12, some_fild2 as f879, _field3 as _f24674_3 from ...." );
String output = SQLServer2005Dialect.getSelectFieldsWithoutAliases( input ).toString();
assertEquals( " some_field1, some_fild2, _field3", output );
}
@Test
public void testReplaceDistinctWithGroupBy() {
StringBuilder input = new StringBuilder( "select distinct f1, f2 as ff, f3 from table where f1 = 5" );
SQLServer2005Dialect.replaceDistinctWithGroupBy( input );
assertEquals( "select f1, f2 as ff, f3 from table where f1 = 5 group by f1, f2, f3 ", input.toString() );
}
@Test
public void testGetLimitString() {
String input = "select distinct f1 as f53245 from table849752 order by f234, f67 desc";
assertEquals( "with query as (select f1 as f53245, row_number() over (order by f234, f67 desc) as __hibernate_row_nr__ from table849752 group by f1) select * from query where __hibernate_row_nr__ >= ? and __hibernate_row_nr__ < ?", dialect.getLimitString(input, 10, 15).toLowerCase() );
assertEquals(
"with query as (select inner_query.*, row_number() over (order by current_timestamp) as __hibernate_row_nr__ from ( " +
"select distinct top(?) f1 as f53245 from table849752 order by f234, f67 desc ) inner_query )" +
" select f53245 from query where __hibernate_row_nr__ >= ? and __hibernate_row_nr__ < ?",
dialect.buildLimitHandler( input, toRowSelection( 10, 15 ) ).getProcessedSql().toLowerCase()
);
}
@Test
@ -91,14 +74,10 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase {
"where persistent0_.customerid=?";
assertEquals(
"WITH query AS (select persistent0_.rid as rid1688_, " +
"persistent0_.deviationfromtarget as deviati16_1688_, " +
"persistent0_.sortindex as sortindex1688_, " +
"ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ " +
"from m_evalstate persistent0_ " +
"where persistent0_.customerid=?) " +
"SELECT * FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
dialect.getLimitString( fromColumnNameSQL, 1, 10 )
"WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " +
fromColumnNameSQL + " ) inner_query ) " +
"SELECT rid1688_, deviati16_1688_, sortindex1688_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
dialect.buildLimitHandler( fromColumnNameSQL, toRowSelection( 1, 10 ) ).getProcessedSql()
);
}
@ -113,35 +92,29 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase {
"where persistent0_.type='v'";
assertEquals(
"WITH query AS (select persistent0_.id as col_0_0_, " +
"(select max(persistent1_.acceptancedate) " +
"from av_advisoryvariant persistent1_ " +
"where persistent1_.clientid=persistent0_.id) as col_1_0_, " +
"ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ " +
"from c_customer persistent0_ " +
"where persistent0_.type='v') " +
"SELECT * FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
dialect.getLimitString( subselectInSelectClauseSQL, 2, 5 )
"WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " +
subselectInSelectClauseSQL + " ) inner_query ) " +
"SELECT col_0_0_, col_1_0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
dialect.buildLimitHandler( subselectInSelectClauseSQL, toRowSelection( 2, 5 ) ).getProcessedSql()
);
}
@Test
@TestForIssue(jiraKey = "HHH-6728")
public void testGetLimitStringCaseSensitive() {
final String caseSensitiveSQL = "select persistent0_.id as col_0_0_, " +
"(select case when persistent0_.name = 'Smith' then 'Neo' else persistent0_.id end) as col_1_0_ " +
final String caseSensitiveSQL = "select persistent0_.id, persistent0_.uid AS tmp1, " +
"(select case when persistent0_.name = 'Smith' then 'Neo' else persistent0_.id end) " +
"from C_Customer persistent0_ " +
"where persistent0_.type='Va' " +
"order by persistent0_.Order";
assertEquals(
"WITH query AS (select persistent0_.id as col_0_0_, " +
"(select case when persistent0_.name = 'Smith' then 'Neo' else persistent0_.id end) as col_1_0_, " +
"ROW_NUMBER() OVER (order by persistent0_.Order) as __hibernate_row_nr__ " +
"from C_Customer persistent0_ " +
"where persistent0_.type='Va' ) " +
"SELECT * FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
dialect.getLimitString( caseSensitiveSQL, 1, 2 )
"WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " +
"select TOP(?) persistent0_.id as page0_, persistent0_.uid AS tmp1, " +
"(select case when persistent0_.name = 'Smith' then 'Neo' else persistent0_.id end) as page1_ " +
"from C_Customer persistent0_ where persistent0_.type='Va' order by persistent0_.Order ) " +
"inner_query ) SELECT page0_, tmp1, page1_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
dialect.buildLimitHandler( caseSensitiveSQL, toRowSelection( 1, 2 ) ).getProcessedSql()
);
}
@ -151,10 +124,39 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase {
final String distinctInAggregateSQL = "select aggregate_function(distinct p.n) as f1 from table849752 p order by f1";
assertEquals(
"WITH query AS (select aggregate_function(distinct p.n) as f1, " +
"ROW_NUMBER() OVER (order by f1) as __hibernate_row_nr__ from table849752 p ) " +
"SELECT * FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
dialect.getLimitString( distinctInAggregateSQL, 2, 5 )
"WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " +
"select TOP(?) aggregate_function(distinct p.n) as f1 from table849752 p order by f1 ) inner_query ) " +
"SELECT f1 FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
dialect.buildLimitHandler( distinctInAggregateSQL, toRowSelection( 2, 5 ) ).getProcessedSql()
);
}
@Test
@TestForIssue(jiraKey = "HHH-7370")
public void testGetLimitStringWithMaxOnly() {
final String query = "select product2x0_.id as id0_, product2x0_.description as descript2_0_ " +
"from Product2 product2x0_ order by product2x0_.id";
assertEquals(
"select TOP(?) product2x0_.id as id0_, product2x0_.description as descript2_0_ " +
"from Product2 product2x0_ order by product2x0_.id",
dialect.buildLimitHandler( query, toRowSelection( 0, 1 ) ).getProcessedSql()
);
final String distinctQuery = "select distinct product2x0_.id as id0_, product2x0_.description as descript2_0_ " +
"from Product2 product2x0_ order by product2x0_.id";
assertEquals(
"select distinct TOP(?) product2x0_.id as id0_, product2x0_.description as descript2_0_ " +
"from Product2 product2x0_ order by product2x0_.id",
dialect.buildLimitHandler( distinctQuery, toRowSelection( 0, 5 ) ).getProcessedSql()
);
}
private RowSelection toRowSelection(int firstRow, int maxRows) {
RowSelection selection = new RowSelection();
selection.setFirstRow( firstRow );
selection.setMaxRows( maxRows );
return selection;
}
}

View File

@ -45,5 +45,36 @@ public class Product2 implements Serializable {
@Column(name = "description", nullable = false)
public String description;
public Product2() {
}
public Product2(Integer id, String description) {
this.id = id;
this.description = description;
}
@Override
public boolean equals(Object o) {
if ( this == o ) return true;
if ( !(o instanceof Product2) ) return false;
Product2 product2 = (Product2) o;
if ( description != null ? !description.equals( product2.description ) : product2.description != null ) return false;
if ( id != null ? !id.equals( product2.id ) : product2.id != null ) return false;
return true;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + ( description != null ? description.hashCode() : 0 );
return result;
}
@Override
public String toString() {
return "Product2(id = " + id + ", description = " + description + ")";
}
}

View File

@ -27,14 +27,15 @@
package org.hibernate.test.dialect.functional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.Session;
@ -42,7 +43,6 @@ import org.hibernate.Transaction;
import org.hibernate.dialect.SQLServer2005Dialect;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.SQLGrammarException;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.jdbc.ReturningWork;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
@ -57,9 +57,8 @@ import org.junit.Test;
*/
@RequiresDialect(value = { SQLServer2005Dialect.class })
public class SQLServerDialectTest extends BaseCoreFunctionalTestCase {
@TestForIssue(jiraKey = "HHH-7198")
@Test
@TestForIssue(jiraKey = "HHH-7198")
public void testMaxResultsSqlServerWithCaseSensitiveCollation() throws Exception {
Session s = openSession();
@ -94,10 +93,7 @@ public class SQLServerDialectTest extends BaseCoreFunctionalTestCase {
Transaction tx = s.beginTransaction();
for ( int i = 1; i <= 20; i++ ) {
Product2 kit = new Product2();
kit.id = i;
kit.description = "Kit" + i;
s.persist( kit );
s.persist( new Product2( i, "Kit" + i ) );
}
s.flush();
s.clear();
@ -118,9 +114,109 @@ public class SQLServerDialectTest extends BaseCoreFunctionalTestCase {
s.close();
}
@TestForIssue(jiraKey = "HHH-3961")
@Test
@TestForIssue(jiraKey = "HHH-7369")
public void testPaginationWithScalarQuery() throws Exception {
Session s = openSession();
Transaction tx = s.beginTransaction();
for ( int i = 0; i < 10; i++ ) {
s.persist( new Product2( i, "Kit" + i ) );
}
s.flush();
s.clear();
List list = s.createSQLQuery( "select id from Product2 where description like 'Kit%' order by id" ).list();
assertEquals(Integer.class, list.get(0).getClass()); // scalar result is an Integer
list = s.createSQLQuery( "select id from Product2 where description like 'Kit%' order by id" ).setFirstResult( 2 ).setMaxResults( 2 ).list();
assertEquals(Integer.class, list.get(0).getClass()); // this fails without patch, as result suddenly has become an array
// same once again with alias
list = s.createSQLQuery( "select id as myint from Product2 where description like 'Kit%' order by id asc" ).setFirstResult( 2 ).setMaxResults( 2 ).list();
assertEquals(Integer.class, list.get(0).getClass());
tx.rollback();
s.close();
}
@Test
@TestForIssue(jiraKey = "HHH-7368")
public void testPaginationWithTrailingSemicolon() throws Exception {
Session s = openSession();
s.createSQLQuery( "select id from Product2 where description like 'Kit%' order by id;" )
.setFirstResult( 2 ).setMaxResults( 2 ).list();
s.close();
}
@Test
public void testPaginationWithHQLProjection() {
Session session = openSession();
Transaction tx = session.beginTransaction();
for ( int i = 10; i < 20; i++ ) {
session.persist( new Product2( i, "Kit" + i ) );
}
session.flush();
session.clear();
List list = session.createQuery(
"select id, description as descr, (select max(id) from Product2) as maximum from Product2"
).setFirstResult( 2 ).setMaxResults( 2 ).list();
assertEquals( 19, ( (Object[]) list.get( 1 ) )[2] );
list = session.createQuery( "select id, description, (select max(id) from Product2) from Product2 order by id" )
.setFirstResult( 2 ).setMaxResults( 2 ).list();
assertEquals( 2, list.size() );
assertArrayEquals( new Object[] {12, "Kit12", 19}, (Object[]) list.get( 0 ));
assertArrayEquals( new Object[] {13, "Kit13", 19}, (Object[]) list.get( 1 ));
tx.rollback();
session.close();
}
@Test
public void testPaginationWithHQL() {
Session session = openSession();
Transaction tx = session.beginTransaction();
for ( int i = 20; i < 30; i++ ) {
session.persist( new Product2( i, "Kit" + i ) );
}
session.flush();
session.clear();
List list = session.createQuery( "from Product2 order by id" ).setFirstResult( 3 ).setMaxResults( 2 ).list();
assertEquals( Arrays.asList( new Product2( 23, "Kit23" ), new Product2( 24, "Kit24" ) ), list );
tx.rollback();
session.close();
}
@Test
@TestForIssue(jiraKey = "HHH-7370")
public void testPaginationWithMaxOnly() {
Session session = openSession();
Transaction tx = session.beginTransaction();
for ( int i = 30; i < 40; i++ ) {
session.persist( new Product2( i, "Kit" + i ) );
}
session.flush();
session.clear();
List list = session.createQuery( "from Product2 order by id" ).setFirstResult( 0 ).setMaxResults( 2 ).list();
assertEquals( Arrays.asList( new Product2( 30, "Kit30" ), new Product2( 31, "Kit31" ) ), list );
list = session.createQuery( "select distinct p from Product2 p order by p.id" ).setMaxResults( 1 ).list();
assertEquals( Arrays.asList( new Product2( 30, "Kit30" ) ), list );
tx.rollback();
session.close();
}
@Test
@TestForIssue(jiraKey = "HHH-3961")
public void testLockNowaitSqlServer() throws Exception {
Session s = openSession();
s.beginTransaction();
@ -189,5 +285,4 @@ public class SQLServerDialectTest extends BaseCoreFunctionalTestCase {
Product2.class
};
}
}