1package pgproto3
2
3import (
4	"encoding/binary"
5	"encoding/hex"
6	"encoding/json"
7
8	"github.com/jackc/pgx/pgio"
9)
10
11type DataRow struct {
12	Values [][]byte
13}
14
15func (*DataRow) Backend() {}
16
17func (dst *DataRow) Decode(src []byte) error {
18	if len(src) < 2 {
19		return &invalidMessageFormatErr{messageType: "DataRow"}
20	}
21	rp := 0
22	fieldCount := int(binary.BigEndian.Uint16(src[rp:]))
23	rp += 2
24
25	// If the capacity of the values slice is too small OR substantially too
26	// large reallocate. This is too avoid one row with many columns from
27	// permanently allocating memory.
28	if cap(dst.Values) < fieldCount || cap(dst.Values)-fieldCount > 32 {
29		newCap := 32
30		if newCap < fieldCount {
31			newCap = fieldCount
32		}
33		dst.Values = make([][]byte, fieldCount, newCap)
34	} else {
35		dst.Values = dst.Values[:fieldCount]
36	}
37
38	for i := 0; i < fieldCount; i++ {
39		if len(src[rp:]) < 4 {
40			return &invalidMessageFormatErr{messageType: "DataRow"}
41		}
42
43		msgSize := int(int32(binary.BigEndian.Uint32(src[rp:])))
44		rp += 4
45
46		// null
47		if msgSize == -1 {
48			dst.Values[i] = nil
49		} else {
50			if len(src[rp:]) < msgSize {
51				return &invalidMessageFormatErr{messageType: "DataRow"}
52			}
53
54			dst.Values[i] = src[rp : rp+msgSize]
55			rp += msgSize
56		}
57	}
58
59	return nil
60}
61
62func (src *DataRow) Encode(dst []byte) []byte {
63	dst = append(dst, 'D')
64	sp := len(dst)
65	dst = pgio.AppendInt32(dst, -1)
66
67	dst = pgio.AppendUint16(dst, uint16(len(src.Values)))
68	for _, v := range src.Values {
69		if v == nil {
70			dst = pgio.AppendInt32(dst, -1)
71			continue
72		}
73
74		dst = pgio.AppendInt32(dst, int32(len(v)))
75		dst = append(dst, v...)
76	}
77
78	pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
79
80	return dst
81}
82
83func (src *DataRow) MarshalJSON() ([]byte, error) {
84	formattedValues := make([]map[string]string, len(src.Values))
85	for i, v := range src.Values {
86		if v == nil {
87			continue
88		}
89
90		var hasNonPrintable bool
91		for _, b := range v {
92			if b < 32 {
93				hasNonPrintable = true
94				break
95			}
96		}
97
98		if hasNonPrintable {
99			formattedValues[i] = map[string]string{"binary": hex.EncodeToString(v)}
100		} else {
101			formattedValues[i] = map[string]string{"text": string(v)}
102		}
103	}
104
105	return json.Marshal(struct {
106		Type   string
107		Values []map[string]string
108	}{
109		Type:   "DataRow",
110		Values: formattedValues,
111	})
112}
113