Skip to content

Commit 2109556

Browse files
committed
HHH-7913 - Schema replacement in <subselect> / @subselect
1 parent c8a6678 commit 2109556

File tree

8 files changed

+451
-53
lines changed

8 files changed

+451
-53
lines changed

documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,12 @@ include::{example-dir-mapping}/basic/SubselectTest.java[tag=mapping-Subselect-en
432432
----
433433
====
434434

435+
[NOTE]
436+
====
437+
The underlying `@Subselect` SQL query supports standard Hibernate placeholders ( see <<chapters/query/native/Native.adoc#sql-global-catalog-schema,Catalog and schema in SQL queries>> ).
438+
Global settings can be overridden using the `schema` or `catalog` attributes of the `@Table` annotation.
439+
====
440+
435441
If we add a new `AccountTransaction` entity and refresh the `AccountSummary` entity, the balance is updated accordingly:
436442

437443
[[mapping-Subselect-refresh-find-example]]

hibernate-core/src/main/java/org/hibernate/boot/model/relational/SqlStringGenerationContext.java

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,14 @@ public interface SqlStringGenerationContext {
4040
Identifier getDefaultCatalog();
4141

4242
/**
43-
* @param explicitCatalogOrNull An explicitly configured catalog, or {@code null}.
44-
* @return The given identifier if non-{@code null}, or the default catalog otherwise.
43+
* Interpret the incoming catalog, returning the incoming value if it is non-null.
44+
* Otherwise, returns the current {@linkplain #getDefaultCatalog() default catalog}.
45+
*
46+
* @apiNote May return {@code null} if {@linkplain #getDefaultCatalog() default catalog} is {@code null}.
4547
*/
46-
Identifier catalogWithDefault(Identifier explicitCatalogOrNull);
48+
default Identifier catalogWithDefault(Identifier explicitCatalogOrNull) {
49+
return explicitCatalogOrNull != null ? explicitCatalogOrNull : getDefaultCatalog();
50+
}
4751

4852
/**
4953
* @return The default schema, used for table/sequence names that do not explicitly mention a schema.
@@ -54,10 +58,14 @@ public interface SqlStringGenerationContext {
5458
Identifier getDefaultSchema();
5559

5660
/**
57-
* @param explicitSchemaOrNull An explicitly configured schema, or {@code null}.
58-
* @return The given identifier if non-{@code null}, or the default schema otherwise.
61+
* Interpret the incoming schema, returning the incoming value if it is non-null.
62+
* Otherwise, returns the current {@linkplain #getDefaultSchema() default schema}.
63+
*
64+
* @apiNote May return {@code null} if {@linkplain #getDefaultSchema() default schema} is {@code null}.
5965
*/
60-
Identifier schemaWithDefault(Identifier explicitSchemaOrNull);
66+
default Identifier schemaWithDefault(Identifier explicitSchemaOrNull) {
67+
return explicitSchemaOrNull != null ? explicitSchemaOrNull : getDefaultSchema();
68+
}
6169

6270
/**
6371
* Render a formatted a table name
@@ -101,4 +109,49 @@ public interface SqlStringGenerationContext {
101109
* @return {@code true} if and only if this is a migration
102110
*/
103111
boolean isMigration();
112+
113+
/**
114+
* Apply default catalog and schema, if necessary, to the given name. May return a new reference.
115+
*/
116+
default QualifiedTableName withDefaults(QualifiedTableName name) {
117+
if ( name.getCatalogName() == null && getDefaultCatalog() != null
118+
|| name.getSchemaName() == null && getDefaultSchema() != null ) {
119+
return new QualifiedTableName(
120+
catalogWithDefault( name.getCatalogName() ),
121+
schemaWithDefault( name.getSchemaName() ),
122+
name.getTableName()
123+
);
124+
}
125+
return name;
126+
}
127+
128+
/**
129+
* Apply default catalog and schema, if necessary, to the given name. May return a new reference.
130+
*/
131+
default QualifiedSequenceName withDefaults(QualifiedSequenceName name) {
132+
if ( name.getCatalogName() == null && getDefaultCatalog() != null
133+
|| name.getSchemaName() == null && getDefaultSchema() != null ) {
134+
return new QualifiedSequenceName(
135+
catalogWithDefault( name.getCatalogName() ),
136+
schemaWithDefault( name.getSchemaName() ),
137+
name.getSequenceName()
138+
);
139+
}
140+
return name;
141+
}
142+
143+
/**
144+
* Apply default catalog and schema, if necessary, to the given name. May return a new reference.
145+
*/
146+
default QualifiedName withDefaults(QualifiedName name) {
147+
if ( name.getCatalogName() == null && getDefaultCatalog() != null
148+
|| name.getSchemaName() == null && getDefaultSchema() != null ) {
149+
return new QualifiedNameImpl(
150+
catalogWithDefault( name.getCatalogName() ),
151+
schemaWithDefault( name.getSchemaName() ),
152+
name.getObjectName()
153+
);
154+
}
155+
return name;
156+
}
104157
}

hibernate-core/src/main/java/org/hibernate/boot/model/relational/internal/SqlStringGenerationContextImpl.java

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -146,48 +146,11 @@ public Identifier getDefaultCatalog() {
146146
return defaultCatalog;
147147
}
148148

149-
@Override
150-
public Identifier catalogWithDefault(Identifier explicitCatalogOrNull) {
151-
return explicitCatalogOrNull != null ? explicitCatalogOrNull : defaultCatalog;
152-
}
153-
154149
@Override
155150
public Identifier getDefaultSchema() {
156151
return defaultSchema;
157152
}
158153

159-
@Override
160-
public Identifier schemaWithDefault(Identifier explicitSchemaOrNull) {
161-
return explicitSchemaOrNull != null ? explicitSchemaOrNull : defaultSchema;
162-
}
163-
164-
private QualifiedTableName withDefaults(QualifiedTableName name) {
165-
if ( name.getCatalogName() == null && defaultCatalog != null
166-
|| name.getSchemaName() == null && defaultSchema != null ) {
167-
return new QualifiedTableName( catalogWithDefault( name.getCatalogName() ),
168-
schemaWithDefault( name.getSchemaName() ), name.getTableName() );
169-
}
170-
return name;
171-
}
172-
173-
private QualifiedSequenceName withDefaults(QualifiedSequenceName name) {
174-
if ( name.getCatalogName() == null && defaultCatalog != null
175-
|| name.getSchemaName() == null && defaultSchema != null ) {
176-
return new QualifiedSequenceName( catalogWithDefault( name.getCatalogName() ),
177-
schemaWithDefault( name.getSchemaName() ), name.getSequenceName() );
178-
}
179-
return name;
180-
}
181-
182-
private QualifiedName withDefaults(QualifiedName name) {
183-
if ( name.getCatalogName() == null && defaultCatalog != null
184-
|| name.getSchemaName() == null && defaultSchema != null ) {
185-
return new QualifiedSequenceName( catalogWithDefault( name.getCatalogName() ),
186-
schemaWithDefault( name.getSchemaName() ), name.getObjectName() );
187-
}
188-
return name;
189-
}
190-
191154
@Override
192155
public String format(QualifiedTableName qualifiedName) {
193156
return qualifiedObjectNameFormatter.format( withDefaults( qualifiedName ), dialect );

hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4620,9 +4620,21 @@ protected int determineTableNumberForColumn(String columnName) {
46204620
}
46214621

46224622
protected String determineTableName(Table table) {
4623-
return MappingModelCreationHelper.getTableIdentifierExpression( table, factory );
4623+
if ( table.getSubselect() != null ) {
4624+
final SQLQueryParser sqlQueryParser = new SQLQueryParser(
4625+
table.getSubselect(),
4626+
null,
4627+
// NOTE : this allows finer control over catalog and schema used for placeholder
4628+
// handling (`{h-catalog}`, `{h-schema}`, `{h-domain}`)
4629+
new ExplicitSqlStringGenerationContext( table.getCatalog(), table.getSchema(), factory )
4630+
);
4631+
return "( " + sqlQueryParser.process() + " )";
4632+
}
4633+
4634+
return factory.getSqlStringGenerationContext().format( table.getQualifiedTableName() );
46244635
}
46254636

4637+
46264638
@Override
46274639
public EntityEntryFactory getEntityEntryFactory() {
46284640
return this.entityEntryFactory;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.persister.entity;
6+
7+
import org.hibernate.boot.model.naming.Identifier;
8+
import org.hibernate.boot.model.relational.QualifiedName;
9+
import org.hibernate.boot.model.relational.QualifiedSequenceName;
10+
import org.hibernate.boot.model.relational.QualifiedTableName;
11+
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
12+
import org.hibernate.dialect.Dialect;
13+
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
14+
import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter;
15+
import org.hibernate.engine.spi.SessionFactoryImplementor;
16+
17+
/**
18+
* SqlStringGenerationContext implementation with support for overriding the
19+
* default catalog and schema
20+
*
21+
* @author Steve Ebersole
22+
*/
23+
public class ExplicitSqlStringGenerationContext implements SqlStringGenerationContext {
24+
private final SessionFactoryImplementor factory;
25+
private final Identifier defaultCatalog;
26+
private final Identifier defaultSchema;
27+
28+
public ExplicitSqlStringGenerationContext(
29+
String defaultCatalog,
30+
String defaultSchema,
31+
SessionFactoryImplementor factory) {
32+
this.factory = factory;
33+
this.defaultCatalog = defaultCatalog != null
34+
? toIdentifier( defaultCatalog )
35+
: toIdentifier( factory.getSessionFactoryOptions().getDefaultCatalog() );
36+
this.defaultSchema = defaultSchema != null
37+
? toIdentifier( defaultSchema )
38+
: toIdentifier( factory.getSessionFactoryOptions().getDefaultSchema() );
39+
}
40+
41+
@Override
42+
public Dialect getDialect() {
43+
return factory.getJdbcServices().getDialect();
44+
}
45+
46+
@Override
47+
public Identifier toIdentifier(String text) {
48+
return factory.getJdbcServices().getJdbcEnvironment().getIdentifierHelper().toIdentifier( text );
49+
}
50+
51+
@Override
52+
public Identifier getDefaultCatalog() {
53+
return defaultCatalog;
54+
}
55+
56+
@Override
57+
public Identifier getDefaultSchema() {
58+
return defaultSchema;
59+
}
60+
61+
@Override
62+
public String format(QualifiedTableName qualifiedName) {
63+
return nameFormater().format( withDefaults( qualifiedName ), getDialect() );
64+
}
65+
66+
private QualifiedObjectNameFormatter nameFormater() {
67+
final JdbcEnvironment jdbcEnvironment = factory.getJdbcServices().getJdbcEnvironment();
68+
//noinspection deprecation
69+
return jdbcEnvironment.getQualifiedObjectNameFormatter();
70+
}
71+
72+
@Override
73+
public String format(QualifiedSequenceName qualifiedName) {
74+
return nameFormater().format( withDefaults( qualifiedName ), getDialect() );
75+
}
76+
77+
@Override
78+
public String format(QualifiedName qualifiedName) {
79+
return nameFormater().format( withDefaults( qualifiedName ), getDialect() );
80+
}
81+
82+
@Override
83+
public String formatWithoutCatalog(QualifiedSequenceName qualifiedName) {
84+
QualifiedSequenceName nameToFormat;
85+
if ( qualifiedName.getCatalogName() != null
86+
|| qualifiedName.getSchemaName() == null && defaultSchema != null ) {
87+
nameToFormat = new QualifiedSequenceName( null,
88+
schemaWithDefault( qualifiedName.getSchemaName() ), qualifiedName.getSequenceName() );
89+
}
90+
else {
91+
nameToFormat = qualifiedName;
92+
}
93+
return nameFormater().format( nameToFormat, getDialect() );
94+
}
95+
96+
@Override
97+
public boolean isMigration() {
98+
return false;
99+
}
100+
}

hibernate-core/src/main/java/org/hibernate/query/sql/internal/SQLQueryParser.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
*/
55
package org.hibernate.query.sql.internal;
66

7-
import java.util.Map;
8-
97
import org.hibernate.QueryException;
108
import org.hibernate.boot.model.naming.Identifier;
119
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
@@ -14,6 +12,8 @@
1412
import org.hibernate.persister.collection.CollectionPersister;
1513
import org.hibernate.persister.entity.EntityPersister;
1614

15+
import java.util.Map;
16+
1717
/**
1818
* Substitutes escape sequences of form {@code {alias}},
1919
* {@code {alias.field}}, and {@code {alias.*}} in a
@@ -25,11 +25,11 @@
2525
* @author Paul Benedict
2626
*/
2727
public class SQLQueryParser {
28-
29-
private final SessionFactoryImplementor factory;
3028
private final String originalQueryString;
3129
private final ParserContext context;
3230

31+
private final SqlStringGenerationContext sqlStringGenerationContext;
32+
3333
private long aliasesFound;
3434

3535
public interface ParserContext {
@@ -43,9 +43,16 @@ public interface ParserContext {
4343
}
4444

4545
public SQLQueryParser(String queryString, ParserContext context, SessionFactoryImplementor factory) {
46+
this( queryString, context, factory.getSqlStringGenerationContext() );
47+
}
48+
49+
public SQLQueryParser(
50+
String queryString,
51+
ParserContext context,
52+
SqlStringGenerationContext sqlStringGenerationContext) {
4653
this.originalQueryString = queryString;
4754
this.context = context;
48-
this.factory = factory;
55+
this.sqlStringGenerationContext = sqlStringGenerationContext;
4956
}
5057

5158
public boolean queryHasAliases() {
@@ -169,10 +176,9 @@ else if ( context.isEntityAlias( aliasName ) ) {
169176
}
170177

171178
private void handlePlaceholder(String token, StringBuilder result) {
172-
final SqlStringGenerationContext context = factory.getSqlStringGenerationContext();
173-
final Identifier defaultCatalog = context.getDefaultCatalog();
174-
final Identifier defaultSchema = context.getDefaultSchema();
175-
final Dialect dialect = context.getDialect();
179+
final Identifier defaultCatalog = sqlStringGenerationContext.getDefaultCatalog();
180+
final Identifier defaultSchema = sqlStringGenerationContext.getDefaultSchema();
181+
final Dialect dialect = sqlStringGenerationContext.getDialect();
176182
switch (token) {
177183
case "h-domain":
178184
if ( defaultCatalog != null ) {

0 commit comments

Comments
 (0)