diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2005Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2005Dialect.java new file mode 100644 index 0000000000..c959530f74 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2005Dialect.java @@ -0,0 +1,173 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.dialect; + +import org.hibernate.dialect.function.NoArgSQLFunction; +import org.hibernate.type.StandardBasicTypes; + +/** + * A dialect for Microsoft SQL 2005 + * + * @author Yoryos Valotasios + */ +public class SQLServer2005Dialect extends SQLServerDialect { + private static final String SELECT = "select"; + private static final String FROM = "from"; + private static final String DISTINCT = "distinct"; + + public SQLServer2005Dialect() { + registerFunction("row_number", new NoArgSQLFunction("row_number", StandardBasicTypes.INTEGER, true)); + } + + /** + * Add a LIMIT clause to the given SQL SELECT (HHH-2655: ROW_NUMBER for Paging) + * + * The LIMIT SQL will look like: + * + *
+	 * WITH query AS (
+	 *   SELECT ROW_NUMBER() OVER (ORDER BY orderby) as __hibernate_row_nr__, 
+	 *   original_query_without_orderby
+	 * )
+	 * SELECT * FROM query WHERE __hibernate_row_nr__ BEETWIN offset AND offset + last
+	 * 
+ * + * + * @param querySqlString + * The SQL statement to base the limit query off of. + * @param offset + * Offset of the first row to be returned by the query (zero-based) + * @param limit + * Maximum number of rows to be returned by the query + * + * @return A new SQL statement with the LIMIT clause applied. + */ + @Override + public String getLimitString(String querySqlString, int offset, int limit) { + StringBuilder sb = new StringBuilder(querySqlString.trim().toLowerCase()); + + int orderByIndex = sb.indexOf("order by"); + 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__ BETWEEN ").append(offset + 1).append(" AND ").append(limit); + + 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 equivelant simple select with a group by clause. See + * {@link SQLServer2005DialectTestCase#testReplaceDistinctWithGroupBy()} + * + * @param an + * sql query + */ + protected static void replaceDistinctWithGroupBy(StringBuilder sql) { + int distinctIndex = sql.indexOf(DISTINCT); + if (distinctIndex > 0) { + sql.delete(distinctIndex, distinctIndex + DISTINCT.length() + 1); + sql.append(" group by").append(getSelectFieldsWithoutAliases(sql)); + } + } + + /** + * This utility method searches the given sql query for the fields of the select statement and returns them without + * the aliases. See {@link SQLServer2005DialectTestCase#testGetSelectFieldsWithoutAliases()} + * + * @param an + * sql query + * @return the fields of the select statement without their alias + */ + protected static CharSequence getSelectFieldsWithoutAliases(StringBuilder sql) { + String select = sql.substring(sql.indexOf(SELECT) + SELECT.length(), sql.indexOf(FROM)); + + // Strip the as clauses + return stripAliases(select); + } + + /** + * Utility method that strips the aliases. See {@link SQLServer2005DialectTestCase#testStripAliases()} + * + * @param a + * string to replace the as statements + * @return a string without the as statements + */ + protected static String stripAliases(String str) { + return str.replaceAll("\\sas[^,]+(,?)", "$1"); + } + + /** + * Right after the select statement of a given query we must place the row_number function + * + * @param sql + * the initial sql query without the order by clause + * @param orderby + * the order by clause of the query + */ + protected static void insertRowNumberFunction(StringBuilder sql, CharSequence orderby) { + // Find the end of the select statement + int selectEndIndex = sql.indexOf(SELECT) + SELECT.length(); + + // Isert after the select statement the row_number() function: + sql.insert(selectEndIndex, " ROW_NUMBER() OVER (" + orderby + ") as __hibernate_row_nr__,"); + } + + @Override + public boolean supportsLimit() { + return true; + } + + @Override + public boolean useMaxForLimit() { + return true; + } + + @Override + public boolean supportsLimitOffset() { + return true; + } + + @Override + public boolean bindLimitParametersFirst() { + return false; + } + + public boolean supportsVariableLimit() { + return false; + } + +}