{"record":{"id":"6c5d4331f991a9cd","repo":"apache/druid","slug":"clusteringcolumns-must-be-the-leading-prefix-of-co","errorCode":null,"errorMessage":"clusteringColumns must be the leading prefix of columns, in order; got %s vs columns prefix %s","messagePattern":"clusteringColumns must be the leading prefix of columns, in order; got (.+?) vs columns prefix (.+?)","errorType":"validation","errorClass":"DruidException","httpStatus":400,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/data/input/impl/ClusteredValueGroupsBaseTableProjectionSpec.java","lineNumber":301,"sourceCode":"      }\n    }\n    return builder()\n        .virtualColumns(VirtualColumns.create(remaining))\n        .clusteringColumns(clusteringColumns)\n        .columns(columns)\n        .build();\n  }\n\n  private static void validate(List<DimensionSchema> columns, List<String> clusteringColumns)\n  {\n    if (CollectionUtils.isNullOrEmpty(clusteringColumns)) {\n      throw InvalidInput.exception(\"clusteringColumns must be non-empty for clusteredValueGroups base table\");\n    }\n    if (CollectionUtils.isNullOrEmpty(columns)) {\n      throw InvalidInput.exception(\"columns must be non-empty for clusteredValueGroups base table\");\n    }\n    if (clusteringColumns.size() > columns.size()) {\n      throw clusteringPrefixException(columns, clusteringColumns);\n    }\n    for (int i = 0; i < clusteringColumns.size(); i++) {\n      final DimensionSchema clusteringColumn = columns.get(i);\n      if (!clusteringColumn.getName().equals(clusteringColumns.get(i))) {\n        throw clusteringPrefixException(columns, clusteringColumns);\n      }\n      // Clustering values are dictionary-encoded into per-type dictionaries on the write side, which supports only\n      // these scalar types; reject anything else up front rather than failing later at ingest.\n      if (!Projections.isAllowedClusteringType(clusteringColumn.getColumnType())) {\n        throw InvalidInput.exception(\n            \"clustering column [%s] has unsupported type [%s]; clustering columns must be STRING, LONG, DOUBLE, or FLOAT\",\n            clusteringColumn.getName(),\n            clusteringColumn.getColumnType()\n        );\n      }\n    }\n\n    final Set<String> seen = Sets.newHashSetWithExpectedSize(columns.size());","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/data/input/impl/ClusteredValueGroupsBaseTableProjectionSpec.java#L283-L319","documentation":"ClusteredValueGroupsBaseTableProjectionSpec.validate rejects projection specs whose clusteringColumns do not form an exact leading prefix of columns in the same order. Druid's clusteredValueGroups base-table projection encodes clustering as the first N columns of the projection's column list, so a mismatch (extra clustering columns, or columns out of order) makes the spec invalid. The message shows the full columns list versus the clustering prefix actually found.","triggerScenarios":"Building a ClusteredValueGroupsBaseTableProjectionSpec (directly or via projection spec JSON) where clusteringColumns.size() > columns.size(), or where columns.get(i).getName() != clusteringColumns.get(i) for some i < clusteringColumns.size().","commonSituations":"Hand-written projection specs listing clustering columns not present in columns; reordering columns without reordering clusteringColumns; appending clustering columns beyond the declared column list; tooling generating the two lists independently.","solutions":["Reorder columns so the clustering columns come first, in exactly the same order as clusteringColumns.","Ensure every clustering column appears in columns with an identical name.","Remove clustering columns that are not part of the leading prefix, or add them to columns if intended.","Validate the projection spec JSON (names and order) before submitting the ingestion/projection spec."],"exampleFix":"// before: mismatched order\nclusteringColumns: [\"country\", \"city\"]\ncolumns: [\"city\", \"country\", \"ts\"]\n// after: clustering columns are the ordered prefix\nclusteringColumns: [\"country\", \"city\"]\ncolumns: [\"country\", \"city\", \"ts\"]","handlingStrategy":"validation","validationCode":"// ensure clusteringColumns is an ordered prefix of column names before building the spec\nList<String> names = columns.stream().map(DimensionSchema::getName).collect(Collectors.toList());\nif (names.size() < clusteringColumns.size() ||\n    !names.subList(0, clusteringColumns.size()).equals(clusteringColumns)) {\n  throw new IllegalArgumentException(\"clusteringColumns must be a leading prefix of columns, in order\");\n}","typeGuard":null,"tryCatchPattern":"try { new ClusteredValueGroupsBaseTableProjectionSpec(...); } catch (DruidException e) { if (e.getMessage().contains(\"leading prefix\")) { fixColumnOrder(); } throw e; }","preventionTips":["Generate columns from clusteringColumns plus remaining columns so the prefix relation holds by construction.","Never reorder columns without reordering clusteringColumns.","Unit-test projection spec builders with the exact prefix invariant."],"tags":["projection-spec","clustering","validation","ingestion"],"backgroundTag":"schema-validation-failed","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"}