From 310eb69b6ef38caae6a8abcc0f1fd0021518dd14 Mon Sep 17 00:00:00 2001 From: ageery Date: Tue, 31 Oct 2017 16:08:08 -0400 Subject: [PATCH] HHH-12072 - BasicFormatterImpl throws a NPE if native SQL begins with a parentheses --- .../jdbc/internal/BasicFormatterImpl.java | 4 ++ .../jdbc/util/BasicFormatterTest.java | 13 +++- .../hibernate/jdbc/util/DdlFormatterTest.java | 10 ++- .../query/NativeQueryWithParenthesesTest.java | 68 +++++++++++++++++++ 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/query/NativeQueryWithParenthesesTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java index 29e8e56ad4..8dc3a659b9 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java @@ -356,6 +356,10 @@ public class BasicFormatterImpl implements Formatter { } private static boolean isFunctionName(String tok) { + if ( tok == null || tok.length() == 0 ) { + return false; + } + final char begin = tok.charAt( 0 ); final boolean isIdentifier = Character.isJavaIdentifierStart( begin ) || '"' == begin; return isIdentifier && diff --git a/hibernate-core/src/test/java/org/hibernate/jdbc/util/BasicFormatterTest.java b/hibernate-core/src/test/java/org/hibernate/jdbc/util/BasicFormatterTest.java index 60eb93e583..bf47b46db2 100644 --- a/hibernate-core/src/test/java/org/hibernate/jdbc/util/BasicFormatterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jdbc/util/BasicFormatterTest.java @@ -13,6 +13,8 @@ import org.junit.Test; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.jboss.logging.Logger; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -22,6 +24,9 @@ import static org.junit.Assert.assertFalse; * @author Steve Ebersole */ public class BasicFormatterTest extends BaseUnitTestCase { + + private static final Logger log = Logger.getLogger( BasicFormatterTest.class ); + @Test public void testNoLoss() { assertNoLoss( "insert into Address (city, state, zip, \"from\") values (?, ?, ?, 'insert value')" ); @@ -43,6 +48,9 @@ public class BasicFormatterTest extends BaseUnitTestCase { assertNoLoss( "/* Here we' go! */ select case when p.age > 50 then 'old' when p.age > 18 then 'adult' else 'child' end from Person p where ( case when p.age > 50 then 'old' when p.age > 18 then 'adult' else 'child' end ) like ?" ); + assertNoLoss( + "(select p.pid from Address where city = 'Boston') union (select p.pid from Address where city = 'Taipei')" + ); } private void assertNoLoss(String query) { @@ -50,8 +58,9 @@ public class BasicFormatterTest extends BaseUnitTestCase { StringTokenizer formatted = new StringTokenizer( formattedQuery, " \t\n\r\f()" ); StringTokenizer plain = new StringTokenizer( query, " \t\n\r\f()" ); - System.out.println( "Original: " + query ); - System.out.println( "Formatted: " + formattedQuery ); + log.debugf( "Original: {}", query ); + log.debugf( "Formatted: {}", formattedQuery ); + while ( formatted.hasMoreTokens() && plain.hasMoreTokens() ) { String plainToken = plain.nextToken(); String formattedToken = formatted.nextToken(); diff --git a/hibernate-core/src/test/java/org/hibernate/jdbc/util/DdlFormatterTest.java b/hibernate-core/src/test/java/org/hibernate/jdbc/util/DdlFormatterTest.java index f3767224cd..9ed3362832 100644 --- a/hibernate-core/src/test/java/org/hibernate/jdbc/util/DdlFormatterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jdbc/util/DdlFormatterTest.java @@ -13,6 +13,8 @@ import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; +import org.jboss.logging.Logger; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -20,6 +22,9 @@ import static org.junit.Assert.assertFalse; * @author Vlad Mihalcea */ public class DdlFormatterTest extends BaseUnitTestCase { + + private static final Logger log = Logger.getLogger( DdlFormatterTest.class ); + @Test public void testNoLoss() { @@ -44,8 +49,9 @@ public class DdlFormatterTest extends BaseUnitTestCase { StringTokenizer formatted = new StringTokenizer( formattedQuery, " \t\n\r\f()" ); StringTokenizer plain = new StringTokenizer( query, " \t\n\r\f()" ); - System.out.println( "Original: " + query ); - System.out.println( "Formatted: " + formattedQuery ); + log.debugf( "Original: {}", query ); + log.debugf( "Formatted: {}", formattedQuery ); + while ( formatted.hasMoreTokens() && plain.hasMoreTokens() ) { String plainToken = plain.nextToken(); String formattedToken = formatted.nextToken(); diff --git a/hibernate-core/src/test/java/org/hibernate/query/NativeQueryWithParenthesesTest.java b/hibernate-core/src/test/java/org/hibernate/query/NativeQueryWithParenthesesTest.java new file mode 100644 index 0000000000..d7514fc569 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/NativeQueryWithParenthesesTest.java @@ -0,0 +1,68 @@ +/* + * 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.query; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class NativeQueryWithParenthesesTest extends BaseEntityManagerFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Test + public void testParseParentheses() { + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( + "(SELECT p.id, p.name FROM Person p WHERE p.name LIKE 'A%') " + + "UNION " + + "(SELECT p.id, p.name FROM Person p WHERE p.name LIKE 'B%')", Person.class) + .getResultList(); + } ); + } + + @Entity + @Table(name = "Person") + public static class Person { + + @Id + private Integer id; + + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +}