From f366a68fc5a30874aac88fa509550ad6ca17ac00 Mon Sep 17 00:00:00 2001 From: rriski Date: Fri, 14 Jun 2024 15:02:27 +0000 Subject: [PATCH] feat: add new ClickHouse endpoints (#78) Co-authored-by: Murad Biashimov --- config.yaml | 2 + generator/models.go | 26 ++++++++--- handler/clickhouse/clickhouse.go | 74 ++++++++++++++++++++++++++++++-- 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/config.yaml b/config.yaml index a3f6b81..a4d2d0d 100644 --- a/config.yaml +++ b/config.yaml @@ -59,9 +59,11 @@ BillingGroup: - BillingGroupProjectsAssign - BillingGroupUpdate ClickHouse: + - ServiceClickHouseCurrentQueries - ServiceClickHouseDatabaseCreate - ServiceClickHouseDatabaseDelete - ServiceClickHouseDatabaseList + - ServiceClickHouseQuery - ServiceClickHouseQueryStats - ServiceClickHouseTieredStorageSummary Cloud: diff --git a/generator/models.go b/generator/models.go index 8c63121..4de7a3f 100644 --- a/generator/models.go +++ b/generator/models.go @@ -125,6 +125,8 @@ const ( SchemaTypeBoolean = "boolean" // SchemaTypeTime represents a time schema type. SchemaTypeTime = "time" + // SchemaTypeAny internal type for anyOf + SchemaTypeAny = "any" ) // Schema represents a parsed OpenAPI schema. @@ -234,6 +236,12 @@ func (s *Schema) init(doc *Doc, scope map[string]*Schema, name string) { } if s.isArray() { + // a workaround for invalid schema + // fixme: on the backend + if s.Items == nil { + s.Items = &Schema{Type: SchemaTypeAny} + } + s.Items.parent = s s.Items.required = true // a workaround to not have slices with pointers s.Items.init(doc, scope, toSingle(name)) @@ -287,6 +295,10 @@ func (s *Schema) isArray() bool { return s.Type == SchemaTypeArray } +func (s *Schema) isNestedArray() bool { + return s.isArray() && s.Items.isArray() +} + func (s *Schema) isScalar() bool { switch s.Type { case SchemaTypeString, SchemaTypeInteger, SchemaTypeNumber, SchemaTypeBoolean, SchemaTypeTime: @@ -342,11 +354,12 @@ func getScalarType(s *Schema) *jen.Statement { // getType returns go type with/wo a pointer func getType(s *Schema) *jen.Statement { - if s.isEnum() { + switch { + case s.Type == SchemaTypeAny: + return jen.Any() + case s.isEnum(): return jen.Id(s.CamelName) - } - - if s.isScalar() { + case s.isScalar(): scalar := getScalarType(s) if !s.required && s.Type != SchemaTypeString { return jen.Op("*").Add(scalar) @@ -363,7 +376,10 @@ func getType(s *Schema) *jen.Statement { } // No pointers for complex objects - if s.Items.isObject() || s.Items.isArray() { + switch { + case s.isNestedArray(): + // but not nested array + case s.Items.isObject() || s.Items.isArray(): return a.Id(s.Items.CamelName) } diff --git a/handler/clickhouse/clickhouse.go b/handler/clickhouse/clickhouse.go index b817046..d80c3a7 100644 --- a/handler/clickhouse/clickhouse.go +++ b/handler/clickhouse/clickhouse.go @@ -10,6 +10,11 @@ import ( ) type Handler interface { + // ServiceClickHouseCurrentQueries list active queries + // GET /v1/project/{project}/service/{service_name}/clickhouse/query + // https://api.aiven.io/doc/#tag/Service:_ClickHouse/operation/ServiceClickHouseCurrentQueries + ServiceClickHouseCurrentQueries(ctx context.Context, project string, serviceName string) ([]QueryOut, error) + // ServiceClickHouseDatabaseCreate create a database // POST /v1/project/{project}/service/{service_name}/clickhouse/db // https://api.aiven.io/doc/#tag/Service:_ClickHouse/operation/ServiceClickHouseDatabaseCreate @@ -25,10 +30,15 @@ type Handler interface { // https://api.aiven.io/doc/#tag/Service:_ClickHouse/operation/ServiceClickHouseDatabaseList ServiceClickHouseDatabaseList(ctx context.Context, project string, serviceName string) ([]DatabaseOut, error) + // ServiceClickHouseQuery execute an SQL query + // POST /v1/project/{project}/service/{service_name}/clickhouse/query + // https://api.aiven.io/doc/#tag/Service:_ClickHouse/operation/ServiceClickHouseQuery + ServiceClickHouseQuery(ctx context.Context, project string, serviceName string, in *ServiceClickHouseQueryIn) (*ServiceClickHouseQueryOut, error) + // ServiceClickHouseQueryStats return statistics on recent queries // GET /v1/project/{project}/service/{service_name}/clickhouse/query/stats // https://api.aiven.io/doc/#tag/Service:_ClickHouse/operation/ServiceClickHouseQueryStats - ServiceClickHouseQueryStats(ctx context.Context, project string, serviceName string) ([]QueryOut, error) + ServiceClickHouseQueryStats(ctx context.Context, project string, serviceName string) ([]QueryOutAlt, error) // ServiceClickHouseTieredStorageSummary get the ClickHouse tiered storage summary // GET /v1/project/{project}/service/{service_name}/clickhouse/tiered-storage/summary @@ -48,6 +58,19 @@ type ClickHouseHandler struct { doer doer } +func (h *ClickHouseHandler) ServiceClickHouseCurrentQueries(ctx context.Context, project string, serviceName string) ([]QueryOut, error) { + path := fmt.Sprintf("/v1/project/%s/service/%s/clickhouse/query", url.PathEscape(project), url.PathEscape(serviceName)) + b, err := h.doer.Do(ctx, "ServiceClickHouseCurrentQueries", "GET", path, nil) + if err != nil { + return nil, err + } + out := new(serviceClickHouseCurrentQueriesOut) + err = json.Unmarshal(b, out) + if err != nil { + return nil, err + } + return out.Queries, nil +} func (h *ClickHouseHandler) ServiceClickHouseDatabaseCreate(ctx context.Context, project string, serviceName string, in *ServiceClickHouseDatabaseCreateIn) error { path := fmt.Sprintf("/v1/project/%s/service/%s/clickhouse/db", url.PathEscape(project), url.PathEscape(serviceName)) _, err := h.doer.Do(ctx, "ServiceClickHouseDatabaseCreate", "POST", path, in) @@ -71,7 +94,20 @@ func (h *ClickHouseHandler) ServiceClickHouseDatabaseList(ctx context.Context, p } return out.Databases, nil } -func (h *ClickHouseHandler) ServiceClickHouseQueryStats(ctx context.Context, project string, serviceName string) ([]QueryOut, error) { +func (h *ClickHouseHandler) ServiceClickHouseQuery(ctx context.Context, project string, serviceName string, in *ServiceClickHouseQueryIn) (*ServiceClickHouseQueryOut, error) { + path := fmt.Sprintf("/v1/project/%s/service/%s/clickhouse/query", url.PathEscape(project), url.PathEscape(serviceName)) + b, err := h.doer.Do(ctx, "ServiceClickHouseQuery", "POST", path, in) + if err != nil { + return nil, err + } + out := new(ServiceClickHouseQueryOut) + err = json.Unmarshal(b, out) + if err != nil { + return nil, err + } + return out, nil +} +func (h *ClickHouseHandler) ServiceClickHouseQueryStats(ctx context.Context, project string, serviceName string) ([]QueryOutAlt, error) { path := fmt.Sprintf("/v1/project/%s/service/%s/clickhouse/query/stats", url.PathEscape(project), url.PathEscape(serviceName)) b, err := h.doer.Do(ctx, "ServiceClickHouseQueryStats", "GET", path, nil) if err != nil { @@ -121,7 +157,18 @@ type HourlyOut struct { HourStart string `json:"hour_start"` PeakStoredBytes int `json:"peak_stored_bytes"` } +type MetaOut struct { + Name string `json:"name"` + Type string `json:"type"` +} type QueryOut struct { + ClientName string `json:"client_name,omitempty"` + Database string `json:"database,omitempty"` + Elapsed *float64 `json:"elapsed,omitempty"` + Query string `json:"query,omitempty"` + User string `json:"user,omitempty"` +} +type QueryOutAlt struct { Calls *int `json:"calls,omitempty"` Database string `json:"database,omitempty"` MaxTime *int `json:"max_time,omitempty"` @@ -136,6 +183,15 @@ type QueryOut struct { type ServiceClickHouseDatabaseCreateIn struct { Database string `json:"database"` } +type ServiceClickHouseQueryIn struct { + Database string `json:"database"` + Query string `json:"query"` +} +type ServiceClickHouseQueryOut struct { + Data [][]any `json:"data"` + Meta []MetaOut `json:"meta"` + Summary SummaryOut `json:"summary"` +} type ServiceClickHouseTieredStorageSummaryOut struct { CurrentCost string `json:"current_cost"` ForecastedCost string `json:"forecasted_cost"` @@ -146,9 +202,21 @@ type ServiceClickHouseTieredStorageSummaryOut struct { type StorageUsageHistoryOut struct { Hourly []HourlyOut `json:"hourly"` } +type SummaryOut struct { + ElapsedNs *int `json:"elapsed_ns,omitempty"` + ReadBytes *int `json:"read_bytes,omitempty"` + ReadRows *int `json:"read_rows,omitempty"` + ResultBytes *int `json:"result_bytes,omitempty"` + ResultRows *int `json:"result_rows,omitempty"` + WrittenBytes *int `json:"written_bytes,omitempty"` + WrittenRows *int `json:"written_rows,omitempty"` +} +type serviceClickHouseCurrentQueriesOut struct { + Queries []QueryOut `json:"queries"` +} type serviceClickHouseDatabaseListOut struct { Databases []DatabaseOut `json:"databases"` } type serviceClickHouseQueryStatsOut struct { - Queries []QueryOut `json:"queries"` + Queries []QueryOutAlt `json:"queries"` }