apache/iceberg · error · UnsupportedOperationException
Cannot convert predicate to SQL
Error message
Cannot convert predicate to SQL: ${pred} What it means
DescribeExpressionVisitor's unbound predicate handler covers the SQL-representable operators (isNull, notNull, comparisons, like/notLike, in/notIn). Any other operator reaching predicate(UnboundPredicate) — e.g. STARTS_ENDS-style or user-defined predicates — has no SQL rendering and throws UnsupportedOperationException naming the offending predicate.
Solutions
- Check the predicate operator and only describe expressions with supported operators (isNull, notNull, =, !=, <, <=, >, >=, like, notLike, in, notIn)
- Wrap unsupported operators (e.g. convert not(startsWith(...)) to NOT LIKE 'x%') before describing
- Upgrade to a Spark/Iceberg version where the operator you use is supported by DescribeExpressionVisitor
Example fix
// before
Expression e = Expressions.not(Expressions.startsWith("col", "abc")); // not(startsWith) not handled
String sql = Spark3Util.describe(schema, e); // throws
// after
String sql = "col NOT LIKE 'abc%'"; // or use an operator the visitor supports Defensive patterns
Strategy: validation
Validate before calling
Set<Expression.Operation> supported = Set.of(IS_NULL, NOT_NULL, LT, LT_EQ, GT, GT_EQ, EQ, NOT_EQ, LIKE, NOT_LIKE, IN, NOT_IN); if (!supported.contains(expr.op())) { /* use fallback rendering */ } Type guard
boolean hasSupportedOp(Expression e) { return e instanceof UnboundPredicate && supported.contains(((UnboundPredicate<?>) e).op()); } Try / catch
try { return Spark3Util.describe(schema, expr); } catch (UnsupportedOperationException e) { return "<unsupported predicate: " + expr + ">"; } Prevention
- Whitelist operators before calling describe
- Keep iceberg-spark and iceberg-core versions aligned
- Handle not(...) wrappers by unwrapping them first
When it happens
Trigger: Calling Spark3Util.describe(schema, expr) with an unbound predicate whose operator is not handled by the switch (anything outside the explicitly rendered cases, e.g. custom or newly added predicate kinds), causing the default branch to throw.
Common situations: Using newer Iceberg expression types with an older Spark3Util copy, or describing expressions built programmatically with operators the visitor predates; forwarding user-defined predicates into table description.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Cannot convert bound predicates to SQL
- Cannot convert term to SQL
- Already closed files for partition:
- ALTER TABLE contains multiple distribution clauses
- ALTER TABLE contains multiple distribution clauses
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/a07a1ae56aa16675.
Report an issue: GitHub.
Appendix: source
Thrown at spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java:733
return sqlString(pred.term()) + " <= " + sqlString(pred.literal());
case GT:
return sqlString(pred.term()) + " > " + sqlString(pred.literal());
case GT_EQ:
return sqlString(pred.term()) + " >= " + sqlString(pred.literal());
case EQ:
return sqlString(pred.term()) + " = " + sqlString(pred.literal());
case NOT_EQ:
return sqlString(pred.term()) + " != " + sqlString(pred.literal());
case STARTS_WITH:
return sqlString(pred.term()) + " LIKE '" + pred.literal().value() + "%'";
case NOT_STARTS_WITH:
return sqlString(pred.term()) + " NOT LIKE '" + pred.literal().value() + "%'";
case IN:
return sqlString(pred.term()) + " IN (" + sqlString(pred.literals()) + ")";
case NOT_IN:
return sqlString(pred.term()) + " NOT IN (" + sqlString(pred.literals()) + ")";
default:
throw new UnsupportedOperationException("Cannot convert predicate to SQL: " + pred);
}
}
private static <T> String sqlString(UnboundTerm<T> term) {
if (term instanceof org.apache.iceberg.expressions.NamedReference) {
return term.ref().name();
} else if (term instanceof UnboundTransform) {
UnboundTransform<?, ?> transform = (UnboundTransform<?, ?>) term;
return transform.transform().toString() + "(" + transform.ref().name() + ")";
} else {
throw new UnsupportedOperationException("Cannot convert term to SQL: " + term);
}
}
private static <T> String sqlString(List<org.apache.iceberg.expressions.Literal<T>> literals) {
return literals.stream()
.map(DescribeExpressionVisitor::sqlString)
.collect(Collectors.joining(", "));View on GitHub (pinned to 86d9c8fc54)