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.sql.SQLException;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
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.
|
* LIMIT clause handler compatible with SQL Server 2005 and later.
|
||||||
*
|
*
|
||||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
* @author Chris Cranford
|
||||||
*/
|
*/
|
||||||
public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
||||||
private static final String SELECT = "select";
|
private static final String SELECT = "select";
|
||||||
private static final String SELECT_WITH_SPACE = SELECT + ' ';
|
|
||||||
private static final String FROM = "from";
|
private static final String FROM = "from";
|
||||||
private static final String DISTINCT = "distinct";
|
private static final String DISTINCT = "distinct";
|
||||||
private static final String ORDER_BY = "order by";
|
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(.)+$" );
|
private static final Pattern ALIAS_PATTERN = Pattern.compile( "(?i)\\sas\\s(.)+$" );
|
||||||
|
|
||||||
// Flag indicating whether TOP(?) expression has been added to the original query.
|
// 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 ) ) {
|
if ( LimitHelper.hasFirstRow( selection ) ) {
|
||||||
final String selectClause = fillAliasInSelectClause( sb );
|
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 ) {
|
if ( orderByIndex > 0 ) {
|
||||||
// ORDER BY requires using TOP.
|
// ORDER BY requires using TOP.
|
||||||
addTopExpression( sb );
|
addTopExpression( sb );
|
||||||
|
@ -140,8 +145,9 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
||||||
*/
|
*/
|
||||||
protected String fillAliasInSelectClause(StringBuilder sb) {
|
protected String fillAliasInSelectClause(StringBuilder sb) {
|
||||||
final List<String> aliases = new LinkedList<String>();
|
final List<String> aliases = new LinkedList<String>();
|
||||||
final int startPos = shallowIndexOf( sb, SELECT_WITH_SPACE, 0 );
|
final int startPos = shallowIndexOfPattern( sb, SELECT_PATTERN, 0 );
|
||||||
int endPos = shallowIndexOfWord( sb, FROM, startPos );
|
int endPos = shallowIndexOfPattern( sb, FROM_PATTERN, startPos );
|
||||||
|
|
||||||
int nextComa = startPos;
|
int nextComa = startPos;
|
||||||
int prevComa = startPos;
|
int prevComa = startPos;
|
||||||
int unique = 0;
|
int unique = 0;
|
||||||
|
@ -149,7 +155,7 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
||||||
|
|
||||||
while ( nextComa != -1 ) {
|
while ( nextComa != -1 ) {
|
||||||
prevComa = nextComa;
|
prevComa = nextComa;
|
||||||
nextComa = shallowIndexOf( sb, ",", nextComa );
|
nextComa = shallowIndexOfPattern( sb, COMMA_PATTERN, nextComa );
|
||||||
if ( nextComa > endPos ) {
|
if ( nextComa > endPos ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -176,7 +182,7 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
||||||
}
|
}
|
||||||
// Processing last column.
|
// Processing last column.
|
||||||
// Refreshing end position, because we might have inserted new alias.
|
// 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 );
|
final String expression = sb.substring( prevComa, endPos );
|
||||||
if ( selectsMultipleColumns( expression ) ) {
|
if ( selectsMultipleColumns( expression ) ) {
|
||||||
selectsMultipleColumns = true;
|
selectsMultipleColumns = true;
|
||||||
|
@ -240,13 +246,13 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler {
|
||||||
* @param sql SQL query.
|
* @param sql SQL query.
|
||||||
*/
|
*/
|
||||||
protected void addTopExpression(StringBuilder sql) {
|
protected void addTopExpression(StringBuilder sql) {
|
||||||
final int distinctStartPos = shallowIndexOfWord( sql, DISTINCT, 0 );
|
final int distinctStartPos = shallowIndexOfPattern( sql, DISTINCT_PATTERN, 0 );
|
||||||
if ( distinctStartPos > 0 ) {
|
if ( distinctStartPos > 0 ) {
|
||||||
// Place TOP after DISTINCT.
|
// Place TOP after DISTINCT.
|
||||||
sql.insert( distinctStartPos + DISTINCT.length(), " TOP(?)" );
|
sql.insert( distinctStartPos + DISTINCT.length(), " TOP(?)" );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final int selectStartPos = shallowIndexOf( sql, SELECT_WITH_SPACE, 0 );
|
final int selectStartPos = shallowIndexOfPattern( sql, SELECT_PATTERN, 0 );
|
||||||
// Place TOP after SELECT.
|
// Place TOP after SELECT.
|
||||||
sql.insert( selectStartPos + SELECT.length(), " TOP(?)" );
|
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
|
* Returns index of the first case-insensitive match of search pattern that is not
|
||||||
* that is not enclosed in parentheses.
|
* enclosed in parenthesis.
|
||||||
*
|
*
|
||||||
* @param sb String to search.
|
* @param sb String to search.
|
||||||
* @param search Search term.
|
* @param pattern Compiled search pattern.
|
||||||
* @param fromIndex The index from which to start the search.
|
* @param fromIndex The index from which to start the search.
|
||||||
*
|
*
|
||||||
* @return Position of the first match, or {@literal -1} if not found.
|
* @return Position of the first match, or {@literal -1} if not found.
|
||||||
*/
|
*/
|
||||||
private static int shallowIndexOfWord(final StringBuilder sb, final String search, int fromIndex) {
|
private static int shallowIndexOfPattern(final StringBuilder sb, final Pattern pattern, int fromIndex) {
|
||||||
final int index = shallowIndexOf( sb, ' ' + search + ' ', fromIndex );
|
int index = -1;
|
||||||
// In case of match adding one because of space placed in front of search term.
|
final String matchString = sb.toString();
|
||||||
return index != -1 ? ( index + 1 ) : -1;
|
|
||||||
|
// 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 pattern String search pattern.
|
||||||
* @param search Search term.
|
* @return Compiled {@link 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 shallowIndexOf(StringBuilder sb, String search, int fromIndex) {
|
private static Pattern buildShallowIndexPattern(String pattern) {
|
||||||
// case-insensitive match
|
return Pattern.compile( "(\\b" + pattern + ")(?![^\\(]*\\))", Pattern.CASE_INSENSITIVE );
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,45 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase {
|
||||||
String input = "select distinct f1 as f53245 from table849752 order by f234, f67 desc";
|
String input = "select distinct f1 as f53245 from table849752 order by f234, f67 desc";
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"with query as (select inner_query.*, row_number() over (order by current_timestamp) as __hibernate_row_nr__ from ( " +
|
"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 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__ < ?",
|
" select f53245 from query where __hibernate_row_nr__ >= ? and __hibernate_row_nr__ < ?",
|
||||||
dialect.getLimitHandler().processSql( input, toRowSelection( 10, 15 ) ).toLowerCase(Locale.ROOT) );
|
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
|
@Test
|
||||||
|
@ -159,9 +194,9 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase {
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " +
|
"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_ " +
|
"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 ) " +
|
"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 col_0_0_, col_1_0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?",
|
||||||
dialect.getLimitHandler().processSql( query, toRowSelection( 1, 3 ) )
|
dialect.getLimitHandler().processSql( query, toRowSelection( 1, 3 ) )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue