From e9c62d36bafa53353422d3a3b6c7e712285a42de Mon Sep 17 00:00:00 2001 From: Ahmed Hassan Date: Tue, 20 Jan 2026 00:23:40 -0800 Subject: [PATCH 1/3] add holt_winters to cortex parser Signed-off-by: Ahmed Hassan --- pkg/parser/parser.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 07940c6aaaa..e370361c432 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -3,6 +3,7 @@ package parser import ( "maps" + "github.com/prometheus/prometheus/promql" promqlparser "github.com/prometheus/prometheus/promql/parser" "github.com/thanos-io/promql-engine/execution/parse" ) @@ -13,6 +14,17 @@ func buildFunctions() map[string]*promqlparser.Function { fns := make(map[string]*promqlparser.Function, len(promqlparser.Functions)) maps.Copy(fns, promqlparser.Functions) maps.Copy(fns, parse.XFunctions) + + // The holt_winters function was renamed to double_exponential_smoothing and marked experimental in Prometheus v3. + // Register holt_winters as an alias in cortexparser to maintain backward compatibility. + if des, ok := fns["double_exponential_smoothing"]; ok { + holtWinters := *des + holtWinters.Experimental = false + holtWinters.Name = "holt_winters" + fns["holt_winters"] = &holtWinters + promql.FunctionCalls["holt_winters"] = promql.FunctionCalls["double_exponential_smoothing"] + } + return fns } From a6318f90c1763a250f3e8b6c89d9e2d75ccaa675 Mon Sep 17 00:00:00 2001 From: Ahmed Hassan Date: Mon, 26 Jan 2026 18:48:45 -0800 Subject: [PATCH 2/3] refactor cortex parser setup Signed-off-by: Ahmed Hassan --- pkg/cortex/cortex.go | 5 ++--- pkg/parser/parser.go | 31 ++++++++++++++++++++++--------- pkg/querier/querier.go | 13 ------------- pkg/querier/querier_test.go | 27 +++++++++++++++------------ 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/pkg/cortex/cortex.go b/pkg/cortex/cortex.go index a00b5f34809..a39081cc51f 100644 --- a/pkg/cortex/cortex.go +++ b/pkg/cortex/cortex.go @@ -39,6 +39,7 @@ import ( "github.com/cortexproject/cortex/pkg/ingester" "github.com/cortexproject/cortex/pkg/ingester/client" "github.com/cortexproject/cortex/pkg/parquetconverter" + cortexparser "github.com/cortexproject/cortex/pkg/parser" "github.com/cortexproject/cortex/pkg/querier" "github.com/cortexproject/cortex/pkg/querier/tenantfederation" "github.com/cortexproject/cortex/pkg/querier/tripperware" @@ -551,7 +552,5 @@ func (t *Cortex) readyHandler(sm *services.Manager) http.HandlerFunc { } func (t *Cortex) setupPromQLFunctions() { - // The holt_winters function is renamed to double_exponential_smoothing and has been experimental since Prometheus v3. (https://github.com/prometheus/prometheus/pull/14930) - // The cortex supports holt_winters for users using this function. - querier.EnableExperimentalPromQLFunctions(t.Cfg.Querier.EnablePromQLExperimentalFunctions, true) + cortexparser.Setup(t.Cfg.Querier.EnablePromQLExperimentalFunctions, true) } diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index e370361c432..d37ff965b44 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -8,21 +8,34 @@ import ( "github.com/thanos-io/promql-engine/execution/parse" ) -var functions = buildFunctions() +var functions = buildFunctions(true) -func buildFunctions() map[string]*promqlparser.Function { +func Setup(enableExperimentalFunctions, enableHoltWinters bool) { + promqlparser.EnableExperimentalFunctions = enableExperimentalFunctions + buildFunctions(enableHoltWinters) +} + +func buildFunctions(enableHoltWinters bool) map[string]*promqlparser.Function { fns := make(map[string]*promqlparser.Function, len(promqlparser.Functions)) maps.Copy(fns, promqlparser.Functions) maps.Copy(fns, parse.XFunctions) // The holt_winters function was renamed to double_exponential_smoothing and marked experimental in Prometheus v3. - // Register holt_winters as an alias in cortexparser to maintain backward compatibility. - if des, ok := fns["double_exponential_smoothing"]; ok { - holtWinters := *des - holtWinters.Experimental = false - holtWinters.Name = "holt_winters" - fns["holt_winters"] = &holtWinters - promql.FunctionCalls["holt_winters"] = promql.FunctionCalls["double_exponential_smoothing"] + // Register holt_winters as an alias to maintain backward compatibility. + if enableHoltWinters { + if des, ok := fns["double_exponential_smoothing"]; ok { + holtWinters := *des + holtWinters.Experimental = false + holtWinters.Name = "holt_winters" + fns["holt_winters"] = &holtWinters + + // Also register in global Prometheus parser for engine execution + promqlparser.Functions["holt_winters"] = &holtWinters + promql.FunctionCalls["holt_winters"] = promql.FunctionCalls["double_exponential_smoothing"] + } + } else { + delete(promqlparser.Functions, "holt_winters") + delete(promql.FunctionCalls, "holt_winters") } return fns diff --git a/pkg/querier/querier.go b/pkg/querier/querier.go index 720e304dfcb..8ebc66a16dc 100644 --- a/pkg/querier/querier.go +++ b/pkg/querier/querier.go @@ -17,7 +17,6 @@ import ( "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/promql" - "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/util/annotations" "github.com/thanos-io/thanos/pkg/strutil" @@ -719,15 +718,3 @@ func validateQueryTimeRange(ctx context.Context, userID string, startMs, endMs i return int64(startTime), int64(endTime), nil } - -func EnableExperimentalPromQLFunctions(enablePromQLExperimentalFunctions, enableHoltWinters bool) { - parser.EnableExperimentalFunctions = enablePromQLExperimentalFunctions - - if enableHoltWinters { - holtWinters := *parser.Functions["double_exponential_smoothing"] - holtWinters.Experimental = false - holtWinters.Name = "holt_winters" - parser.Functions["holt_winters"] = &holtWinters - promql.FunctionCalls["holt_winters"] = promql.FunctionCalls["double_exponential_smoothing"] - } -} diff --git a/pkg/querier/querier_test.go b/pkg/querier/querier_test.go index ec08fee2ed2..4a13dae9aaf 100644 --- a/pkg/querier/querier_test.go +++ b/pkg/querier/querier_test.go @@ -34,6 +34,7 @@ import ( promchunk "github.com/cortexproject/cortex/pkg/chunk/encoding" "github.com/cortexproject/cortex/pkg/cortexpb" "github.com/cortexproject/cortex/pkg/ingester/client" + cortexparser "github.com/cortexproject/cortex/pkg/parser" "github.com/cortexproject/cortex/pkg/querier/batch" "github.com/cortexproject/cortex/pkg/querier/series" "github.com/cortexproject/cortex/pkg/util" @@ -1693,32 +1694,34 @@ func TestConfig_Validate(t *testing.T) { } func Test_EnableExperimentalPromQLFunctions(t *testing.T) { - EnableExperimentalPromQLFunctions(true, true) + cortexparser.Setup(true, true) + defer cortexparser.Setup(false, false) - // holt_winters function should exist - holtWintersFunc, ok := parser.Functions["holt_winters"] - require.True(t, ok) + // Verify experimental functions flag is set + require.True(t, parser.EnableExperimentalFunctions) // double_exponential_smoothing function should exist doubleExponentialSmoothingFunc, ok := parser.Functions["double_exponential_smoothing"] require.True(t, ok) + require.Equal(t, "double_exponential_smoothing", doubleExponentialSmoothingFunc.Name) + require.True(t, parser.Functions["double_exponential_smoothing"].Experimental) - // holt_winters should not experimental function. + // holt_winters should exist + holtWintersFunc, ok := parser.Functions["holt_winters"] + require.True(t, ok) + require.Equal(t, "holt_winters", holtWintersFunc.Name) require.False(t, holtWintersFunc.Experimental) - // holt_winters's Variadic, ReturnType, and ArgTypes are the same as the double_exponential_smoothing. + + // holt_winters's Variadic, ReturnType, and ArgTypes are the same as the double_exponential_smoothing require.Equal(t, doubleExponentialSmoothingFunc.Variadic, holtWintersFunc.Variadic) require.Equal(t, doubleExponentialSmoothingFunc.ReturnType, holtWintersFunc.ReturnType) require.Equal(t, doubleExponentialSmoothingFunc.ArgTypes, holtWintersFunc.ArgTypes) - // double_exponential_smoothing shouldn't be changed. - require.Equal(t, "double_exponential_smoothing", doubleExponentialSmoothingFunc.Name) - require.True(t, parser.Functions["double_exponential_smoothing"].Experimental) - - // holt_winters function calls should exist. + // holt_winters function calls should exist _, ok = promql.FunctionCalls["holt_winters"] require.True(t, ok) - // double_exponential_smoothing function calls should exist. + // double_exponential_smoothing function calls should exist _, ok = promql.FunctionCalls["double_exponential_smoothing"] require.True(t, ok) } From f4ab757a19be3623c835364e44e576e3853b063a Mon Sep 17 00:00:00 2001 From: Ahmed Hassan Date: Wed, 4 Feb 2026 00:51:22 -0800 Subject: [PATCH 3/3] fix holt winters backward compatibility test Signed-off-by: Ahmed Hassan --- integration/backward_compatibility_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration/backward_compatibility_test.go b/integration/backward_compatibility_test.go index 1df6f1bdf3e..60cb13457e4 100644 --- a/integration/backward_compatibility_test.go +++ b/integration/backward_compatibility_test.go @@ -190,6 +190,8 @@ func TestCanSupportHoltWintersFunc(t *testing.T) { "-alertmanager.web.external-url": "http://localhost/alertmanager", // enable experimental promQL funcs "-querier.enable-promql-experimental-functions": "true", + // max query length + "-store.max-query-length": "30d", }, ) // make alert manager config dir