{"record":{"id":"f789b84e86b8b0df","repo":"apache/druid","slug":"a-not-b-requires-at-least-1-sketch","errorCode":null,"errorMessage":"A-Not-B requires at least 1 sketch","messagePattern":"A-Not-B requires at least 1 sketch","errorType":"validation","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/theta/SketchHolder.java","lineNumber":297,"sourceCode":"    //\"true\" returns an ordered sketch but slower to compute. advantage of ordered sketch\n    //is that they are faster to \"union\" later but given that this method is used in\n    //the final stages of query processing, ordered sketch would be of no use.\n    switch (func) {\n      case UNION:\n        Union union = (Union) SetOperation.builder().setNominalEntries(sketchSize).build(Family.UNION);\n        for (Object o : holders) {\n          ((SketchHolder) o).updateUnion(union);\n        }\n        return SketchHolder.of(union);\n      case INTERSECT:\n        Intersection intersection = (Intersection) SetOperation.builder().setNominalEntries(sketchSize).build(Family.INTERSECTION);\n        for (Object o : holders) {\n          intersection.intersect(((SketchHolder) o).getSketch());\n        }\n        return SketchHolder.of(intersection.getResult(false, null));\n      case NOT:\n        if (holders.length < 1) {\n          throw new IllegalArgumentException(\"A-Not-B requires at least 1 sketch\");\n        }\n\n        if (holders.length == 1) {\n          return (SketchHolder) holders[0];\n        }\n\n        Sketch result = ((SketchHolder) holders[0]).getSketch();\n        for (int i = 1; i < holders.length; i++) {\n          AnotB anotb = (AnotB) SetOperation.builder().setNominalEntries(sketchSize).build(Family.A_NOT_B);\n          result = anotb.aNotB(result, ((SketchHolder) holders[i]).getSketch());\n        }\n        return SketchHolder.of(result);\n      default:\n        throw new IllegalArgumentException(\"Unknown sketch operation \" + func);\n    }\n  }\n\n  /**","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/theta/SketchHolder.java#L279-L315","documentation":"sketchSetOperation() implements the NOT (A-Not-B) set operation, which mathematically needs at least one sketch (the A side). When the holders array is empty, there is nothing to compute, so it throws an IllegalArgumentException. Other operations (UNION/INTERSECT) tolerate empty inputs but NOT does not.","triggerScenarios":"Calling SketchHolder.sketchSetOperation(Func.NOT, size) with an empty Object[] holders array — e.g. a SketchSetPostAggregator whose field list resolved to zero sketches at runtime.","commonSituations":"A post-aggregator configured in a query whose input fields were filtered out or renamed, leaving zero dependencies; programmatic query building that passes an empty fields list for a NOT aggregator.","solutions":["Ensure at least one input sketch/field is provided for the NOT post-aggregator","Validate fields.size() >= 1 when constructing the SketchSetPostAggregator or building the query programmatically","If A may legitimately be absent, guard the call and return null or a default empty sketch instead of invoking NOT"],"exampleFix":"// before\nObject[] holders = collectFields(); // may be empty\nSketchHolder result = SketchHolder.sketchSetOperation(SketchHolder.Func.NOT, size, holders);\n// after\nif (holders.length < 1) {\n  throw new IllegalArgumentException(\"NOT requires at least one input sketch field\");\n}\nSketchHolder result = SketchHolder.sketchSetOperation(SketchHolder.Func.NOT, size, holders);","handlingStrategy":"validation","validationCode":"if (holders == null || holders.length < 1) {\n  throw new IllegalArgumentException(\"NOT operation requires at least 1 input sketch\");\n}","typeGuard":"boolean hasInputs(Object[] holders) {\n  return holders != null && holders.length >= 1;\n}","tryCatchPattern":"try {\n  SketchHolder r = SketchHolder.sketchSetOperation(Func.NOT, size, holders);\n} catch (IllegalArgumentException e) {\n  // handle empty-input case: return null or empty sketch holder\n}","preventionTips":["Always validate field counts when building SketchSetPostAggregators programmatically","Check query JSON post-aggregator fields arrays are non-empty","Resolve field dependencies before executing set operations"],"tags":["java","datasketches","post-aggregator"],"backgroundTag":"missing-required-argument","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}