{"record":{"id":"aca6e88b5f526a26","repo":"apache/iceberg","slug":"cannot-convert-bound-predicates-to-sql","errorCode":null,"errorMessage":"Cannot convert bound predicates to SQL","messagePattern":"Cannot convert bound predicates to SQL","errorType":"exception","errorClass":"java.lang.UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java","lineNumber":647,"sourceCode":"\n    @Override\n    public String not(String result) {\n      return \"NOT (\" + result + \")\";\n    }\n\n    @Override\n    public String and(String leftResult, String rightResult) {\n      return \"(\" + leftResult + \" AND \" + rightResult + \")\";\n    }\n\n    @Override\n    public String or(String leftResult, String rightResult) {\n      return \"(\" + leftResult + \" OR \" + rightResult + \")\";\n    }\n\n    @Override\n    public <T> String predicate(BoundPredicate<T> pred) {\n      throw new UnsupportedOperationException(\"Cannot convert bound predicates to SQL\");\n    }\n\n    @Override\n    public <T> String predicate(UnboundPredicate<T> pred) {\n      switch (pred.op()) {\n        case IS_NULL:\n          return sqlString(pred.term()) + \" IS NULL\";\n        case NOT_NULL:\n          return sqlString(pred.term()) + \" IS NOT NULL\";\n        case IS_NAN:\n          return \"is_nan(\" + sqlString(pred.term()) + \")\";\n        case NOT_NAN:\n          return \"not_nan(\" + sqlString(pred.term()) + \")\";\n        case LT:\n          return sqlString(pred.term()) + \" < \" + sqlString(pred.literal());\n        case LT_EQ:\n          return sqlString(pred.term()) + \" <= \" + sqlString(pred.literal());\n        case GT:","sourceCodeStart":629,"sourceCodeEnd":665,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java#L629-L665","documentation":"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.","triggerScenarios":"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)).","commonSituations":"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.","solutions":["Convert the bound predicate back to unbound first (e.g. rebuild via Expressions util from the predicate's op/term/literal) before describing","Use the unbound Expression (the original user filter) with Spark3Util.describe instead of the bound variant","Handle bound predicates yourself with a custom ExpressionVisitor that renders BoundPredicate"],"exampleFix":"// before\nExpression bound = Binder.bind(table.schema(), Expressions.equal(\"id\", 1));\nString sql = Spark3Util.describe(bound); // throws\n// after\nExpression unbound = Expressions.equal(\"id\", 1);\nString sql = Spark3Util.describe(unbound);","handlingStrategy":"try-catch","validationCode":"if (expr instanceof BoundPredicate || hasBoundChildren(expr)) { /* convert to unbound first */ }","typeGuard":"boolean isUnbound(Expression e) { return !(e instanceof BoundPredicate); }","tryCatchPattern":"try { return Spark3Util.describe(expr); } catch (UnsupportedOperationException e) { return expr.toString(); }","preventionTips":["Always keep the original unbound Expression for SQL rendering","Never pass Binder.bind results to Spark3Util.describe","Document that describe() expects unbound expressions only"],"tags":["spark","expressions","sql"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}