tarantool / go-tarantool

Tarantool 1.10+ client for Go language
https://pkg.go.dev/github.com/tarantool/go-tarantool/v2
BSD 2-Clause "Simplified" License
180 stars 57 forks source link

No access to response fields #344

Closed a1div0 closed 10 months ago

a1div0 commented 10 months ago

Reproducer

  1. We launch a cluster with Cartridge and the role of crud. This is an example, any cluster on a tarantula will do.
  2. Execute the command, for example, crud.replace, or crud.insert or any.
    
    func (Tarantool) Call(conn *tarantool.Connection, fnName string, args interface{}) *tarantool.Response {
    req := tarantool.NewCallRequest(fnName).Args(args)
    resp, err := conn.Do(req).Get()
    if err != nil { /* ... */ }
    return resp
    }

Call("localhost:3301", "crud.replave_object_many", args)


Output:

{ "request_id": 104, "code": 0, "error": "", "data": [ {}, null ], "pos": [], "meta_data": [], "sql_info": { "affected_count": 0, "info_autoincrement_ids": [] } }


Pay attention to field `data` - it contains two elements - result and error. Error = null, it's right. In data = void struct - it's not right.
In fact, there is an answer like:

crud.replace('customers', {1, box.NULL, 'Alice', 22})

Specifically in this:

fmt.Sprintf("%+v", resp.Data[0])
map[metadata:[map[name:id type:unsigned] map[name:bucket_id type:unsigned] map[name:data type:any]] rows:[[993 13802 map[array:[a a 1] bool:true dec:934.22452070173 map:map[key:i] num:88152273 str:bqak time:2017-02-19T03:52:56Z uuid:e71c27b5-db75-4609-ad29-bb46b0836e85]] [994 10883 map[array:[1 a a 1] bool:true dec:858.5405680067324 map:map[key:t q] num:247090 str:sw jqtk time:2017-02-06T17:41:30Z uuid:bf1abe5c-17dc-42d7-9d06-97c60ab61a7d]] [997 10519 map[array:[a false a] bool:true dec:161.62182397534568 map:map[key:rczsroe] num:1711937 str: vpex time:2011-05-02T16:29:02Z uuid:d639d2b2-b0ac-46f4-85fb-42ec1a708a19]] [998 7619 map[array:[1 a 1 a] bool:true dec:890.2226166378725 map:map[key:nlb] num:6394776 str:skxbcf time:2022-01-15T02:05:51Z uuid:8baed496-e3d5-44af-b86d-1ee9dfa7dddd]] [1000 2603 map[array:[1 1 false false false] bool:true dec:512.2494938106767 map:map[key:jfkfgl] num:8154636 str:aatpyea time:2003-03-15T19:11:38Z uuid:7a195b9d-d64b-4609-89d0-6f9d81cd8ab7]] [991 20703 map[array:[false 1 1] bool:true dec:359.77481609979964 map:map[key:up] num:69933998 str:vsxc time:2020-06-12T18:15:26Z uuid:557cea2b-0e5d-4fe5-bd42-4cec3fc46ab2]] [992 28219 map[array:[false 1 1 a a] bool:true dec:761.0353213689805 map:map[key:sp] num:18936014 str:th qsn time:2008-01-01T11:18:41Z uuid:aaf16870-80db-43a0-8460-484611562b72]] [995 27522 map[array:[false a 1 false false] bool:true dec:591.9781213667983 map:map[key:hjx] num:76513196 str:phdzrvu time:2003-12-16T06:58:52Z uuid:f2ac2140-78e5-46d6-998a-8bd6442749a3]] [996 28742 map[array:[a a false a 1] bool:true dec:866.1781417547014 map:map[key:du] num:84725520 str:jadyuot time:2006-10-23T05:28:45Z uuid:14573105-b0d4-40b9-aa99-73987dd74ff0]] [999 21026 map[array:[a 1 1 false] bool:true dec:140.08652467167707 map:map[key:njwqc] num:59971393 str:jmit t time:2022-08-13T08:06:38Z uuid:702f103c-9946-42bd-8d8e-7927d8e7ccd6]]]]

Problem

The problem is that the field names in the response are written in lowercase letters. Thus, these are private fields and we do not have access to them. Therefore, in the end we see an empty response, although it is not empty.

DifferentialOrange commented 10 months ago

field names in the response are written in lowercase letters

