HHH-7913 - Schema replacement in <subselect> / @Subselect
This commit is contained in:
parent
c8a66789d1
commit
21095565bc
|
@ -432,6 +432,12 @@ include::{example-dir-mapping}/basic/SubselectTest.java[tag=mapping-Subselect-en
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
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>> ).
|
||||||
|
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:
|
If we add a new `AccountTransaction` entity and refresh the `AccountSummary` entity, the balance is updated accordingly:
|
||||||
|
|
||||||
[[mapping-Subselect-refresh-find-example]]
|
[[mapping-Subselect-refresh-find-example]]
|
||||||
|
|
|
@ -40,10 +40,14 @@ public interface SqlStringGenerationContext {
|
||||||
Identifier getDefaultCatalog();
|
Identifier getDefaultCatalog();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param explicitCatalogOrNull An explicitly configured catalog, or {@code null}.
|
* Interpret the incoming catalog, returning the incoming value if it is non-null.
|
||||||
* @return The given identifier if non-{@code null}, or the default catalog otherwise.
|
* Otherwise, returns the current {@linkplain #getDefaultCatalog() default catalog}.
|
||||||
|
*
|
||||||
|
* @apiNote May return {@code null} if {@linkplain #getDefaultCatalog() default catalog} is {@code null}.
|
||||||
*/
|
*/
|
||||||
Identifier catalogWithDefault(Identifier explicitCatalogOrNull);
|
default Identifier catalogWithDefault(Identifier explicitCatalogOrNull) {
|
||||||
|
return explicitCatalogOrNull != null ? explicitCatalogOrNull : getDefaultCatalog();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The default schema, used for table/sequence names that do not explicitly mention a schema.
|
* @return The default schema, used for table/sequence names that do not explicitly mention a schema.
|
||||||
|
@ -54,10 +58,14 @@ public interface SqlStringGenerationContext {
|
||||||
Identifier getDefaultSchema();
|
Identifier getDefaultSchema();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param explicitSchemaOrNull An explicitly configured schema, or {@code null}.
|
* Interpret the incoming schema, returning the incoming value if it is non-null.
|
||||||
* @return The given identifier if non-{@code null}, or the default schema otherwise.
|
* Otherwise, returns the current {@linkplain #getDefaultSchema() default schema}.
|
||||||
|
*
|
||||||
|
* @apiNote May return {@code null} if {@linkplain #getDefaultSchema() default schema} is {@code null}.
|
||||||
*/
|
*/
|
||||||
Identifier schemaWithDefault(Identifier explicitSchemaOrNull);
|
default Identifier schemaWithDefault(Identifier explicitSchemaOrNull) {
|
||||||
|
return explicitSchemaOrNull != null ? explicitSchemaOrNull : getDefaultSchema();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a formatted a table name
|
* Render a formatted a table name
|
||||||
|
@ -101,4 +109,49 @@ public interface SqlStringGenerationContext {
|
||||||
* @return {@code true} if and only if this is a migration
|
* @return {@code true} if and only if this is a migration
|
||||||
*/
|
*/
|
||||||
boolean isMigration();
|
boolean isMigration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply default catalog and schema, if necessary, to the given name. May return a new reference.
|
||||||
|
*/
|
||||||
|
default QualifiedTableName withDefaults(QualifiedTableName name) {
|
||||||
|
if ( name.getCatalogName() == null && getDefaultCatalog() != null
|
||||||
|
|| name.getSchemaName() == null && getDefaultSchema() != null ) {
|
||||||
|
return new QualifiedTableName(
|
||||||
|
catalogWithDefault( name.getCatalogName() ),
|
||||||
|
schemaWithDefault( name.getSchemaName() ),
|
||||||
|
name.getTableName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply default catalog and schema, if necessary, to the given name. May return a new reference.
|
||||||
|
*/
|
||||||
|
default QualifiedSequenceName withDefaults(QualifiedSequenceName name) {
|
||||||
|
if ( name.getCatalogName() == null && getDefaultCatalog() != null
|
||||||
|
|| name.getSchemaName() == null && getDefaultSchema() != null ) {
|
||||||
|
return new QualifiedSequenceName(
|
||||||
|
catalogWithDefault( name.getCatalogName() ),
|
||||||
|
schemaWithDefault( name.getSchemaName() ),
|
||||||
|
name.getSequenceName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply default catalog and schema, if necessary, to the given name. May return a new reference.
|
||||||
|
*/
|
||||||
|
default QualifiedName withDefaults(QualifiedName name) {
|
||||||
|
if ( name.getCatalogName() == null && getDefaultCatalog() != null
|
||||||
|
|| name.getSchemaName() == null && getDefaultSchema() != null ) {
|
||||||
|
return new QualifiedNameImpl(
|
||||||
|
catalogWithDefault( name.getCatalogName() ),
|
||||||
|
schemaWithDefault( name.getSchemaName() ),
|
||||||
|
name.getObjectName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,48 +146,11 @@ public class SqlStringGenerationContextImpl
|
||||||
return defaultCatalog;
|
return defaultCatalog;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Identifier catalogWithDefault(Identifier explicitCatalogOrNull) {
|
|
||||||
return explicitCatalogOrNull != null ? explicitCatalogOrNull : defaultCatalog;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Identifier getDefaultSchema() {
|
public Identifier getDefaultSchema() {
|
||||||
return defaultSchema;
|
return defaultSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Identifier schemaWithDefault(Identifier explicitSchemaOrNull) {
|
|
||||||
return explicitSchemaOrNull != null ? explicitSchemaOrNull : defaultSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
private QualifiedTableName withDefaults(QualifiedTableName name) {
|
|
||||||
if ( name.getCatalogName() == null && defaultCatalog != null
|
|
||||||
|| name.getSchemaName() == null && defaultSchema != null ) {
|
|
||||||
return new QualifiedTableName( catalogWithDefault( name.getCatalogName() ),
|
|
||||||
schemaWithDefault( name.getSchemaName() ), name.getTableName() );
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private QualifiedSequenceName withDefaults(QualifiedSequenceName name) {
|
|
||||||
if ( name.getCatalogName() == null && defaultCatalog != null
|
|
||||||
|| name.getSchemaName() == null && defaultSchema != null ) {
|
|
||||||
return new QualifiedSequenceName( catalogWithDefault( name.getCatalogName() ),
|
|
||||||
schemaWithDefault( name.getSchemaName() ), name.getSequenceName() );
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private QualifiedName withDefaults(QualifiedName name) {
|
|
||||||
if ( name.getCatalogName() == null && defaultCatalog != null
|
|
||||||
|| name.getSchemaName() == null && defaultSchema != null ) {
|
|
||||||
return new QualifiedSequenceName( catalogWithDefault( name.getCatalogName() ),
|
|
||||||
schemaWithDefault( name.getSchemaName() ), name.getObjectName() );
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String format(QualifiedTableName qualifiedName) {
|
public String format(QualifiedTableName qualifiedName) {
|
||||||
return qualifiedObjectNameFormatter.format( withDefaults( qualifiedName ), dialect );
|
return qualifiedObjectNameFormatter.format( withDefaults( qualifiedName ), dialect );
|
||||||
|
|
|
@ -4620,9 +4620,21 @@ public abstract class AbstractEntityPersister
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String determineTableName(Table table) {
|
protected String determineTableName(Table table) {
|
||||||
return MappingModelCreationHelper.getTableIdentifierExpression( table, factory );
|
if ( table.getSubselect() != null ) {
|
||||||
|
final SQLQueryParser sqlQueryParser = new SQLQueryParser(
|
||||||
|
table.getSubselect(),
|
||||||
|
null,
|
||||||
|
// NOTE : this allows finer control over catalog and schema used for placeholder
|
||||||
|
// handling (`{h-catalog}`, `{h-schema}`, `{h-domain}`)
|
||||||
|
new ExplicitSqlStringGenerationContext( table.getCatalog(), table.getSchema(), factory )
|
||||||
|
);
|
||||||
|
return "( " + sqlQueryParser.process() + " )";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return factory.getSqlStringGenerationContext().format( table.getQualifiedTableName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EntityEntryFactory getEntityEntryFactory() {
|
public EntityEntryFactory getEntityEntryFactory() {
|
||||||
return this.entityEntryFactory;
|
return this.entityEntryFactory;
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.persister.entity;
|
||||||
|
|
||||||
|
import org.hibernate.boot.model.naming.Identifier;
|
||||||
|
import org.hibernate.boot.model.relational.QualifiedName;
|
||||||
|
import org.hibernate.boot.model.relational.QualifiedSequenceName;
|
||||||
|
import org.hibernate.boot.model.relational.QualifiedTableName;
|
||||||
|
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
|
||||||
|
import org.hibernate.dialect.Dialect;
|
||||||
|
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||||
|
import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SqlStringGenerationContext implementation with support for overriding the
|
||||||
|
* default catalog and schema
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class ExplicitSqlStringGenerationContext implements SqlStringGenerationContext {
|
||||||
|
private final SessionFactoryImplementor factory;
|
||||||
|
private final Identifier defaultCatalog;
|
||||||
|
private final Identifier defaultSchema;
|
||||||
|
|
||||||
|
public ExplicitSqlStringGenerationContext(
|
||||||
|
String defaultCatalog,
|
||||||
|
String defaultSchema,
|
||||||
|
SessionFactoryImplementor factory) {
|
||||||
|
this.factory = factory;
|
||||||
|
this.defaultCatalog = defaultCatalog != null
|
||||||
|
? toIdentifier( defaultCatalog )
|
||||||
|
: toIdentifier( factory.getSessionFactoryOptions().getDefaultCatalog() );
|
||||||
|
this.defaultSchema = defaultSchema != null
|
||||||
|
? toIdentifier( defaultSchema )
|
||||||
|
: toIdentifier( factory.getSessionFactoryOptions().getDefaultSchema() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dialect getDialect() {
|
||||||
|
return factory.getJdbcServices().getDialect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Identifier toIdentifier(String text) {
|
||||||
|
return factory.getJdbcServices().getJdbcEnvironment().getIdentifierHelper().toIdentifier( text );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Identifier getDefaultCatalog() {
|
||||||
|
return defaultCatalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Identifier getDefaultSchema() {
|
||||||
|
return defaultSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(QualifiedTableName qualifiedName) {
|
||||||
|
return nameFormater().format( withDefaults( qualifiedName ), getDialect() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private QualifiedObjectNameFormatter nameFormater() {
|
||||||
|
final JdbcEnvironment jdbcEnvironment = factory.getJdbcServices().getJdbcEnvironment();
|
||||||
|
//noinspection deprecation
|
||||||
|
return jdbcEnvironment.getQualifiedObjectNameFormatter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(QualifiedSequenceName qualifiedName) {
|
||||||
|
return nameFormater().format( withDefaults( qualifiedName ), getDialect() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(QualifiedName qualifiedName) {
|
||||||
|
return nameFormater().format( withDefaults( qualifiedName ), getDialect() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String formatWithoutCatalog(QualifiedSequenceName qualifiedName) {
|
||||||
|
QualifiedSequenceName nameToFormat;
|
||||||
|
if ( qualifiedName.getCatalogName() != null
|
||||||
|
|| qualifiedName.getSchemaName() == null && defaultSchema != null ) {
|
||||||
|
nameToFormat = new QualifiedSequenceName( null,
|
||||||
|
schemaWithDefault( qualifiedName.getSchemaName() ), qualifiedName.getSequenceName() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nameToFormat = qualifiedName;
|
||||||
|
}
|
||||||
|
return nameFormater().format( nameToFormat, getDialect() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMigration() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,6 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.query.sql.internal;
|
package org.hibernate.query.sql.internal;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.hibernate.QueryException;
|
import org.hibernate.QueryException;
|
||||||
import org.hibernate.boot.model.naming.Identifier;
|
import org.hibernate.boot.model.naming.Identifier;
|
||||||
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
|
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
|
||||||
|
@ -14,6 +12,8 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.persister.collection.CollectionPersister;
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Substitutes escape sequences of form {@code {alias}},
|
* Substitutes escape sequences of form {@code {alias}},
|
||||||
* {@code {alias.field}}, and {@code {alias.*}} in a
|
* {@code {alias.field}}, and {@code {alias.*}} in a
|
||||||
|
@ -25,11 +25,11 @@ import org.hibernate.persister.entity.EntityPersister;
|
||||||
* @author Paul Benedict
|
* @author Paul Benedict
|
||||||
*/
|
*/
|
||||||
public class SQLQueryParser {
|
public class SQLQueryParser {
|
||||||
|
|
||||||
private final SessionFactoryImplementor factory;
|
|
||||||
private final String originalQueryString;
|
private final String originalQueryString;
|
||||||
private final ParserContext context;
|
private final ParserContext context;
|
||||||
|
|
||||||
|
private final SqlStringGenerationContext sqlStringGenerationContext;
|
||||||
|
|
||||||
private long aliasesFound;
|
private long aliasesFound;
|
||||||
|
|
||||||
public interface ParserContext {
|
public interface ParserContext {
|
||||||
|
@ -43,9 +43,16 @@ public class SQLQueryParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SQLQueryParser(String queryString, ParserContext context, SessionFactoryImplementor factory) {
|
public SQLQueryParser(String queryString, ParserContext context, SessionFactoryImplementor factory) {
|
||||||
|
this( queryString, context, factory.getSqlStringGenerationContext() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLQueryParser(
|
||||||
|
String queryString,
|
||||||
|
ParserContext context,
|
||||||
|
SqlStringGenerationContext sqlStringGenerationContext) {
|
||||||
this.originalQueryString = queryString;
|
this.originalQueryString = queryString;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.factory = factory;
|
this.sqlStringGenerationContext = sqlStringGenerationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean queryHasAliases() {
|
public boolean queryHasAliases() {
|
||||||
|
@ -169,10 +176,9 @@ public class SQLQueryParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePlaceholder(String token, StringBuilder result) {
|
private void handlePlaceholder(String token, StringBuilder result) {
|
||||||
final SqlStringGenerationContext context = factory.getSqlStringGenerationContext();
|
final Identifier defaultCatalog = sqlStringGenerationContext.getDefaultCatalog();
|
||||||
final Identifier defaultCatalog = context.getDefaultCatalog();
|
final Identifier defaultSchema = sqlStringGenerationContext.getDefaultSchema();
|
||||||
final Identifier defaultSchema = context.getDefaultSchema();
|
final Dialect dialect = sqlStringGenerationContext.getDialect();
|
||||||
final Dialect dialect = context.getDialect();
|
|
||||||
switch (token) {
|
switch (token) {
|
||||||
case "h-domain":
|
case "h-domain":
|
||||||
if ( defaultCatalog != null ) {
|
if ( defaultCatalog != null ) {
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* 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.persister.entity.ExplicitSqlStringGenerationContext;
|
||||||
|
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 testDomainParsing(SessionFactoryScope scope) {
|
||||||
|
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||||
|
final String sqlQuery = "select id, name from {h-domain}the_table";
|
||||||
|
|
||||||
|
final String full = processSqlString( sqlQuery, "my_catalog", "my_schema", sessionFactory );
|
||||||
|
assertThat( full ).endsWith( " my_catalog.my_schema.the_table" );
|
||||||
|
|
||||||
|
final String catalogOnly = processSqlString( sqlQuery, "my_catalog", null, sessionFactory );
|
||||||
|
assertThat( catalogOnly ).endsWith( " my_catalog.the_table" );
|
||||||
|
|
||||||
|
final String schemaOnly = processSqlString( sqlQuery, null, "my_schema", sessionFactory );
|
||||||
|
assertThat( schemaOnly ).endsWith( " my_schema.the_table" );
|
||||||
|
|
||||||
|
final String none = processSqlString( sqlQuery, null, null, sessionFactory );
|
||||||
|
assertThat( none ).endsWith( " the_table" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DomainModel
|
||||||
|
@SessionFactory
|
||||||
|
@RequiresDialect(H2Dialect.class)
|
||||||
|
void testCatalogParsing(SessionFactoryScope scope) {
|
||||||
|
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||||
|
final String sqlQuery = "select id, name from {h-catalog}the_table";
|
||||||
|
|
||||||
|
final String full = processSqlString( sqlQuery, "my_catalog", "my_schema", sessionFactory );
|
||||||
|
assertThat( full ).endsWith( " my_catalog.the_table" );
|
||||||
|
|
||||||
|
final String catalogOnly = processSqlString( sqlQuery, "my_catalog", null, sessionFactory );
|
||||||
|
assertThat( catalogOnly ).endsWith( " my_catalog.the_table" );
|
||||||
|
|
||||||
|
final String schemaOnly = processSqlString( sqlQuery, null, "my_schema", sessionFactory );
|
||||||
|
assertThat( schemaOnly ).endsWith( " the_table" );
|
||||||
|
|
||||||
|
final String none = processSqlString( sqlQuery, null, null, sessionFactory );
|
||||||
|
assertThat( none ).endsWith( " the_table" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DomainModel
|
||||||
|
@SessionFactory
|
||||||
|
@RequiresDialect(H2Dialect.class)
|
||||||
|
void testSchemaParsing(SessionFactoryScope scope) {
|
||||||
|
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||||
|
final String sqlQuery = "select id, name from {h-schema}the_table";
|
||||||
|
|
||||||
|
final String full = processSqlString( sqlQuery, "my_catalog", "my_schema", sessionFactory );
|
||||||
|
assertThat( full ).endsWith( " my_schema.the_table" );
|
||||||
|
|
||||||
|
final String catalogOnly = processSqlString( sqlQuery, "my_catalog", null, sessionFactory );
|
||||||
|
assertThat( catalogOnly ).endsWith( " the_table" );
|
||||||
|
|
||||||
|
final String schemaOnly = processSqlString( sqlQuery, null, "my_schema", sessionFactory );
|
||||||
|
assertThat( schemaOnly ).endsWith( " my_schema.the_table" );
|
||||||
|
|
||||||
|
final String none = processSqlString( sqlQuery, null, null, sessionFactory );
|
||||||
|
assertThat( none ).endsWith( " the_table" );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String processSqlString(
|
||||||
|
String sqlQuery,
|
||||||
|
String catalogName,
|
||||||
|
String schemaName,
|
||||||
|
SessionFactoryImplementor sessionFactory) {
|
||||||
|
// Use a custom SqlStringGenerationContext to integrate the catalog and schema
|
||||||
|
final ExplicitSqlStringGenerationContext stringGenerationContext
|
||||||
|
= new ExplicitSqlStringGenerationContext( catalogName, schemaName, sessionFactory );
|
||||||
|
return new SQLQueryParser( sqlQuery, null, stringGenerationContext ).process();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.subselect;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import org.hibernate.annotations.Subselect;
|
||||||
|
import org.hibernate.cfg.MappingSettings;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
|
import org.hibernate.metamodel.spi.RuntimeMetamodelsImplementor;
|
||||||
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.hibernate.testing.orm.junit.Setting;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple tests that catalog/schema placeholders in {@link org.hibernate.annotations.Subselect}
|
||||||
|
* mappings are handled correctly.
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("JUnitMalformedDeclaration")
|
||||||
|
public class SubselectWithPlaceholdersUnitTests {
|
||||||
|
@Test
|
||||||
|
@DomainModel(annotatedClasses = {EntityInCatalog.class, EntityInCatalogAndSchema.class, EntityInSchema.class, EntityInLocalCatalog.class})
|
||||||
|
@SessionFactory(exportSchema = false)
|
||||||
|
void testPlaceholdersWithNone(SessionFactoryScope scope) {
|
||||||
|
final RuntimeMetamodelsImplementor runtimeMetamodels = scope.getSessionFactory().getRuntimeMetamodels();
|
||||||
|
final MappingMetamodelImplementor mappingMetamodel = runtimeMetamodels.getMappingMetamodel();
|
||||||
|
|
||||||
|
final EntityPersister entityInCatalogDescriptor = mappingMetamodel.getEntityDescriptor( EntityInCatalog.class );
|
||||||
|
assertThat( entityInCatalogDescriptor.getTableName() ).endsWith( " from table_in_catalog )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInSchemaDescriptor = mappingMetamodel.getEntityDescriptor( EntityInSchema.class );
|
||||||
|
assertThat( entityInSchemaDescriptor.getTableName() ).endsWith( " from table_in_schema )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInCatalogAndSchemaDescriptor = mappingMetamodel.getEntityDescriptor( EntityInCatalogAndSchema.class );
|
||||||
|
assertThat( entityInCatalogAndSchemaDescriptor.getTableName() ).endsWith( " from table_in_both )" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ServiceRegistry(settings = {
|
||||||
|
@Setting(name= MappingSettings.DEFAULT_CATALOG, value = "the_catalog")
|
||||||
|
})
|
||||||
|
@DomainModel(annotatedClasses = {EntityInCatalog.class, EntityInCatalogAndSchema.class, EntityInSchema.class, EntityInLocalCatalog.class})
|
||||||
|
@SessionFactory(exportSchema = false)
|
||||||
|
void testPlaceholdersWithCatalog(SessionFactoryScope scope) {
|
||||||
|
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||||
|
final RuntimeMetamodelsImplementor runtimeMetamodels = sessionFactory.getRuntimeMetamodels();
|
||||||
|
final MappingMetamodelImplementor mappingMetamodel = runtimeMetamodels.getMappingMetamodel();
|
||||||
|
|
||||||
|
final EntityPersister entityInCatalogDescriptor = mappingMetamodel.getEntityDescriptor( EntityInCatalog.class );
|
||||||
|
assertThat( entityInCatalogDescriptor.getTableName() ).endsWith( " from the_catalog.table_in_catalog )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInSchemaDescriptor = mappingMetamodel.getEntityDescriptor( EntityInSchema.class );
|
||||||
|
assertThat( entityInSchemaDescriptor.getTableName() ).endsWith( " from table_in_schema )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInCatalogAndSchemaDescriptor = mappingMetamodel.getEntityDescriptor( EntityInCatalogAndSchema.class );
|
||||||
|
assertThat( entityInCatalogAndSchemaDescriptor.getTableName() ).endsWith( " from the_catalog.table_in_both )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInLocalCatalogDescriptor = mappingMetamodel.getEntityDescriptor( EntityInLocalCatalog.class );
|
||||||
|
assertThat( entityInLocalCatalogDescriptor.getTableName() ).endsWith( " from local_catalog.table_in_local_catalog )" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ServiceRegistry(settings = {
|
||||||
|
@Setting(name= MappingSettings.DEFAULT_SCHEMA, value = "the_schema")
|
||||||
|
})
|
||||||
|
@DomainModel(annotatedClasses = {EntityInCatalog.class, EntityInCatalogAndSchema.class, EntityInSchema.class, EntityInLocalCatalog.class})
|
||||||
|
@SessionFactory(exportSchema = false)
|
||||||
|
void testPlaceholdersWithSchema(SessionFactoryScope scope) {
|
||||||
|
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||||
|
final RuntimeMetamodelsImplementor runtimeMetamodels = sessionFactory.getRuntimeMetamodels();
|
||||||
|
final MappingMetamodelImplementor mappingMetamodel = runtimeMetamodels.getMappingMetamodel();
|
||||||
|
|
||||||
|
|
||||||
|
final EntityPersister entityInCatalogDescriptor = mappingMetamodel.getEntityDescriptor( EntityInCatalog.class );
|
||||||
|
assertThat( entityInCatalogDescriptor.getTableName() ).endsWith( " from table_in_catalog )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInSchemaDescriptor = mappingMetamodel.getEntityDescriptor( EntityInSchema.class );
|
||||||
|
assertThat( entityInSchemaDescriptor.getTableName() ).endsWith( " from the_schema.table_in_schema )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInCatalogAndSchemaDescriptor = mappingMetamodel.getEntityDescriptor( EntityInCatalogAndSchema.class );
|
||||||
|
assertThat( entityInCatalogAndSchemaDescriptor.getTableName() ).endsWith( " from the_schema.table_in_both )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInLocalCatalogDescriptor = mappingMetamodel.getEntityDescriptor( EntityInLocalCatalog.class );
|
||||||
|
assertThat( entityInLocalCatalogDescriptor.getTableName() ).endsWith( " from local_catalog.table_in_local_catalog )" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ServiceRegistry(settings = {
|
||||||
|
@Setting(name= MappingSettings.DEFAULT_CATALOG, value = "the_catalog"),
|
||||||
|
@Setting(name= MappingSettings.DEFAULT_SCHEMA, value = "the_schema")
|
||||||
|
})
|
||||||
|
@DomainModel(annotatedClasses = {EntityInCatalog.class, EntityInCatalogAndSchema.class, EntityInSchema.class, EntityInLocalCatalog.class})
|
||||||
|
@SessionFactory(exportSchema = false)
|
||||||
|
void testPlaceholdersWithCatalogAndSchema(SessionFactoryScope scope) {
|
||||||
|
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||||
|
final RuntimeMetamodelsImplementor runtimeMetamodels = sessionFactory.getRuntimeMetamodels();
|
||||||
|
final MappingMetamodelImplementor mappingMetamodel = runtimeMetamodels.getMappingMetamodel();
|
||||||
|
|
||||||
|
final EntityPersister entityInCatalogDescriptor = mappingMetamodel.getEntityDescriptor( EntityInCatalog.class );
|
||||||
|
assertThat( entityInCatalogDescriptor.getTableName() ).endsWith( " from the_catalog.table_in_catalog )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInSchemaDescriptor = mappingMetamodel.getEntityDescriptor( EntityInSchema.class );
|
||||||
|
assertThat( entityInSchemaDescriptor.getTableName() ).endsWith( " from the_schema.table_in_schema )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInCatalogAndSchemaDescriptor = mappingMetamodel.getEntityDescriptor( EntityInCatalogAndSchema.class );
|
||||||
|
assertThat( entityInCatalogAndSchemaDescriptor.getTableName() ).endsWith( " from the_catalog.the_schema.table_in_both )" );
|
||||||
|
|
||||||
|
final EntityPersister entityInLocalCatalogDescriptor = mappingMetamodel.getEntityDescriptor( EntityInLocalCatalog.class );
|
||||||
|
assertThat( entityInLocalCatalogDescriptor.getTableName() ).endsWith( " from local_catalog.table_in_local_catalog )" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Subselect("select id, name from {h-catalog}table_in_catalog")
|
||||||
|
public static class EntityInCatalog {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Subselect("select id, name from {h-catalog}table_in_local_catalog")
|
||||||
|
@Table(catalog = "local_catalog")
|
||||||
|
public static class EntityInLocalCatalog {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "EntityInCatalogAndSchema")
|
||||||
|
@Subselect("select id, name from {h-domain}table_in_both")
|
||||||
|
public static class EntityInCatalogAndSchema {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "EntityInSchema")
|
||||||
|
@Subselect("select id, name from {h-schema}table_in_schema")
|
||||||
|
public static class EntityInSchema {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue