Skip to content

Commit 18234d0

Browse files
authored
Enable aggregates and group_by (#109)
### TL;DR Enhanced the query functionality to support multiple grouping fields and aggregations, with improved type handling for ClickHouse data types. ### What changed? - Modified `GroupBy` field in `QueryParams` to accept an array of strings instead of a single string - Added `GetAggregations` method to handle grouped and aggregated queries separately - Updated `Aggregations` field in `QueryResponse` to use `[]map[string]interface{}` for flexible result handling - Implemented comprehensive ClickHouse type mapping to Go types with support for nullable, array, and low cardinality types - Separated data retrieval logic in handlers to handle aggregations distinctly from regular queries - Added unit tests for ClickHouse type mapping functionality ### How to test? 1. Query logs/transactions endpoints with multiple group_by parameters 2. Test various aggregation functions (COUNT, SUM, etc.) with grouping 3. Verify response structure for both regular and aggregated queries 4. Test handling of different ClickHouse data types and their nullable variants 5. Run new unit tests for type mapping functionality ### Why make this change? This enhancement provides more robust support for data analysis by properly handling grouped queries and aggregations. The improved type mapping system ensures accurate data representation when working with ClickHouse's various data types, while maintaining clean separation between regular queries and aggregations.
2 parents 2f29801 + 3e4e848 commit 18234d0

File tree

10 files changed

+513
-86
lines changed

10 files changed

+513
-86
lines changed

api/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type QueryParams struct {
3030
// @Description Map of filter parameters
3131
FilterParams map[string]string `schema:"-"`
3232
// @Description Field to group results by
33-
GroupBy string `schema:"group_by"`
33+
GroupBy []string `schema:"group_by"`
3434
// @Description Field to sort results by
3535
SortBy string `schema:"sort_by"`
3636
// @Description Sort order (asc or desc)
@@ -70,7 +70,7 @@ type QueryResponse struct {
7070
// @Description Query result data
7171
Data interface{} `json:"data,omitempty"`
7272
// @Description Aggregation results
73-
Aggregations map[string]string `json:"aggregations,omitempty"`
73+
Aggregations []map[string]interface{} `json:"aggregations,omitempty"`
7474
}
7575

7676
func writeError(w http.ResponseWriter, message string, code int) {

internal/handlers/logs_handlers.go

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,39 +133,59 @@ func handleLogsRequest(c *gin.Context, contractAddress, signature string) {
133133
return
134134
}
135135

136-
logs, err := mainStorage.GetLogs(storage.QueryFilter{
136+
// Prepare the QueryFilter
137+
qf := storage.QueryFilter{
137138
FilterParams: queryParams.FilterParams,
138-
GroupBy: []string{queryParams.GroupBy},
139+
ContractAddress: contractAddress,
140+
Signature: signatureHash,
141+
ChainId: chainId,
139142
SortBy: queryParams.SortBy,
140143
SortOrder: queryParams.SortOrder,
141144
Page: queryParams.Page,
142145
Limit: queryParams.Limit,
143-
Aggregates: queryParams.Aggregates,
144-
ContractAddress: contractAddress,
145-
Signature: signatureHash,
146-
ChainId: chainId,
147-
})
148-
if err != nil {
149-
log.Error().Err(err).Msg("Error querying logs")
150-
api.InternalErrorHandler(c)
151-
return
152146
}
153147

154-
response := api.QueryResponse{
148+
// Initialize the QueryResult
149+
queryResult := api.QueryResponse{
155150
Meta: api.Meta{
156151
ChainId: chainId.Uint64(),
157152
ContractAddress: contractAddress,
158153
Signature: signatureHash,
159154
Page: queryParams.Page,
160155
Limit: queryParams.Limit,
161-
TotalItems: len(logs.Data),
156+
TotalItems: 0,
162157
TotalPages: 0, // TODO: Implement total pages count
163158
},
164-
Data: logs.Data,
165-
Aggregations: logs.Aggregates,
159+
Data: nil,
160+
Aggregations: nil,
161+
}
162+
163+
// If aggregates or groupings are specified, retrieve them
164+
if len(queryParams.Aggregates) > 0 || len(queryParams.GroupBy) > 0 {
165+
qf.Aggregates = queryParams.Aggregates
166+
qf.GroupBy = queryParams.GroupBy
167+
168+
aggregatesResult, err := mainStorage.GetAggregations("logs", qf)
169+
if err != nil {
170+
log.Error().Err(err).Msg("Error querying aggregates")
171+
api.InternalErrorHandler(c)
172+
return
173+
}
174+
queryResult.Aggregations = aggregatesResult.Aggregates
175+
queryResult.Meta.TotalItems = len(aggregatesResult.Aggregates)
176+
} else {
177+
// Retrieve logs data
178+
logsResult, err := mainStorage.GetLogs(qf)
179+
if err != nil {
180+
log.Error().Err(err).Msg("Error querying logs")
181+
api.InternalErrorHandler(c)
182+
return
183+
}
184+
queryResult.Data = logsResult.Data
185+
queryResult.Meta.TotalItems = len(logsResult.Data)
166186
}
167187

168-
sendJSONResponse(c, response)
188+
sendJSONResponse(c, queryResult)
169189
}
170190

171191
func getMainStorage() (storage.IMainStorage, error) {

internal/handlers/transactions_handlers.go

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -134,37 +134,57 @@ func handleTransactionsRequest(c *gin.Context, contractAddress, signature string
134134
return
135135
}
136136

137-
result, err := mainStorage.GetTransactions(storage.QueryFilter{
137+
// Prepare the QueryFilter
138+
qf := storage.QueryFilter{
138139
FilterParams: queryParams.FilterParams,
139-
GroupBy: []string{queryParams.GroupBy},
140+
ContractAddress: contractAddress,
141+
Signature: signatureHash,
142+
ChainId: chainId,
140143
SortBy: queryParams.SortBy,
141144
SortOrder: queryParams.SortOrder,
142145
Page: queryParams.Page,
143146
Limit: queryParams.Limit,
144-
Aggregates: queryParams.Aggregates,
145-
ContractAddress: contractAddress,
146-
Signature: signatureHash,
147-
ChainId: chainId,
148-
})
149-
if err != nil {
150-
log.Error().Err(err).Msg("Error querying transactions")
151-
api.InternalErrorHandler(c)
152-
return
153147
}
154148

155-
response := api.QueryResponse{
149+
// Initialize the QueryResult
150+
queryResult := api.QueryResponse{
156151
Meta: api.Meta{
157152
ChainId: chainId.Uint64(),
158153
ContractAddress: contractAddress,
159-
Signature: signature,
154+
Signature: signatureHash,
160155
Page: queryParams.Page,
161156
Limit: queryParams.Limit,
162-
TotalItems: 0, // TODO: Implement total items count
157+
TotalItems: 0,
163158
TotalPages: 0, // TODO: Implement total pages count
164159
},
165-
Data: result.Data,
166-
Aggregations: result.Aggregates,
160+
Data: nil,
161+
Aggregations: nil,
162+
}
163+
164+
// If aggregates or groupings are specified, retrieve them
165+
if len(queryParams.Aggregates) > 0 || len(queryParams.GroupBy) > 0 {
166+
qf.Aggregates = queryParams.Aggregates
167+
qf.GroupBy = queryParams.GroupBy
168+
169+
aggregatesResult, err := mainStorage.GetAggregations("transactions", qf)
170+
if err != nil {
171+
log.Error().Err(err).Msg("Error querying aggregates")
172+
api.InternalErrorHandler(c)
173+
return
174+
}
175+
queryResult.Aggregations = aggregatesResult.Aggregates
176+
queryResult.Meta.TotalItems = len(aggregatesResult.Aggregates)
177+
} else {
178+
// Retrieve logs data
179+
transactionsResult, err := mainStorage.GetTransactions(qf)
180+
if err != nil {
181+
log.Error().Err(err).Msg("Error querying tran")
182+
api.InternalErrorHandler(c)
183+
return
184+
}
185+
queryResult.Data = transactionsResult.Data
186+
queryResult.Meta.TotalItems = len(transactionsResult.Data)
167187
}
168188

169-
c.JSON(http.StatusOK, response)
189+
c.JSON(http.StatusOK, queryResult)
170190
}

0 commit comments

Comments
 (0)