apache/iceberg · error · java.lang.UnsupportedOperationException

Cannot convert predicate to SQL: %s

Error message

Cannot convert predicate to SQL: %s

What it means

The unbound-predicate branch of DescribeExpressionVisitor throws when the predicate's operator is not one of the explicitly rendered SQL operators (IS_NULL, NOT_NULL, EQ, LT, LTE, GT, GTE, STARTS_WITH, NOT_STARTS_WITH, LIKE, NOT_LIKE, IN, NOT_IN).

Source

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

          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)

Solutions

  1. Pre-filter or transform the expression to only supported operators before calling describe
  2. Wrap the call in try/catch for UnsupportedOperationException and fall back to expression.toString()
  3. Upgrade Iceberg — newer versions add more operator cases (e.g. STARTS_WITH was added over time)

Example fix

// before
String sql = Spark3Util.describe(Expressions.contains("col", "x")); // throws
// after
String sql;
try { sql = Spark3Util.describe(expr); }
catch (UnsupportedOperationException e) { sql = expr.toString(); }
Defensive patterns

Strategy: try-catch

Validate before calling

Set<Predicate.Op> SUPPORTED = Set.of(IS_NULL, NOT_NULL, EQ, LT, LTE, GT, GTE, IN, NOT_IN, LIKE, NOT_LIKE);
// check pred.op() before describing

Type guard

boolean isSqlRenderableOp(Predicate p) { return SUPPORTED_OPS.contains(p.op()); }

Try / catch

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

Prevention

When it happens

Trigger: Calling Spark3Util.describe with an unbound predicate whose op() is unsupported, e.g. Expressions.startsWith variants not covered in older versions, COUNT/aggregate predicates, or custom predicate ops.

Common situations: Rendering user filters that use operators without direct SQL equivalents (e.g. 'str.contains' composed as predicate variants, nan/not-nan checks) for SHOW CREATE TABLE or EXPLAIN-style output.

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/644c7ad6fe344e8f. Report an issue: GitHub.