{"record":{"id":"007fdeeb70f504ef","repo":"apache/druid","slug":"cannot-operate-on-a-dimension-with-no-dictionary-007fde","errorCode":null,"errorMessage":"Cannot operate on a dimension with no dictionary","messagePattern":"Cannot operate on a dimension with no dictionary","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/topn/PooledTopNAlgorithm.java","lineNumber":234,"sourceCode":"  public PooledTopNAlgorithm(\n      TopNQuery query,\n      TopNCursorInspector cursorInspector,\n      NonBlockingPool<ByteBuffer> bufferPool\n  )\n  {\n    super(cursorInspector);\n    this.query = query;\n    this.bufferPool = bufferPool;\n  }\n\n  @Override\n  public PooledTopNParams makeInitParams(ColumnSelectorPlus selectorPlus, Cursor cursor, CursorGranularizer granularizer)\n  {\n    final DimensionSelector dimSelector = (DimensionSelector) selectorPlus.getSelector();\n    final int cardinality = dimSelector.getValueCardinality();\n\n    if (cardinality < 0) {\n      throw new UnsupportedOperationException(\"Cannot operate on a dimension with no dictionary\");\n    }\n\n    final TopNMetricSpecBuilder<int[]> arrayProvider = new BaseArrayProvider<>(dimSelector, query, cursorInspector)\n    {\n      private final int[] positions = new int[cardinality];\n\n      @Override\n      public int[] build()\n      {\n        Pair<Integer, Integer> startEnd = computeStartEnd(cardinality);\n\n        Arrays.fill(positions, 0, startEnd.lhs, SKIP_POSITION_VALUE);\n        Arrays.fill(positions, startEnd.lhs, startEnd.rhs, INIT_POSITION_VALUE);\n        Arrays.fill(positions, startEnd.rhs, positions.length, SKIP_POSITION_VALUE);\n\n        return positions;\n      }\n    };","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/topn/PooledTopNAlgorithm.java#L216-L252","documentation":"PooledTopNAlgorithm.makeInitParams requires a dictionary-encoded dimension selector; getValueCardinality() must return >= 0. A negative cardinality means the dimension has no dictionary (e.g. a string dimension backed by a non-dictionary or an uninitialized/foreign storage adapter), so the algorithm cannot allocate its int[cardinality] positions array. Druid throws UnsupportedOperationException because topN over an undictionary-able dimension is not supported in the pooled algorithm.","triggerScenarios":"Running a topN query where the 'dimension' resolves to a selector whose getValueCardinality() returns -1 — typically a non-dictionary-encoded column, a virtual/expression dimension without dictionary support, or a segment whose string dictionary is unavailable.","commonSituations":"TopN on an expression/virtual column lacking a dictionary; querying segments written with dictionary-less string columns; misuse of topN where groupBy v2 should be used for high-cardinality or unencoded dimensions.","solutions":["Use a dictionary-encoded string column as the topN dimension","Enable/verify dictionary encoding for the column at ingestion (string columns are dictionary-encoded by default)","Rewrite the query as a groupBy if the dimension cannot have a dictionary or cardinality is unknown","If using an expression dimension, ensure it produces dictionary-encoded values or switch to groupBy"],"exampleFix":"// before\ntopN(dimension: \"concat(country, '-x')\") // expression dim with no dictionary\n// after\ngroupBy(dimension: \"concat(country, '-x')\") // groupBy supports unknown cardinality","handlingStrategy":"validation","validationCode":"DimensionSelector selector = (DimensionSelector) selectorPlus.getSelector();\nif (selector.getValueCardinality() < 0) {\n  // fall back to groupBy or choose a dictionary-encoded dimension\n}","typeGuard":"boolean hasDictionary(DimensionSelector selector) {\n  return selector.getValueCardinality() >= 0;\n}","tryCatchPattern":"try {\n  return topNEngine.query(...);\n} catch (UnsupportedOperationException e) {\n  if (e.getMessage().contains(\"no dictionary\")) { /* run groupBy instead */ }\n  throw e;\n}","preventionTips":["Prefer dictionary-encoded string columns for topN dimensions","Use groupBy for expression/virtual dimensions or unknown cardinality","Check cardinality (ColumnHolder/Column capabilities) before choosing topN at query-build time"],"tags":["query","topn","dictionary-encoding","druid"],"backgroundTag":"unsupported-operation","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"}