github.com/jackc/pgx/v5@v5.5.5/pgproto3/data_row.go (about) 1 package pgproto3 2 3 import ( 4 "encoding/binary" 5 "encoding/hex" 6 "encoding/json" 7 "errors" 8 "math" 9 10 "github.com/jackc/pgx/v5/internal/pgio" 11 ) 12 13 type DataRow struct { 14 Values [][]byte 15 } 16 17 // Backend identifies this message as sendable by the PostgreSQL backend. 18 func (*DataRow) Backend() {} 19 20 // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message 21 // type identifier and 4 byte message length. 22 func (dst *DataRow) Decode(src []byte) error { 23 if len(src) < 2 { 24 return &invalidMessageFormatErr{messageType: "DataRow"} 25 } 26 rp := 0 27 fieldCount := int(binary.BigEndian.Uint16(src[rp:])) 28 rp += 2 29 30 // If the capacity of the values slice is too small OR substantially too 31 // large reallocate. This is too avoid one row with many columns from 32 // permanently allocating memory. 33 if cap(dst.Values) < fieldCount || cap(dst.Values)-fieldCount > 32 { 34 newCap := 32 35 if newCap < fieldCount { 36 newCap = fieldCount 37 } 38 dst.Values = make([][]byte, fieldCount, newCap) 39 } else { 40 dst.Values = dst.Values[:fieldCount] 41 } 42 43 for i := 0; i < fieldCount; i++ { 44 if len(src[rp:]) < 4 { 45 return &invalidMessageFormatErr{messageType: "DataRow"} 46 } 47 48 valueLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) 49 rp += 4 50 51 // null 52 if valueLen == -1 { 53 dst.Values[i] = nil 54 } else { 55 if len(src[rp:]) < valueLen || valueLen < 0 { 56 return &invalidMessageFormatErr{messageType: "DataRow"} 57 } 58 59 dst.Values[i] = src[rp : rp+valueLen : rp+valueLen] 60 rp += valueLen 61 } 62 } 63 64 return nil 65 } 66 67 // Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. 68 func (src *DataRow) Encode(dst []byte) ([]byte, error) { 69 dst, sp := beginMessage(dst, 'D') 70 71 if len(src.Values) > math.MaxUint16 { 72 return nil, errors.New("too many values") 73 } 74 dst = pgio.AppendUint16(dst, uint16(len(src.Values))) 75 for _, v := range src.Values { 76 if v == nil { 77 dst = pgio.AppendInt32(dst, -1) 78 continue 79 } 80 81 dst = pgio.AppendInt32(dst, int32(len(v))) 82 dst = append(dst, v...) 83 } 84 85 return finishMessage(dst, sp) 86 } 87 88 // MarshalJSON implements encoding/json.Marshaler. 89 func (src DataRow) MarshalJSON() ([]byte, error) { 90 formattedValues := make([]map[string]string, len(src.Values)) 91 for i, v := range src.Values { 92 if v == nil { 93 continue 94 } 95 96 var hasNonPrintable bool 97 for _, b := range v { 98 if b < 32 { 99 hasNonPrintable = true 100 break 101 } 102 } 103 104 if hasNonPrintable { 105 formattedValues[i] = map[string]string{"binary": hex.EncodeToString(v)} 106 } else { 107 formattedValues[i] = map[string]string{"text": string(v)} 108 } 109 } 110 111 return json.Marshal(struct { 112 Type string 113 Values []map[string]string 114 }{ 115 Type: "DataRow", 116 Values: formattedValues, 117 }) 118 } 119 120 // UnmarshalJSON implements encoding/json.Unmarshaler. 121 func (dst *DataRow) UnmarshalJSON(data []byte) error { 122 // Ignore null, like in the main JSON package. 123 if string(data) == "null" { 124 return nil 125 } 126 127 var msg struct { 128 Values []map[string]string 129 } 130 if err := json.Unmarshal(data, &msg); err != nil { 131 return err 132 } 133 134 dst.Values = make([][]byte, len(msg.Values)) 135 for n, parameter := range msg.Values { 136 var err error 137 dst.Values[n], err = getValueFromJSON(parameter) 138 if err != nil { 139 return err 140 } 141 } 142 return nil 143 }