HHH-8507 - Fix SQL Server 2005 limit handler to be tolerant of carriage returns.
This commit is contained in:
parent
ef6c9266e6
commit
2b6ab8e8bb
|
@ -10,7 +10,6 @@ import java.sql.PreparedStatement;
|
|||
import java.sql.SQLException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -21,14 +20,20 @@ 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)
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
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 SELECT_PATTERN = buildShallowIndexPattern( SELECT + "(.)*" );
|
||||
private static final Pattern FROM_PATTERN = buildShallowIndexPattern( FROM );
|
||||
private static final Pattern DISTINCT_PATTERN = buildShallowIndexPattern( DISTINCT );
|
||||
private static final Pattern ORDER_BY_PATTERN = buildShallowIndexPattern( ORDER_BY );
|
||||
private static final Pattern COMMA_PATTERN = buildShallowIndexPattern( "," );
|
||||
|
||||
private static final Pattern ALIAS_PATTERN = Pattern.compile( "(?i)\\sas\\s(.)+$" );
|
||||
|
||||
// Flag indicating whether TOP(?) expression has been added to the original query.
|
||||
|
@ -95,7 +100,7 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
|||
if ( LimitHelper.hasFirstRow( selection ) ) {
|
||||
final String selectClause = fillAliasInSelectClause( sb );
|
||||
|
||||
final int orderByIndex = shallowIndexOfWord( sb, ORDER_BY, 0 );
|
||||
final int orderByIndex = shallowIndexOfPattern( sb, ORDER_BY_PATTERN, 0 );
|
||||
if ( orderByIndex > 0 ) {
|
||||
// ORDER BY requires using TOP.
|
||||
addTopExpression( sb );
|
||||
|
@ -140,8 +145,9 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
|||
*/
|
||||
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 );
|
||||
final int startPos = shallowIndexOfPattern( sb, SELECT_PATTERN, 0 );
|
||||
int endPos = shallowIndexOfPattern( sb, FROM_PATTERN, startPos );
|
||||
|
||||
int nextComa = startPos;
|
||||
int prevComa = startPos;
|
||||
int unique = 0;
|
||||
|
@ -149,7 +155,7 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
|||
|
||||
while ( nextComa != -1 ) {
|
||||
prevComa = nextComa;
|
||||
nextComa = shallowIndexOf( sb, ",", nextComa );
|
||||
nextComa = shallowIndexOfPattern( sb, COMMA_PATTERN, nextComa );
|
||||
if ( nextComa > endPos ) {
|
||||
break;
|
||||
}
|
||||
|
@ -176,7 +182,7 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
|||
}
|
||||
// Processing last column.
|
||||
// Refreshing end position, because we might have inserted new alias.
|
||||
endPos = shallowIndexOfWord( sb, FROM, startPos );
|
||||
endPos = shallowIndexOfPattern( sb, FROM_PATTERN, startPos );
|
||||
final String expression = sb.substring( prevComa, endPos );
|
||||
if ( selectsMultipleColumns( expression ) ) {
|
||||
selectsMultipleColumns = true;
|
||||
|
@ -240,13 +246,13 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
|||
* @param sql SQL query.
|
||||
*/
|
||||
protected void addTopExpression(StringBuilder sql) {
|
||||
final int distinctStartPos = shallowIndexOfWord( sql, DISTINCT, 0 );
|
||||
final int distinctStartPos = shallowIndexOfPattern( sql, DISTINCT_PATTERN, 0 );
|
||||
if ( distinctStartPos > 0 ) {
|
||||
// Place TOP after DISTINCT.
|
||||
sql.insert( distinctStartPos + DISTINCT.length(), " TOP(?)" );
|
||||
}
|
||||
else {
|
||||
final int selectStartPos = shallowIndexOf( sql, SELECT_WITH_SPACE, 0 );
|
||||
final int selectStartPos = shallowIndexOfPattern( sql, SELECT_PATTERN, 0 );
|
||||
// Place TOP after SELECT.
|
||||
sql.insert( selectStartPos + SELECT.length(), " TOP(?)" );
|
||||
}
|
||||
|
@ -254,53 +260,41 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns index of the first case-insensitive match of search term surrounded by spaces
|
||||
* that is not enclosed in parentheses.
|
||||
* Returns index of the first case-insensitive match of search pattern that is not
|
||||
* enclosed in parenthesis.
|
||||
*
|
||||
* @param sb String to search.
|
||||
* @param search Search term.
|
||||
* @param pattern Compiled search pattern.
|
||||
* @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 );
|
||||
// In case of match adding one because of space placed in front of search term.
|
||||
return index != -1 ? ( index + 1 ) : -1;
|
||||
private static int shallowIndexOfPattern(final StringBuilder sb, final Pattern pattern, int fromIndex) {
|
||||
int index = -1;
|
||||
final String matchString = sb.toString();
|
||||
|
||||
// quick exit, save performance and avoid exceptions
|
||||
if ( matchString.length() < fromIndex || fromIndex < 0 ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Matcher matcher = pattern.matcher( matchString );
|
||||
matcher.region( fromIndex, matchString.length() );
|
||||
|
||||
if ( matcher.find() && matcher.groupCount() > 0 ) {
|
||||
index = matcher.start();
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns index of the first case-insensitive match of search term that is not enclosed in parentheses.
|
||||
* Builds a pattern that can be used to find matches of case-insensitive matches
|
||||
* based on the search pattern that is not enclosed in parenthesis.
|
||||
*
|
||||
* @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.
|
||||
* @param pattern String search pattern.
|
||||
* @return Compiled {@link Pattern}.
|
||||
*/
|
||||
private static int shallowIndexOf(StringBuilder sb, String search, int fromIndex) {
|
||||
// case-insensitive match
|
||||
final String lowercase = sb.toString().toLowerCase(Locale.ROOT);
|
||||
final int len = lowercase.length();
|
||||
final int searchlen = search.length();
|
||||
int pos = -1;
|
||||
int depth = 0;
|
||||
int cur = fromIndex;
|
||||
do {
|
||||
pos = lowercase.indexOf( search, cur );
|
||||
if ( pos != -1 ) {
|
||||
for ( int iter = cur; iter < pos; iter++ ) {
|
||||
final 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;
|
||||
private static Pattern buildShallowIndexPattern(String pattern) {
|
||||
return Pattern.compile( "(\\b" + pattern + ")(?![^\\(]*\\))", Pattern.CASE_INSENSITIVE );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,10 +43,45 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase {
|
|||
String input = "select distinct f1 as f53245 from table849752 order by f234, f67 desc";
|
||||
|
||||
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.getLimitHandler().processSql( input, toRowSelection( 10, 15 ) ).toLowerCase(Locale.ROOT) );
|
||||
"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.getLimitHandler().processSql( input, toRowSelection( 10, 15 ) ).toLowerCase(Locale.ROOT) );
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-10736")
|
||||
public void testGetLimitStringWithNewlineAfterSelect() {
|
||||
final String query = "select" + System.lineSeparator() + "* FROM Employee E WHERE E.firstName = :firstName";
|
||||
assertEquals(
|
||||
"WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " +
|
||||
query + " ) inner_query ) SELECT * FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
|
||||
dialect.getLimitHandler().processSql( query, toRowSelection( 1, 25 ) )
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-10736")
|
||||
public void testGetLimitStringWithNewlineAfterSelectWithMultipleSpaces() {
|
||||
final String query = "select " + System.lineSeparator() + "* FROM Employee E WHERE E.firstName = :firstName";
|
||||
assertEquals(
|
||||
"WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " +
|
||||
query + " ) inner_query ) SELECT * FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
|
||||
dialect.getLimitHandler().processSql( query, toRowSelection( 1, 25 ) )
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-8507")
|
||||
public void testGetLimitStringWithNewlineAfterColumnList() {
|
||||
final String query = "select E.fieldA,E.fieldB" + System.lineSeparator() + "FROM Employee E WHERE E.firstName = :firstName";
|
||||
assertEquals(
|
||||
"WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " +
|
||||
"select E.fieldA as page0_,E.fieldB as page1_" + System.lineSeparator() +
|
||||
"FROM Employee E WHERE E.firstName = :firstName ) inner_query ) SELECT page0_, page1_ FROM query " +
|
||||
"WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
|
||||
dialect.getLimitHandler().processSql( query, toRowSelection( 1, 25 ) )
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -159,9 +194,9 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase {
|
|||
|
||||
assertEquals(
|
||||
"WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " +
|
||||
"select TOP(?) cast(lc302_doku6_.redniBrojStavke as varchar(255)) as col_0_0_, lc302_doku6_.dokumentiID as col_1_0_ " +
|
||||
"from LC302_Dokumenti lc302_doku6_ order by lc302_doku6_.dokumentiID DESC ) inner_query ) " +
|
||||
"SELECT col_0_0_, col_1_0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
|
||||
"select TOP(?) cast(lc302_doku6_.redniBrojStavke as varchar(255)) as col_0_0_, lc302_doku6_.dokumentiID as col_1_0_ " +
|
||||
"from LC302_Dokumenti lc302_doku6_ order by lc302_doku6_.dokumentiID DESC ) inner_query ) " +
|
||||
"SELECT col_0_0_, col_1_0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
|
||||
dialect.getLimitHandler().processSql( query, toRowSelection( 1, 3 ) )
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue