Support sort-by-alias and aliased duplicate parameterised fields

Problem

GLQL's sort pipeline has no awareness of user-defined aliases (as "Name"). The sort field is parsed independently from fields/dimensions/metrics via Field::from(&str), which only resolves hardcoded field name synonyms (e.g. dueDate -> Due). User-defined aliases are a display-layer concept that sort never cross-references.

This means:

  • Standard mode: fields: title as "Name", sort: Name does not sort by title. It either resolves to Field::Name (a different field entirely) or errors as unrecognized.
  • Analytics mode: dimensions: finished(weekly) as "Week", sort: Week produces UnknownField("Week") and fails sort coupling validation.

This gap blocks a specific use case: aliased duplicate parameterised fields. After !411 (merged), metrics: durationQuantile(0.5), durationQuantile(0.8) is correctly rejected as ambiguous. The natural fix is aliases (durationQuantile(0.5) as "p50", durationQuantile(0.8) as "p80"), but sort: p50 can't work without sort-by-alias support.

Proposal

Add alias resolution to the sort pipeline so that sort field names are matched against user-defined aliases in the selected fields list. This is a cross-cutting change that benefits all modes, not just analytics parameterised fields.

Sort alias resolution (all modes)

The sort pipeline needs a resolution step that cross-references sort field names against the aliases in the compiled fields/dimensions/metrics lists. If sort: p50 doesn't match a known Field variant, check whether any selected field has alias "p50" and resolve to that field.

This likely means:

  • A new resolution pass after sort parsing and field compilation, before sort validation
  • The Sort struct may need to carry the resolved DisplayField reference (or at least the resolved Field) rather than just the raw parsed Field
  • validate_sort_coupling (analytics) and the standard sort validator both need to accept alias-resolved sorts

Analytics-specific: accept aliased duplicates

Once sort-by-alias works, the duplicate check from !411 (merged) can be relaxed to accept duplicates when each instance has a unique alias:

  1. key() (src/types/display_field.rs): AliasedDisplayField returns the alias as the key, producing unique keys for aliased duplicates.
  2. GraphQL codegen (src/codegen/graphql.rs): Emit GraphQL field aliases (e.g. p50: durationQuantile(quantile: 0.5)) when the outer AliasedDisplayField wraps a ParameterizedField.
  3. Transform layer (src/transformer/field_functions.rs): Extract data using the alias as the GraphQL response key.
  4. Validation (src/analyzer/analytics.rs): Change the duplicate check to use key() instead of base_field().

Estimated effort

~100-150 lines across 6-8 files, ~20-25 test cases. The sort alias resolution is the foundational piece; the analytics duplicate relaxation builds on top.

References

Edited by Robert Hunt