apache/iceberg · error · java.lang.UnsupportedOperationException

Cannot convert bound predicates to SQL

Error message

Cannot convert bound predicates to SQL

What it means

DescribeExpressionVisitor explicitly rejects BoundPredicate: SQL rendering is only implemented for unbound predicates, so visiting an already-bound predicate (with bound terms/literals resolved against a schema) throws UnsupportedOperationException.

Source

Thrown at spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java:647

    @Override
    public String not(String result) {
      return "NOT (" + result + ")";
    }

    @Override
    public String and(String leftResult, String rightResult) {
      return "(" + leftResult + " AND " + rightResult + ")";
    }

    @Override
    public String or(String leftResult, String rightResult) {
      return "(" + leftResult + " OR " + rightResult + ")";
    }

    @Override
    public <T> String predicate(BoundPredicate<T> pred) {
      throw new UnsupportedOperationException("Cannot convert bound predicates to SQL");
    }

    @Override
    public <T> String predicate(UnboundPredicate<T> pred) {
      switch (pred.op()) {
        case IS_NULL:
          return sqlString(pred.term()) + " IS NULL";
        case NOT_NULL:
          return sqlString(pred.term()) + " IS NOT NULL";
        case IS_NAN:
          return "is_nan(" + sqlString(pred.term()) + ")";
        case NOT_NAN:
          return "not_nan(" + sqlString(pred.term()) + ")";
        case LT:
          return sqlString(pred.term()) + " < " + sqlString(pred.literal());
        case LT_EQ:
          return sqlString(pred.term()) + " <= " + sqlString(pred.literal());
        case GT:

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Convert the bound predicate back to unbound first (e.g. rebuild via Expressions util from the predicate's op/term/literal) before describing
  2. Use the unbound Expression (the original user filter) with Spark3Util.describe instead of the bound variant
  3. Handle bound predicates yourself with a custom ExpressionVisitor that renders BoundPredicate

Example fix

// before
Expression bound = Binder.bind(table.schema(), Expressions.equal("id", 1));
String sql = Spark3Util.describe(bound); // throws
// after
Expression unbound = Expressions.equal("id", 1);
String sql = Spark3Util.describe(unbound);
Defensive patterns

Strategy: try-catch

Validate before calling

if (expr instanceof BoundPredicate || hasBoundChildren(expr)) { /* convert to unbound first */ }

Type guard

boolean isUnbound(Expression e) { return !(e instanceof BoundPredicate); }

Try / catch

try { return Spark3Util.describe(expr); } catch (UnsupportedOperationException e) { return expr.toString(); }

Prevention

When it happens

Trigger: Calling Spark3Util.describe (expressionsToSQL) with an Expression containing a predicate that has already been bound to a table schema (e.g. from SparkFilters.convert filters bound via Binder, or passing a result of binder.bind(expr)).

Common situations: Developers take a bound filter from a scan context and try to render it back to SQL for display/logging, hitting this throw; also when passing bound residual expressions from deletes/scan residual filtering.

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


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/aca6e88b5f526a26. Report an issue: GitHub.