{"record":{"id":"99d755d24dae474e","repo":"apache/druid","slug":"must-use-natural-comparator-for-column-s-of-typ","errorCode":null,"errorMessage":"Must use natural comparator for column [%s] of type [%s]","messagePattern":"Must use natural comparator for column \\[(.+?)\\] of type \\[(.+?)\\]","errorType":"validation","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"multi-stage-query/src/main/java/org/apache/druid/msq/querykit/groupby/GroupByQueryKit.java","lineNumber":393,"sourceCode":"    Preconditions.checkState(\n        query.getLimitSpec() instanceof NoopLimitSpec || query.getLimitSpec() instanceof DefaultLimitSpec,\n        \"Must have noop or default limitSpec\"\n    );\n\n    final RowSignature resultSignature = computeResultSignature(query);\n    QueryKitUtils.verifyRowSignature(resultSignature);\n\n    if (query.getLimitSpec() instanceof DefaultLimitSpec) {\n      final DefaultLimitSpec defaultLimitSpec = (DefaultLimitSpec) query.getLimitSpec();\n\n      for (final OrderByColumnSpec column : defaultLimitSpec.getColumns()) {\n        final Optional<ColumnType> type = resultSignature.getColumnType(column.getDimension());\n\n        if (!type.isPresent() || !DimensionComparisonUtils.isNaturalComparator(\n            type.get().getType(),\n            column.getDimensionComparator()\n        )) {\n          throw new ISE(\n              \"Must use natural comparator for column [%s] of type [%s]\",\n              column.getDimension(),\n              type.orElse(null)\n          );\n        }\n      }\n    }\n  }\n\n}\n","sourceCodeStart":375,"sourceCodeEnd":404,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/groupby/GroupByQueryKit.java#L375-L404","documentation":"GroupByQueryKit.validateQuery enforces that any OrderByColumnSpec in an MSQ group-by uses the natural (default) comparator consistent with the column's result type. DimensionComparisonUtils.isNaturalComparator fails for other comparators (e.g. lexicographic/alphanumeric/numeric overrides or custom strategies), so an IllegalStateException is thrown because sort-merge group-by cannot honor them.","triggerScenarios":"Submitting a group-by query via MSQ whose ordering (orderBySpec / limitSpec columns) specifies a non-natural dimension comparator — e.g. comparator \"numeric\" or \"strlen\" — or an ordering column whose result type is unknown.","commonSituations":"Native group-by queries ported from the classic engine with explicit comparators; SQL tooling generating orderBy columns with strategies; ordering on expression columns without a resolvable type.","solutions":["Remove the explicit comparator from the order-by column so the natural comparator is used.","Ensure the column exists in the result signature so its type can be resolved (fix typos / include the column in dimensions).","If non-natural ordering is required, sort results outside MSQ (outer query or client-side) instead of in the group-by limitSpec.","Rewrite the ordering on an expression column that Druid can compare naturally."],"exampleFix":"// before\n\"orderBySpec\": [{\"dimension\": \"d\", \"orderBySpec\": {\"comparator\": \"numeric\"}}]\n// after\n\"orderBySpec\": [{\"dimension\": \"d\"}]  // natural comparator","handlingStrategy":"validation","validationCode":"// Ensure order-by columns use the natural comparator and are present in the result signature\nfor (const col of q.orderBy || []) {\n  if (col.comparator && col.comparator !== 'natural') {\n    throw new Error('MSQ group-by requires natural comparator for column ' + col.dimension);\n  }\n  if (!resultSignature.some(c => c.name === col.dimension)) {\n    throw new Error('Order-by column not in result signature: ' + col.dimension);\n  }\n}","typeGuard":"const usesNaturalComparator = (col, sig) => !col.dimensionComparator || sig.find(c => c.name === col.dimension)?.type !== undefined;","tryCatchPattern":null,"preventionTips":["Omit explicit comparators in MSQ group-by order-by specs.","Only order by columns present in the output signature.","Sort non-naturally outside MSQ (outer query) instead."],"tags":["msq","groupby","sorting"],"backgroundTag":"invalid-config-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}