Revamp the query compilation process.

This commit is contained in:
2024-05-14 11:18:31 +01:00
parent 569867d04b
commit 4f438b7eac
2 changed files with 96 additions and 18 deletions
+31 -12
View File
@@ -2,37 +2,56 @@ package gql
import ( import (
"fmt" "fmt"
"sync"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/gookit/goutil" "github.com/gookit/goutil"
"github.com/gookit/goutil/strutil" "github.com/gookit/goutil/strutil"
) )
var jsonBufferPool = sync.Pool{
New: func() interface{} {
return new([]byte)
},
}
func (b *BaseClient) convertToJSON(v any) []byte { func (b *BaseClient) convertToJSON(v any) []byte {
jsonBuffer := jsonBufferPool.Get().(*[]byte)
defer jsonBufferPool.Put(jsonBuffer)
*jsonBuffer = (*jsonBuffer)[:0]
jsonData, err := json.Marshal(v) jsonData, err := json.Marshal(v)
if err != nil { if err != nil {
b.Logger.Error("Can't convert to JSON", map[string]interface{}{"error": err.Error()}) b.Logger.Error("Can't convert to JSON", map[string]interface{}{"error": err.Error()})
return nil return nil
} }
return jsonData *jsonBuffer = append(*jsonBuffer, jsonData...)
return *jsonBuffer
} }
func (b *BaseClient) compileQuery(queryPartials ...any) *Query { func (b *BaseClient) compileQuery(queryPartials ...any) *Query {
q := &Query{} var query string
var variables map[string]interface{}
for _, partial := range queryPartials { for _, partial := range queryPartials {
switch val := partial.(type) { switch val := partial.(type) {
case string: case string:
q.Query = val query = val
case map[string]interface{}: 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"}) b.Logger.Error("Can't compile query", map[string]interface{}{"error": "query is empty"})
return nil 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) { 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") { if (enableCache || b.cache_global) && strutil.HasPrefix(compiledQuery.Query, "query") {
b.Logger.Debug("Cache enabled", nil) b.Logger.Debug("Cache enabled", nil)
queryHash = calculateHash(compiledQuery) queryHash = calculateHash(compiledQuery)
cachedValue := b.cacheLookup(queryHash) if cachedValue := b.cacheLookup(queryHash); cachedValue != nil {
if cachedValue != nil {
b.Logger.Debug("Cache hit", map[string]interface{}{"query": compiledQuery}) b.Logger.Debug("Cache hit", map[string]interface{}{"query": compiledQuery})
return cachedValue, nil return cachedValue, nil
} }
@@ -76,7 +94,6 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade
}(), }(),
Retries: enableRetries || b.retries_enable, Retries: enableRetries || b.retries_enable,
} }
defer func() { q = nil }()
rv, err := q.executeQuery() rv, err := q.executeQuery()
if err != nil { if err != nil {
@@ -84,8 +101,7 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade
return nil, err return nil, err
} }
returnedValue, err := q.decodeResponse(rv) return q.decodeResponse(rv)
return returnedValue, err
} }
func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enableCache, enableRetries, recompileRequired bool) { func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enableCache, enableRetries, recompileRequired bool) {
@@ -97,10 +113,13 @@ func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enable
varEnableRetries, _ := goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlretries")) varEnableRetries, _ := goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlretries"))
enableCache = enableCache || varEnableCache enableCache = enableCache || varEnableCache
enableRetries = enableRetries || varEnableRetries enableRetries = enableRetries || varEnableRetries
if varEnableCache || varEnableRetries {
delete(q.Variables, "gqlcache") delete(q.Variables, "gqlcache")
delete(q.Variables, "gqlretries") delete(q.Variables, "gqlretries")
recompileRequired = true recompileRequired = true
} }
}
return enableCache, enableRetries, recompileRequired return enableCache, enableRetries, recompileRequired
} }
+62 -3
View File
@@ -1,6 +1,8 @@
package gql package gql
import "testing" import (
"testing"
)
func Benchmark_compileQuery(bn *testing.B) { func Benchmark_compileQuery(bn *testing.B) {
b := NewConnection() 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() b := NewConnection()
variables := map[string]interface{}{
"var1": "value1",
}
bn.ResetTimer() bn.ResetTimer()
for i := 0; i < bn.N; i++ { for i := 0; i < bn.N; i++ {
b.convertToJSON( b.compileQuery(
`query { viewer { login } }`, `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)
}
}