{"record":{"id":"05731e259cea3983","repo":"hibernate/hibernate-orm","slug":"couldn-t-infer-conflict-constraint-columns","errorCode":null,"errorMessage":"Couldn't infer conflict constraint columns","messagePattern":"Couldn't infer conflict constraint columns","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java","lineNumber":1100,"sourceCode":"\t\t\t\t\tnull,\n\t\t\t\t\tsessionFactory\n\t\t\t);\n\t\t\tsubquery.getSelectClause().addSqlSelection(\n\t\t\t\t\tnew SqlSelectionImpl( new QueryLiteral<>( 1, nodeBuilder.getIntegerType() ) )\n\t\t\t);\n\t\t\tsubquery.getFromClause().addRoot( tableGroup );\n\t\t\tList<String> columnsToMatch;\n\t\t\tif ( constraintColumnNames.isEmpty() ) {\n\t\t\t\t// Assume the primary key columns\n\t\t\t\tPredicate predicate = buildColumnMatchPredicate(\n\t\t\t\t\t\tcolumnsToMatch = Arrays.asList( ((EntityPersister) entityDescriptor).getKeyColumns( tableIndex ) ),\n\t\t\t\t\t\tinsertStatement,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tbooleanType\n\t\t\t\t);\n\t\t\t\tif ( predicate == null ) {\n\t\t\t\t\tthrow new IllegalArgumentException( \"Couldn't infer conflict constraint columns\" );\n\t\t\t\t}\n\t\t\t\tsubquery.applyPredicate( predicate );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolumnsToMatch = constraintColumnNames;\n\t\t\t\tsubquery.applyPredicate(\n\t\t\t\t\t\tbuildColumnMatchPredicate( constraintColumnNames, insertStatement, true, true, booleanType ) );\n\t\t\t}\n\n\t\t\tinsertQuerySpec.applyPredicate( new ExistsPredicate( subquery, true, booleanType ) );\n\n\t\t\t// Emulate the conflict do update clause by creating a separate update CTEs\n\t\t\tif ( conflictClause.isDoUpdate() ) {\n\t\t\t\tfinal TableGroup temporaryTableGroup = insertQuerySpec.getFromClause().getRoots().get( 0 );\n\t\t\t\tfinal QuerySpec renamingSubquery = new QuerySpec( false, 1 );\n\t\t\t\tfinal List<String> columnNames = buildCteRenaming(\n\t\t\t\t\t\trenamingSubquery,\n\t\t\t\t\t\ttemporaryTableGroup,","sourceCodeStart":1082,"sourceCodeEnd":1118,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java#L1082-L1118","documentation":"For HQL `insert ... on conflict` without explicit constraint columns, the CTE emulation falls back to the entity's primary key columns to build the row-match predicate. buildColumnMatchPredicate returns null when those key columns aren't present among the insert target columns (typical when the id is generated and omitted), and the handler aborts with this IllegalArgumentException because no conflict predicate could be inferred.","triggerScenarios":"`insert into Customer(email, name) select ... on conflict` (no column list) with a @GeneratedValue id: the PK is not among targetColumns, so PK inference finds nothing to match; happens on the emulation path (dialects without native upsert, or when the CTE handler emulates the clause).","commonSituations":"Portable upserts written as HQL insert...select...on-conflict with generated ids — they work on PostgreSQL (native ON CONFLICT) but fail when the same query runs through emulation on other databases; adopting Hibernate 6.5+ conflict clauses without specifying an index.","solutions":["Name the conflict columns explicitly and make sure they are inserted: `on conflict (email)`","Include the id/PK in the insert target list when you want PK-based conflict detection (e.g. insert...select that supplies ids)","Use native SQL upsert (INSERT ... ON CONFLICT / MERGE) for dialect-specific upserts on non-native databases"],"exampleFix":"// before\ninsert into Customer(email, name)\nselect o.email, o.name from Lead o\non conflict\n\n// after\ninsert into Customer(email, name)\nselect o.email, o.name from Lead o\non conflict (email)","handlingStrategy":"validation","validationCode":"// Always name conflict columns explicitly when ids are generated\nif (idIsGenerated(entityClass) && conflictClauseColumns.isEmpty()) {\n    throw new IllegalArgumentException(\n        \"on conflict needs explicit columns when the PK is not inserted\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    em.createQuery(insertHql).executeUpdate();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"infer conflict constraint\")) {\n        throw new QueryBuildException(\n            \"Add explicit conflict columns, e.g. 'on conflict (email)'\", e);\n    }\n    throw e;\n}","preventionTips":["Write `on conflict (col)` with explicit columns instead of bare `on conflict`","Ensure named conflict columns appear in the insert target list","Test upsert queries on the same database used in production — native and emulated paths differ"],"tags":["hql","upsert","on-conflict","cte","hibernate-6"],"backgroundTag":"upsert-conflict-column-inference","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}