{"record":{"id":"a07a1ae56aa16675","repo":"apache/iceberg","slug":"cannot-convert-predicate-to-sql-pred","errorCode":null,"errorMessage":"Cannot convert predicate to SQL: ${pred}","messagePattern":"Cannot convert predicate to SQL: (.+?)","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java","lineNumber":733,"sourceCode":"          return sqlString(pred.term()) + \" <= \" + sqlString(pred.literal());\n        case GT:\n          return sqlString(pred.term()) + \" > \" + sqlString(pred.literal());\n        case GT_EQ:\n          return sqlString(pred.term()) + \" >= \" + sqlString(pred.literal());\n        case EQ:\n          return sqlString(pred.term()) + \" = \" + sqlString(pred.literal());\n        case NOT_EQ:\n          return sqlString(pred.term()) + \" != \" + sqlString(pred.literal());\n        case STARTS_WITH:\n          return sqlString(pred.term()) + \" LIKE '\" + pred.literal().value() + \"%'\";\n        case NOT_STARTS_WITH:\n          return sqlString(pred.term()) + \" NOT LIKE '\" + pred.literal().value() + \"%'\";\n        case IN:\n          return sqlString(pred.term()) + \" IN (\" + sqlString(pred.literals()) + \")\";\n        case NOT_IN:\n          return sqlString(pred.term()) + \" NOT IN (\" + sqlString(pred.literals()) + \")\";\n        default:\n          throw new UnsupportedOperationException(\"Cannot convert predicate to SQL: \" + pred);\n      }\n    }\n\n    private static <T> String sqlString(UnboundTerm<T> term) {\n      if (term instanceof org.apache.iceberg.expressions.NamedReference) {\n        return term.ref().name();\n      } else if (term instanceof UnboundTransform) {\n        UnboundTransform<?, ?> transform = (UnboundTransform<?, ?>) term;\n        return transform.transform().toString() + \"(\" + transform.ref().name() + \")\";\n      } else {\n        throw new UnsupportedOperationException(\"Cannot convert term to SQL: \" + term);\n      }\n    }\n\n    private static <T> String sqlString(List<org.apache.iceberg.expressions.Literal<T>> literals) {\n      return literals.stream()\n          .map(DescribeExpressionVisitor::sqlString)\n          .collect(Collectors.joining(\", \"));","sourceCodeStart":715,"sourceCodeEnd":751,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java#L715-L751","documentation":"DescribeExpressionVisitor's unbound predicate handler covers the SQL-representable operators (isNull, notNull, comparisons, like/notLike, in/notIn). Any other operator reaching predicate(UnboundPredicate) — e.g. STARTS_ENDS-style or user-defined predicates — has no SQL rendering and throws UnsupportedOperationException naming the offending predicate.","triggerScenarios":"Calling Spark3Util.describe(schema, expr) with an unbound predicate whose operator is not handled by the switch (anything outside the explicitly rendered cases, e.g. custom or newly added predicate kinds), causing the default branch to throw.","commonSituations":"Using newer Iceberg expression types with an older Spark3Util copy, or describing expressions built programmatically with operators the visitor predates; forwarding user-defined predicates into table description.","solutions":["Check the predicate operator and only describe expressions with supported operators (isNull, notNull, =, !=, <, <=, >, >=, like, notLike, in, notIn)","Wrap unsupported operators (e.g. convert not(startsWith(...)) to NOT LIKE 'x%') before describing","Upgrade to a Spark/Iceberg version where the operator you use is supported by DescribeExpressionVisitor"],"exampleFix":"// before\nExpression e = Expressions.not(Expressions.startsWith(\"col\", \"abc\")); // not(startsWith) not handled\nString sql = Spark3Util.describe(schema, e); // throws\n// after\nString sql = \"col NOT LIKE 'abc%'\"; // or use an operator the visitor supports","handlingStrategy":"validation","validationCode":"Set<Expression.Operation> supported = Set.of(IS_NULL, NOT_NULL, LT, LT_EQ, GT, GT_EQ, EQ, NOT_EQ, LIKE, NOT_LIKE, IN, NOT_IN); if (!supported.contains(expr.op())) { /* use fallback rendering */ }","typeGuard":"boolean hasSupportedOp(Expression e) { return e instanceof UnboundPredicate && supported.contains(((UnboundPredicate<?>) e).op()); }","tryCatchPattern":"try { return Spark3Util.describe(schema, expr); } catch (UnsupportedOperationException e) { return \"<unsupported predicate: \" + expr + \">\"; }","preventionTips":["Whitelist operators before calling describe","Keep iceberg-spark and iceberg-core versions aligned","Handle not(...) wrappers by unwrapping them first"],"tags":["spark","sql-translation","unsupported-operator"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}