open-vault/vendor/github.com/couchbase/gocb/v2/cluster_analyticsquery.go

301 lines
7.9 KiB
Go

package gocb
import (
"encoding/json"
"time"
gocbcore "github.com/couchbase/gocbcore/v9"
)
type jsonAnalyticsMetrics struct {
ElapsedTime string `json:"elapsedTime"`
ExecutionTime string `json:"executionTime"`
ResultCount uint64 `json:"resultCount"`
ResultSize uint64 `json:"resultSize"`
MutationCount uint64 `json:"mutationCount,omitempty"`
SortCount uint64 `json:"sortCount,omitempty"`
ErrorCount uint64 `json:"errorCount,omitempty"`
WarningCount uint64 `json:"warningCount,omitempty"`
ProcessedObjects uint64 `json:"processedObjects,omitempty"`
}
type jsonAnalyticsWarning struct {
Code uint32 `json:"code"`
Message string `json:"msg"`
}
type jsonAnalyticsResponse struct {
RequestID string `json:"requestID"`
ClientContextID string `json:"clientContextID"`
Status string `json:"status"`
Warnings []jsonAnalyticsWarning `json:"warnings"`
Metrics jsonAnalyticsMetrics `json:"metrics"`
Signature interface{} `json:"signature"`
}
// AnalyticsMetrics encapsulates various metrics gathered during a queries execution.
type AnalyticsMetrics struct {
ElapsedTime time.Duration
ExecutionTime time.Duration
ResultCount uint64
ResultSize uint64
MutationCount uint64
SortCount uint64
ErrorCount uint64
WarningCount uint64
ProcessedObjects uint64
}
func (metrics *AnalyticsMetrics) fromData(data jsonAnalyticsMetrics) error {
elapsedTime, err := time.ParseDuration(data.ElapsedTime)
if err != nil {
logDebugf("Failed to parse query metrics elapsed time: %s", err)
}
executionTime, err := time.ParseDuration(data.ExecutionTime)
if err != nil {
logDebugf("Failed to parse query metrics execution time: %s", err)
}
metrics.ElapsedTime = elapsedTime
metrics.ExecutionTime = executionTime
metrics.ResultCount = data.ResultCount
metrics.ResultSize = data.ResultSize
metrics.MutationCount = data.MutationCount
metrics.SortCount = data.SortCount
metrics.ErrorCount = data.ErrorCount
metrics.WarningCount = data.WarningCount
metrics.ProcessedObjects = data.ProcessedObjects
return nil
}
// AnalyticsWarning encapsulates any warnings returned by a query.
type AnalyticsWarning struct {
Code uint32
Message string
}
func (warning *AnalyticsWarning) fromData(data jsonAnalyticsWarning) error {
warning.Code = data.Code
warning.Message = data.Message
return nil
}
// AnalyticsMetaData provides access to the meta-data properties of a query result.
type AnalyticsMetaData struct {
RequestID string
ClientContextID string
Metrics AnalyticsMetrics
Signature interface{}
Warnings []AnalyticsWarning
}
func (meta *AnalyticsMetaData) fromData(data jsonAnalyticsResponse) error {
metrics := AnalyticsMetrics{}
if err := metrics.fromData(data.Metrics); err != nil {
return err
}
warnings := make([]AnalyticsWarning, len(data.Warnings))
for wIdx, jsonWarning := range data.Warnings {
err := warnings[wIdx].fromData(jsonWarning)
if err != nil {
return err
}
}
meta.RequestID = data.RequestID
meta.ClientContextID = data.ClientContextID
meta.Metrics = metrics
meta.Signature = data.Signature
meta.Warnings = warnings
return nil
}
// AnalyticsResult allows access to the results of a query.
type AnalyticsResult struct {
reader analyticsRowReader
rowBytes []byte
}
func newAnalyticsResult(reader analyticsRowReader) *AnalyticsResult {
return &AnalyticsResult{
reader: reader,
}
}
type analyticsRowReader interface {
NextRow() []byte
Err() error
MetaData() ([]byte, error)
Close() error
}
// Next assigns the next result from the results into the value pointer, returning whether the read was successful.
func (r *AnalyticsResult) Next() bool {
rowBytes := r.reader.NextRow()
if rowBytes == nil {
return false
}
r.rowBytes = rowBytes
return true
}
// Row returns the value of the current row
func (r *AnalyticsResult) Row(valuePtr interface{}) error {
if r.rowBytes == nil {
return ErrNoResult
}
if bytesPtr, ok := valuePtr.(*json.RawMessage); ok {
*bytesPtr = r.rowBytes
return nil
}
return json.Unmarshal(r.rowBytes, valuePtr)
}
// Err returns any errors that have occurred on the stream
func (r *AnalyticsResult) Err() error {
return r.reader.Err()
}
// Close marks the results as closed, returning any errors that occurred during reading the results.
func (r *AnalyticsResult) Close() error {
return r.reader.Close()
}
// One assigns the first value from the results into the value pointer.
// It will close the results but not before iterating through all remaining
// results, as such this should only be used for very small resultsets - ideally
// of, at most, length 1.
func (r *AnalyticsResult) One(valuePtr interface{}) error {
// Read the bytes from the first row
valueBytes := r.reader.NextRow()
if valueBytes == nil {
return ErrNoResult
}
// Skip through the remaining rows
for r.reader.NextRow() != nil {
// do nothing with the row
}
return json.Unmarshal(valueBytes, valuePtr)
}
// MetaData returns any meta-data that was available from this query. Note that
// the meta-data will only be available once the object has been closed (either
// implicitly or explicitly).
func (r *AnalyticsResult) MetaData() (*AnalyticsMetaData, error) {
metaDataBytes, err := r.reader.MetaData()
if err != nil {
return nil, err
}
var jsonResp jsonAnalyticsResponse
err = json.Unmarshal(metaDataBytes, &jsonResp)
if err != nil {
return nil, err
}
var metaData AnalyticsMetaData
err = metaData.fromData(jsonResp)
if err != nil {
return nil, err
}
return &metaData, nil
}
// AnalyticsQuery executes the analytics query statement on the server.
func (c *Cluster) AnalyticsQuery(statement string, opts *AnalyticsOptions) (*AnalyticsResult, error) {
if opts == nil {
opts = &AnalyticsOptions{}
}
span := c.tracer.StartSpan("Query", opts.parentSpan).
SetTag("couchbase.service", "analytics")
defer span.Finish()
timeout := opts.Timeout
if opts.Timeout == 0 {
timeout = c.timeoutsConfig.AnalyticsTimeout
}
deadline := time.Now().Add(timeout)
retryStrategy := c.retryStrategyWrapper
if opts.RetryStrategy != nil {
retryStrategy = newRetryStrategyWrapper(opts.RetryStrategy)
}
queryOpts, err := opts.toMap()
if err != nil {
return nil, AnalyticsError{
InnerError: wrapError(err, "failed to generate query options"),
Statement: statement,
ClientContextID: opts.ClientContextID,
}
}
var priorityInt int32
if opts.Priority {
priorityInt = -1
}
queryOpts["statement"] = statement
return c.execAnalyticsQuery(span, queryOpts, priorityInt, deadline, retryStrategy)
}
func maybeGetAnalyticsOption(options map[string]interface{}, name string) string {
if value, ok := options[name].(string); ok {
return value
}
return ""
}
func (c *Cluster) execAnalyticsQuery(
span requestSpan,
options map[string]interface{},
priority int32,
deadline time.Time,
retryStrategy *retryStrategyWrapper,
) (*AnalyticsResult, error) {
provider, err := c.getAnalyticsProvider()
if err != nil {
return nil, AnalyticsError{
InnerError: wrapError(err, "failed to get query provider"),
Statement: maybeGetAnalyticsOption(options, "statement"),
ClientContextID: maybeGetAnalyticsOption(options, "client_context_id"),
}
}
reqBytes, err := json.Marshal(options)
if err != nil {
return nil, AnalyticsError{
InnerError: wrapError(err, "failed to marshall query body"),
Statement: maybeGetAnalyticsOption(options, "statement"),
ClientContextID: maybeGetAnalyticsOption(options, "client_context_id"),
}
}
res, err := provider.AnalyticsQuery(gocbcore.AnalyticsQueryOptions{
Payload: reqBytes,
Priority: int(priority),
RetryStrategy: retryStrategy,
Deadline: deadline,
TraceContext: span.Context(),
})
if err != nil {
return nil, maybeEnhanceAnalyticsError(err)
}
return newAnalyticsResult(res), nil
}