{"record":{"id":"4f988dc9c843aab8","repo":"hibernate/hibernate-orm","slug":"locking-with-joins-is-not-supported","errorCode":null,"errorMessage":"Locking with joins is not supported","messagePattern":"Locking with joins is not supported","errorType":"exception","errorClass":"IllegalQueryOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/AltibaseSqlAstTranslator.java","lineNumber":242,"sourceCode":"\t\telse {\n\t\t\tsuper.visitQuerySpec( querySpec );\n\t\t}\n\t}\n\n\tprotected boolean shouldEmulateFetchClause(QueryPart queryPart) {\n\t\t// Check if current query part is already row numbering to avoid infinite recursion\n\t\treturn useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart\n\t\t\t\t&& getDialect().supportsWindowFunctions() && !isRowsOnlyFetchClauseType( queryPart );\n\t}\n\n\t@Override\n\tprotected LockStrategy determineLockingStrategy(QuerySpec querySpec, Locking.FollowOn followOnStrategy) {\n\t\tfinal LockStrategy lockStrategy = super.determineLockingStrategy( querySpec, followOnStrategy );\n\t\tfinal LockingClauseStrategy lockingClauseStrategy = getLockingClauseStrategy();\n\t\tif ( lockingClauseStrategy != null && lockingClauseStrategy.containsJoins() ) {\n\t\t\t// Altibase does not allow FOR UPDATE when the query also contains joins.\n\t\t\tif ( followOnStrategy == Locking.FollowOn.DISALLOW ) {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Locking with joins is not supported\" );\n\t\t\t}\n\t\t\telse if ( followOnStrategy == Locking.FollowOn.IGNORE ) {\n\t\t\t\treturn LockStrategy.NONE;\n\t\t\t}\n\t\t\treturn LockStrategy.FOLLOW_ON;\n\t\t}\n\t\treturn lockStrategy;\n\t}\n\n\t@Override\n\tprotected void renderPartitionItem(Expression expression) {\n\t\tif ( expression instanceof Literal ) {\n\t\t\tappendSql( \"'0' || '0'\" );\n\t\t}\n\t\telse if ( expression instanceof Summarization ) {\n\t\t\t// This could theoretically be emulated by rendering all grouping variations of the query and\n\t\t\t// connect them via union all but that's probably pretty inefficient and would have to happen\n\t\t\t// on the query spec level","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/AltibaseSqlAstTranslator.java#L224-L260","documentation":"For aggregate-typed columns (SQL STRUCT/aggregate and JSON mappings), AggregateColumnWriteExpression keeps parallel arrays - selectableMappings[] and valueExpressions[] - and getValueExpression(selectableMapping) resolves by instance identity (==, not equals). IllegalArgumentException means the SelectableMapping you asked with is not one of the mappings this aggregate write expression was built from, i.e. the caller used a mapping from a different part of the mapping model.","triggerScenarios":"Custom JdbcType/SqlAstTranslator or aggregate-mapping code calling getValueExpression with a SelectableMapping obtained elsewhere (another aggregate part, a rebuilt mapping instance) instead of one delivered by this aggregate mapping's forEachSelectable; also possible after metadata enhancement creates duplicate mapping instances for the same column.","commonSituations":"Projects with custom aggregate/JSON JdbcTypes after a Hibernate upgrade changed how selectable mapping instances are created and cached; code that caches SelectableMappings across SessionFactory rebuilds; mixing mapping instances from two metamodels.","solutions":["Always obtain the SelectableMapping from the same aggregate mapping that produced the write expression: iterate mapping.forEachSelectable(...) and pass exactly those instances.","Never cache SelectableMapping instances across SessionFactory lifecycles - re-fetch them from the current mapping.","Align custom aggregate-column rendering code with the current Hibernate version's AggregateColumnWriteExpression API.","Upgrade Hibernate; if the call comes from core rendering, report HHH with the aggregate mapping."],"exampleFix":"// before: asking with a mapping from another source\nExpression e = writeExpr.getValueExpression( foreignMapping.getSelectable() );\n\n// after: resolve through the aggregate's own selectables\nfinal Expression[] found = new Expression[1];\naggregateMapping.forEachSelectable( (i, sel) -> {\n    if ( sel == candidateSelectable ) found[0] = writeExpr.getValueExpression( sel );\n} );","handlingStrategy":"type-guard","validationCode":"// Verify the candidate mapping belongs to this aggregate write expression before asking\nboolean owned = false;\naggregateMapping.forEachSelectable( (i, sel) -> { if ( sel == candidateSelectable ) owned = true; } );\nif ( !owned ) {\n    throw new IllegalArgumentException(\"candidateSelectable is not part of this aggregate mapping\");\n}","typeGuard":"// Java: only resolve value expressions through the aggregate's own selectables\nstatic Expression valueExpressionFor(AggregateColumnWriteExpression write,\n                                     AggregateMapping owner,\n                                     SelectableMapping candidate) {\n    final boolean[] found = { false };\n    owner.forEachSelectable( (i, sel) -> { if ( sel == candidate ) found[0] = true; } );\n    if ( !found[0] ) {\n        return null; // caller falls back / reports instead of risking IllegalArgumentException\n    }\n    return write.getValueExpression( candidate );\n}","tryCatchPattern":"try {\n    return writeExpr.getValueExpression( selectable );\n} catch ( IllegalArgumentException e ) {\n    if ( e.getMessage() != null && e.getMessage().startsWith(\"Couldn't find value expression\") ) {\n        // re-resolve the selectable from the current aggregate mapping and retry\n        return writeExpr.getValueExpression( resolveFreshSelectable( aggregateMapping, selectable ) );\n    }\n    throw e;\n}","preventionTips":["Never pass SelectableMapping instances that were not handed to you by the same aggregate mapping's forEachSelectable.","Do not cache SelectableMapping instances across SessionFactory rebuilds.","Re-verify custom JdbcType rendering code against the current Hibernate version's aggregate write API after upgrades."],"tags":["aggregate-mapping","json","struct","internal","api-misuse"],"backgroundTag":"aggregate-column-write-mapping","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}