diff --git a/binaries/version.go b/binaries/version.go index c54f73c3..2e46fd77 100644 --- a/binaries/version.go +++ b/binaries/version.go @@ -1,8 +1,8 @@ package binaries // PrismaVersion is a hardcoded version of the Prisma CLI. -const PrismaVersion = "5.16.2" +const PrismaVersion = "5.19.0" // EngineVersion is a hardcoded version of the Prisma Engine. // The versions can be found under https://github.com/prisma/prisma-engines/commits/main -const EngineVersion = "34ace0eb2704183d2c05b60b52fba5c43c13f303" +const EngineVersion = "5fe21811a6ba0b952a3bc71400666511fe3b902f" diff --git a/engine/request.go b/engine/request.go index b5bf22ff..cb4313fe 100644 --- a/engine/request.go +++ b/engine/request.go @@ -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) } @@ -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) } diff --git a/engine/transform.go b/engine/transform.go index 576ded82..b9ab2f6b 100644 --- a/engine/transform.go +++ b/engine/transform.go @@ -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 } diff --git a/engine/transform_test.go b/engine/transform_test.go index 316cad8d..f4b0286e 100644 --- a/engine/transform_test.go +++ b/engine/transform_test.go @@ -1,8 +1,9 @@ package engine import ( - "reflect" "testing" + + "github.com/stretchr/testify/assert" ) func Test_transformResponse(t *testing.T) { @@ -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)) }) } } diff --git a/runtime/transaction/result.go b/runtime/transaction/result.go index b8cfbe03..d3a0743d 100644 --- a/runtime/transaction/result.go +++ b/runtime/transaction/result.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/steebchen/prisma-client-go/engine" "github.com/steebchen/prisma-client-go/logger" ) @@ -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 }