diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index fdf7a3ad3d..70658c749e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -99,7 +99,7 @@ public abstract class Dialect { private final Properties properties = new Properties(); private final Map sqlFunctions = new HashMap(); - private final Set sqlKeywords = new HashSet(); + private final Set sqlKeywords = new HashSet(); // constructors and factory methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -197,7 +197,7 @@ public abstract class Dialect { throw new HibernateException( "Dialect class not found: " + dialectName ); } catch ( Exception e ) { - throw new HibernateException( "Could not instantiate dialect class", e ); + throw new HibernateException( "Could not instantiate given dialect class: " + dialectName, e ); } } @@ -373,7 +373,7 @@ public abstract class Dialect { * * @return The map of registered functions. */ - public final Map getFunctions() { + public final Map getFunctions() { return sqlFunctions; } @@ -384,7 +384,7 @@ public abstract class Dialect { sqlKeywords.add(word); } - public Set getKeywords() { + public Set getKeywords() { return sqlKeywords; } @@ -481,7 +481,7 @@ public abstract class Dialect { * @throws MappingException If IDENTITY generation is not supported. */ protected String getIdentitySelectString() throws MappingException { - throw new MappingException( "Dialect does not support identity key generation" ); + throw new MappingException( getClass().getName() + " does not support identity key generation" ); } /** @@ -503,7 +503,7 @@ public abstract class Dialect { * @throws MappingException If IDENTITY generation is not supported. */ protected String getIdentityColumnString() throws MappingException { - throw new MappingException( "Dialect does not support identity key generation" ); + throw new MappingException( getClass().getName() + " does not support identity key generation" ); } /** @@ -551,7 +551,7 @@ public abstract class Dialect { * @throws MappingException If sequences are not supported. */ public String getSequenceNextValString(String sequenceName) throws MappingException { - throw new MappingException( "Dialect does not support sequences" ); + throw new MappingException( getClass().getName() + " does not support sequences" ); } /** @@ -566,7 +566,7 @@ public abstract class Dialect { * @throws MappingException If sequences are not supported. */ public String getSelectSequenceNextValString(String sequenceName) throws MappingException { - throw new MappingException( "Dialect does not support sequences" ); + throw new MappingException( getClass().getName() + " does not support sequences" ); } /** @@ -609,7 +609,7 @@ public abstract class Dialect { * @throws MappingException If sequences are not supported. */ protected String getCreateSequenceString(String sequenceName) throws MappingException { - throw new MappingException( "Dialect does not support sequences" ); + throw new MappingException( getClass().getName() + " does not support sequences" ); } /** @@ -633,7 +633,7 @@ public abstract class Dialect { if ( supportsPooledSequences() ) { return getCreateSequenceString( sequenceName ) + " start with " + initialValue + " increment by " + incrementSize; } - throw new MappingException( "Dialect does not support pooled sequences" ); + throw new MappingException( getClass().getName() + " does not support pooled sequences" ); } /** @@ -662,7 +662,7 @@ public abstract class Dialect { * @throws MappingException If sequences are not supported. */ protected String getDropSequenceString(String sequenceName) throws MappingException { - throw new MappingException( "Dialect does not support sequences" ); + throw new MappingException( getClass().getName() + " does not support sequences" ); } /** @@ -686,7 +686,7 @@ public abstract class Dialect { * @return The appropriate command. */ public String getSelectGUIDString() { - throw new UnsupportedOperationException( "dialect does not support GUIDs" ); + throw new UnsupportedOperationException( getClass().getName() + " does not support GUIDs" ); } @@ -802,7 +802,7 @@ public abstract class Dialect { * @return the modified SQL */ protected String getLimitString(String query, boolean hasOffset) { - throw new UnsupportedOperationException( "paged queries not supported" ); + throw new UnsupportedOperationException( "Paged queries not supported by " + getClass().getName()); } /** @@ -1502,7 +1502,7 @@ public abstract class Dialect { * @return The "add column" fragment. */ public String getAddColumnString() { - throw new UnsupportedOperationException( "No add column syntax supported by Dialect" ); + throw new UnsupportedOperationException( "No add column syntax supported by " + getClass().getName() ); } public String getDropForeignKeyString() { 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..dda5008696 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2005Dialect.java @@ -0,0 +1,203 @@ +/* + * 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 java.sql.Types; + +import org.hibernate.dialect.function.NoArgSQLFunction; +import org.hibernate.type.StandardBasicTypes; + +/** + * A dialect for Microsoft SQL 2005. (HHH-3936 fix) + * + * @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"; + private static final int MAX_LENGTH = 8000; + + public SQLServer2005Dialect() { + // HHH-3965 fix + // As per http://www.sql-server-helper.com/faq/sql-server-2005-varchar-max-p01.aspx + // use varchar(max) and varbinary(max) instead of TEXT and IMAGE types + registerColumnType( Types.BLOB, "varbinary(MAX)" ); + registerColumnType( Types.VARBINARY, "varbinary(MAX)" ); + registerColumnType( Types.VARBINARY, MAX_LENGTH, "varbinary($l)" ); + registerColumnType( Types.LONGVARBINARY, "varbinary(MAX)" ); + + registerColumnType( Types.CLOB, "varchar(MAX)" ); + registerColumnType( Types.LONGVARCHAR, "varchar(MAX)" ); + registerColumnType( Types.VARCHAR, "varchar(MAX)" ); + registerColumnType( Types.VARCHAR, MAX_LENGTH, "varchar($l)" ); + + + registerFunction("row_number", new NoArgSQLFunction("row_number", StandardBasicTypes.INTEGER, true)); + } + + @Override + public boolean supportsLimit() { + return true; + } + + @Override + public boolean useMaxForLimit() { + return true; + } + + @Override + public boolean supportsLimitOffset() { + return true; + } + + @Override + public boolean bindLimitParametersFirst() { + return false; + } + + @Override + public boolean supportsVariableLimit() { + return true; + } + + @Override + public int convertToFirstRowValue(int zeroBasedFirstResult) { + // Our dialect paginated results aren't zero based. The first row should get the number 1 and so on + return zeroBasedFirstResult + 1; + } + + @Override + public String getLimitString(String query, int offset, int limit) { + // We transform the query to one with an offset and limit if we have an offset and limit to bind + if (offset > 1 || limit > 1) return getLimitString(query, true); + return query; + } + + /** + * 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, boolean hasOffset) { + 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 ? AND ?"); + + 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__,"); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2008Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2008Dialect.java index fcf26c89fb..022c44826a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2008Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2008Dialect.java @@ -33,7 +33,7 @@ import org.hibernate.type.StandardBasicTypes; * * @author Gavin King */ -public class SQLServer2008Dialect extends SQLServerDialect { +public class SQLServer2008Dialect extends SQLServer2005Dialect { public SQLServer2008Dialect() { registerColumnType( Types.DATE, "date" ); registerColumnType( Types.TIME, "time" ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index 1a517a6dd7..d456c2d688 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -26,9 +26,9 @@ package org.hibernate.dialect; import java.sql.Types; import org.hibernate.LockMode; -import org.hibernate.dialect.function.AnsiTrimEmulationFunction; import org.hibernate.dialect.function.SQLFunctionTemplate; import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.dialect.function.AnsiTrimEmulationFunction; import org.hibernate.type.StandardBasicTypes; /** @@ -37,11 +37,7 @@ import org.hibernate.type.StandardBasicTypes; * @author Gavin King */ public class SQLServerDialect extends AbstractTransactSQLDialect { - private static final String SELECT = "select"; - private static final String FROM = "from"; - private static final String DISTINCT = "distinct"; - - + public SQLServerDialect() { registerColumnType( Types.VARBINARY, "image" ); registerColumnType( Types.VARBINARY, 8000, "varbinary($l)" ); @@ -72,103 +68,14 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { return selectIndex + ( selectDistinctIndex == selectIndex ? 15 : 6 ); } - /** - * 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) { - if ( offset == 0 ) { - return new StringBuffer( querySqlString.length() + 8 ).append( querySqlString ) - .insert( getAfterSelectInsertPoint( querySqlString ), " top " + limit ) - .toString(); + public String getLimitString(String querySelect, int offset, int limit) { + if ( offset > 0 ) { + throw new UnsupportedOperationException( "query result offset is not supported" ); } - - 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 SQLServerDialectTestCase#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 SQLServerDialectTestCase#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 SQLServerDialectTestCase#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__," ); + return new StringBuffer( querySelect.length() + 8 ) + .append( querySelect ) + .insert( getAfterSelectInsertPoint( querySelect ), " top " + limit ) + .toString(); } /** @@ -187,11 +94,6 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { } public boolean supportsLimitOffset() { - return true; - } - - @Override - public boolean bindLimitParametersFirst() { return false; } @@ -209,9 +111,9 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { public String appendLockHint(LockMode mode, String tableName) { if ( ( mode == LockMode.UPGRADE ) || - ( mode == LockMode.UPGRADE_NOWAIT ) || - ( mode == LockMode.PESSIMISTIC_WRITE ) || - ( mode == LockMode.WRITE ) ) { + ( mode == LockMode.UPGRADE_NOWAIT ) || + ( mode == LockMode.PESSIMISTIC_WRITE ) || + ( mode == LockMode.WRITE ) ) { return tableName + " with (updlock, rowlock)"; } else if ( mode == LockMode.PESSIMISTIC_READ ) { @@ -224,7 +126,6 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { // The current_timestamp is more accurate, but only known to be supported // in SQL Server 7.0 and later (i.e., Sybase not known to support it at all) - public String getCurrentTimestampSelectString() { return "select current_timestamp"; } @@ -260,3 +161,4 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { } } + diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java b/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java index fcf5454b6a..f5d06fa3d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/TypeNames.java @@ -26,7 +26,6 @@ package org.hibernate.dialect; import java.util.Map; import java.util.HashMap; import java.util.TreeMap; -import java.util.Iterator; import org.hibernate.MappingException; import org.hibernate.util.StringHelper; @@ -65,8 +64,8 @@ import org.hibernate.util.StringHelper; */ public class TypeNames { - private HashMap weighted = new HashMap(); - private HashMap defaults = new HashMap(); + private Map> weighted = new HashMap>(); + private Map defaults = new HashMap(); /** * get default type name for specified type @@ -74,7 +73,7 @@ public class TypeNames { * @return the default type name associated with specified key */ public String get(int typecode) throws MappingException { - String result = (String) defaults.get( new Integer(typecode) ); + String result = defaults.get( new Integer(typecode) ); if (result==null) throw new MappingException("No Dialect mapping for JDBC type: " + typecode); return result; } @@ -89,12 +88,10 @@ public class TypeNames { * if available and the default type name otherwise */ public String get(int typecode, int size, int precision, int scale) throws MappingException { - Map map = (Map) weighted.get( new Integer(typecode) ); + Map map = weighted.get( new Integer(typecode) ); if ( map!=null && map.size()>0 ) { // iterate entries ordered by capacity to find first fit - Iterator entries = map.entrySet().iterator(); - while ( entries.hasNext() ) { - Map.Entry entry = (Map.Entry)entries.next(); + for (Map.Entry entry: map.entrySet()) { if ( size <= ( (Integer) entry.getKey() ).intValue() ) { return replace( (String) entry.getValue(), size, precision, scale ); } @@ -114,9 +111,9 @@ public class TypeNames { * @param typecode the type key */ public void put(int typecode, int capacity, String value) { - TreeMap map = (TreeMap)weighted.get( new Integer(typecode) ); + Map map = weighted.get( new Integer(typecode) ); if (map == null) {// add new ordered map - map = new TreeMap(); + map = new TreeMap(); weighted.put( new Integer(typecode), map ); } map.put(new Integer(capacity), value); diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServerDialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java similarity index 77% rename from hibernate-core/src/test/java/org/hibernate/dialect/SQLServerDialectTestCase.java rename to hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java index a14857ec1a..da0ac56af1 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServerDialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2005DialectTestCase.java @@ -8,17 +8,17 @@ import junit.framework.TestCase; * @author Valotasion Yoryos * */ -public class SQLServerDialectTestCase extends TestCase { +public class SQLServer2005DialectTestCase extends TestCase { public void testStripAliases() { String input = "some_field1 as f1, some_fild2 as f2, _field3 as f3 "; - assertEquals( "some_field1, some_fild2, _field3", SQLServerDialect.stripAliases(input) ); + assertEquals( "some_field1, some_fild2, _field3", SQLServer2005Dialect.stripAliases(input) ); } public void testGetSelectFieldsWithoutAliases() { StringBuilder input = new StringBuilder( "select some_field1 as f12, some_fild2 as f879, _field3 as _f24674_3 from...." ); - String output = SQLServerDialect.getSelectFieldsWithoutAliases( input ).toString(); + String output = SQLServer2005Dialect.getSelectFieldsWithoutAliases( input ).toString(); assertEquals( " some_field1, some_fild2, _field3", output ); } @@ -26,7 +26,7 @@ public class SQLServerDialectTestCase extends TestCase { public void testReplaceDistinctWithGroupBy() { StringBuilder input = new StringBuilder( "select distinct f1, f2 as ff, f3 from table where f1 = 5" ); - SQLServerDialect.replaceDistinctWithGroupBy( input ); + SQLServer2005Dialect.replaceDistinctWithGroupBy( input ); assertEquals( "select f1, f2 as ff, f3 from table where f1 = 5 group by f1, f2, f3 ", input.toString() ); } @@ -35,7 +35,7 @@ public class SQLServerDialectTestCase extends TestCase { public void testGetLimitString() { String input = "select distinct f1 as f53245 from table849752 order by f234, f67 desc"; - SQLServerDialect sqlDialect = new SQLServerDialect(); + Dialect sqlDialect = new SQLServer2005Dialect(); assertEquals( "with query as (select row_number() over (order by f234, f67 desc) as __hibernate_row_nr__, f1 as f53245 from table849752 group by f1) select * from query where __hibernate_row_nr__ between 11 and 15", sqlDialect.getLimitString(input, 10, 15).toLowerCase() ); }