apache/iceberg · error · java.lang.UnsupportedOperationException

Cannot convert term to SQL: %s

Error message

Cannot convert term to SQL: %s

What it means

sqlString in DescribeExpressionVisitor renders only NamedReference and UnboundTransform terms; any other UnboundTerm (e.g. a constant/extract term) throws UnsupportedOperationException because it has no direct SQL rendering.

Source

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

        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)

Solutions

  1. Restrict predicates to column or transform terms before describing
  2. Catch UnsupportedOperationException and fall back to a generic string rendering of the expression
  3. Extend the visitor (fork or newer version) to handle the specific term type

Example fix

// before
throw new UnsupportedOperationException("Cannot convert term to SQL: " + term);
// after
} else if (term instanceof UnboundExtract) {
  UnboundExtract<?> extract = (UnboundExtract<?>) term;
  return "extract(" + extract.ref().name() + ")";
} else {
  throw new UnsupportedOperationException("Cannot convert term to SQL: " + term);
}
Defensive patterns

Strategy: try-catch

Validate before calling

boolean ok = term instanceof NamedReference || term instanceof UnboundTransform;

Type guard

boolean isRenderableTerm(UnboundTerm<?> t) { return t instanceof NamedReference || t instanceof UnboundTransform; }

Try / catch

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

Prevention

When it happens

Trigger: Calling Spark3Util.describe on a predicate whose term is neither a plain column reference nor a transform, e.g. predicates built over Literal references or other exotic terms.

Common situations: Programmatically constructed expressions with unusual terms being pretty-printed to SQL for catalog partition predicates or metadata filters.

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