Skip to content

Commit a324dda

Browse files
committed
Port ChartHistogram from FireFly Core (#196)
Signed-off-by: Apostlex0 <[email protected]>
1 parent 9fae2a7 commit a324dda

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed

pkg/dbsql/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const (
3737
SQLConfMaxIdleConns = "maxIdleConns"
3838
// SQLConfMaxConnLifetime maximum connections to the database
3939
SQLConfMaxConnLifetime = "maxConnLifetime"
40+
// SQLConfHistogramsMaxChartRows maximum rows to fetch
41+
SQLConfHistogramsMaxChartRows = "histograms.maxChartRows"
4042
)
4143

4244
const (
@@ -51,4 +53,5 @@ func (s *Database) InitConfig(provider Provider, config config.Section) {
5153
config.AddKnownKey(SQLConfMaxConnIdleTime, "1m")
5254
config.AddKnownKey(SQLConfMaxIdleConns) // defaults to the max connections
5355
config.AddKnownKey(SQLConfMaxConnLifetime)
56+
config.AddKnownKey(SQLConfHistogramsMaxChartRows, 100) // as per ff core
5457
}

pkg/dbsql/histogram_sql.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Copyright © 2024 Kaleido, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package dbsql
18+
19+
import (
20+
"context"
21+
"database/sql"
22+
"strconv"
23+
24+
sq "github.com/Masterminds/squirrel"
25+
"github.com/hyperledger/firefly-common/pkg/config"
26+
"github.com/hyperledger/firefly-common/pkg/fftypes"
27+
"github.com/hyperledger/firefly-common/pkg/i18n"
28+
)
29+
30+
// GetChartHistogram executes a collection of queries (one per interval) and builds
31+
// a histogram response for the specified table and time intervals.
32+
func (s *Database) GetChartHistogram(
33+
ctx context.Context,
34+
tableName string,
35+
timestampColumn string,
36+
typeColumn string,
37+
namespaceColumn string,
38+
namespaceValue string,
39+
intervals []fftypes.ChartHistogramInterval,
40+
) ([]*fftypes.ChartHistogram, error) {
41+
42+
maxRows := config.GetUint64(SQLConfHistogramsMaxChartRows)
43+
44+
// check if we have a type column for grouping
45+
hasTypeColumn := typeColumn != ""
46+
47+
// Build qs for each interval
48+
queries := s.buildHistogramQueries(
49+
tableName, timestampColumn, typeColumn,
50+
namespaceColumn, namespaceValue,
51+
intervals, maxRows,
52+
)
53+
54+
histogramList := []*fftypes.ChartHistogram{}
55+
56+
for i, query := range queries {
57+
rows, _, err := s.Query(ctx, tableName, query)
58+
if err != nil {
59+
return nil, err
60+
}
61+
defer rows.Close()
62+
63+
// Process results
64+
data, total, err := s.processHistogramRows(ctx, tableName, rows, hasTypeColumn)
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
// Build histogram bucket
70+
histBucket := &fftypes.ChartHistogram{
71+
Count: strconv.FormatInt(total, 10),
72+
Timestamp: intervals[i].StartTime,
73+
Types: []*fftypes.ChartHistogramType{},
74+
IsCapped: total == int64(maxRows), //warning here doesn't matter since maxrows value is capped at 100
75+
}
76+
77+
// Add type counts if applicable
78+
if hasTypeColumn {
79+
for t, c := range data {
80+
histBucket.Types = append(histBucket.Types,
81+
&fftypes.ChartHistogramType{
82+
Count: strconv.Itoa(c),
83+
Type: t,
84+
})
85+
}
86+
}
87+
88+
histogramList = append(histogramList, histBucket)
89+
}
90+
91+
return histogramList, nil
92+
}
93+
94+
// buildHistogramQueries constructs SQL queries for each time interval.
95+
// each query selects data within the interval's time range, optionally
96+
// filtered by namespace, and limited to maxRows.
97+
func (s *Database) buildHistogramQueries(
98+
tableName string,
99+
timestampColumn string,
100+
typeColumn string,
101+
namespaceColumn string,
102+
namespaceValue string,
103+
intervals []fftypes.ChartHistogramInterval,
104+
maxRows uint64,
105+
) []sq.SelectBuilder {
106+
107+
queries := []sq.SelectBuilder{}
108+
109+
// Determine columns to select
110+
cols := []string{timestampColumn}
111+
if typeColumn != "" {
112+
cols = append(cols, typeColumn)
113+
}
114+
115+
for _, interval := range intervals {
116+
whereClause := sq.And{
117+
sq.GtOrEq{timestampColumn: interval.StartTime},
118+
sq.Lt{timestampColumn: interval.EndTime},
119+
}
120+
121+
// namespace filter
122+
if namespaceColumn != "" && namespaceValue != "" {
123+
whereClause = append(whereClause, sq.Eq{namespaceColumn: namespaceValue})
124+
}
125+
126+
// Build query with PlaceholderFormat applied
127+
query := sq.Select(cols...).
128+
From(tableName).
129+
Where(whereClause).
130+
OrderBy(timestampColumn).
131+
Limit(maxRows).
132+
PlaceholderFormat(s.features.PlaceholderFormat)
133+
134+
queries = append(queries, query)
135+
}
136+
137+
return queries
138+
}
139+
140+
// processHistogramRows scans SQL result rows and builds histogram data
141+
// If hasTypeColumn is true, it groups counts by type else it just
142+
// counts total rows.
143+
func (s *Database) processHistogramRows(
144+
ctx context.Context,
145+
tableName string,
146+
rows *sql.Rows,
147+
hasTypeColumn bool,
148+
) (map[string]int, int64, error) {
149+
150+
total := int64(0)
151+
152+
if !hasTypeColumn {
153+
// counting rows
154+
for rows.Next() {
155+
var timestamp interface{}
156+
if err := rows.Scan(&timestamp); err != nil {
157+
return nil, 0, i18n.NewError(ctx, i18n.MsgDBReadErr, tableName)
158+
}
159+
total++
160+
}
161+
return map[string]int{}, total, nil
162+
}
163+
164+
// Count by type
165+
typeMap := map[string]int{}
166+
for rows.Next() {
167+
var timestamp interface{}
168+
var typeStr string
169+
if err := rows.Scan(&timestamp, &typeStr); err != nil {
170+
return nil, 0, i18n.NewError(ctx, i18n.MsgDBReadErr, tableName)
171+
}
172+
173+
typeMap[typeStr]++
174+
total++
175+
}
176+
177+
return typeMap, total, nil
178+
}

pkg/fftypes/chart_histogram.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright © 2024 Kaleido, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package fftypes
18+
19+
const (
20+
ChartHistogramMaxBuckets = 100
21+
ChartHistogramMinBuckets = 1
22+
)
23+
24+
// ChartHistogram: list of buckets with types
25+
type ChartHistogram struct {
26+
Count string `ffstruct:"ChartHistogram" json:"count"`
27+
Timestamp *FFTime `ffstruct:"ChartHistogram" json:"timestamp"`
28+
Types []*ChartHistogramType `ffstruct:"ChartHistogram" json:"types"`
29+
IsCapped bool `ffstruct:"ChartHistogram" json:"isCapped"`
30+
}
31+
32+
type ChartHistogramType struct {
33+
Count string `ffstruct:"ChartHistogramType" json:"count"`
34+
Type string `ffstruct:"ChartHistogramType" json:"type"`
35+
}
36+
37+
type ChartHistogramInterval struct {
38+
StartTime *FFTime `json:"startTime"`
39+
EndTime *FFTime `json:"endTime"`
40+
}

0 commit comments

Comments
 (0)