hibernate/hibernate-orm · error · IllegalQueryOperationException

Unsupported tuple assignment in update query with joins.

Error message

Unsupported tuple assignment in update query with joins.

What it means

updateSourceAsSubquery rewrites a bulk UPDATE that references other tables (update with joins) into an inline-view/subquery form — used by visitUpdateStatementEmulateMerge (H2, HANA, HSQLDB, DB2) and visitUpdateStatementEmulateTupleSet (DB2). When an assignment targets multiple columns (columnReferences.size() > 1) but the assigned value is not a SqlTuple, the rewrite cannot split the value per column and throws IllegalQueryOperationException.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java:1615

		final SelectClause selectClause = inlineView.getSelectClause();
		final List<Assignment> assignments = statement.getAssignments();
		final List<String> columnNames = new ArrayList<>( assignments.size() );
		for ( Assignment assignment : assignments ) {
			final List<ColumnReference> columnReferences = assignment.getAssignable().getColumnReferences();
			final Expression assignedValue = assignment.getAssignedValue();
			if ( columnReferences.size() == 1 ) {
				selectClause.addSqlSelection( new SqlSelectionImpl( assignedValue ) );
				columnNames.add( "c" + columnNames.size() );
			}
			else if ( assignedValue instanceof SqlTuple sqlTuple ) {
				final List<? extends Expression> expressions = sqlTuple.getExpressions();
				for ( int i = 0; i < columnReferences.size(); i++ ) {
					selectClause.addSqlSelection( new SqlSelectionImpl( expressions.get( i ) ) );
					columnNames.add( "c" + columnNames.size() );
				}
			}
			else {
				throw new IllegalQueryOperationException( "Unsupported tuple assignment in update query with joins." );
			}
		}
		if ( !correlated ) {
			final TableGroup dmlTargetTableGroup = statement.getFromClause().getRoots().get( 0 );
			assert dmlTargetTableGroup.getPrimaryTableReference() == statement.getTargetTable();
			final EntityMappingType entityMappingType = dmlTargetTableGroup.getModelPart().asEntityMappingType();
			final EntityRowIdMapping rowIdMapping =
					entityMappingType == null ? null : entityMappingType.getRowIdMapping();
			final String rowIdExpression = dialect.rowId( null );
			if ( rowIdMapping != null ) {
				selectClause.addSqlSelection( new SqlSelectionImpl(
						new ColumnReference( statement.getTargetTable(), rowIdMapping )
				) );
				columnNames.add( "c" + columnNames.size() );
			}
			else if ( rowIdExpression == null ) {
				final var identifierTableMapping = statement.getMutationTarget().getIdentifierTableMapping();
				identifierTableMapping.getKeyDetails().forEachSelectable( 0,

View on GitHub (pinned to fad1729dce)

Solutions

  1. Rewrite the multi-column assignment as separate single-column assignments in the SET clause.
  2. Remove the join from the bulk update (move the join condition into a subquery WHERE), so no emulation rewrite is needed.
  3. Use a native UPDATE ... (correlated subquery) statement for the multi-column assignment.
  4. As a last resort perform the update row-by-row through managed entities.

Example fix

// before — tuple assignment to multiple columns with a join (fails on DB2/H2 emulation)
int n = session.createQuery(
    "update Order o set o.billing = (select a from Address a where a.id = o.addressId) " +
    "where o.status = 'NEW' and o.customer.id = :cid")
    .executeUpdate();

// after — split into single-column assignments
int n = session.createQuery(
    "update Order o set o.billing.street = (select a.street from Address a where a.id = o.addressId), " +
    "o.billing.zip = (select a.zip from Address a where a.id = o.addressId) " +
    "where o.status = 'NEW' and o.customer.id = :cid")
    .executeUpdate();
Defensive patterns

Strategy: try-catch

Validate before calling

org.hibernate.dialect.Dialect d = sessionFactory.getJdbcServices().getDialect();
boolean updateJoinEmulated = d instanceof org.hibernate.dialect.DB2Dialect
        || d instanceof org.hibernate.dialect.H2Dialect
        || d instanceof org.hibernate.dialect.HANADialect
        || d instanceof org.hibernate.dialect.HSQLDialect;
if (updateJoinEmulated && assignmentTargetsMultipleColumns(hql)) {
    // tuple assignment cannot be emulated: split into single-column assignments up front
    hql = splitTupleAssignments(hql);
}

Try / catch

try { session.createQuery(updateHql).executeUpdate(); }
catch (org.hibernate.query.IllegalQueryOperationException e) {
    if (e.getMessage().equals("Unsupported tuple assignment in update query with joins.")) {
        // split multi-column SET into single-column assignments and retry
        session.createQuery(splitTupleAssignments(updateHql)).executeUpdate();
    } else { throw e; }
}

Prevention

When it happens

Trigger: HQL/JPQL bulk update with joins whose SET assigns several columns at once from a non-tuple source, e.g., 'set (a,b) = (select x,y from ...)' or a multi-column assignable paired with a scalar/subquery expression, executed on H2, HANA, HSQLDB or DB2 where update-with-join must be emulated.

Common situations: Assigning embeddables or composite foreign keys in bulk updates ('set e.address = ...') on dialects without native UPDATE...FROM; criteria bulk update mutation queries; queries that work on MySQL/PostgreSQL failing in tests on H2.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/494d709f96950228. Report an issue: GitHub.