HHH-15161 Infer parameter types in insert-select and avoid rendering cast in simple insert-select

This commit is contained in:
Christian Beikov 2022-04-02 13:08:04 +02:00
parent a4b6b237dd
commit 6a7bec612f
9 changed files with 113 additions and 61 deletions

View File

@ -32,6 +32,7 @@ import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.UnionTableGroup;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.InsertStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
@ -125,7 +126,9 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
// Even if Oracle supports the OFFSET/FETCH clause, there are conditions where we still want to use the ROWNUM pagination
if ( supportsOffsetFetchClause() ) {
// When the query has no sort specifications and offset, we want to use the ROWNUM pagination as that is a special locking case
return !queryPart.hasSortSpecifications() && !hasOffset( queryPart );
return !queryPart.hasSortSpecifications() && !hasOffset( queryPart )
// Workaround an Oracle bug, segmentation fault for insert queries with a plain query group and fetch clause
|| queryPart instanceof QueryGroup && getClauseStack().isEmpty() && getStatement() instanceof InsertStatement;
}
return true;
}

View File

@ -191,7 +191,14 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
);
appendSql( "* from (" );
renderQueryGroup( queryGroup, false );
appendSql( ") grp_" );
appendSql( ") grp_(c0" );
// Sybase doesn't have implicit names for non-column select expressions, so we need to assign names
final int itemCount = queryGroup.getFirstQuerySpec().getSelectClause().getSqlSelections().size();
for (int i = 1; i < itemCount; i++) {
appendSql( ",c" );
appendSql( i );
}
appendSql( ')' );
visitOrderBy( queryGroup.getSortSpecifications() );
}
else {

View File

@ -21,7 +21,7 @@ import org.hibernate.query.hql.spi.SqmCreationProcessingState;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.sqm.internal.SqmDmlCreationProcessingState;
import org.hibernate.query.sqm.internal.SqmQuerySpecCreationProcessingStateStandardImpl;
import org.hibernate.query.sqm.internal.SqmQueryPartCreationProcessingStateStandardImpl;
import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker;
import org.hibernate.query.sqm.spi.SqmCreationContext;
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
@ -266,7 +266,7 @@ public class QuerySplitter {
final SqmSelectStatement<R> copy = new SqmSelectStatement<>( statement.nodeBuilder() );
processingStateStack.push(
new SqmQuerySpecCreationProcessingStateStandardImpl(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
copy,
this

View File

@ -94,7 +94,7 @@ import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.internal.ParameterCollector;
import org.hibernate.query.sqm.internal.SqmCreationProcessingStateImpl;
import org.hibernate.query.sqm.internal.SqmDmlCreationProcessingState;
import org.hibernate.query.sqm.internal.SqmQuerySpecCreationProcessingStateStandardImpl;
import org.hibernate.query.sqm.internal.SqmQueryPartCreationProcessingStateStandardImpl;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.query.sqm.spi.ParameterDeclarationContext;
import org.hibernate.query.sqm.spi.SqmCreationContext;
@ -383,7 +383,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
parameterCollector = selectStatement;
processingStateStack.push(
new SqmQuerySpecCreationProcessingStateStandardImpl(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
selectStatement,
this
@ -470,7 +470,6 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
try {
for ( HqlParser.SimplePathContext stateFieldCtx : targetFieldsSpecContext.simplePath() ) {
final SqmPath<?> stateField = (SqmPath<?>) visitSimplePath( stateFieldCtx );
// todo : validate each resolved stateField...
insertStatement.addInsertTargetStateField( stateField );
}
}
@ -509,7 +508,6 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
for ( HqlParser.SimplePathContext stateFieldCtx : targetFieldsSpecContext.simplePath() ) {
final SqmPath<?> stateField = (SqmPath<?>) visitSimplePath( stateFieldCtx );
// todo : validate each resolved stateField...
insertStatement.addInsertTargetStateField( stateField );
}
@ -518,7 +516,6 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
finally {
processingStateStack.pop();
}
}
}
@ -639,9 +636,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
if ( children.size() > 3 ) {
final SqmCreationProcessingState firstProcessingState = processingStateStack.pop();
processingStateStack.push(
new SqmQuerySpecCreationProcessingStateStandardImpl(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
(SqmSelectQuery<?>) firstProcessingState.getProcessingQuery(),
firstProcessingState.getProcessingQuery(),
this
)
);
@ -676,9 +673,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
(HqlParser.OrderedQueryContext) children.get( i + 1 );
final List<SqmQueryPart<Object>> queryParts;
processingStateStack.push(
new SqmQuerySpecCreationProcessingStateStandardImpl(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
(SqmSelectQuery<?>) firstProcessingState.getProcessingQuery(),
firstProcessingState.getProcessingQuery(),
this
)
);
@ -709,7 +706,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
try {
final SqmSelectStatement<Object> selectStatement = new SqmSelectStatement<>( creationContext.getNodeBuilder() );
processingStateStack.push(
new SqmQuerySpecCreationProcessingStateStandardImpl(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
selectStatement,
this
@ -4531,7 +4528,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
);
processingStateStack.push(
new SqmQuerySpecCreationProcessingStateStandardImpl(
new SqmQueryPartCreationProcessingStateStandardImpl(
processingStateStack.getCurrent(),
subQuery,
this

View File

@ -1,21 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.hql.spi;
import org.hibernate.Incubating;
import org.hibernate.query.sqm.tree.select.SqmSelectQuery;
/**
* SqmCreationProcessingState specialization for processing a SQM query-spec
*
* @author Steve Ebersole
*/
@Incubating
public interface SqmQuerySpecCreationProcessingState extends SqmCreationProcessingState {
@Override
SqmSelectQuery<?> getProcessingQuery();
}

View File

@ -8,8 +8,7 @@ package org.hibernate.query.sqm.internal;
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.hql.spi.SqmQuerySpecCreationProcessingState;
import org.hibernate.query.sqm.tree.select.SqmSelectQuery;
import org.hibernate.query.sqm.tree.SqmQuery;
/**
* Models the state related to parsing a sqm spec. As a "linked list" to account for
@ -18,15 +17,13 @@ import org.hibernate.query.sqm.tree.select.SqmSelectQuery;
* @author Steve Ebersole
* @author Andrea Boriero
*/
public class SqmQuerySpecCreationProcessingStateStandardImpl
extends SqmCreationProcessingStateImpl
implements SqmQuerySpecCreationProcessingState {
public class SqmQueryPartCreationProcessingStateStandardImpl extends SqmCreationProcessingStateImpl {
private final SqmCreationProcessingState parentState;
public SqmQuerySpecCreationProcessingStateStandardImpl(
public SqmQueryPartCreationProcessingStateStandardImpl(
SqmCreationProcessingState parentState,
SqmSelectQuery<?> processingQuery,
SqmQuery<?> processingQuery,
SqmCreationState creationState) {
super( processingQuery, creationState );
this.parentState = parentState;
@ -37,8 +34,4 @@ public class SqmQuerySpecCreationProcessingStateStandardImpl
return parentState;
}
@Override
public SqmSelectQuery<?> getProcessingQuery() {
return (SqmSelectQuery<?>) super.getProcessingQuery();
}
}

View File

@ -108,7 +108,6 @@ import org.hibernate.query.QueryLogging;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.SemanticException;
import org.hibernate.query.criteria.JpaPath;
import org.hibernate.spi.NavigablePath;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.query.spi.QueryParameterBindings;
@ -255,6 +254,7 @@ import org.hibernate.query.sqm.tree.select.SqmSubQuery;
import org.hibernate.query.sqm.tree.update.SqmAssignment;
import org.hibernate.query.sqm.tree.update.SqmSetClause;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.SqlTreeCreationException;
@ -1886,10 +1886,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final SelectClause sqlSelectClause = currentQuerySpec().getSelectClause();
if ( selectClause == null ) {
final SqmFrom<?, ?> implicitSelection = determineImplicitSelection( (SqmQuerySpec<?>) currentSqmQueryPart );
visitSelection( new SqmSelection<>( implicitSelection, implicitSelection.nodeBuilder() ) );
visitSelection( 0, new SqmSelection<>( implicitSelection, implicitSelection.nodeBuilder() ) );
}
else {
super.visitSelectClause( selectClause );
final List<SqmSelection<?>> selections = selectClause.getSelections();
for ( int i = 0; i < selections.size(); i++ ) {
visitSelection( i, selections.get( i ) );
}
sqlSelectClause.makeDistinct( selectClause.isDistinct() );
}
return sqlSelectClause;
@ -1906,6 +1909,20 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public Void visitSelection(SqmSelection<?> sqmSelection) {
return visitSelection(
currentSqmQueryPart.getFirstQuerySpec().getSelectClause().getSelections().indexOf( sqmSelection ),
sqmSelection
);
}
public Void visitSelection(int index, SqmSelection<?> sqmSelection) {
final boolean contributesToTopLevelSelectClause = currentClauseStack.depth() == 1 && currentClauseStack.getCurrent() == Clause.SELECT;
// Only infer the type on the "top level" select clauses
final boolean inferTargetPath = statement instanceof SqmInsertSelectStatement<?> && contributesToTopLevelSelectClause;
if ( inferTargetPath ) {
final SqmPath<?> path = ( (SqmInsertSelectStatement<?>) statement ).getInsertionTargetPaths().get( index );
inferrableTypeAccessStack.push( () -> determineValueMapping( path ) );
}
final List<Map.Entry<String, DomainResultProducer<?>>> resultProducers;
final SqmSelectableNode<?> selectionNode = sqmSelection.getSelectableNode();
if ( selectionNode instanceof SqmJpaCompoundSelection<?> ) {
@ -1937,7 +1954,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
final Stack<SqlAstProcessingState> processingStateStack = getProcessingStateStack();
final boolean needsDomainResults = domainResults != null && currentClauseContributesToTopLevelSelectClause();
final boolean needsDomainResults = domainResults != null && contributesToTopLevelSelectClause;
final boolean collectDomainResults;
if ( processingStateStack.depth() == 1 ) {
collectDomainResults = needsDomainResults;
@ -2002,14 +2019,12 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
);
}
if ( inferTargetPath ) {
inferrableTypeAccessStack.pop();
}
return null;
}
private boolean currentClauseContributesToTopLevelSelectClause() {
// The current clause contributes to the top level select if the clause stack contains just SELECT
return currentClauseStack.findCurrentFirst( clause -> clause == Clause.SELECT ? null : clause ) == null;
}
protected Expression resolveGroupOrOrderByExpression(SqmExpression<?> groupByClauseExpression) {
final int sqmPosition;
final NavigablePath path;

View File

@ -2903,7 +2903,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
appendSql( OPEN_PARENTHESIS );
}
appendSql( "select " );
if ( getClauseStack().isEmpty() ) {
if ( getClauseStack().isEmpty() && !( statement instanceof InsertStatement ) ) {
appendSql( '*' );
}
else {
@ -3097,6 +3097,14 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
selectItemsToInline = null;
}
final SqlAstNodeRenderingMode original = parameterRenderingMode;
final SqlAstNodeRenderingMode defaultRenderingMode;
if ( statement instanceof InsertStatement && clauseStack.depth() == 1 && queryPartStack.depth() == 1 ) {
// Databases support inferring parameter types for simple insert-select statements
defaultRenderingMode = SqlAstNodeRenderingMode.DEFAULT;
}
else {
defaultRenderingMode = SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER;
}
if ( needsSelectAliases || referenceStrategy == SelectItemReferenceStrategy.ALIAS && hasSelectAliasInGroupByClause() ) {
String separator = NO_SEPARATOR;
if ( columnAliases == null ) {
@ -3107,7 +3115,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
parameterRenderingMode = SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS;
}
else {
parameterRenderingMode = SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER;
parameterRenderingMode = defaultRenderingMode;
}
visitSqlSelection( sqlSelection );
parameterRenderingMode = original;
@ -3124,7 +3132,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
parameterRenderingMode = SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS;
}
else {
parameterRenderingMode = SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER;
parameterRenderingMode = defaultRenderingMode;
}
visitSqlSelection( sqlSelection );
parameterRenderingMode = original;
@ -3146,7 +3154,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
parameterRenderingMode = SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS;
}
else {
parameterRenderingMode = SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER;
parameterRenderingMode = defaultRenderingMode;
}
visitSqlSelection( sqlSelection );
parameterRenderingMode = original;
@ -3162,7 +3170,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
parameterRenderingMode = SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS;
}
else {
parameterRenderingMode = SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER;
parameterRenderingMode = defaultRenderingMode;
}
visitSqlSelection( sqlSelection );
appendSql( WHITESPACE );

View File

@ -273,6 +273,56 @@ public class BulkManipulationTest extends BaseCoreFunctionalTestCase {
data.cleanup();
}
@Test
@TestForIssue( jiraKey = "HHH-15161")
public void testInsertWithNullParamValue() {
TestData data = new TestData();
data.prepare();
Session s = openSession();
Transaction t = s.beginTransaction();
Query q = s.createQuery( "insert into Pickup (id, owner, vin) select id, :owner, vin from Car" );
q.setParameter( "owner", null );
q.executeUpdate();
t.commit();
t = s.beginTransaction();
s.createQuery( "delete Vehicle" ).executeUpdate();
t.commit();
s.close();
data.cleanup();
}
@Test
@TestForIssue( jiraKey = "HHH-15161")
public void testInsertWithNullParamValueSetOperation() {
TestData data = new TestData();
data.prepare();
Session s = openSession();
Transaction t = s.beginTransaction();
Query q = s.createQuery( "insert into Pickup (id, owner, vin) (select id, :owner, vin from Car union all select id, :owner, vin from Car) order by 1 limit 1" );
q.setParameter( "owner", null );
q.executeUpdate();
t.commit();
t = s.beginTransaction();
s.createQuery( "delete Vehicle" ).executeUpdate();
t.commit();
s.close();
data.cleanup();
}
@Test
public void testInsertWithMultipleNamedParams() {
TestData data = new TestData();