From 3d874c22b0299f65286ebec7bf79a35c385f68d9 Mon Sep 17 00:00:00 2001 From: Vibhu Pandey Date: Sun, 3 Aug 2025 17:18:51 +0530 Subject: [PATCH] chore(resp): add omitempty to timestamp (#8688) For the requestType: Trace, we don't care about the timestamp in the rawRow. - Handling Zero timestamp values in the rawData response - simplify RawRow `map[string]*any` to `map[string]any` and eliminate unnecessary pointer indirection. --- pkg/querier/bucket_cache_test.go | 17 ++-- pkg/querier/consume.go | 7 +- .../querybuildertypesv5/resp.go | 26 +++--- .../querybuildertypesv5/resp_test.go | 87 +++++++++++++++++++ 4 files changed, 109 insertions(+), 28 deletions(-) diff --git a/pkg/querier/bucket_cache_test.go b/pkg/querier/bucket_cache_test.go index f141be1ae42f..57d9a53aca2f 100644 --- a/pkg/querier/bucket_cache_test.go +++ b/pkg/querier/bucket_cache_test.go @@ -67,11 +67,6 @@ func (m *mockQuery) Execute(ctx context.Context) (*qbtypes.Result, error) { }, nil } -// ptr is a helper function to get a pointer to a value -func ptr[T any](v T) *T { - return &v -} - // createTestBucketCache creates a test bucket cache func createTestBucketCache(t *testing.T) *bucketCache { memCache := createTestCache(t) @@ -425,16 +420,16 @@ func TestBucketCache_RawData(t *testing.T) { Rows: []*qbtypes.RawRow{ { Timestamp: time.Unix(1, 0), - Data: map[string]*any{ - "value": ptr[any](10.5), - "label": ptr[any]("test1"), + Data: map[string]any{ + "value": 10.5, + "label": "test1", }, }, { Timestamp: time.Unix(2, 0), - Data: map[string]*any{ - "value": ptr[any](20.5), - "label": ptr[any]("test2"), + Data: map[string]any{ + "value": 20.5, + "label": "test2", }, }, }, diff --git a/pkg/querier/consume.go b/pkg/querier/consume.go index 63ad7292687f..b43f4461ef65 100644 --- a/pkg/querier/consume.go +++ b/pkg/querier/consume.go @@ -350,7 +350,6 @@ func derefValue(v interface{}) interface{} { } func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) { - colNames := rows.Columns() colTypes := rows.ColumnTypes() colCnt := len(colNames) @@ -375,7 +374,7 @@ func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) { } rr := qbtypes.RawRow{ - Data: make(map[string]*any, colCnt), + Data: make(map[string]any, colCnt), } for i, cellPtr := range scan { @@ -398,9 +397,7 @@ func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) { } } - // store value in map as *any, to match the schema - v := any(val) - rr.Data[name] = &v + rr.Data[name] = val } outRows = append(outRows, &rr) } diff --git a/pkg/types/querybuildertypes/querybuildertypesv5/resp.go b/pkg/types/querybuildertypes/querybuildertypesv5/resp.go index 51919b91e0d3..1ed3b6c3a9b5 100644 --- a/pkg/types/querybuildertypes/querybuildertypesv5/resp.go +++ b/pkg/types/querybuildertypes/querybuildertypesv5/resp.go @@ -164,8 +164,8 @@ type RawData struct { } type RawRow struct { - Timestamp time.Time `json:"timestamp"` - Data map[string]*any `json:"data"` + Timestamp time.Time `json:"timestamp"` + Data map[string]any `json:"data"` } func sanitizeValue(v any) any { @@ -255,22 +255,24 @@ func (s ScalarData) MarshalJSON() ([]byte, error) { func (r RawRow) MarshalJSON() ([]byte, error) { type Alias RawRow - sanitizedData := make(map[string]*any) + sanitizedData := make(map[string]any) for k, v := range r.Data { - if v != nil { - sanitized := sanitizeValue(*v) - sanitizedData[k] = &sanitized - } else { - sanitizedData[k] = nil - } + sanitizedData[k] = sanitizeValue(v) + } + + var timestamp *time.Time + if !r.Timestamp.IsZero() { + timestamp = &r.Timestamp } return json.Marshal(&struct { *Alias - Data map[string]*any `json:"data"` + Data map[string]any `json:"data"` + Timestamp *time.Time `json:"timestamp,omitempty"` }{ - Alias: (*Alias)(&r), - Data: sanitizedData, + Alias: (*Alias)(&r), + Data: sanitizedData, + Timestamp: timestamp, }) } diff --git a/pkg/types/querybuildertypes/querybuildertypesv5/resp_test.go b/pkg/types/querybuildertypes/querybuildertypesv5/resp_test.go index d55dbac25368..3f5c517ad389 100644 --- a/pkg/types/querybuildertypes/querybuildertypesv5/resp_test.go +++ b/pkg/types/querybuildertypes/querybuildertypesv5/resp_test.go @@ -5,8 +5,10 @@ import ( "math" "strings" "testing" + "time" "github.com/SigNoz/signoz/pkg/types/telemetrytypes" + "github.com/stretchr/testify/assert" ) func TestTimeSeriesValue_MarshalJSON(t *testing.T) { @@ -308,3 +310,88 @@ func TestSanitizeValue(t *testing.T) { }) } } + +func TestRawData_MarshalJSON(t *testing.T) { + str1 := "val1" + num1 := float64(1.1) + + tests := []struct { + name string + data RawData + expected string + }{ + { + name: "ValidTimestamp_NoData", + data: RawData{ + QueryName: "test_query", + Rows: []*RawRow{ + { + Timestamp: time.Unix(1717334400, 0).UTC(), + Data: map[string]any{}, + }, + }, + }, + expected: `{"nextCursor":"","queryName":"test_query","rows":[{"data":{},"timestamp":"2024-06-02T13:20:00Z"}]}`, + }, + { + name: "NoTimestamp_NoData", + data: RawData{ + QueryName: "test_query", + Rows: []*RawRow{ + { + Data: map[string]any{}, + }, + }, + }, + expected: `{"nextCursor":"","queryName":"test_query","rows":[{"data":{}}]}`, + }, + { + name: "ZeroTimestamp_NoData", + data: RawData{ + QueryName: "test_query", + Rows: []*RawRow{ + { + Timestamp: time.Time{}, + Data: map[string]any{}, + }, + }, + }, + expected: `{"nextCursor":"","queryName":"test_query","rows":[{"data":{}}]}`, + }, + { + name: "NoTimestamp_WithData", + data: RawData{ + QueryName: "test_query", + Rows: []*RawRow{ + { + Data: map[string]any{ + "value": "val1", + }, + }, + }, + }, + expected: `{"nextCursor":"","queryName":"test_query","rows":[{"data":{"value":"val1"}}]}`, + }, + { + name: "WithTimestamp_WithPointerData", + data: RawData{ + QueryName: "test_query", + Rows: []*RawRow{ + { + Timestamp: time.Unix(1717334400, 0).UTC(), + Data: map[string]any{"str1": &str1, "num1": &num1, "num2": nil}, + }, + }, + }, + expected: `{"nextCursor":"","queryName":"test_query","rows":[{"data":{"num1":1.1,"str1":"val1","num2":null},"timestamp":"2024-06-02T13:20:00Z"}]}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := json.Marshal(tt.data) + assert.NoError(t, err) + assert.JSONEq(t, tt.expected, string(got)) + }) + } +}