Skip to content

Commit

Permalink
HHH-7913 - Schema replacement in <subselect> / @subselect
Browse files Browse the repository at this point in the history
  • Loading branch information
sebersole committed Nov 21, 2024
1 parent c8a6678 commit 2109556
Show file tree
Hide file tree
Showing 8 changed files with 451 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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:

[[mapping-Subselect-refresh-find-example]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@ public interface SqlStringGenerationContext {
Identifier getDefaultCatalog();

/**
* @param explicitCatalogOrNull An explicitly configured catalog, or {@code null}.
* @return The given identifier if non-{@code null}, or the default catalog otherwise.
* Interpret the incoming catalog, returning the incoming value if it is non-null.
* 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.
Expand All @@ -54,10 +58,14 @@ public interface SqlStringGenerationContext {
Identifier getDefaultSchema();

/**
* @param explicitSchemaOrNull An explicitly configured schema, or {@code null}.
* @return The given identifier if non-{@code null}, or the default schema otherwise.
* Interpret the incoming schema, returning the incoming value if it is non-null.
* 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
Expand Down Expand Up @@ -101,4 +109,49 @@ public interface SqlStringGenerationContext {
* @return {@code true} if and only if this is a migration
*/
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,48 +146,11 @@ public Identifier getDefaultCatalog() {
return defaultCatalog;
}

@Override
public Identifier catalogWithDefault(Identifier explicitCatalogOrNull) {
return explicitCatalogOrNull != null ? explicitCatalogOrNull : defaultCatalog;
}

@Override
public Identifier getDefaultSchema() {
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
public String format(QualifiedTableName qualifiedName) {
return qualifiedObjectNameFormatter.format( withDefaults( qualifiedName ), dialect );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4620,9 +4620,21 @@ protected int determineTableNumberForColumn(String columnName) {
}

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
public EntityEntryFactory getEntityEntryFactory() {
return this.entityEntryFactory;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
*/
package org.hibernate.query.sql.internal;

import java.util.Map;

import org.hibernate.QueryException;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
Expand All @@ -14,6 +12,8 @@
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;

import java.util.Map;

/**
* Substitutes escape sequences of form {@code {alias}},
* {@code {alias.field}}, and {@code {alias.*}} in a
Expand All @@ -25,11 +25,11 @@
* @author Paul Benedict
*/
public class SQLQueryParser {

private final SessionFactoryImplementor factory;
private final String originalQueryString;
private final ParserContext context;

private final SqlStringGenerationContext sqlStringGenerationContext;

private long aliasesFound;

public interface ParserContext {
Expand All @@ -43,9 +43,16 @@ public interface ParserContext {
}

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.context = context;
this.factory = factory;
this.sqlStringGenerationContext = sqlStringGenerationContext;
}

public boolean queryHasAliases() {
Expand Down Expand Up @@ -169,10 +176,9 @@ else if ( context.isEntityAlias( aliasName ) ) {
}

private void handlePlaceholder(String token, StringBuilder result) {
final SqlStringGenerationContext context = factory.getSqlStringGenerationContext();
final Identifier defaultCatalog = context.getDefaultCatalog();
final Identifier defaultSchema = context.getDefaultSchema();
final Dialect dialect = context.getDialect();
final Identifier defaultCatalog = sqlStringGenerationContext.getDefaultCatalog();
final Identifier defaultSchema = sqlStringGenerationContext.getDefaultSchema();
final Dialect dialect = sqlStringGenerationContext.getDialect();
switch (token) {
case "h-domain":
if ( defaultCatalog != null ) {
Expand Down
Loading

0 comments on commit 2109556

Please sign in to comment.