diff --git a/query.go b/query.go index ab7a4b1..8faf728 100644 --- a/query.go +++ b/query.go @@ -2,37 +2,56 @@ package gql import ( "fmt" + "sync" "github.com/goccy/go-json" "github.com/gookit/goutil" "github.com/gookit/goutil/strutil" ) +var jsonBufferPool = sync.Pool{ + New: func() interface{} { + return new([]byte) + }, +} + func (b *BaseClient) convertToJSON(v any) []byte { + jsonBuffer := jsonBufferPool.Get().(*[]byte) + defer jsonBufferPool.Put(jsonBuffer) + + *jsonBuffer = (*jsonBuffer)[:0] jsonData, err := json.Marshal(v) if err != nil { b.Logger.Error("Can't convert to JSON", map[string]interface{}{"error": err.Error()}) return nil } - return jsonData + *jsonBuffer = append(*jsonBuffer, jsonData...) + return *jsonBuffer } func (b *BaseClient) compileQuery(queryPartials ...any) *Query { - q := &Query{} + var query string + var variables map[string]interface{} for _, partial := range queryPartials { switch val := partial.(type) { case string: - q.Query = val + query = val case map[string]interface{}: - q.Variables = val + variables = val } } - if q.Query == "" { + + if query == "" { b.Logger.Error("Can't compile query", map[string]interface{}{"error": "query is empty"}) return nil } - q.JsonQuery = b.convertToJSON(q) - return q + + jsonQuery := b.convertToJSON(&Query{Query: query, Variables: variables}) + return &Query{ + Query: query, + Variables: variables, + JsonQuery: jsonQuery, + } } func (b *BaseClient) Query(query string, variables map[string]interface{}, headers map[string]interface{}) (any, error) { @@ -52,8 +71,7 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade if (enableCache || b.cache_global) && strutil.HasPrefix(compiledQuery.Query, "query") { b.Logger.Debug("Cache enabled", nil) queryHash = calculateHash(compiledQuery) - cachedValue := b.cacheLookup(queryHash) - if cachedValue != nil { + if cachedValue := b.cacheLookup(queryHash); cachedValue != nil { b.Logger.Debug("Cache hit", map[string]interface{}{"query": compiledQuery}) return cachedValue, nil } @@ -76,7 +94,6 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade }(), Retries: enableRetries || b.retries_enable, } - defer func() { q = nil }() rv, err := q.executeQuery() if err != nil { @@ -84,8 +101,7 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade return nil, err } - returnedValue, err := q.decodeResponse(rv) - return returnedValue, err + return q.decodeResponse(rv) } func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enableCache, enableRetries, recompileRequired bool) { @@ -97,9 +113,12 @@ func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enable varEnableRetries, _ := goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlretries")) enableCache = enableCache || varEnableCache enableRetries = enableRetries || varEnableRetries - delete(q.Variables, "gqlcache") - delete(q.Variables, "gqlretries") - recompileRequired = true + + if varEnableCache || varEnableRetries { + delete(q.Variables, "gqlcache") + delete(q.Variables, "gqlretries") + recompileRequired = true + } } return enableCache, enableRetries, recompileRequired diff --git a/query_bench_test.go b/query_bench_test.go index e2000ab..018845f 100644 --- a/query_bench_test.go +++ b/query_bench_test.go @@ -1,6 +1,8 @@ package gql -import "testing" +import ( + "testing" +) func Benchmark_compileQuery(bn *testing.B) { b := NewConnection() @@ -12,12 +14,69 @@ func Benchmark_compileQuery(bn *testing.B) { } } -func Benchmark_convertToJson(bn *testing.B) { +func Benchmark_compileQueryWithVariables(bn *testing.B) { b := NewConnection() + variables := map[string]interface{}{ + "var1": "value1", + } bn.ResetTimer() for i := 0; i < bn.N; i++ { - b.convertToJSON( + b.compileQuery( `query { viewer { login } }`, + variables, ) } } + +func Benchmark_convertToJson(bn *testing.B) { + b := NewConnection() + query := `query { viewer { login } }` + bn.ResetTimer() + for i := 0; i < bn.N; i++ { + b.convertToJSON(query) + } +} + +func Benchmark_Query(bn *testing.B) { + b := NewConnection() + query := `query { viewer { login } }` + variables := map[string]interface{}{ + "var1": "value1", + } + headers := map[string]interface{}{ + "Authorization": "Bearer token", + "gqlcache": "true", + } + bn.ResetTimer() + for i := 0; i < bn.N; i++ { + _, _ = b.Query(query, variables, headers) + } +} + +func Benchmark_QueryNoCacheNoRetry(bn *testing.B) { + b := NewConnection() + query := `query { viewer { login } }` + variables := map[string]interface{}{ + "var1": "value1", + } + headers := map[string]interface{}{ + "Authorization": "Bearer token", + } + bn.ResetTimer() + for i := 0; i < bn.N; i++ { + _, _ = b.Query(query, variables, headers) + } +} + +func Benchmark_QueryWithEmptyVariables(bn *testing.B) { + b := NewConnection() + query := `query { viewer { login } }` + headers := map[string]interface{}{ + "Authorization": "Bearer token", + "gqlcache": "true", + } + bn.ResetTimer() + for i := 0; i < bn.N; i++ { + _, _ = b.Query(query, nil, headers) + } +}