{"record":{"id":"1f2a66e477684531","repo":"hibernate/hibernate-orm","slug":"can-t-emulate-conflict-clause-with-constraint-name","errorCode":null,"errorMessage":"Can't emulate conflict clause with constraint name for more than one row to insert","messagePattern":"Can't emulate conflict clause with constraint name for more than one row to insert","errorType":"exception","errorClass":"IllegalQueryOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java","lineNumber":863,"sourceCode":"\t\tvisitInsertStatement( sqlAst );\n\n\t\treturn new JdbcOperationQueryInsertImpl(\n\t\t\t\tgetSql(),\n\t\t\t\tgetParameterBinders(),\n\t\t\t\tgetAffectedTableNames(),\n\t\t\t\tgetUniqueConstraintNameThatMayFail(sqlAst)\n\t\t);\n\t}\n\n\tprotected String getUniqueConstraintNameThatMayFail(InsertSelectStatement sqlAst) {\n\t\tfinal ConflictClause conflictClause = sqlAst.getConflictClause();\n\t\tif ( conflictClause == null || !conflictClause.getConstraintColumnNames().isEmpty() ) {\n\t\t\treturn null;\n\t\t}\n\t\telse {\n\t\t\tif ( sqlAst.getSourceSelectStatement() != null && !isFetchFirstRowOnly( sqlAst.getSourceSelectStatement() )\n\t\t\t\t\t|| sqlAst.getValuesList().size() > 1 ) {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Can't emulate conflict clause with constraint name for more than one row to insert\" );\n\t\t\t}\n\t\t\treturn conflictClause.getConstraintName() == null ? \"\" : conflictClause.getConstraintName();\n\t\t}\n\t}\n\n\tprotected JdbcSelect translateSelect(SelectStatement selectStatement) {\n\t\tlogDomainResultGraph( selectStatement.getDomainResultDescriptors() );\n\t\tlogSqlAst( selectStatement );\n\n\t\t// we need to make a cope here for later since visitSelectStatement clears it :(\n\t\tfinal LockOptions lockOptions = this.lockOptions;\n\n\t\tvisitSelectStatement( selectStatement );\n\n\t\tfinal int rowsToSkip;\n\t\tfinal JdbcOperationQuerySelect jdbcSelect = new JdbcOperationQuerySelect(\n\t\t\t\tgetSql(),\n\t\t\t\tgetParameterBinders(),","sourceCodeStart":845,"sourceCodeEnd":881,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java#L845-L881","documentation":"getUniqueConstraintNameThatMayFail implements upsert on dialects (e.g., MariaDB per MariaDBSqlAstTranslator:186) by attempting the insert and catching the unique-constraint failure identified by constraint name. That emulation can only attribute a failure to a single row, so when the conflict clause carries a constraint name (and no constraint column names) but the statement inserts more than one row — a values list with >1 tuple, or an insert-select not capped to first-row-only — it throws IllegalQueryOperationException.","triggerScenarios":"HQL/JPA insert with 'on conflict on constraint <name> do nothing' (empty constraint column names) plus either multiple value tuples ('values (...),(...)') or an insert-from-select whose source is not restricted to fetch first row only, executed on a dialect using the constraint-name failure emulation (MariaDB).","commonSituations":"Porting PostgreSQL-style upsert HQL to MariaDB; batch-insert helpers that aggregate rows into one multi-values statement; enabling the Hibernate 6.5+ HQL INSERT ... ON CONFLICT syntax on MariaDB.","solutions":["Insert one row per statement so the emulation can attribute the constraint failure.","Specify the conflict as constraint column names ('on conflict (col1, col2)') instead of a named constraint — the method returns null for that shape and the multi-row path proceeds.","Omit the constraint name entirely if the single unique constraint makes it redundant.","Execute a native MariaDB 'INSERT ... ON DUPLICATE KEY UPDATE' / 'INSERT IGNORE' statement for multi-row upserts."],"exampleFix":"// before — multi-row insert + named constraint on MariaDB\nint n = session.createQuery(\n    \"insert into Person (id,name) values (:i1,:n1),(:i2,:n2) on conflict on constraint uk_name do nothing\")\n    .executeUpdate();\n\n// after — one row per statement (or conflict column names)\nint n = session.createQuery(\n    \"insert into Person (id,name) values (:i,:nm) on conflict (name) do nothing\")\n    .setParameter(\"i\", 1).setParameter(\"nm\", \"a\").executeUpdate();","handlingStrategy":"validation","validationCode":"boolean constraintNameUpsert = hql.contains(\"on conflict on constraint\");\nint rows = countValueTuples(hql); // rows aggregated into the insert\nif (constraintNameUpsert && rows > 1) {\n    // MariaDB-style emulation cannot attribute multi-row failures to one constraint\n    throw new IllegalArgumentException(\"Split into single-row inserts or use conflict column names\");\n}","typeGuard":null,"tryCatchPattern":"try { session.createQuery(hql).executeUpdate(); }\ncatch (org.hibernate.query.IllegalQueryOperationException e) {\n    if (e.getMessage().contains(\"constraint name for more than one row\")) {\n        splitIntoSingleRowInserts(hql).forEach(q -> q.executeUpdate());\n    } else { throw e; }\n}","preventionTips":["On MariaDB, pair named-constraint upserts with single-row inserts only.","Prefer conflict column names over named constraints for portability.","Keep multi-row upserts in dialect-specific native SQL."],"tags":["hibernate","orm","upsert","on-conflict","mariadb","insert"],"backgroundTag":"upsert-conflict-clause-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}