Search code examples
gotarantool

How to serialize Tarantool select results to ready structure with Go connector


Please describe a way how to serialize select results in go-tarantool connector to struct to have an ability to get access to fields as tuple.key1.key2


Solution

  • We always use conn.*Typed() queries in our projects to do it.

    First you need to define your structure that represents tuple in Tarantool. Then you need implement two interfaces for it, msgpack.CustomDecoder and msgpack.CustomEncoder .

    You should be able to do something like this:

    type Session struct {
        ID         string
        UserID     int64 
    }
    
    func (s *Session) EncodeMsgpack(e *msgpack.Encoder) error {
        if err := e.EncodeArrayLen(2); err != nil {
            return err
        }
      
        if err := e.EncodeString(s.ID); err != nil {
            return err
        }
      
        if err := e.EncodeInt64(s.UserID); err != nil {
            return err
        }
    
        return nil
    }
    
    func (s *Session) DecodeMsgpack(d *msgpack.Decoder) error {
        l, err := d.DecodeArrayLen()
        if err != nil {
            return err
        }
      
        decodedFields := 1
        if s.ID, err = d.DecodeString(); err != nil || decodedFields == l {
            return err
        }
    
        decodedFields++
        if s.UserID, err = d.DecodeInt64(); err != nil || decodedFields == l {
            return err
        }
     
        for i := 0; i < l-decodedFields; i++ {
            _ = d.Skip()
        }
      
        return nil
    }
    

    Pay attention to the decoder. It contains counting fields. This is necessary for non-breaking migrations.

    For example, if the msgpack array has fewer fields than we are trying to decode, nothing will break.

    The response from the select query is a sequential array of msgpack tuples, so if we do not skip unknown fields, the decoding of the next instance of the structure will not start from the beginning of a next tuple.

    Then you can try to do query:

    func() ([]Session, error) {
        const userID = 822
        
        var sessions []Session
        err := conn.SelectTyped("session", "user", 0, 10, tarantool.IterEq, []interface{}{userID}, &resp)
        if err != nil {
            return nil, err
        }
    
        if len(resp) == 0 {
            return nil, nil
        }
      
        return sessions, nil
    }
    

    This is the best way in my opinion as there is a minimum of reflections, type conversions and type assertions that can cause panic in production with careless use. Also this is a more performance way.