From 1d2342d0c53fcd7d5b1b54c09f25edfc77a8e1dd Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 1 Sep 2017 15:28:57 -0700 Subject: [PATCH 1/4] HHH-11957 : DB2 substring method needs to be exposed in DB297Dialect (cherry picked from commit 385a18b11b0a7bbe6ec4530184e2ae31e9f6cdf7) --- .../test/hql/DB297SubStringFunctionsTest.java | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/DB297SubStringFunctionsTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/DB297SubStringFunctionsTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/DB297SubStringFunctionsTest.java new file mode 100644 index 000000000000..74086582c0b0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/DB297SubStringFunctionsTest.java @@ -0,0 +1,177 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.hql; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.PersistenceException; + +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.DB297Dialect; +import org.hibernate.exception.SQLGrammarException; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * DB2 has 2 functions for getting a substring: "substr" and "substring" + * + * @author Gail Badner + */ +@RequiresDialect(DB297Dialect.class) +public class DB297SubStringFunctionsTest extends BaseCoreFunctionalTestCase { + private static final MostRecentStatementInspector mostRecentStatementInspector = new MostRecentStatementInspector(); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + AnEntity.class + }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( Environment.STATEMENT_INSPECTOR, mostRecentStatementInspector ); + } + + @Before + public void setup() { + AnEntity anEntity = new AnEntity(); + anEntity.description = "A very long, boring description."; + + doInHibernate( + this::sessionFactory, session -> { + session.persist( anEntity ); + } + ); + } + + @After + public void cleanup() { + doInHibernate( + this::sessionFactory, session -> { + session.createQuery( "delete from AnEntity" ).executeUpdate(); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-11957") + public void testSubstringWithStringUnits() { + + mostRecentStatementInspector.clear(); + + doInHibernate( + this::sessionFactory, session -> { + String value = session.createQuery( + "select substring( e.description, 21, 11, octets ) from AnEntity e", + String.class + ).uniqueResult(); + assertEquals( "description", value ); + } + ); + + assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "substring(" ) ); + assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "octets" ) ); + } + + @Test + @TestForIssue( jiraKey = "HHH-11957") + public void testSubstringWithoutStringUnits() { + + mostRecentStatementInspector.clear(); + + doInHibernate( + this::sessionFactory, session -> { + String value = session.createQuery( + "select substring( e.description, 21, 11 ) from AnEntity e", + String.class + ).uniqueResult(); + assertEquals( "description", value ); + } + ); + assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "substr(" ) ); + } + + @Test + @TestForIssue( jiraKey = "HHH-11957") + public void testSubstrWithStringUnits() { + + mostRecentStatementInspector.clear(); + + try { + doInHibernate( + this::sessionFactory, session -> { + String value = session.createQuery( + "select substr( e.description, 21, 11, octets ) from AnEntity e", + String.class + ).uniqueResult(); + assertEquals( "description", value ); + } + ); + fail( "Should have failed because substr cannot be used with string units." ); + } + catch (PersistenceException expected) { + assertTrue( SQLGrammarException.class.isInstance( expected.getCause() ) ); + } + + assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "substr(" ) ); + assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "octets" ) ); + } + + @Test + @TestForIssue( jiraKey = "HHH-11957") + public void testSubstrWithoutStringUnits() { + + mostRecentStatementInspector.clear(); + + doInHibernate( + this::sessionFactory, session -> { + String value = session.createQuery( + "select substr( e.description, 21, 11 ) from AnEntity e", + String.class + ).uniqueResult(); + assertEquals( "description", value ); + } + ); + assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "substr(" ) ); + } + + @Entity(name="AnEntity") + public static class AnEntity { + @Id + @GeneratedValue + private long id; + private String description; + } + + private static class MostRecentStatementInspector implements StatementInspector { + private String mostRecentSql; + + public String inspect(String sql) { + mostRecentSql = sql; + return sql; + } + private void clear() { + mostRecentSql = null; + } + } +} \ No newline at end of file From 2fa8dc4518f38dbe010b4d02fbe54ecf56e8a338 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 1 Sep 2017 15:57:03 -0700 Subject: [PATCH 2/4] HHH-11957 : Fix test case to work pre-5.2 (cherry picked from commit 57e63081b783156b9ffe1235e0fadfc19258f13b) --- .../test/hql/DB297SubStringFunctionsTest.java | 93 +++++++++++-------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/DB297SubStringFunctionsTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/DB297SubStringFunctionsTest.java index 74086582c0b0..6a83bf8f1848 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/DB297SubStringFunctionsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/DB297SubStringFunctionsTest.java @@ -11,6 +11,7 @@ import javax.persistence.Id; import javax.persistence.PersistenceException; +import org.hibernate.Session; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.dialect.DB297Dialect; @@ -25,7 +26,6 @@ import org.junit.Before; import org.junit.Test; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -57,20 +57,24 @@ public void setup() { AnEntity anEntity = new AnEntity(); anEntity.description = "A very long, boring description."; - doInHibernate( - this::sessionFactory, session -> { - session.persist( anEntity ); - } - ); + Session session = openSession(); + session.beginTransaction(); + { + session.persist( anEntity ); + } + session.getTransaction().commit(); + session.close(); } @After public void cleanup() { - doInHibernate( - this::sessionFactory, session -> { + Session session = openSession(); + session.beginTransaction(); + { session.createQuery( "delete from AnEntity" ).executeUpdate(); - } - ); + } + session.getTransaction().commit(); + session.close(); } @Test @@ -79,15 +83,16 @@ public void testSubstringWithStringUnits() { mostRecentStatementInspector.clear(); - doInHibernate( - this::sessionFactory, session -> { - String value = session.createQuery( - "select substring( e.description, 21, 11, octets ) from AnEntity e", - String.class + Session session = openSession(); + session.beginTransaction(); + { + String value = (String) session.createQuery( + "select substring( e.description, 21, 11, octets ) from AnEntity e" ).uniqueResult(); assertEquals( "description", value ); - } - ); + } + session.getTransaction().commit(); + session.close(); assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "substring(" ) ); assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "octets" ) ); @@ -99,15 +104,17 @@ public void testSubstringWithoutStringUnits() { mostRecentStatementInspector.clear(); - doInHibernate( - this::sessionFactory, session -> { - String value = session.createQuery( - "select substring( e.description, 21, 11 ) from AnEntity e", - String.class + Session session = openSession(); + session.beginTransaction(); + { + String value = (String) session.createQuery( + "select substring( e.description, 21, 11 ) from AnEntity e" ).uniqueResult(); assertEquals( "description", value ); - } - ); + } + session.getTransaction().commit(); + session.close(); + assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "substr(" ) ); } @@ -117,20 +124,22 @@ public void testSubstrWithStringUnits() { mostRecentStatementInspector.clear(); + Session session = openSession(); + session.beginTransaction(); + try { - doInHibernate( - this::sessionFactory, session -> { - String value = session.createQuery( - "select substr( e.description, 21, 11, octets ) from AnEntity e", - String.class + String value = (String) session.createQuery( + "select substr( e.description, 21, 11, octets ) from AnEntity e" ).uniqueResult(); assertEquals( "description", value ); - } - ); - fail( "Should have failed because substr cannot be used with string units." ); + fail( "Should have failed because substr cannot be used with string units." ); + } + catch (SQLGrammarException expected) { + // expected } - catch (PersistenceException expected) { - assertTrue( SQLGrammarException.class.isInstance( expected.getCause() ) ); + finally { + session.getTransaction().rollback(); + session.close(); } assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "substr(" ) ); @@ -143,15 +152,17 @@ public void testSubstrWithoutStringUnits() { mostRecentStatementInspector.clear(); - doInHibernate( - this::sessionFactory, session -> { - String value = session.createQuery( - "select substr( e.description, 21, 11 ) from AnEntity e", - String.class + Session session = openSession(); + session.beginTransaction(); + { + String value = (String) session.createQuery( + "select substr( e.description, 21, 11 ) from AnEntity e" ).uniqueResult(); assertEquals( "description", value ); - } - ); + } + session.getTransaction().commit(); + session.close(); + assertTrue( mostRecentStatementInspector.mostRecentSql.contains( "substr(" ) ); } From bbf3418c854195c18dd13f2d0820b3df3f836196 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 1 Sep 2017 15:34:30 -0700 Subject: [PATCH 3/4] HHH-11957 : DB2 substring method needs to be exposed in DB297Dialect (cherry picked from commit 80937ee5839891cc08ef617cbf3fc031548e34e6) --- .../org/hibernate/dialect/DB297Dialect.java | 8 +++ .../function/DB2SubstringFunction.java | 51 +++++++++++++++++++ .../dialect/function/StandardSQLFunction.java | 6 ++- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java index fd80bc4eeb2b..314f1eba2d54 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB297Dialect.java @@ -6,11 +6,14 @@ */ package org.hibernate.dialect; +import org.hibernate.dialect.function.DB2SubstringFunction; +import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.hql.spi.id.IdTableSupportStandardImpl; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; import org.hibernate.hql.spi.id.local.AfterUseAction; import org.hibernate.hql.spi.id.local.LocalTemporaryTableBulkIdStrategy; +import org.hibernate.type.StandardBasicTypes; /** * An SQL dialect for DB2 9.7. @@ -19,6 +22,11 @@ */ public class DB297Dialect extends DB2Dialect { + public DB297Dialect() { + super(); + registerFunction( "substring", new DB2SubstringFunction() ); + } + @Override public String getCrossJoinSeparator() { // DB2 9.7 and later support "cross join" diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java new file mode 100644 index 000000000000..fd9c5033aabe --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java @@ -0,0 +1,51 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.dialect.function; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.type.StandardBasicTypes; + +/** + * When "substring" function is used for DB2, this implementation of {@link StandardSQLFunction} + * will render "substr" or "substring", depending on the last argument being used. If the last + * argument is a string unit ("CODEUNITS16", "CODEUNITS32", or "OCTETS"), then the function + * will be rendered as "substring"; otherwise, it will be rendered as "substr". + *

+ * ANSI SQL-92 standard defines "substring" without string units, which is more similar to DB2's "substr", + * so it makes sense to use DB2's "substr" function when string units are not provided. + *

+ * Background: DB2 has both "substr" and "substring", which are different functions that are not + * interchangeable. Prior to DB2 11.1, DB2's "substring" function requires an argument for string + * units; without this argument, DB2 throws an exception. DB2's "substr" function throws an exception + * if string unit is provided as an argument. + * + * @author Gail Badner + */ +public class DB2SubstringFunction extends StandardSQLFunction { + private static final Set possibleStringUnits = new HashSet<>( + Arrays.asList( "CODEUNITS16", "CODEUNITS32", "OCTETS" ) + ); + + public DB2SubstringFunction() { + super( "substring", StandardBasicTypes.STRING ); + } + + @Override + protected String getRenderedName(List arguments) { + final String lastArgument = (String) arguments.get( arguments.size() - 1 ); + if ( lastArgument != null && possibleStringUnits.contains( lastArgument.toUpperCase() ) ) { + return getName(); + } + else{ + return "substr"; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java index d3fd714b9d51..bdd98d3a39b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/StandardSQLFunction.java @@ -86,7 +86,7 @@ public Type getReturnType(Type firstArgumentType, Mapping mapping) { @Override public String render(Type firstArgumentType, List arguments, SessionFactoryImplementor sessionFactory) { final StringBuilder buf = new StringBuilder(); - buf.append( name ).append( '(' ); + buf.append( getRenderedName( arguments) ).append( '(' ); for ( int i = 0; i < arguments.size(); i++ ) { buf.append( arguments.get( i ) ); if ( i < arguments.size() - 1 ) { @@ -96,6 +96,10 @@ public String render(Type firstArgumentType, List arguments, SessionFactoryImple return buf.append( ')' ).toString(); } + protected String getRenderedName(List arguments) { + return getName(); + } + @Override public String toString() { return name; From c3dbd70a79920b5dd1c034ccd26d24bb70eaddfb Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 1 Sep 2017 16:19:31 -0700 Subject: [PATCH 4/4] HHH-11957 : Fix to work with jdk 1.6 source (cherry picked from commit 2287bdd5e09b804a57e49753eb1a0ea547c6526d) --- .../org/hibernate/dialect/function/DB2SubstringFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java index fd9c5033aabe..787ad14a6c4d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2SubstringFunction.java @@ -30,7 +30,7 @@ * @author Gail Badner */ public class DB2SubstringFunction extends StandardSQLFunction { - private static final Set possibleStringUnits = new HashSet<>( + private static final Set possibleStringUnits = new HashSet( Arrays.asList( "CODEUNITS16", "CODEUNITS32", "OCTETS" ) );