{"record":{"id":"494d709f96950228","repo":"hibernate/hibernate-orm","slug":"unsupported-tuple-assignment-in-update-query-with","errorCode":null,"errorMessage":"Unsupported tuple assignment in update query with joins.","messagePattern":"Unsupported tuple assignment in update query with joins\\.","errorType":"exception","errorClass":"IllegalQueryOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java","lineNumber":1615,"sourceCode":"\t\tfinal SelectClause selectClause = inlineView.getSelectClause();\n\t\tfinal List<Assignment> assignments = statement.getAssignments();\n\t\tfinal List<String> columnNames = new ArrayList<>( assignments.size() );\n\t\tfor ( Assignment assignment : assignments ) {\n\t\t\tfinal List<ColumnReference> columnReferences = assignment.getAssignable().getColumnReferences();\n\t\t\tfinal Expression assignedValue = assignment.getAssignedValue();\n\t\t\tif ( columnReferences.size() == 1 ) {\n\t\t\t\tselectClause.addSqlSelection( new SqlSelectionImpl( assignedValue ) );\n\t\t\t\tcolumnNames.add( \"c\" + columnNames.size() );\n\t\t\t}\n\t\t\telse if ( assignedValue instanceof SqlTuple sqlTuple ) {\n\t\t\t\tfinal List<? extends Expression> expressions = sqlTuple.getExpressions();\n\t\t\t\tfor ( int i = 0; i < columnReferences.size(); i++ ) {\n\t\t\t\t\tselectClause.addSqlSelection( new SqlSelectionImpl( expressions.get( i ) ) );\n\t\t\t\t\tcolumnNames.add( \"c\" + columnNames.size() );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Unsupported tuple assignment in update query with joins.\" );\n\t\t\t}\n\t\t}\n\t\tif ( !correlated ) {\n\t\t\tfinal TableGroup dmlTargetTableGroup = statement.getFromClause().getRoots().get( 0 );\n\t\t\tassert dmlTargetTableGroup.getPrimaryTableReference() == statement.getTargetTable();\n\t\t\tfinal EntityMappingType entityMappingType = dmlTargetTableGroup.getModelPart().asEntityMappingType();\n\t\t\tfinal EntityRowIdMapping rowIdMapping =\n\t\t\t\t\tentityMappingType == null ? null : entityMappingType.getRowIdMapping();\n\t\t\tfinal String rowIdExpression = dialect.rowId( null );\n\t\t\tif ( rowIdMapping != null ) {\n\t\t\t\tselectClause.addSqlSelection( new SqlSelectionImpl(\n\t\t\t\t\t\tnew ColumnReference( statement.getTargetTable(), rowIdMapping )\n\t\t\t\t) );\n\t\t\t\tcolumnNames.add( \"c\" + columnNames.size() );\n\t\t\t}\n\t\t\telse if ( rowIdExpression == null ) {\n\t\t\t\tfinal var identifierTableMapping = statement.getMutationTarget().getIdentifierTableMapping();\n\t\t\t\tidentifierTableMapping.getKeyDetails().forEachSelectable( 0,","sourceCodeStart":1597,"sourceCodeEnd":1633,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java#L1597-L1633","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Rewrite the multi-column assignment as separate single-column assignments in the SET clause.","Remove the join from the bulk update (move the join condition into a subquery WHERE), so no emulation rewrite is needed.","Use a native UPDATE ... (correlated subquery) statement for the multi-column assignment.","As a last resort perform the update row-by-row through managed entities."],"exampleFix":"// before — tuple assignment to multiple columns with a join (fails on DB2/H2 emulation)\nint n = session.createQuery(\n    \"update Order o set o.billing = (select a from Address a where a.id = o.addressId) \" +\n    \"where o.status = 'NEW' and o.customer.id = :cid\")\n    .executeUpdate();\n\n// after — split into single-column assignments\nint n = session.createQuery(\n    \"update Order o set o.billing.street = (select a.street from Address a where a.id = o.addressId), \" +\n    \"o.billing.zip = (select a.zip from Address a where a.id = o.addressId) \" +\n    \"where o.status = 'NEW' and o.customer.id = :cid\")\n    .executeUpdate();","handlingStrategy":"try-catch","validationCode":"org.hibernate.dialect.Dialect d = sessionFactory.getJdbcServices().getDialect();\nboolean updateJoinEmulated = d instanceof org.hibernate.dialect.DB2Dialect\n        || d instanceof org.hibernate.dialect.H2Dialect\n        || d instanceof org.hibernate.dialect.HANADialect\n        || d instanceof org.hibernate.dialect.HSQLDialect;\nif (updateJoinEmulated && assignmentTargetsMultipleColumns(hql)) {\n    // tuple assignment cannot be emulated: split into single-column assignments up front\n    hql = splitTupleAssignments(hql);\n}","typeGuard":null,"tryCatchPattern":"try { session.createQuery(updateHql).executeUpdate(); }\ncatch (org.hibernate.query.IllegalQueryOperationException e) {\n    if (e.getMessage().equals(\"Unsupported tuple assignment in update query with joins.\")) {\n        // split multi-column SET into single-column assignments and retry\n        session.createQuery(splitTupleAssignments(updateHql)).executeUpdate();\n    } else { throw e; }\n}","preventionTips":["In bulk updates with joins, always assign one column per SET item.","Prefer moving join conditions into WHERE subqueries so no emulation is needed.","Cover bulk mutation queries with tests on every supported dialect."],"tags":["hibernate","orm","bulk-update","jpql","db2","h2","emulation"],"backgroundTag":"bulk-update-join-emulation-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}