HHH-11324 - Fix LimitHandler parsing of subqueries in the select clause for SQLServer2005Dialect.

(cherry picked from commit 087729c023)
This commit is contained in:
Chris Cranford 2016-12-16 12:52:50 -05:00 committed by Gail Badner
parent 6b928f9379
commit f7eb28bd20
1 changed files with 91 additions and 2 deletions

View File

@ -8,6 +8,7 @@ package org.hibernate.dialect.pagination;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
@ -316,11 +317,28 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
return -1;
}
List<IgnoreRange> ignoreRangeList = generateIgnoreRanges( matchString );
Matcher matcher = pattern.matcher( matchString );
matcher.region( fromIndex, matchString.length() );
if ( matcher.find() && matcher.groupCount() > 0 ) {
index = matcher.start();
if ( ignoreRangeList.isEmpty() ) {
// old behavior where the first match is used if no ignorable ranges
// were deduced from the matchString.
if ( matcher.find() && matcher.groupCount() > 0 ) {
index = matcher.start();
}
}
else {
// rather than taking the first match, we now iterate all matches
// until we determine a match that isn't considered "ignorable'.
while ( matcher.find() && matcher.groupCount() > 0 ) {
final int position = matcher.start();
if ( !isPositionIgnorable( ignoreRangeList, position ) ) {
index = position;
break;
}
}
}
return index;
}
@ -342,4 +360,75 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
Pattern.CASE_INSENSITIVE
);
}
/**
* Geneartes a list of {@code IgnoreRange} objects that represent nested sections of the
* provided SQL buffer that should be ignored when performing regular expression matches.
*
* @param sql The SQL buffer.
* @return list of {@code IgnoreRange} objects, never {@code null}.
*/
private static List<IgnoreRange> generateIgnoreRanges(String sql) {
List<IgnoreRange> ignoreRangeList = new ArrayList<IgnoreRange>();
int depth = 0;
int start = -1;
for ( int i = 0; i < sql.length(); ++i ) {
final char ch = sql.charAt( i );
if ( ch == '(' ) {
depth++;
if ( depth == 1 ) {
start = i;
}
}
else if ( ch == ')' ) {
if ( depth > 0 ) {
if ( depth == 1 ) {
ignoreRangeList.add( new IgnoreRange( start, i ) );
start = -1;
}
depth--;
}
else {
throw new IllegalStateException( "Found an unmatched ')' at position " + i + ": " + sql );
}
}
}
if ( depth != 0 ) {
throw new IllegalStateException( "Unmatched parenthesis in rendered SQL (" + depth + " depth): " + sql );
}
return ignoreRangeList;
}
/**
* Returns whether the specified {@code position} is within the ranges of the {@code ignoreRangeList}.
*
* @param ignoreRangeList list of {@code IgnoreRange} objects deduced from the SQL buffer.
* @param position the position to determine whether is ignorable.
* @return {@code true} if the position is to ignored/skipped, {@code false} otherwise.
*/
private static boolean isPositionIgnorable(List<IgnoreRange> ignoreRangeList, int position) {
for ( IgnoreRange ignoreRange : ignoreRangeList ) {
if ( ignoreRange.isWithinRange( position ) ) {
return true;
}
}
return false;
}
static class IgnoreRange {
private int start;
private int end;
IgnoreRange(int start, int end) {
this.start = start;
this.end = end;
}
boolean isWithinRange(int position) {
return position >= start && position <= end;
}
}
}