{"record":{"id":"9928df54c269aea3","repo":"apache/iceberg","slug":"no-left-right-flip-for-operation-operation","errorCode":null,"errorMessage":"No left-right flip for operation: ${operation}","messagePattern":"No left-right flip for operation: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/expressions/Expression.java","lineNumber":121,"sourceCode":"      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;\n        case NOT_EQ:\n          return Operation.NOT_EQ;\n        case AND:\n          return Operation.AND;\n        case OR:\n          return Operation.OR;\n        default:\n          throw new IllegalArgumentException(\"No left-right flip for operation: \" + this);\n      }\n    }\n  }\n\n  /** Returns the operation for an expression node. */\n  Operation op();\n\n  /** Returns the negation of this expression, equivalent to not(this). */\n  default Expression negate() {\n    throw new UnsupportedOperationException(String.format(\"%s cannot be negated\", this));\n  }\n\n  /**\n   * Returns whether this expression will accept the same values as another.\n   *\n   * <p>If this returns true, the expressions are guaranteed to return the same evaluation for the\n   * same input. However, if this returns false the expressions may return the same evaluation for\n   * the same input. That is, expressions may be equivalent even if this returns false.","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/expressions/Expression.java#L103-L139","documentation":"Operation.flipLR() returns the equivalent operation with left/right operands exchanged (LT -> GT, LT_EQ -> GT_EQ, EQ stays EQ, AND/OR stay the same). Operations without a meaningful flip throw IllegalArgumentException. It only makes sense for binary comparison operations.","triggerScenarios":"Calling Operation.flipLR() on a non-comparison operation such as IS_NULL, IN, AND on non-symmetric context, or any unary/set operation, when normalizing expressions (e.g. moving a literal to the other side of a predicate).","commonSituations":"Expression normalization/optimization code that rewrites 'literal op column' into 'column flippedOp literal' but encounters a non-comparison operation.","solutions":["Only call flipLR() on comparison operations (EQ, NOT_EQ, LT, LT_EQ, GT, GT_EQ); symmetric ops AND/OR also support it","Check op kind (isPredicate/isComparison) before flipping","Skip or handle other operations explicitly in the rewriter"],"exampleFix":"// before\nOperation flipped = op.flipLR(); // throws for IS_NULL\n// after\nif (op == Operation.EQ || op == Operation.NOT_EQ || op == Operation.LT ||\n    op == Operation.LT_EQ || op == Operation.GT || op == Operation.GT_EQ ||\n    op == Operation.AND || op == Operation.OR) {\n  Operation flipped = op.flipLR();\n}","handlingStrategy":"validation","validationCode":"EnumSet<Operation> flippable = EnumSet.of(EQ, NOT_EQ, LT, LT_EQ, GT, GT_EQ, AND, OR); if (!flippable.contains(op)) { /* skip flip */ }","typeGuard":"boolean canFlip = flippable.contains(op);","tryCatchPattern":"try { flipped = op.flipLR(); } catch (IllegalArgumentException e) { /* keep original operand order */ }","preventionTips":["Call flipLR only for comparison operations","Handle AND/OR and symmetric operations explicitly in normalizers","Guard rewriters with an op-kind check before transformation"],"tags":["expressions","normalization","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"}