It is a thing for structs, but in case of map[string]interface{} any string key is public.

Call("localhost:3301", "crud.replave_object_many", args)

We have proper crud request API in go-tarantool/crud (see example). Try to use it instead of your custom call. It already covers various things, including marshaling response to a structure with public fields.

DifferentialOrange commented 10 months ago

Error = null, it's right. In data = void struct - it's not right.

It is also likely that you had received a Lua error in response. In your implementation, it won't be in err, but in resp.Data

oleg-jukovec commented 10 months ago
  1. The resp.Data is a []insterface of map[interface{}/string]insterface{}. You have access to the all data in the response. But you need to make type-casts manually (see examples for the connector). Or you could decode the response into a data structure directly: https://github.com/tarantool/go-tarantool/blob/a664c6bbb6a1f21aecfccf199858e19a21857700/example_custom_unpacking_test.go#L68-L78

  2. I recommend to use crud submodule from the connector to make requests to crud. We implement special requests + read result helpers:

https://github.com/tarantool/go-tarantool/blob/a664c6bbb6a1f21aecfccf199858e19a21857700/crud/example_test.go#L34-L82

So you don't need to reimplement this logic in your code in fact.

a1div0 commented 10 months ago

We have proper crud request API in go-tarantool/crud (see example). Try to use it instead of your custom call. It already covers various things, including marshaling response to a structure with public fields.

I took crud as an example. If I still need the result of calling call?

a1div0 commented 10 months ago

It is also likely that you had received a Lua error in response. In your implementation, it won't be in err, but in resp.Data

In my example there is definitely no error there.

DifferentialOrange commented 10 months ago

It is also likely that you had received a Lua error in response. In your implementation, it won't be in err, but in resp.Data

In my example there is definitely no error there.

In the example you had provided you call non-existing crud.replave_object_many

DifferentialOrange commented 10 months ago

If I still need the result of calling call?

Then "each map[string] value is public" is still applicable.

type resp struct {
    val1 string // <- private
    Val2 string // <- public
}

type respMap map[string]interface{
    "val1": "someval" // <- public
    "Val2": "someval" // <- public
}
oleg-jukovec commented 10 months ago

We have proper crud request API in go-tarantool/crud (see example). Try to use it instead of your custom call. It already covers various things, including marshaling response to a structure with public fields.

I took crud as an example. If I still need the result of calling call?

So you need to make typecasts from interface{} manually to fetch the result data, as example (I don't recommend that way): https://github.com/tarantool/go-tarantool/blob/master/tarantool_test.go#L2452

Or parse it in some struct{} type with *Typed() requests.

a1div0 commented 10 months ago

So you don't need to reimplement this logic in your code in fact.

Unfortunately no. I use the connector for load tests on K6 (JavaScript). At a minimum, you cannot return more than one result.

a1div0 commented 10 months ago

In the example you had provided you call non-existing crud.replave_object_many

crud.replase_object_many

oleg-jukovec commented 10 months ago

So you don't need to reimplement this logic in your code in fact.

Unfortunately no. I use the connector for load tests on K6 (JavaScript). At a minimum, you cannot return more than one result.

Could you please give an example?

a1div0 commented 10 months ago

Then "each map[string] value is public" is still applicable.

It may be applicable, but I see something else. Maybe of course I'm doing something wrong. For example, we convert the answer to JSON:

func (Tarantool) Call(conn *tarantool.Connection, fnName string, args interface{}) CallResponse {
    req := tarantool.NewCallRequest(fnName).Args(args)
    future := conn.Do(req)
    resp, err := future.Get()
    if err != nil {
        return CallResponse{nil, err.Error()}
    }

    b, _ := json.Marshal(resp.Data[0])

    return CallResponse{ resp.Data[0] , string(b) }
}

Output:

null, "null"
a1div0 commented 10 months ago

Could you please give an example?

My case:

oleg-jukovec commented 10 months ago

Output:

null, "null"

What are Call() arguments and what resp.Data contains in that case?

oleg-jukovec commented 10 months ago

msgpack decodes to map[interface{}]interface{} by default, but decoding/json.Marshal() requires map[string]interface{}. I even don't undestand how your code works at all (and I don't have access to tarantool/k6-tarantool). Please, check json.Marshal() error result too.

a1div0 commented 10 months ago

I really needed to convert the result from map[instance{}]instance{} to map[string]instance{}