{"record":{"id":"c577f68a37465fed","repo":"apache/iceberg","slug":"cannot-convert-bound-predicates-to-sql-c577f6","errorCode":null,"errorMessage":"Cannot convert bound predicates to SQL","messagePattern":"Cannot convert bound predicates to SQL","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java","lineNumber":651,"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":633,"sourceCodeEnd":669,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java#L633-L669","documentation":"Spark3Util.DescribeExpressionVisitor extends the unbound expression visitor; its predicate(BoundPredicate) override unconditionally throws UnsupportedOperationException because DESCRIBE TABLE works on unbound (parsed) Iceberg expressions and cannot render bound predicates that depend on table data/statistics.","triggerScenarios":"Calling DescribeExpressionVisitor (via Spark3Util describe/predicate helpers) with a BoundExpression — e.g. passing an already-bound row-filter from a scan instead of the unbound parsed expression.","commonSituations":"Custom code that reuses the describe visitor on expressions obtained from Iceberg scan/filter APIs after binding; mixing bound/unbound expression APIs in tooling.","solutions":["Pass the unbound expression (from ExpressionParser or the table's parsed filter) to the visitor, not a bound one","Use ExpressionVisitors with a BoundExpressionVisitor subclass if you must evaluate bound predicates","Render SQL from the unbound predicate via Expressions.serialize or build a custom unbound visitor"],"exampleFix":"// before\nvisitor.predicate(boundPred); // throws\n// after\nExpression unbound = Expressions.alwaysTrue(); // reconstruct or keep original unbound expr\nvisitor.predicate((UnboundPredicate<?>) unbound);","handlingStrategy":"type-guard","validationCode":"if (expr instanceof org.apache.iceberg.expressions.BoundExpression) {\n  throw new IllegalArgumentException(\"DescribeExpressionVisitor requires unbound expressions\");\n}","typeGuard":"boolean isUnbound(org.apache.iceberg.expressions.Expression e) {\n  return !(e instanceof org.apache.iceberg.expressions.BoundExpression);\n}","tryCatchPattern":"try {\n  return visitor.describe(expr);\n} catch (UnsupportedOperationException e) {\n  throw new IllegalStateException(\"Bind-free expression required for SQL rendering\", e);\n}","preventionTips":["Render SQL from the parsed (unbound) expression, before any TableScan binding","Keep a reference to the original unbound filter alongside bound scans","Don't reuse DESCRIBE helpers on scan-time bound filters"],"tags":["spark","expressions","bound-predicate"],"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"}