apache/iceberg · error · UnsupportedOperationException
Cannot convert term to SQL:
Error message
Cannot convert term to SQL:
What it means
DescribeExpressionVisitor.sqlString(UnboundTerm) renders a predicate term as SQL: a NamedReference renders as its column name and an UnboundTransform as transform(col). Any other term (e.g. AccessorReference or bound terms) throws UnsupportedOperationException 'Cannot convert term to SQL'.
Solutions
- Use predicates over top-level columns or unbound transforms so terms render as ref or transform(col)
- Extend the visitor (or write a custom ExpressionVisitor) if nested/marshalled term rendering is required
- Flatten nested-field predicates into a supported form before rendering
Example fix
// before
sqlString(customTerm) // throws
// after
if (term instanceof NamedReference || term instanceof UnboundTransform) {
sqlString(term);
} else {
throw new IllegalArgumentException("Unsupported term: " + term);
} Defensive patterns
Strategy: type-guard
Validate before calling
boolean ok = term instanceof org.apache.iceberg.expressions.NamedReference
|| term instanceof org.apache.iceberg.expressions.UnboundTransform;
if (!ok) throw new IllegalArgumentException("Term not renderable as SQL: " + term); Type guard
boolean isRenderableTerm(org.apache.iceberg.expressions.Term t) {
return t instanceof org.apache.iceberg.expressions.NamedReference
|| t instanceof org.apache.iceberg.expressions.UnboundTransform;
} Try / catch
try {
return sqlString(term);
} catch (UnsupportedOperationException e) {
return term.toString();
} Prevention
- Build predicates with Expressions.column/transform-based terms only
- Avoid custom Term implementations in code that must render SQL
- Verify terms are unbound before rendering
When it happens
Trigger: Rendering an Iceberg predicate whose term is not a plain column reference or unbound transform — e.g. terms built over struct accessors or custom term implementations passed into the describe visitor.
Common situations: DESCRIBE output for filters on nested fields or exotic terms; tooling that feeds arbitrary UnboundTerm objects into Spark3Util's describe path.
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 predicate to SQL:
- Already closed files for partition:
- ALTER TABLE contains multiple distribution clauses
- 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/c08f2c534c4256c4.
Report an issue: GitHub.
Appendix: source
Thrown at spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java:697
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(", "));
}
private static String sqlString(org.apache.iceberg.expressions.Literal<?> lit) {
if (lit.value() instanceof String) {
return "'" + lit.value() + "'";
} else if (lit.value() instanceof ByteBuffer) {
byte[] bytes = ByteBuffers.toByteArray((ByteBuffer) lit.value());
return "X'" + BaseEncoding.base16().encode(bytes) + "'";
} else {
return lit.value().toString();
}View on GitHub (pinned to 86d9c8fc54)