Repository navigation
Expand file tree
/
Copy pathcopy_test.go
More file actions
181 lines (169 loc) · 6.31 KB
/
Copy pathcopy_test.go
File metadata and controls
181 lines (169 loc) · 6.31 KB
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
179
180
181
package pglite_test
import (
"errors"
"strings"
"testing"
"github.com/jackc/pgx/v5/pgconn"
)
// The backend cannot resume a COPY FROM STDIN whose data arrives in a later
// call, so the server passes a COPY on only together with its data (the
// simple protocol, where the client streams the data after the query) and
// refuses the forms where it cannot do that, instead of letting the module
// crash.
func TestCopyFromStdinThroughExtendedProtocolIsRefused(t *testing.T) {
ctx := t.Context()
pg := newTestPG(t)
conn, err := pgconn.Connect(ctx, pg.ConnString())
if err != nil {
t.Fatal(err)
}
defer conn.Close(ctx)
if err := conn.Exec(ctx, "CREATE TABLE cp (id int)").Close(); err != nil {
t.Fatal(err)
}
res := conn.ExecParams(ctx, "COPY cp FROM STDIN", nil, nil, nil, nil).Read()
if code := pgErrCode(res.Err); code != "0A000" || !strings.Contains(res.Err.Error(), "simple query protocol") {
t.Fatalf("extended COPY FROM STDIN: %v, want feature_not_supported", res.Err)
}
if conn.TxStatus() != 'I' {
t.Errorf("transaction status %q after refusal, want idle", conn.TxStatus())
}
// Inside a transaction block the refusal leaves the block as it was.
if err := conn.Exec(ctx, "BEGIN").Close(); err != nil {
t.Fatal(err)
}
res = conn.ExecParams(ctx, "COPY cp FROM STDIN", nil, nil, nil, nil).Read()
if code := pgErrCode(res.Err); code != "0A000" {
t.Fatalf("extended COPY FROM STDIN in a transaction: %v", res.Err)
}
if conn.TxStatus() != 'T' {
t.Errorf("transaction status %q, want in transaction", conn.TxStatus())
}
if err := conn.Exec(ctx, "ROLLBACK").Close(); err != nil {
t.Fatal(err)
}
// In a pipeline, the refusal discards the rest of the pipeline up to
// its Sync; the messages before the refused one have reached the
// backend and stand.
p := conn.StartPipeline(ctx)
p.SendQueryParams("INSERT INTO cp VALUES (1)", nil, nil, nil, nil)
p.SendQueryParams("COPY cp FROM STDIN", nil, nil, nil, nil)
p.SendQueryParams("INSERT INTO cp VALUES (2)", nil, nil, nil, nil)
p.SendPipelineSync()
if err := p.Flush(); err != nil {
t.Fatal(err)
}
r, err := p.GetResults()
if err != nil {
t.Fatalf("first insert: %v", err)
}
if rr, ok := r.(*pgconn.ResultReader); !ok || rr.Read().CommandTag.String() != "INSERT 0 1" {
t.Errorf("first insert result: %T %v", r, r)
}
if _, err := p.GetResults(); pgErrCode(err) != "0A000" {
t.Errorf("COPY in pipeline: %v, want feature_not_supported", err)
}
if r, err := p.GetResults(); err != nil {
t.Errorf("pipeline end: %v", err)
} else if _, ok := r.(*pgconn.PipelineSync); !ok {
t.Errorf("pipeline end: %T, want PipelineSync (the third statement is discarded)", r)
}
if err := p.Close(); err != nil {
t.Fatal(err)
}
res = conn.ExecParams(ctx, "SELECT count(*) FROM cp", nil, nil, nil, nil).Read()
if res.Err != nil || string(res.Rows[0][0]) != "1" {
t.Errorf("rows after pipeline: %v, %v", res.Rows, res.Err)
}
if err := pg.Err(); err != nil {
t.Errorf("instance failed: %v", err)
}
}
func TestCopyFromStdinInMultiStatementQuery(t *testing.T) {
ctx := t.Context()
pg := newTestPG(t)
conn, err := pgconn.Connect(ctx, pg.ConnString())
if err != nil {
t.Fatal(err)
}
defer conn.Close(ctx)
if err := conn.Exec(ctx, "CREATE TABLE cp (id int)").Close(); err != nil {
t.Fatal(err)
}
// One COPY among other statements takes its data as usual.
tag, err := conn.CopyFrom(ctx, strings.NewReader("1\n2\n3\n"), "BEGIN; COPY cp FROM STDIN; COMMIT")
if err != nil || tag.String() != "COMMIT" {
t.Fatalf("BEGIN; COPY; COMMIT: %v, %v", tag, err)
}
// If the COPY never runs, the data the client streamed is ignored.
_, err = conn.CopyFrom(ctx, strings.NewReader("4\n"), "SELECT 1/0; COPY cp FROM STDIN")
if code := pgErrCode(err); code != "22012" {
t.Fatalf("failing statement before COPY: %v, want division_by_zero", err)
}
// Two COPYs would need the second data stream after the first reply.
_, err = conn.Exec(ctx, "COPY cp FROM STDIN; COPY cp FROM STDIN").ReadAll()
if code := pgErrCode(err); code != "0A000" {
t.Fatalf("two COPY FROM STDIN: %v, want feature_not_supported", err)
}
res := conn.ExecParams(ctx, "SELECT count(*) FROM cp", nil, nil, nil, nil).Read()
if res.Err != nil || string(res.Rows[0][0]) != "3" {
t.Errorf("rows: %v, %v", res.Rows, res.Err)
}
if err := pg.Err(); err != nil {
t.Errorf("instance failed: %v", err)
}
}
// A client that vanishes in the middle of its COPY data ends the session
// without the COPY reaching the backend.
func TestCopyFromStdinAbandonedByClient(t *testing.T) {
ctx := t.Context()
pg := newTestPG(t)
conn, err := pgconn.Connect(ctx, pg.ConnString())
if err != nil {
t.Fatal(err)
}
if err := conn.Exec(ctx, "CREATE TABLE cp (id int)").Close(); err != nil {
t.Fatal(err)
}
// pgconn sends CopyFail with the reader's error, which the backend
// reports back as query_canceled; the session goes on.
_, err = conn.CopyFrom(ctx, &failingReader{}, "COPY cp FROM STDIN")
if code := pgErrCode(err); code != "57014" || !strings.Contains(err.Error(), "source failed") {
t.Fatalf("CopyFrom with a failing reader: %v, want the reader's error from the backend", err)
}
res := conn.ExecParams(ctx, "SELECT count(*) FROM cp", nil, nil, nil, nil).Read()
if res.Err != nil || string(res.Rows[0][0]) != "0" {
t.Errorf("after CopyFail: %v, %v", res.Rows, res.Err)
}
// Cutting the connection during the data leaves the next session clean.
p, err := pgconn.Connect(ctx, pg.ConnString())
if err == nil {
t.Fatalf("second connection was accepted: %v", err)
}
conn.Close(ctx)
p, err = pgconn.Connect(ctx, pg.ConnString())
if err != nil {
t.Fatal(err)
}
if _, err := p.Conn().Write(append(queryBytes("COPY cp FROM STDIN"), 'd', 0, 0, 0, 6, '5', '\n')); err != nil {
t.Fatal(err)
}
if err := p.Conn().Close(); err != nil {
t.Fatal(err)
}
next := connect(t, pg)
var n int
if err := next.QueryRow(ctx, "SELECT count(*) FROM cp").Scan(&n); err != nil || n != 0 {
t.Errorf("after abandoned COPY: %d, %v", n, err)
}
if err := pg.Err(); err != nil {
t.Errorf("instance failed: %v", err)
}
}
type failingReader struct{}
func (failingReader) Read([]byte) (int, error) { return 0, errors.New("source failed") }
// queryBytes returns a simple-protocol Query message.
func queryBytes(sql string) []byte {
n := uint32(4 + len(sql) + 1)
return append(append([]byte{'Q', byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n)}, sql...), 0)
}