diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java index 1ec2464ac7..99447e7623 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2005LimitHandler.java @@ -28,13 +28,16 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { 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( "," ); + final String SELECT_DISTINCT_SPACE = "select distinct "; + final String SELECT_SPACE = "select "; - private static final Pattern ALIAS_PATTERN = Pattern.compile( "(?i)\\sas\\s(.)+$" ); + private static final Pattern SELECT_PATTERN = buildShallowIndexPattern( SELECT + "(.*)", true ); + private static final Pattern FROM_PATTERN = buildShallowIndexPattern( FROM, true ); + private static final Pattern DISTINCT_PATTERN = buildShallowIndexPattern( DISTINCT, true ); + private static final Pattern ORDER_BY_PATTERN = buildShallowIndexPattern( ORDER_BY, true ); + private static final Pattern COMMA_PATTERN = buildShallowIndexPattern( ",", false ); + private static final Pattern ALIAS_PATTERN = + Pattern.compile( "\\S+\\s*(\\s(?i)as\\s)\\s*(\\S+)*\\s*$|\\s+(\\S+)$" ); // Flag indicating whether TOP(?) expression has been added to the original query. private boolean topAdded; @@ -146,7 +149,7 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { protected String fillAliasInSelectClause(StringBuilder sb) { final String separator = System.lineSeparator(); final List aliases = new LinkedList(); - final int startPos = shallowIndexOfPattern( sb, SELECT_PATTERN, 0 ); + final int startPos = getSelectColumnsStartPosition( sb ); int endPos = shallowIndexOfPattern( sb, FROM_PATTERN, startPos ); int nextComa = startPos; @@ -203,6 +206,35 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { return selectsMultipleColumns ? "*" : StringHelper.join( ", ", aliases.iterator() ); } + /** + * Get the start position for where the column list begins. + * + * @param sb the string builder sql. + * @return the start position where the column list begins. + */ + private int getSelectColumnsStartPosition(StringBuilder sb) { + final int startPos = getSelectStartPosition( sb ); + // adjustment for 'select distinct ' and 'select '. + final String sql = sb.toString().substring( startPos ).toLowerCase(); + if ( sql.startsWith( SELECT_DISTINCT_SPACE ) ) { + return ( startPos + SELECT_DISTINCT_SPACE.length() ); + } + else if ( sql.startsWith( SELECT_SPACE ) ) { + return ( startPos + SELECT_SPACE.length() ); + } + return startPos; + } + + /** + * Get the select start position. + * + * @param sb the string builder sql. + * @return the position where {@code select} is found. + */ + private int getSelectStartPosition(StringBuilder sb) { + return shallowIndexOfPattern( sb, SELECT_PATTERN, 0 ); + } + /** * @param expression Select expression. * @@ -222,11 +254,17 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { * @return Column alias. */ private String getAlias(String expression) { + // remove any function arguments, if any exist. + // 'cast(tab1.col1 as varchar(255)) as col1' -> 'cast as col1' + // 'cast(tab1.col1 as varchar(255)) col1 -> 'cast col1' + // 'cast(tab1.col1 as varchar(255))' -> 'cast' + expression = expression.replaceFirst( "(\\((.)*\\))", "" ).trim(); + + // This will match any text provided with: + // columnName [[as] alias] final Matcher matcher = ALIAS_PATTERN.matcher( expression ); - if ( matcher.find() ) { - // Taking advantage of Java regular expressions greedy behavior while extracting the last AS keyword. - // Note that AS keyword can appear in CAST operator, e.g. 'cast(tab1.col1 as varchar(255)) as col1'. - return matcher.group( 0 ).replaceFirst( "(?i)(.)*\\sas\\s", "" ).trim(); + if ( matcher.find() && matcher.groupCount() > 1 ) { + return matcher.group( 1 ) != null ? matcher.group( 2 ) : matcher.group( 3 ); } return null; } @@ -294,9 +332,16 @@ public class SQLServer2005LimitHandler extends AbstractLimitHandler { * based on the search pattern that is not enclosed in parenthesis. * * @param pattern String search pattern. + * @param wordBoundardy whether to apply a word boundary restriction. * @return Compiled {@link Pattern}. */ - private static Pattern buildShallowIndexPattern(String pattern) { - return Pattern.compile( "(\\b" + pattern + ")(?![^\\(]*\\))", Pattern.CASE_INSENSITIVE ); + private static Pattern buildShallowIndexPattern(String pattern, boolean wordBoundardy) { + return Pattern.compile( + "(" + + ( wordBoundardy ? "\\b" : "" ) + + pattern + + ")(?![^\\(]*\\))", + Pattern.CASE_INSENSITIVE + ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java index c519b82d3f..38b6ba4846 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertEquals; * * @author Valotasion Yoryos * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + * @author Chris Cranford */ public class SQLServer2005DialectTestCase extends BaseUnitTestCase { private SQLServer2005Dialect dialect; @@ -115,6 +116,18 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase { ); } + @Test + @TestForIssue(jiraKey = "HHH-10994") + public void testGetLimitStringAliasGenerationWithAliasesNoAs() { + final String aliasedSQLNoAs = "select column1 c1, column c2, column c3, column c4 from table1"; + assertEquals( + "WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( " + + "select column1 c1, column c2, column c3, column c4 from table1 ) inner_query ) " + + "SELECT c1, c2, c3, c4 FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( aliasedSQLNoAs, toRowSelection( 3, 5 ) ) + ); + } + @Test @TestForIssue(jiraKey = "HHH-7019") public void testGetLimitStringWithSubselect() { @@ -165,6 +178,32 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase { ); } + @Test + @TestForIssue(jiraKey = "HHH-10994") + public void testGetLimitStringDistinctWithinAggregationWithoutAlias() { + final String distinctInAggregateSQL = "select aggregate_function(distinct p.n) from table849752 p order by f1"; + + assertEquals( + "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 page0_ from table849752 p order by f1 ) inner_query ) " + + "SELECT page0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( distinctInAggregateSQL, toRowSelection( 2, 5 ) ) + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-10994") + public void testGetLimitStringDistinctWithinAggregationWithAliasNoAs() { + final String distinctInAggregateSQL = "select aggregate_function(distinct p.n) f1 from table849752 p order by f1"; + + assertEquals( + "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) f1 from table849752 p order by f1 ) inner_query ) " + + "SELECT f1 FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( distinctInAggregateSQL, toRowSelection( 2, 5 ) ) + ); + } + @Test @TestForIssue(jiraKey = "HHH-7370") public void testGetLimitStringWithMaxOnly() { @@ -202,6 +241,36 @@ public class SQLServer2005DialectTestCase extends BaseUnitTestCase { ); } + @Test + @TestForIssue(jiraKey = "HHH-10994") + public void testGetLimitStringWithCastOperatorWithAliasNoAs() { + final String query = "select cast(lc302_doku6_.redniBrojStavke as varchar(255)) f1, lc302_doku6_.dokumentiID f2 " + + "from LC302_Dokumenti lc302_doku6_ order by lc302_doku6_.dokumentiID DESC"; + + 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)) f1, lc302_doku6_.dokumentiID f2 " + + "from LC302_Dokumenti lc302_doku6_ order by lc302_doku6_.dokumentiID DESC ) inner_query ) " + + "SELECT f1, f2 FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( query, toRowSelection( 1, 3 ) ) + ); + } + + @Test + @TestForIssue(jiraKey = "HHH-10994") + public void testGetLimitStringWithCastOperatorWithoutAliases() { + final String query = "select cast(lc302_doku6_.redniBrojStavke as varchar(255)), lc302_doku6_.dokumentiID " + + "from LC302_Dokumenti lc302_doku6_ order by lc302_doku6_.dokumentiID DESC"; + + 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 page0_, lc302_doku6_.dokumentiID as page1_ " + + "from LC302_Dokumenti lc302_doku6_ order by lc302_doku6_.dokumentiID DESC ) inner_query ) " + + "SELECT page0_, page1_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?", + dialect.getLimitHandler().processSql( query, toRowSelection( 1, 3 ) ) + ); + } + @Test @TestForIssue(jiraKey = "HHH-8007") public void testGetLimitStringSelectingMultipleColumnsFromSeveralTables() {