{"record":{"id":"2c72a3a7053dc368","repo":"apache/iceberg","slug":"no-negation-for-operation-operation","errorCode":null,"errorMessage":"No negation for operation: ${operation}","messagePattern":"No negation for operation: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/expressions/Expression.java","lineNumber":95,"sourceCode":"          return Operation.GT;\n        case GT:\n          return Operation.LT_EQ;\n        case GT_EQ:\n          return Operation.LT;\n        case EQ:\n          return Operation.NOT_EQ;\n        case NOT_EQ:\n          return Operation.EQ;\n        case IN:\n          return Operation.NOT_IN;\n        case NOT_IN:\n          return Operation.IN;\n        case STARTS_WITH:\n          return Operation.NOT_STARTS_WITH;\n        case NOT_STARTS_WITH:\n          return Operation.STARTS_WITH;\n        default:\n          throw new IllegalArgumentException(\"No negation for operation: \" + this);\n      }\n    }\n\n    /** Returns the equivalent operation when the left and right operands are exchanged. */\n    // Allow flipLR as a name because it's a public API\n    @SuppressWarnings(\"checkstyle:AbbreviationAsWordInName\")\n    public Operation flipLR() {\n      switch (this) {\n        case LT:\n          return Operation.GT;\n        case LT_EQ:\n          return Operation.GT_EQ;\n        case GT:\n          return Operation.LT;\n        case GT_EQ:\n          return Operation.LT_EQ;\n        case EQ:\n          return Operation.EQ;","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/expressions/Expression.java#L77-L113","documentation":"Operation.negate() returns the inverse operation (e.g. EQ -> NOT_EQ, IN -> NOT_IN). Some operations have no logical negation, so the default branch throws IllegalArgumentException listing the operation. Only invertible operations can be negated at the operation level.","triggerScenarios":"Calling Operation.negate() on an operation without an inverse, such as IS_NAN/NOT_NAN or STARTS_WITH-family operations not covered by the switch, typically while building NOT(expr) via ExpressionParser or calling expr.negate()/not(...).","commonSituations":"Rewriting filters by pushing NOT down into predicates; users negating expressions containing operations that lack operation-level negation.","solutions":["Check whether the operation has a negation before calling negate() (e.g. switch on op)","Wrap the expression in Expressions.not(expr) instead of relying on operation-level negation if the op is non-invertible","Restrict negation to operations like EQ, NOT_EQ, LT, LT_EQ, GT, GT_EQ, IN, NOT_IN, IS_NULL, NOT_NULL, STARTS_WITH, NOT_STARTS_WITH"],"exampleFix":"// before\nOperation negated = op.negate(); // throws for IS_NAN\n// after\nExpression negated = Expressions.not(expr); // safe generic negation","handlingStrategy":"validation","validationCode":"Set<Operation> negatable = Set.of(EQ, NOT_EQ, LT, LT_EQ, GT, GT_EQ, IN, NOT_IN, IS_NULL, NOT_NULL, STARTS_WITH, NOT_STARTS_WITH); if (!negatable.contains(op)) { /* use Expressions.not(expr) instead */ }","typeGuard":"boolean hasNegation = !Set.of(Operation.IS_NAN, Operation.NOT_NAN).contains(op);","tryCatchPattern":"try { negated = op.negate(); } catch (IllegalArgumentException e) { negated = null; /* wrap with Expressions.not instead */ }","preventionTips":["Prefer Expressions.not(expr) over operation-level negate","Only negate operations known to have inverses","Push NOT down only after checking each operation"],"tags":["expressions","negation","enum"],"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"}