-
Notifications
You must be signed in to change notification settings - Fork 19
/
row.go
178 lines (149 loc) · 5.63 KB
/
row.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package wire
import (
"context"
"errors"
"fmt"
"github.com/jeroenrinzema/psql-wire/pkg/buffer"
"github.com/jeroenrinzema/psql-wire/pkg/types"
"github.com/lib/pq/oid"
)
// Columns represent a collection of columns.
type Columns []Column
// Define writes the table [RowDescription] headers for the given table and the
// containing columns. The headers have to be written before any data rows could
// be send back to the client. The given columns are encoded using the given
// format codes. Columns could be encoded as Text or Binary. If you provide a
// single format code, it will be applied to all columns.
//
// [RowDescription]: https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-ROWDESCRIPTION
func (columns Columns) Define(ctx context.Context, writer *buffer.Writer, formats []FormatCode) error {
if len(columns) == 0 {
return nil
}
writer.Start(types.ServerRowDescription)
writer.AddInt16(int16(len(columns)))
if len(formats) == 0 {
formats = []FormatCode{TextFormat}
}
for index := range columns {
format := formats[0]
if len(formats) > index {
format = formats[index]
}
columns[index].Define(ctx, writer, format)
}
return writer.End()
}
// CopyIn sends a [CopyInResponse] to the client, to initiate a CopyIn
// operation. Based on the given columns within the prepared statement.
// https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-COPYINRESPONSE
func (columns Columns) CopyIn(ctx context.Context, writer *buffer.Writer, format FormatCode) error {
if ctx.Err() != nil {
return ctx.Err()
}
if len(columns) == 0 {
return errors.New("at least one column needs to be defined within the prepared statement")
}
writer.Start(types.ServerCopyInResponse)
writer.AddByte(byte(format))
writer.AddInt16(int16(len(columns)))
for range columns {
writer.AddInt16(int16(format))
}
return writer.End()
}
// Write writes the given column values back to the client. The given columns
// are encoded using the given format codes. Columns could be encoded as Text or
// Binary. If you provide a single format code, it will be applied to all
// columns.
func (columns Columns) Write(ctx context.Context, formats []FormatCode, writer *buffer.Writer, srcs []any) (err error) {
if len(srcs) != len(columns) {
return fmt.Errorf("unexpected columns, %d columns are defined inside the given table but %d were given", len(columns), len(srcs))
}
writer.Start(types.ServerDataRow)
writer.AddInt16(int16(len(columns)))
if len(formats) == 0 {
formats = []FormatCode{TextFormat}
}
for index, column := range columns {
format := formats[0]
if len(formats) > index {
format = formats[index]
}
err = column.Write(ctx, writer, format, srcs[index])
if err != nil {
return err
}
}
return writer.End()
}
// Column represents a table column and its [attributes] such as name, type and
// encode formatter.
//
// [attributes]: https://www.postgresql.org/docs/8.3/catalog-pg-attribute.html
type Column struct {
Table int32 // table id
ID int32 // column identifier
Attr int16 // column attribute number
Name string // column name
AttrNo int16 // column attribute no (optional)
Oid oid.Oid
Width int16
TypeModifier int32
}
// Define writes the column header values to the given writer.
// This method is used to define a column inside [RowDescription] message defining
// the column type, width, and name.
//
// [RowDescription]: https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-ROWDESCRIPTION
func (column Column) Define(ctx context.Context, writer *buffer.Writer, format FormatCode) {
writer.AddString(column.Name)
writer.AddNullTerminate()
writer.AddInt32(column.Table)
writer.AddInt16(column.AttrNo)
writer.AddInt32(int32(column.Oid))
writer.AddInt16(column.Width)
// TODO: Support type for type modifiers
//
// Some types could be overridden using the type modifier field within a RowDescription.
// Type modifier (see pg_attribute.atttypmod). The meaning of the
// modifier is type-specific.
// Atttypmod records type-specific data supplied at table creation time (for
// example, the maximum length of a varchar column). It is passed to
// type-specific input functions and length coercion functions. The value
// will generally be -1 for types that do not need atttypmod.
//
// https://www.postgresql.org/docs/current/protocol-message-formats.html
// https://www.postgresql.org/docs/current/catalog-pg-attribute.html
writer.AddInt32(-1)
writer.AddInt16(int16(format))
}
// Write encodes the given source value using the column type definition and connection
// info. The encoded byte buffer is added to the given write buffer. This method
// Is used to encode values and return them inside a [DataRow] message.
//
// [DataRow]: https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-DATAROW
func (column Column) Write(ctx context.Context, writer *buffer.Writer, format FormatCode, src any) (err error) {
if ctx.Err() != nil {
return ctx.Err()
}
tm := TypeMap(ctx)
if tm == nil {
return errors.New("postgres connection info has not been defined inside the given context")
}
bb := make([]byte, 0)
bb, err = tm.Encode(uint32(column.Oid), int16(format), src, bb)
if err != nil {
return err
}
// NOTE: The length of the column value, in bytes (this count does
// not include itself). Can be zero. As a special case, -1 indicates a NULL
// column value. No value bytes follow in the NULL case.
length := int32(len(bb))
if src == nil {
length = -1
}
writer.AddInt32(length)
writer.AddBytes(bb)
return nil
}