{"record":{"id":"bc9f79533dce32db","repo":"hibernate/hibernate-orm","slug":"can-t-emulate-equality-preserving-row-constructor","errorCode":null,"errorMessage":"Can't emulate equality preserving row constructor through string concatenation for expression [%s] which is of type [%s]","messagePattern":"Can't emulate equality preserving row constructor through string concatenation for expression \\[(.+?)\\] which is of type \\[(.+?)\\]","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java","lineNumber":3411,"sourceCode":"\t\t\tcase OFFSET_TIMESTAMP:\n\t\t\tcase ZONE_TIMESTAMP:\n\t\t\t\tif ( dialect.requiresCastForConcatenatingNonStrings() ) {\n\t\t\t\t\treturn castToString( expression );\n\t\t\t\t}\n\t\t\t\t// Should we maybe always cast instead? Not sure what is faster/better...\n\t\t\t\tfinal BasicType<String> stringType = getStringType();\n\t\t\t\treturn new SelfRenderingFunctionSqlAstExpression<>(\n\t\t\t\t\t\t\"concat\",\n\t\t\t\t\t\tfindSelfRenderingFunction( \"concat\", 2 ),\n\t\t\t\t\t\tList.of(\n\t\t\t\t\t\t\t\texpression,\n\t\t\t\t\t\t\t\tnew QueryLiteral<>( \"\", stringType )\n\t\t\t\t\t\t),\n\t\t\t\t\t\tstringType,\n\t\t\t\t\t\tstringType\n\t\t\t\t);\n\t\t}\n\t\tthrow new IllegalArgumentException(\n\t\t\t\tString.format(\n\t\t\t\t\t\t\"Can't emulate equality preserving row constructor through string concatenation for expression [%s] which is of type [%s]\",\n\t\t\t\t\t\texpression,\n\t\t\t\t\t\tjdbcMapping.getCastType()\n\t\t\t\t)\n\t\t);\n\t}\n\n\tprivate int wrapRowComponentAsEqualityPreservingConcatArgumentSizeEstimate(Expression expression) {\n\t\tfinal JdbcMapping jdbcMapping = expression.getExpressionType().getSingleJdbcMapping();\n\t\tswitch ( jdbcMapping.getCastType() ) {\n\t\t\tcase STRING:\n\t\t\t\tif ( expression.getExpressionType() instanceof SqlTypedMapping sqlTypedMapping ) {\n\t\t\t\t\tfinal Long length = sqlTypedMapping.getLength();\n\t\t\t\t\tif ( length != null ) {\n\t\t\t\t\t\treturn length.intValue();\n\t\t\t\t\t}\n\t\t\t\t}","sourceCodeStart":3393,"sourceCodeEnd":3429,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java#L3393-L3429","documentation":"Twin of the order-preserving case: on dialects without row value constructor syntax, Hibernate emulates tuple equality (a,b) = (c,d) by concatenating components into one string and comparing strings. The equality-preserving wrapper only handles STRING cast types (numeric falls through to the failure branch here when no padding rule exists). This IllegalArgumentException reports the expression and its cast type when no equality-preserving string encoding is known.","triggerScenarios":"An HQL/Criteria equality or comparison predicate over a tuple (where (a,b) = :pair, comparing an embeddable/id-class, (key(m), value(m)) comparisons) rendered on a dialect without native row constructor support, where a component's jdbcMapping.getCastType() is not handled by the wrapRowComponentAsEqualityPreservingConcatArgument switch.","commonSituations":"Filtering by composite id class on SQL Server or MySQL < 8.0.19; comparing embeddables in where clauses; @EmbeddedId entities used in Map keys with element comparisons; custom value types with non-standard CastType.","solutions":["Split the tuple comparison into one comparison per component (where a = :a and b = :b)","Cast components to string in the query so the STRING branch applies","Switch to a database with native row value constructor support","Upgrade Hibernate for broader cast-type coverage in the emulation"],"exampleFix":"// before\nsession.createQuery(\"from Order o where (o.id.customerId, o.id.seqNo) = (:cid, :seq)\")...\n\n// after\nsession.createQuery(\"from Order o where o.id.customerId = :cid and o.id.seqNo = :seq\")...","handlingStrategy":"fallback","validationCode":"boolean nativeRowCtor = dialect.supportsRowValueConstructorSyntax();\nboolean eqEmulable = components.stream().allMatch(e ->\n    e.getExpressionType().getSingleJdbcMapping().getCastType() == CastType.STRING);\nif (!nativeRowCtor && !eqEmulable) { /* compare component-wise */ }","typeGuard":null,"tryCatchPattern":"try {\n    query.list();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Can't emulate equality preserving row constructor\")) {\n        // rewrite as component-wise equality\n    } else throw e;\n}","preventionTips":["Avoid (a,b) = :tuple syntax on non-row-constructor dialects; expand to a = :a and b = :b","Compare embeddables field-by-field in JPQL/HQL","Add integration tests that run queries against the actual target dialect, not only H2/PostgreSQL"],"tags":["hibernate","sql-translator","row-constructor","where-clause","dialect-emulation","tuple"],"backgroundTag":"row-value-constructor-emulation","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}