hibernate/hibernate-orm · error · IllegalArgumentException

Can't emulate order preserving row constructor through strin

Error message

Can't emulate order preserving row constructor through string concatenation for numeric expression [%s] without precision or scale

What it means

wrapRowComponentAsOrderPreservingConcatArgument converts each search/cycle key to a string whose lexicographic order matches the value's numeric order; for that it must left-pad fixed-point numbers to a known width, which requires precision and scale from a SqlTypedMapping. When the expression's cast type is FIXED (numeric/decimal) but its mapping exposes no precision or scale — typically a computed expression, function result, or untyped cast — Hibernate cannot build the padding and throws IllegalArgumentException.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java:3318

	 * This is because the translation from the types to strings is not guaranteed to result in the same ordering.
	 */
	private SqlAstNode wrapRowComponentAsOrderPreservingConcatArgument(Expression expression) {
		final JdbcMapping jdbcMapping = expression.getExpressionType().getSingleJdbcMapping();
		return switch ( jdbcMapping.getCastType() ) {
			case STRING -> expression;
			case BOOLEAN, INTEGER_BOOLEAN, TF_BOOLEAN, YN_BOOLEAN -> castToString( expression );
			case INTEGER, LONG -> castNumberToString( expression, 19, 0 );
			case FIXED -> {
				if ( expression.getExpressionType() instanceof SqlTypedMapping sqlTypedMapping ) {
					if ( sqlTypedMapping.getPrecision() != null && sqlTypedMapping.getScale() != null ) {
						yield castNumberToString(
								expression,
								sqlTypedMapping.getPrecision(),
								sqlTypedMapping.getScale()
						);
					}
				}
				throw new IllegalArgumentException(
						String.format(
								"Can't emulate order preserving row constructor through string concatenation for numeric expression [%s] without precision or scale",
								expression
						)
				);
			}
			default -> throw new IllegalArgumentException(
					String.format(
							"Can't emulate order preserving row constructor through string concatenation for expression [%s] which is of type [%s]",
							expression,
							jdbcMapping.getCastType()
					)
			);
		};
	}

	private int wrapRowComponentAsOrderPreservingConcatArgumentSizeEstimate(Expression expression) {
		final JdbcMapping jdbcMapping = expression.getExpressionType().getSingleJdbcMapping();

View on GitHub (pinned to fad1729dce)

Solutions

  1. Declare precision and scale on the numeric mapping used in the search specification: @Column(precision = 19, scale = 2) — and regenerate/verify the schema matches.
  2. Search by a plain, mapped numeric column (or its integral/string projection) instead of a computed expression like a+b or function results.
  3. Cast the computed key to a concrete typed literal via a typed parameter or tuple-cast so the mapping retains precision/scale.
  4. Run the query as native SQL, or switch to a dialect with row/array emulation where the padded-string encoding is unnecessary.

Example fix

// before: BigDecimal attribute without precision/scale used as search key
@Column(name = "sort_key")
private BigDecimal sortKey;
// ...
// "with recursive t as (...) search depth first by sortKey set ord"

// after: give the mapping precision and scale so the order-preserving
// string encoding can left-pad correctly
@Column(name = "sort_key", precision = 19, scale = 4)
private BigDecimal sortKey;
Defensive patterns

Strategy: validation

Validate before calling

// Before running the query: verify the search-by mapping can be order-preservingly encoded
for (SingularAttribute<?, ?> attr : searchByAttributes) {
    if ( attr.getJavaType() == BigDecimal.class ) {
        org.hibernate.mapping.Property p = sessionFactory.getMetamodel()
            .entityPersister(attr.getDeclaringType().getJavaType()).getProperty(attr.getName());
        // simpler: check the column directly
    }
}
// Practical check on the JDBC metadata side:
DatabaseMetaData md = connection.getMetaData();
// ensure columns used in SEARCH BY have NUMERIC(p,s) not bare DECIMAL/DOUBLE without precision
if ( !dialect.supportsRecursiveSearchClause() && usesDecimalSearchKey && !hasPrecisionAndScale ) {
    throw new IllegalStateException("Decimal SEARCH BY key needs precision/scale or a different key");
}

Try / catch

try {
    return session.createQuery(hql, ResultDto.class).getResultList();
} catch (IllegalArgumentException e) {
    if ( e.getMessage() != null && e.getMessage().contains("without precision or scale") ) {
        // switch the search key to a plain mapped column or a cast with explicit type, or use native SQL
        return session.createNativeQuery(nativeSql, ResultDto.class).getResultList();
    }
    throw e;
}

Prevention

When it happens

Trigger: A recursive CTE SEARCH (or CYCLE) specification that searches by a decimal/numeric expression whose type carries no precision+scale — e.g. an arithmetic expression like (a+b), a function return like coalesce/avg, or an attribute mapped without @Column(precision/scale) — rendered through emulateSearchClauseOrderWithString's order-preserving concat on a dialect without row/array emulation.

Common situations: MySQL-family targets (string-concat path) where the search key is a computed BigDecimal; entities with numeric attributes missing precision/scale in the mapping (relying on database defaults Hibernate never sees); queries ported from PostgreSQL (row/array emulation path) that never exposed the missing metadata; dynamically built cast() expressions dropping the SqlTypedMapping.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/f234aaf5b4ca2102. Report an issue: GitHub.