Add parameter resolution and validation in the analyzer
What does this MR do and why?
This is MR 3 of 5 for parameterised field syntax.
MR 1 (!406 (merged)) added FieldParameterDef metadata so each analytics source can declare what parameters its fields accept. MR 2 (!407 (merged)) added the ParameterizedField variant, parser grammar, and FunctionArg types so syntax like finished(weekly) could be parsed. But analytics mode still rejected all function syntax at the parser level to avoid a validation gap (since FieldFunction.base_field() returns None, removing the parser guard without analyzer validation would silently accept invalid syntax).
This MR lifts that parser guard and replaces it with proper analyzer-level resolution and validation. After this MR, users can write parameterised syntax like finished(weekly) or durationQuantile(0.95) in analytics dimensions and metrics, and the compiler validates both the structure and values against the source's parameter metadata.
Resolution flow
A new resolve_analytics_parameters() step runs in the compile pipeline between apply_default_fields and analyze, keeping analyze() as a pure validation pass with immutable &Context. The resolution:
- Converts
FieldFunctiontoParameterizedFieldfor fields that accept parameters, resolving positional or named arguments againstFieldParameterDefmetadata - Applies default parameter values to
Staticfields that are parameterisable (e.g. barestartedbecomesParameterizedField(Started, [("granularity", "weekly")])) - Rejects function syntax on non-parameterisable fields with a clear error
- Validates structural rules: mixed positional/named args, too many args, duplicate keys, unknown keys
- Validates values against constraints: enum membership for granularity, numeric range for quantile
Standard mode rejection
When validate_standard_fields encounters a FieldFunction whose name matches a parameterisable field from the source's analytics mode (currently only Pipelines), it rejects with a clear error directing the user to analytics mode. This narrows the pre-existing validation gap tracked in #139.
What this MR does NOT do
Codegen still uses hardcoded values for granularity and quantile. MR 4 threads the resolved parameters through codegen and sort, and removes the hardcoded defaults. The resolved parameters are stored on the DisplayField but not yet consumed by the rendering layer.
References
- Parent issue: #130 (closed)
- MR 1 (metadata): !406 (merged)
- MR 2 (depends on): !407 (merged)
- Design resolution: comment
- Pre-existing field function gap: #139
How to set up and validate locally
cargo test-- all 937 tests should passcargo clippy-- should be cleancargo fmt --check-- should be clean
Manual testing via the Ruby extension
Build the native extension (re-run after any Rust changes):
cd glql_rb
bundle install
bundle exec rake compileParameterised dimension (positional syntax)
mode: analytics
query: type = Pipeline and project = "gitlab-org/gitlab" and finished > -30d
dimensions: finished(weekly), status
metrics: totalCount, successRate
sort: finished descTest via ruby extension
GLQL_QUERY='type = Pipeline and project = "gitlab-org/gitlab" and finished > -30d'
echo "q = Glql.compile('${GLQL_QUERY}', { mode: 'analytics', dimensions: 'finished(weekly), status', metrics: 'totalCount, successRate', sort: 'finished desc' })[\"output\"]; puts q; exit" | bundle exec rake consoleNote: codegen still uses hardcoded parameter values (MR 4 threads resolved parameters through codegen), so the GraphQL output won't yet reflect the user-specified granularity.
Parameterised metric (named syntax)
mode: analytics
query: type = Pipeline and project = "gitlab-org/gitlab" and finished > -30d
dimensions: ref
metrics: totalCount, durationQuantile(quantile=0.95)Test via ruby extension
GLQL_QUERY='type = Pipeline and project = "gitlab-org/gitlab" and finished > -30d'
echo "q = Glql.compile('${GLQL_QUERY}', { mode: 'analytics', dimensions: 'ref', metrics: 'totalCount, durationQuantile(quantile=0.95)' })[\"output\"]; puts q; exit" | bundle exec rake consoleInvalid parameter error
mode: analytics
query: type = Pipeline and project = "gitlab-org/gitlab" and finished > -30d
dimensions: finished(yearly)
metrics: totalCountTest via ruby extension
GLQL_QUERY='type = Pipeline and project = "gitlab-org/gitlab" and finished > -30d'
echo "puts Glql.compile('${GLQL_QUERY}', { mode: 'analytics', dimensions: 'finished(yearly)', metrics: 'totalCount' })[\"output\"]; exit" | bundle exec rake consoleExpected output:
Error: `yearly` is not a valid value for `finished(granularity=...)`. Supported values: daily, weekly, monthlyStandard mode rejection
mode: standard
query: type = Pipeline and project = "gitlab-org/gitlab"
fields: finished(weekly)Test via ruby extension
GLQL_QUERY='type = Pipeline and project = "gitlab-org/gitlab"'
echo "puts Glql.compile('${GLQL_QUERY}', { fields: 'finished(weekly)' })[\"output\"]; exit" | bundle exec rake consoleExpected output:
Error: `finished` accepts parameters in analytics mode for pipelines. Parameterised fields are not supported in standard mode.