diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc index 5a0ff41dd556..feeb5e340125 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc @@ -339,6 +339,12 @@ include::{sourcedir-mapping}/basic/SubselectTest.java[tag=mapping-Subselect-enti ---- ==== +[NOTE] +==== +The underlying `@Subselect` SQL query supports standard Hibernate placeholders ( see <> ). +Global settings can be overridden using the `schema` or `catalog` attributes of the `@Table` annotation. +==== + If we add a new `AccountTransaction` entity and refresh the `AccountSummary` entity, the balance is updated accordingly: [[mapping-Subselect-refresh-find-example]] diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryParser.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryParser.java index 823753360c04..405efbcc41ed 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryParser.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryParser.java @@ -32,7 +32,10 @@ public class SQLQueryParser { private static final String CATALOG_PLACEHOLDER = "h-catalog"; private static final String SCHEMA_PLACEHOLDER = "h-schema"; - private final SessionFactoryImplementor factory; + private final String schemaName; + private final String catalogName; + private final boolean jdbcStyleParamsZeroBased; + private final String originalQueryString; private final ParserContext context; @@ -51,9 +54,26 @@ interface ParserContext { } public SQLQueryParser(String queryString, ParserContext context, SessionFactoryImplementor factory) { + this( + queryString, + context, + factory.getSettings().getDefaultSchemaName(), + factory.getSettings().getDefaultCatalogName(), + factory.getSessionFactoryOptions().jdbcStyleParamsZeroBased() + ); + } + + protected SQLQueryParser( + String queryString, + ParserContext context, + String schemaName, + String catalogName, + boolean jdbcStyleParamsZeroBased) { this.originalQueryString = queryString; this.context = context; - this.factory = factory; + this.schemaName = schemaName; + this.catalogName = catalogName; + this.jdbcStyleParamsZeroBased = jdbcStyleParamsZeroBased; } public List getParameterValueBinders() { @@ -107,12 +127,10 @@ protected String substituteBrackets(String sqlQuery) throws QueryException { if ( isPlaceholder ) { // Domain replacement if ( DOMAIN_PLACEHOLDER.equals( aliasPath ) ) { - final String catalogName = factory.getSettings().getDefaultCatalogName(); if ( catalogName != null ) { result.append( catalogName ); result.append( "." ); } - final String schemaName = factory.getSettings().getDefaultSchemaName(); if ( schemaName != null ) { result.append( schemaName ); result.append( "." ); @@ -120,7 +138,6 @@ protected String substituteBrackets(String sqlQuery) throws QueryException { } // Schema replacement else if ( SCHEMA_PLACEHOLDER.equals( aliasPath ) ) { - final String schemaName = factory.getSettings().getDefaultSchemaName(); if ( schemaName != null ) { result.append(schemaName); result.append("."); @@ -128,7 +145,6 @@ else if ( SCHEMA_PLACEHOLDER.equals( aliasPath ) ) { } // Catalog replacement else if ( CATALOG_PLACEHOLDER.equals( aliasPath ) ) { - final String catalogName = factory.getSettings().getDefaultCatalogName(); if ( catalogName != null ) { result.append( catalogName ); result.append( "." ); @@ -281,7 +297,7 @@ private String resolveProperties(String aliasName, String propertyName) { * @return The SQL query with parameter substitution complete. */ private String substituteParams(String sqlString) { - final ParameterSubstitutionRecognizer recognizer = new ParameterSubstitutionRecognizer( factory ); + final ParameterSubstitutionRecognizer recognizer = new ParameterSubstitutionRecognizer( jdbcStyleParamsZeroBased ); ParameterParser.parse( sqlString, recognizer ); paramValueBinders = recognizer.getParameterValueBinders(); @@ -295,10 +311,8 @@ public static class ParameterSubstitutionRecognizer implements ParameterParser.R int jdbcPositionalParamCount; private List paramValueBinders; - public ParameterSubstitutionRecognizer(SessionFactoryImplementor factory) { - this.jdbcPositionalParamCount = factory.getSessionFactoryOptions().jdbcStyleParamsZeroBased() - ? 0 - : 1; + public ParameterSubstitutionRecognizer(boolean jdbcStyleParamsZeroBased) { + this.jdbcPositionalParamCount = jdbcStyleParamsZeroBased ? 0 : 1; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 8f2cf37e8881..7c119b0e2816 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -4147,6 +4147,10 @@ private String substituteBrackets(String sql) { return new SubstituteBracketSQLQueryParser( sql, getFactory() ).process(); } + private String substituteBrackets(String sql, String schemaName, String catalogName) { + return new SubstituteBracketSQLQueryParser( sql, schemaName, catalogName ).process(); + } + public final void postInstantiate() throws MappingException { doLateInit(); @@ -5465,7 +5469,7 @@ public int determineTableNumberForColumn(String columnName) { protected String determineTableName(Table table, JdbcEnvironment jdbcEnvironment) { if ( table.getSubselect() != null ) { - return "( " + table.getSubselect() + " )"; + return "( " + substituteBrackets( table.getSubselect(), table.getSchema(), table.getCatalog() ) + " )"; } return jdbcEnvironment.getQualifiedObjectNameFormatter().format( @@ -5792,6 +5796,10 @@ private static class SubstituteBracketSQLQueryParser extends SQLQueryParser { super( queryString, null, factory ); } + SubstituteBracketSQLQueryParser(String queryString, String schemaName, String catalogName) { + super( queryString, null, schemaName, catalogName, true /*ignored*/ ); + } + @Override public String process() { return substituteBrackets( getOriginalQueryString() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsNonDefaultSchemaTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsNonDefaultSchemaTest.hbm.xml new file mode 100755 index 000000000000..1026a7d28a4f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsNonDefaultSchemaTest.hbm.xml @@ -0,0 +1,39 @@ + + + + + + + + + + SELECT ID,NAME FROM {h-schema}FOO + + + + + + + + + + + SELECT ID,NAME FROM {h-catalog}FOO + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsNonDefaultSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsNonDefaultSchemaTest.java new file mode 100644 index 000000000000..480b0955b532 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsNonDefaultSchemaTest.java @@ -0,0 +1,176 @@ +package org.hibernate.test.subselect; + +import java.util.Properties; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.annotations.Subselect; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +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; + +/** + * @author Oleksii Chumak + */ +@RequiresDialect(H2Dialect.class) +public class SubselectSubstituteBracketsNonDefaultSchemaTest extends BaseCoreFunctionalTestCase { + + private static final String SCHEMA_NAME = "SubselectSubstituteBracketsNonDefaultSchemaTest"; + + @Before + public void prepareTestData() { + doInHibernate( this::sessionFactory, session -> { + session.doWork( connection -> { + connection.createStatement() + .executeUpdate( "CREATE TABLE " + SCHEMA_NAME + ".FOO (ID int not null, NAME varchar(255), primary key (ID))" ); + connection.createStatement() + .executeUpdate( "INSERT INTO " + SCHEMA_NAME + ".FOO (ID,NAME) VALUES(1,'name1')" ); + connection.createStatement() + .executeUpdate( "INSERT INTO " + SCHEMA_NAME + ".FOO (ID,NAME) VALUES(2,'name2')" ); + connection.createStatement() + .executeUpdate( "INSERT INTO " + SCHEMA_NAME + ".FOO (ID,NAME) VALUES(3,'name3')" ); + } ); + } ); + } + + @After + public void deleteTestData() { + doInHibernate( this::sessionFactory, session -> { + session.doWork( connection -> { + connection.createStatement().executeUpdate( "DROP TABLE " + SCHEMA_NAME + ".FOO" ); + } ); + } ); + } + + @Test + public void testAnnotationConfiguredSubselect() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( "name1", session.get( ViewFoo.class, 1 ).name ); + assertEquals( "name2", session.get( ViewFoo.class, 2 ).name ); + assertEquals( "name3", session.get( ViewFoo.class, 3 ).name ); + + long count = (long) session.createQuery( + "SELECT COUNT(*) FROM SubselectSubstituteBracketsNonDefaultSchemaTest$ViewFoo" ) + .uniqueResult(); + assertEquals( 3L, count ); + } ); + } + + @Test + public void testAnnotationConfiguredSubselectCatalog() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( "name1", session.get( CatalogViewFoo.class, 1 ).name ); + assertEquals( "name2", session.get( CatalogViewFoo.class, 2 ).name ); + assertEquals( "name3", session.get( CatalogViewFoo.class, 3 ).name ); + + long count = (long) session.createQuery( + "SELECT COUNT(*) FROM SubselectSubstituteBracketsNonDefaultSchemaTest$CatalogViewFoo" ) + .uniqueResult(); + assertEquals( 3L, count ); + } ); + } + + @Test + public void testXmlConfiguredSubselect() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( "name1", session.get( XmlViewFoo.class, 1 ).name ); + assertEquals( "name2", session.get( XmlViewFoo.class, 2 ).name ); + assertEquals( "name3", session.get( XmlViewFoo.class, 3 ).name ); + + long count = (long) session.createQuery( + "SELECT COUNT(*) FROM SubselectSubstituteBracketsNonDefaultSchemaTest$XmlViewFoo" ) + .uniqueResult(); + assertEquals( 3L, count ); + } ); + } + + @Test + public void testXmlConfiguredSubselectCatalog() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( "name1", session.get( CatalogXmlViewFoo.class, 1 ).name ); + assertEquals( "name2", session.get( CatalogXmlViewFoo.class, 2 ).name ); + assertEquals( "name3", session.get( CatalogXmlViewFoo.class, 3 ).name ); + + long count = (long) session.createQuery( + "SELECT COUNT(*) FROM SubselectSubstituteBracketsNonDefaultSchemaTest$CatalogXmlViewFoo" ) + .uniqueResult(); + assertEquals( 3L, count ); + } ); + } + + @Override + public String[] getMappings() { + return new String[] { "subselect/SubselectSubstituteBracketsNonDefaultSchemaTest.hbm.xml" }; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + ViewFoo.class, + CatalogViewFoo.class + }; + } + + + @Override + protected void configure(Configuration configuration) { + final Properties properties = new Properties(); + properties.put( AvailableSettings.DEFAULT_SCHEMA, "WRONG_VALUE" ); + properties.put( AvailableSettings.DEFAULT_CATALOG, "WRONG_VALUE" ); + configuration.addProperties( properties ); + } + + @Override + protected String createSecondSchema() { + return SCHEMA_NAME; + } + + + @Entity + @Table(schema = SCHEMA_NAME) + @Subselect("SELECT ID,NAME FROM {h-schema}FOO") + public static class ViewFoo { + + @Id + @Column(name = "ID") + public Integer id; + + @Column(name = "NAME") + public String name; + } + + + @Entity + @Table(catalog = SCHEMA_NAME) + @Subselect("SELECT ID,NAME FROM {h-catalog}FOO") + public static class CatalogViewFoo { + + @Id + @Column(name = "ID") + public Integer id; + + @Column(name = "NAME") + public String name; + } + + public static class XmlViewFoo { + public Integer id; + public String name; + } + + public static class CatalogXmlViewFoo { + public Integer id; + public String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsTest.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsTest.hbm.xml new file mode 100755 index 000000000000..1bbc39b4f748 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsTest.hbm.xml @@ -0,0 +1,37 @@ + + + + + + + + + + SELECT ID,NAME FROM {h-schema}FOO + + + + + + + + + + + SELECT ID,NAME FROM {h-catalog}FOO + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsTest.java b/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsTest.java new file mode 100644 index 000000000000..959a6c6acaa6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/subselect/SubselectSubstituteBracketsTest.java @@ -0,0 +1,169 @@ +package org.hibernate.test.subselect; + +import java.util.Properties; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.Subselect; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +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; + +/** + * @author Oleksii Chumak + */ +@RequiresDialect(H2Dialect.class) +public class SubselectSubstituteBracketsTest extends BaseCoreFunctionalTestCase { + + private static final String SCHEMA_NAME = "SubselectSubstituteBracketsTest"; + + @Before + public void prepareTestData() { + doInHibernate( this::sessionFactory, session -> { + session.doWork( connection -> { + connection.createStatement() + .executeUpdate( "CREATE TABLE " + SCHEMA_NAME + ".FOO (ID int not null, NAME varchar(255), primary key (ID))" ); + connection.createStatement() + .executeUpdate( "INSERT INTO " + SCHEMA_NAME + ".FOO (ID,NAME) VALUES(1,'name1')" ); + connection.createStatement() + .executeUpdate( "INSERT INTO " + SCHEMA_NAME + ".FOO (ID,NAME) VALUES(2,'name2')" ); + connection.createStatement() + .executeUpdate( "INSERT INTO " + SCHEMA_NAME + ".FOO (ID,NAME) VALUES(3,'name3')" ); + } ); + } ); + } + + @After + public void deleteTestData() { + doInHibernate( this::sessionFactory, session -> { + session.doWork( connection -> { + connection.createStatement().executeUpdate( "DROP TABLE " + SCHEMA_NAME + ".FOO" ); + } ); + } ); + } + + @Test + public void testAnnotationConfiguredSubselect() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( "name1", session.get( ViewFoo.class, 1 ).name ); + assertEquals( "name2", session.get( ViewFoo.class, 2 ).name ); + assertEquals( "name3", session.get( ViewFoo.class, 3 ).name ); + + long count = (long) session.createQuery( "SELECT COUNT(*) FROM SubselectSubstituteBracketsTest$ViewFoo" ) + .uniqueResult(); + assertEquals( 3L, count ); + } ); + } + + @Test + public void testAnnotationConfiguredSubselectCatalog() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( "name1", session.get( CatalogViewFoo.class, 1 ).name ); + assertEquals( "name2", session.get( CatalogViewFoo.class, 2 ).name ); + assertEquals( "name3", session.get( CatalogViewFoo.class, 3 ).name ); + + long count = (long) session.createQuery( + "SELECT COUNT(*) FROM SubselectSubstituteBracketsTest$CatalogViewFoo" ) + .uniqueResult(); + assertEquals( 3L, count ); + } ); + } + + @Test + public void testXmlConfiguredSubselect() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( "name1", session.get( XmlViewFoo.class, 1 ).name ); + assertEquals( "name2", session.get( XmlViewFoo.class, 2 ).name ); + assertEquals( "name3", session.get( XmlViewFoo.class, 3 ).name ); + + long count = (long) session.createQuery( "SELECT COUNT(*) FROM SubselectSubstituteBracketsTest$XmlViewFoo" ) + .uniqueResult(); + assertEquals( 3L, count ); + } ); + } + + @Test + public void testXmlConfiguredSubselectCatalog() { + doInHibernate( this::sessionFactory, session -> { + assertEquals( "name1", session.get( CatalogXmlViewFoo.class, 1 ).name ); + assertEquals( "name2", session.get( CatalogXmlViewFoo.class, 2 ).name ); + assertEquals( "name3", session.get( CatalogXmlViewFoo.class, 3 ).name ); + + long count = (long) session.createQuery( + "SELECT COUNT(*) FROM SubselectSubstituteBracketsTest$CatalogXmlViewFoo" ) + .uniqueResult(); + assertEquals( 3L, count ); + } ); + } + + @Override + public String[] getMappings() { + return new String[] { "subselect/SubselectSubstituteBracketsTest.hbm.xml" }; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + ViewFoo.class, + CatalogViewFoo.class + }; + } + + + @Override + protected void configure(Configuration configuration) { + final Properties properties = new Properties(); + properties.put( AvailableSettings.DEFAULT_SCHEMA, SCHEMA_NAME ); + properties.put( AvailableSettings.DEFAULT_CATALOG, SCHEMA_NAME ); + configuration.addProperties( properties ); + } + + @Override + protected String createSecondSchema() { + return SCHEMA_NAME; + } + + @Entity + @Subselect("SELECT ID,NAME FROM {h-schema}FOO") + public static class ViewFoo { + + @Id + @Column(name = "ID") + public Integer id; + + @Column(name = "NAME") + public String name; + } + + @Entity + @Subselect("SELECT ID,NAME FROM {h-catalog}FOO") + public static class CatalogViewFoo { + + @Id + @Column(name = "ID") + public Integer id; + + @Column(name = "NAME") + public String name; + } + + public static class XmlViewFoo { + public Integer id; + public String name; + } + + public static class CatalogXmlViewFoo { + public Integer id; + public String name; + } +}