Skip to content

Commit

Permalink
adapt to new raw queries
Browse files Browse the repository at this point in the history
  • Loading branch information
steebchen committed Aug 11, 2024
1 parent 71f9157 commit db19f73
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 162 deletions.
4 changes: 2 additions & 2 deletions engine/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (e *QueryEngine) Do(ctx context.Context, payload interface{}, v interface{}
return fmt.Errorf("internal error: %s", e.RawMessage())
}

response.Data.Result, err = transformResponse(response.Data.Result)
response.Data.Result, err = TransformResponse(response.Data.Result)
if err != nil {
return fmt.Errorf("transform response: %w", err)
}
Expand All @@ -69,7 +69,7 @@ func (e *QueryEngine) Batch(ctx context.Context, payload interface{}, v interfac
return fmt.Errorf("request failed: %w", err)
}

body, err = transformResponse(body)
body, err = TransformResponse(body)
if err != nil {
return fmt.Errorf("transform response: %w", err)
}
Expand Down
124 changes: 26 additions & 98 deletions engine/transform.go
Original file line number Diff line number Diff line change
@@ -1,115 +1,43 @@
package engine

import (
"encoding/base64"
"encoding/json"
"fmt"

"github.com/steebchen/prisma-client-go/logger"
"strings"
)

// transformResponse for raw queries
// transforms all custom prisma types into native go types, such as
// [{"prisma__type":"string","prisma__value":"asdf"},{"prisma__type":"null","prisma__value":null}]
// ->
// ["asdf", null]
func transformResponse(data []byte) ([]byte, error) {
var m interface{}
if err := json.Unmarshal(data, &m); err != nil {
return nil, err
}
type Input struct {
Columns []string `json:"columns"`
Types []string `json:"types"`
Rows [][]interface{} `json:"rows"`
}

forEachValue(&m, func(k *string, i *int, v *interface{}) (interface{}, bool) {
if v == nil {
return nil, false
}
var n = *v
o, isObject := (*v).(map[string]interface{})
if isObject {
var ok bool
n, ok = handleObject(o)
if !ok {
return n, false
}
}
return n, true
})
// TransformResponse for raw queries
func TransformResponse(data []byte) ([]byte, error) {
// TODO properly detect a json response
if !strings.HasPrefix(string(data), `{"columns":[`) {
return data, nil
}

out, err := json.Marshal(m)
var input Input
err := json.Unmarshal(data, &input)
if err != nil {
return nil, fmt.Errorf("transform response marshal: %w", err)
return nil, err
}

logger.Debug.Printf("transformed response: %s", out)

return out, nil
}
output := make([]map[string]interface{}, 0)

func handleObject(o map[string]interface{}) (interface{}, bool) {
if t, ok := o["prisma__type"]; ok {
if t == "bytes" {
// bytes from prisma are base64 encoded
bytes, ok := o["prisma__value"].(string)
if !ok {
panic("expected bytes")
}
dst := make([]byte, base64.StdEncoding.DecodedLen(len(bytes)))
n, err := base64.StdEncoding.Decode(dst, []byte(bytes))
if err != nil {
panic(err)
}
dst = dst[:n]
return dst, false
for _, row := range input.Rows {
m := make(map[string]interface{})
for i, column := range input.Columns {
m[column] = row[i]
}
if t == "array" {
value, ok := o["prisma__value"].([]interface{})
if !ok {
panic("expected array")
}
var items []interface{}
for _, item := range value {
item, _ := handleObject(item.(map[string]interface{}))
items = append(items, item)
}
return items, false
}
return o["prisma__value"], false
output = append(output, m)
}
return o, true
}

func forEachValue(obj *interface{}, handler func(*string, *int, *interface{}) (interface{}, bool)) {
if obj == nil {
return
}
var ok bool
var n = *obj
// Yield all key/value pairs for objects.
o, isObject := (*obj).(map[string]interface{})
if isObject {
for k := range o {
item := o[k]
o[k], ok = handler(&k, nil, &item)
item = o[k]
if ok {
forEachValue(&item, handler)
}
}
n = o
}
// Yield each index/value for arrays.
a, isArray := (*obj).([]interface{})
if isArray {
for i := range a {
item := a[i]
a[i], ok = handler(nil, &i, &item)
item = a[i]
if ok {
forEachValue(&item, handler)
}
}
n = a
o, err := json.Marshal(output)
if err != nil {
return nil, err
}
*obj = n
// Do nothing for primitives since the handler got them.

return o, nil
}
69 changes: 7 additions & 62 deletions engine/transform_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package engine

import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
)

func Test_transformResponse(t *testing.T) {
Expand All @@ -14,75 +15,19 @@ func Test_transformResponse(t *testing.T) {
args args
want []byte
}{{
name: "replace nulls array",
args: args{
data: []byte(`[{"prisma__type":"string","prisma__value":"asdf"},{"prisma__type":"null","prisma__value":null}]`),
},
want: []byte(`["asdf",null]`),
}, {
name: "replace nulls object",
args: args{
data: []byte(`[{"id":{"prisma__type":"null","prisma__value":null}}]`),
},
want: []byte(`[{"id":null}]`),
}, {
name: "replace string",
args: args{
data: []byte(`[{"id":{"prisma__type":"string","prisma__value":"asdf"}}]`),
},
want: []byte(`[{"id":"asdf"}]`),
}, {
name: "replace string with other objects",
args: args{
data: []byte(`[{"id":{"prisma__type":"string","prisma__value":"asdf"}},{"some":{"other":"object"}},{"value":5}]`),
},
want: []byte(`[{"id":"asdf"},{"some":{"other":"object"}},{"value":5}]`),
}, {
name: "native string",
args: args{
data: []byte(`"asdf"`),
},
want: []byte(`"asdf"`),
}, {
name: "native number",
args: args{
data: []byte(`5`),
},
want: []byte(`5`),
}, { // edge cases which are specifically handled
name: "bytes",
args: args{
data: []byte(`{"item":{"prisma__type":"bytes","prisma__value":"eyJzb21lIjo1fQ=="}}`),
},
want: []byte(`{"item":"eyJzb21lIjo1fQ=="}`),
}, {
name: "bytes",
name: "transform",
args: args{
data: []byte(`[{"prisma__type":"bytes","prisma__value":"eyJzb21lIjp7ImEiOiJiIn19"}]`),
data: []byte(`{"columns":["id","email","username","str","strOpt","date","dateOpt","int","intOpt","float","floatOpt","bool","boolOpt"],"types":["string","string","string","string","string","datetime","datetime","int","int","double","double","int","int"],"rows":[["id1","email1","a","str","strOpt","2020-01-01T00:00:00+00:00","2020-01-01T00:00:00+00:00",5,5,5.5,5.5,1,0],["id2","email2","b","str","strOpt","2020-01-01T00:00:00+00:00","2020-01-01T00:00:00+00:00",5,5,5.5,5.5,1,0]]}`),
},
want: []byte(`["eyJzb21lIjp7ImEiOiJiIn19"]`),
}, {
name: "bytes",
args: args{
data: []byte(`[{"prisma__type":"bytes","prisma__value":"MTIz"}]`),
},
want: []byte(`["MTIz"]`),
}, {
name: "big",
args: args{
data: []byte(`[{"id":{"prisma__type":"string","prisma__value":"id1"},"email":{"prisma__type":"string","prisma__value":"email1"},"username":{"prisma__type":"string","prisma__value":"a"},"str":{"prisma__type":"string","prisma__value":"str"},"strOpt":{"prisma__type":"string","prisma__value":"strOpt"},"strEmpty":{"prisma__type":"null","prisma__value":null},"time":{"prisma__type":"datetime","prisma__value":"2020-01-01T00:00:00+00:00"},"timeOpt":{"prisma__type":"datetime","prisma__value":"2020-01-01T00:00:00+00:00"},"timeEmpty":{"prisma__type":"null","prisma__value":null},"int":{"prisma__type":"int","prisma__value":5},"intOpt":{"prisma__type":"int","prisma__value":5},"intEmpty":{"prisma__type":"null","prisma__value":null},"float":{"prisma__type":"double","prisma__value":5.5},"floatOpt":{"prisma__type":"double","prisma__value":5.5},"floatEmpty":{"prisma__type":"null","prisma__value":null},"bool":{"prisma__type":"bool","prisma__value":true},"boolOpt":{"prisma__type":"bool","prisma__value":false},"boolEmpty":{"prisma__type":"null","prisma__value":null},"decimal":{"prisma__type":"decimal","prisma__value":"5.5"},"decimalOpt":{"prisma__type":"decimal","prisma__value":"5.5"},"decimalEmpty":{"prisma__type":"null","prisma__value":null},"json":{"prisma__type":"json","prisma__value":{"field":"value"}},"jsonOpt":{"prisma__type":"json","prisma__value":{"field":"value"}},"jsonEmpty":{"prisma__type":"null","prisma__value":null},"bytes":{"prisma__type":"bytes","prisma__value":"eyJmaWVsZCI6InZhbHVlIn0="},"bytesOpt":{"prisma__type":"bytes","prisma__value":"eyJmaWVsZCI6InZhbHVlIn0="},"bytesEmpty":{"prisma__type":"null","prisma__value":null}},{"id":{"prisma__type":"string","prisma__value":"id2"},"email":{"prisma__type":"string","prisma__value":"email2"},"username":{"prisma__type":"string","prisma__value":"b"},"str":{"prisma__type":"string","prisma__value":"str"},"strOpt":{"prisma__type":"string","prisma__value":"strOpt"},"strEmpty":{"prisma__type":"null","prisma__value":null},"time":{"prisma__type":"datetime","prisma__value":"2020-01-01T00:00:00+00:00"},"timeOpt":{"prisma__type":"datetime","prisma__value":"2020-01-01T00:00:00+00:00"},"timeEmpty":{"prisma__type":"null","prisma__value":null},"int":{"prisma__type":"int","prisma__value":5},"intOpt":{"prisma__type":"int","prisma__value":5},"intEmpty":{"prisma__type":"null","prisma__value":null},"float":{"prisma__type":"double","prisma__value":5.5},"floatOpt":{"prisma__type":"double","prisma__value":5.5},"floatEmpty":{"prisma__type":"null","prisma__value":null},"bool":{"prisma__type":"bool","prisma__value":true},"boolOpt":{"prisma__type":"bool","prisma__value":false},"boolEmpty":{"prisma__type":"null","prisma__value":null},"decimal":{"prisma__type":"decimal","prisma__value":"5.5"},"decimalOpt":{"prisma__type":"decimal","prisma__value":"5.5"},"decimalEmpty":{"prisma__type":"null","prisma__value":null},"json":{"prisma__type":"json","prisma__value":{"field":"value"}},"jsonOpt":{"prisma__type":"json","prisma__value":{"field":"value"}},"jsonEmpty":{"prisma__type":"null","prisma__value":null},"bytes":{"prisma__type":"bytes","prisma__value":"eyJmaWVsZCI6InZhbHVlIn0="},"bytesOpt":{"prisma__type":"bytes","prisma__value":"eyJmaWVsZCI6InZhbHVlIn0="},"bytesEmpty":{"prisma__type":"null","prisma__value":null}}]`),
},
want: []byte(`[{"bool":true,"boolEmpty":null,"boolOpt":false,"bytes":"eyJmaWVsZCI6InZhbHVlIn0=","bytesEmpty":null,"bytesOpt":"eyJmaWVsZCI6InZhbHVlIn0=","decimal":"5.5","decimalEmpty":null,"decimalOpt":"5.5","email":"email1","float":5.5,"floatEmpty":null,"floatOpt":5.5,"id":"id1","int":5,"intEmpty":null,"intOpt":5,"json":{"field":"value"},"jsonEmpty":null,"jsonOpt":{"field":"value"},"str":"str","strEmpty":null,"strOpt":"strOpt","time":"2020-01-01T00:00:00+00:00","timeEmpty":null,"timeOpt":"2020-01-01T00:00:00+00:00","username":"a"},{"bool":true,"boolEmpty":null,"boolOpt":false,"bytes":"eyJmaWVsZCI6InZhbHVlIn0=","bytesEmpty":null,"bytesOpt":"eyJmaWVsZCI6InZhbHVlIn0=","decimal":"5.5","decimalEmpty":null,"decimalOpt":"5.5","email":"email2","float":5.5,"floatEmpty":null,"floatOpt":5.5,"id":"id2","int":5,"intEmpty":null,"intOpt":5,"json":{"field":"value"},"jsonEmpty":null,"jsonOpt":{"field":"value"},"str":"str","strEmpty":null,"strOpt":"strOpt","time":"2020-01-01T00:00:00+00:00","timeEmpty":null,"timeOpt":"2020-01-01T00:00:00+00:00","username":"b"}]`),
want: []byte(`[{"bool":1,"boolOpt":0,"date":"2020-01-01T00:00:00+00:00","dateOpt":"2020-01-01T00:00:00+00:00","email":"email1","float":5.5,"floatOpt":5.5,"id":"id1","int":5,"intOpt":5,"str":"str","strOpt":"strOpt","username":"a"},{"bool":1,"boolOpt":0,"date":"2020-01-01T00:00:00+00:00","dateOpt":"2020-01-01T00:00:00+00:00","email":"email2","float":5.5,"floatOpt":5.5,"id":"id2","int":5,"intOpt":5,"str":"str","strOpt":"strOpt","username":"b"}]`),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := transformResponse(tt.args.data)
got, err := TransformResponse(tt.args.data)
if err != nil {
t.Fatalf("transformResponse() error = %v", err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("transformResponse() = %s, want %s", got, tt.want)
}
assert.Equal(t, string(tt.want), string(got))
})
}
}
5 changes: 5 additions & 0 deletions runtime/transaction/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"

"github.com/steebchen/prisma-client-go/engine"
"github.com/steebchen/prisma-client-go/logger"
)

Expand All @@ -20,6 +21,10 @@ func (r *Result) Get(c <-chan []byte, v interface{}) error {
if !ok {
return fmt.Errorf("result not fetched")
}
data, err := engine.TransformResponse(data)
if err != nil {
return fmt.Errorf("could not transform response: %w", err)
}
res = data
r.cache = data
}
Expand Down

0 comments on commit db19f73

Please sign in to comment.