Skip to content
This repository was archived by the owner on Nov 7, 2025. It is now read-only.

Commit f527396

Browse files
authored
Quickfix to remove nondeterminism for pipeline aggr with multiple parents (#943)
All dashboards work after this Again, hacky, but when we implement fully correct `bucket_script`, hacks will disappear.
1 parent 7f5dd2b commit f527396

File tree

4 files changed

+186
-11
lines changed

4 files changed

+186
-11
lines changed

quesma/model/bucket_aggregations/auto_date_histogram.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func (query *AutoDateHistogram) TranslateSqlResponseToJson(rows []model.QueryRes
3939
return model.JsonMap{
4040
"buckets": []model.JsonMap{{
4141
"key": query.key,
42-
"key_as_string": time.UnixMilli(query.key).Format("2006-01-02T15:04:05.000-07:00"),
42+
"key_as_string": time.UnixMilli(query.key).UTC().Format("2006-01-02T15:04:05.000"),
4343
"doc_count": rows[0].LastColValue(),
4444
}},
4545
"interval": "100y", // seems working for bucketsNr=1 case. Will have to be changed for other cases.

quesma/model/pipeline_aggregations/bucket_script.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,13 @@ func (query BucketScript) findFilterValue(rows []model.QueryResultRow, filterNam
8080
for _, row := range rows {
8181
for _, col := range row.Cols {
8282
colName := col.ColName
83-
if !strings.HasSuffix(colName, "_col_0") {
84-
continue
83+
switch { // remove possible suffix
84+
case strings.HasSuffix(colName, "_col_0"):
85+
colName = strings.TrimSuffix(colName, "_col_0")
86+
case strings.HasSuffix(colName, "__count"):
87+
colName = strings.TrimSuffix(colName, "__count")
8588
}
86-
colName = strings.TrimSuffix(colName, "_col_0")
87-
if strings.HasSuffix(colName, "-"+filterName) {
89+
if strings.HasSuffix(colName, filterName) {
8890
return util.ExtractNumeric64(col.Value)
8991
}
9092
}

quesma/queryparser/pipeline_aggregations.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"quesma/logger"
77
"quesma/model"
88
"quesma/model/pipeline_aggregations"
9+
"quesma/util"
910
"strings"
1011
)
1112

@@ -225,14 +226,16 @@ func (cw *ClickhouseQueryTranslator) parseBucketsPath(shouldBeQueryMap any, aggr
225226
case QueryMap:
226227
// TODO: handle arbitrary nr of keys (and arbitrary scripts, because we also handle only one special case)
227228
if len(bucketsPath) == 1 || len(bucketsPath) == 2 {
228-
for _, bucketPath := range bucketsPath {
229-
if _, ok = bucketPath.(string); !ok {
230-
logger.WarnWithCtx(cw.Ctx).Msgf("buckets_path is not a map with string values, but %T. Skipping this aggregation", bucketPath)
229+
// We return just 1 value here (for smallest key) (determinism here important, returning any of them is incorrect)
230+
// Seems iffy, but works for all cases so far.
231+
// After fixing the TODO above, it should also get fixed.
232+
for _, key := range util.MapKeysSorted(bucketsPath) {
233+
if path, ok := bucketsPath[key].(string); ok {
234+
return path, true
235+
} else {
236+
logger.WarnWithCtx(cw.Ctx).Msgf("buckets_path is not a map with string values, but %T. Skipping this aggregation", path)
231237
return
232238
}
233-
// Kinda weird to return just the first value, but seems working on all cases so far.
234-
// After fixing the TODO above, it should also get fixed.
235-
return bucketPath.(string), true
236239
}
237240
} else {
238241
logger.WarnWithCtx(cw.Ctx).Msgf("buckets_path is not a map with one or two keys, but %d. Skipping this aggregation", len(bucketsPath))

quesma/testdata/clients/clover.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,4 +473,174 @@ var CloverTests = []testdata.AggregationTestCase{
473473
fromUnixTimestamp64Milli(1728635627125))`,
474474
AdditionalAcceptableDifference: []string{"key_as_string"}, // timezone differences between local and github runs... There's always 2h difference between those, need to investigate. Maybe come back to .UTC() so there's no "+timezone" (e.g. +02:00)?
475475
},
476+
{ // [6]
477+
TestName: "bucket_script with multiple buckets_path",
478+
QueryRequestJson: `
479+
{
480+
"aggs": {
481+
"timeseries": {
482+
"aggs": {
483+
"f2": {
484+
"bucket_script": {
485+
"buckets_path": {
486+
"denominator": "f2-denominator>_count",
487+
"numerator": "f2-numerator>_count"
488+
},
489+
"script": "params.numerator != null && params.denominator != null && params.denominator != 0 ? params.numerator / params.denominator : 0"
490+
}
491+
},
492+
"f2-denominator": {
493+
"filter": {
494+
"bool": {
495+
"filter": [],
496+
"must": [],
497+
"must_not": [],
498+
"should": []
499+
}
500+
}
501+
},
502+
"f2-numerator": {
503+
"filter": {
504+
"bool": {
505+
"filter": [],
506+
"must": [
507+
{
508+
"query_string": {
509+
"analyze_wildcard": true,
510+
"query": "!_exists_:a.b_str"
511+
}
512+
}
513+
],
514+
"must_not": [],
515+
"should": []
516+
}
517+
}
518+
}
519+
},
520+
"auto_date_histogram": {
521+
"buckets": 1,
522+
"field": "@timestamp"
523+
},
524+
"meta": {
525+
"indexPatternString": "ab*",
526+
"intervalString": "9075600000ms",
527+
"normalized": true,
528+
"panelId": "f0",
529+
"seriesId": "f1",
530+
"timeField": "@timestamp"
531+
}
532+
}
533+
},
534+
"query": {
535+
"bool": {
536+
"filter": [],
537+
"must": [
538+
{
539+
"range": {
540+
"@timestamp": {
541+
"format": "strict_date_optional_time",
542+
"gte": "2024-07-19T14:38:24.783Z",
543+
"lte": "2024-11-01T15:38:24.783Z"
544+
}
545+
}
546+
},
547+
{
548+
"bool": {
549+
"filter": [],
550+
"must": [],
551+
"must_not": [],
552+
"should": []
553+
}
554+
},
555+
{
556+
"bool": {
557+
"filter": [],
558+
"must": [],
559+
"must_not": [],
560+
"should": []
561+
}
562+
}
563+
],
564+
"must_not": [],
565+
"should": []
566+
}
567+
},
568+
"runtime_mappings": {},
569+
"size": 0,
570+
"timeout": "30000ms",
571+
"track_total_hits": true
572+
}`,
573+
ExpectedResponse: `
574+
{
575+
"completion_status": 200,
576+
"completion_time_in_millis": 1730475504882,
577+
"expiration_time_in_millis": 1730898929116,
578+
"id": "quesma_async_0192e860-b4cb-7be4-a193-43d38686c80d",
579+
"is_partial": false,
580+
"is_running": false,
581+
"response": {
582+
"_shards": {
583+
"failed": 0,
584+
"skipped": 0,
585+
"successful": 1,
586+
"total": 1
587+
},
588+
"aggregations": {
589+
"timeseries": {
590+
"buckets": [
591+
{
592+
"f2": {
593+
"value": 0.178
594+
},
595+
"f2-denominator": {
596+
"doc_count": 1000
597+
},
598+
"f2-numerator": {
599+
"doc_count": 178
600+
},
601+
"doc_count": 1000,
602+
"key": 1721399904783,
603+
"key_as_string": "2024-07-19T14:38:24.783"
604+
}
605+
],
606+
"interval": "100y",
607+
"meta": {
608+
"indexPatternString": "ab*",
609+
"intervalString": "9075600000ms",
610+
"normalized": true,
611+
"panelId": "f0",
612+
"seriesId": "f1",
613+
"timeField": "@timestamp"
614+
}
615+
}
616+
},
617+
"hits": {
618+
"hits": [],
619+
"max_score": null,
620+
"total": {
621+
"relation": "eq",
622+
"value": 1000
623+
}
624+
},
625+
"timed_out": false,
626+
"took": 0
627+
},
628+
"start_time_in_millis": 0
629+
}`,
630+
ExpectedPancakeResults: []model.QueryResultRow{
631+
{Cols: []model.QueryResultCol{
632+
model.NewQueryResultCol("aggr__timeseries__count", int64(1000)),
633+
model.NewQueryResultCol("metric__timeseries__f2-numerator_col_0", int64(178)),
634+
model.NewQueryResultCol("aggr__timeseries__f2-denominator__count", int64(1000)),
635+
}},
636+
},
637+
ExpectedPancakeSQL: `
638+
SELECT count(*) AS "aggr__timeseries__count",
639+
countIf(NOT ("a.b_str" IS NOT NULL)) AS
640+
"metric__timeseries__f2-numerator_col_0",
641+
countIf(true) AS "aggr__timeseries__f2-denominator__count"
642+
FROM __quesma_table_name
643+
WHERE ("@timestamp">=fromUnixTimestamp64Milli(1721399904783) AND "@timestamp"<=
644+
fromUnixTimestamp64Milli(1730475504783))`,
645+
},
476646
}

0 commit comments

Comments
 (0)