From 002ca625849c0a4cbb35cace221497e715acef70 Mon Sep 17 00:00:00 2001 From: Stefan Trienen Date: Wed, 18 Jun 2025 15:51:58 +0200 Subject: [PATCH 1/3] HHH-19558: Fixed support of JDBC escape syntax --- .../query/sql/internal/SQLQueryParser.java | 12 +++- .../orm/test/sql/SQLQueryParserUnitTests.java | 55 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/sql/SQLQueryParserUnitTests.java diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/SQLQueryParser.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/SQLQueryParser.java index 801d04af673c..a0a9c9619394 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/SQLQueryParser.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/SQLQueryParser.java @@ -84,13 +84,21 @@ protected String substituteBrackets(String sqlQuery) throws QueryException { if (!doubleQuoted && !escaped) { singleQuoted = !singleQuoted; } - result.append(ch); + if (escaped) { + token.append(ch); + } else { + result.append(ch); + } break; case '"': if (!singleQuoted && !escaped) { doubleQuoted = !doubleQuoted; } - result.append(ch); + if (escaped) { + token.append(ch); + } else { + result.append(ch); + } break; case '{': if (!singleQuoted && !doubleQuoted) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/SQLQueryParserUnitTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/SQLQueryParserUnitTests.java new file mode 100644 index 000000000000..7978dcb4fcdd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/SQLQueryParserUnitTests.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.sql; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.sql.internal.SQLQueryParser; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SQLQueryParser} + * + * @author Steve Ebersole + */ +@SuppressWarnings("JUnitMalformedDeclaration") +public class SQLQueryParserUnitTests { + + @Test + @DomainModel + @SessionFactory + @RequiresDialect(H2Dialect.class) + void testJDBCEscapeSyntaxParsing(SessionFactoryScope scope) { + final SessionFactoryImplementor sessionFactory = scope.getSessionFactory(); + final String sqlQuery = "select id, name from {h-domain}the_table where date = {d '2025-06-18'}"; + + final String full = processSqlString( sqlQuery, "my_catalog", "my_schema", sessionFactory ); + assertThat( full ).contains( "{d '2025-06-18'}" ); + + final String catalogOnly = processSqlString( sqlQuery, "my_catalog", null, sessionFactory ); + assertThat( catalogOnly ).contains( "{d '2025-06-18'}" ); + + final String schemaOnly = processSqlString( sqlQuery, null, "my_schema", sessionFactory ); + assertThat( schemaOnly ).contains( "{d '2025-06-18'}" ); + + final String none = processSqlString( sqlQuery, null, null, sessionFactory ); + assertThat( none ).contains( "{d '2025-06-18'}" ); + } + + private static String processSqlString( + String sqlQuery, + String catalogName, + String schemaName, + SessionFactoryImplementor sessionFactory) { + return new SQLQueryParser( sqlQuery, null, sessionFactory ).process(); + } +} From 9f432e81ef7c6dae19be30282e9a6de9216e69b7 Mon Sep 17 00:00:00 2001 From: Stefan Trienen Date: Wed, 18 Jun 2025 16:48:23 +0200 Subject: [PATCH 2/3] HHH-19558: Improved Test --- .../orm/test/sql/SQLQueryParserUnitTests.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/SQLQueryParserUnitTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/SQLQueryParserUnitTests.java index 7978dcb4fcdd..98c211350237 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/SQLQueryParserUnitTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/SQLQueryParserUnitTests.java @@ -13,6 +13,8 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; @@ -24,25 +26,33 @@ @SuppressWarnings("JUnitMalformedDeclaration") public class SQLQueryParserUnitTests { - @Test + @ParameterizedTest @DomainModel @SessionFactory @RequiresDialect(H2Dialect.class) - void testJDBCEscapeSyntaxParsing(SessionFactoryScope scope) { + @ValueSource(strings = { + "{d '2025-06-18'}", + "{t '14:00'}", + "{t '14:00:00'}", + "{ts '2025-06-18T14:00'}", + "{ts '2025-06-18T14:00:00'}", + "{ts '2025-06-18T14:00:00.123'}", + "{ts '2025-06-18T14:00:00+01:00'}"}) + void testJDBCEscapeSyntaxParsing(String variant, SessionFactoryScope scope) { final SessionFactoryImplementor sessionFactory = scope.getSessionFactory(); - final String sqlQuery = "select id, name from {h-domain}the_table where date = {d '2025-06-18'}"; + final String sqlQuery = "select id, name from {h-domain}the_table where date = " + variant; final String full = processSqlString( sqlQuery, "my_catalog", "my_schema", sessionFactory ); - assertThat( full ).contains( "{d '2025-06-18'}" ); + assertThat( full ).contains( variant ); final String catalogOnly = processSqlString( sqlQuery, "my_catalog", null, sessionFactory ); - assertThat( catalogOnly ).contains( "{d '2025-06-18'}" ); + assertThat( catalogOnly ).contains( variant ); final String schemaOnly = processSqlString( sqlQuery, null, "my_schema", sessionFactory ); - assertThat( schemaOnly ).contains( "{d '2025-06-18'}" ); + assertThat( schemaOnly ).contains( variant ); final String none = processSqlString( sqlQuery, null, null, sessionFactory ); - assertThat( none ).contains( "{d '2025-06-18'}" ); + assertThat( none ).contains( variant ); } private static String processSqlString( From 5a906aaf8da5f77eddbe4bfc2dc3e4d33488cfbd Mon Sep 17 00:00:00 2001 From: Stefan Trienen Date: Mon, 30 Jun 2025 09:35:00 +0200 Subject: [PATCH 3/3] HHH-19558: Removed double quoted case --- .../query/sql/internal/SQLQueryParser.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/SQLQueryParser.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/SQLQueryParser.java index a0a9c9619394..94420df0893f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/SQLQueryParser.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/SQLQueryParser.java @@ -81,12 +81,13 @@ protected String substituteBrackets(String sqlQuery) throws QueryException { final char ch = sqlQuery.charAt( index ); switch (ch) { case '\'': - if (!doubleQuoted && !escaped) { - singleQuoted = !singleQuoted; - } if (escaped) { token.append(ch); - } else { + } + else { + if (!doubleQuoted) { + singleQuoted = !singleQuoted; + } result.append(ch); } break; @@ -94,11 +95,7 @@ protected String substituteBrackets(String sqlQuery) throws QueryException { if (!singleQuoted && !escaped) { doubleQuoted = !doubleQuoted; } - if (escaped) { - token.append(ch); - } else { - result.append(ch); - } + result.append(ch); break; case '{': if (!singleQuoted && !doubleQuoted) { @@ -224,7 +221,7 @@ private String resolveCollectionProperties( } aliasesFound++; return collectionPersister.selectFragment( aliasName, collectionSuffix ) - + ", " + resolveProperties( aliasName, propertyName ); + + ", " + resolveProperties( aliasName, propertyName ); case "element.*": return resolveProperties( aliasName, "*" ); default: @@ -274,7 +271,7 @@ private void validate(String aliasName, String propertyName, String[] columnAlia // TODO: better error message since we actually support composites if names are explicitly listed throw new QueryException( "SQL queries only support properties mapped to a single column - property [" + - propertyName + "] is mapped to " + columnAliases.length + " columns.", + propertyName + "] is mapped to " + columnAliases.length + " columns.", originalQueryString ); }