1 package pq
2
3 import (
4 "bytes"
5 "database/sql"
6 "fmt"
7 "regexp"
8 "testing"
9 "time"
10
11 "github.com/lib/pq/oid"
12 )
13
14 func TestScanTimestamp(t *testing.T) {
15 var nt NullTime
16 tn := time.Now()
17 nt.Scan(tn)
18 if !nt.Valid {
19 t.Errorf("Expected Valid=false")
20 }
21 if nt.Time != tn {
22 t.Errorf("Time value mismatch")
23 }
24 }
25
26 func TestScanNilTimestamp(t *testing.T) {
27 var nt NullTime
28 nt.Scan(nil)
29 if nt.Valid {
30 t.Errorf("Expected Valid=false")
31 }
32 }
33
34 var timeTests = []struct {
35 str string
36 timeval time.Time
37 }{
38 {"22001-02-03", time.Date(22001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
39 {"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
40 {"0001-12-31 BC", time.Date(0, time.December, 31, 0, 0, 0, 0, time.FixedZone("", 0))},
41 {"2001-02-03 BC", time.Date(-2000, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
42 {"2001-02-03 04:05:06", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
43 {"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.FixedZone("", 0))},
44 {"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.FixedZone("", 0))},
45 {"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.FixedZone("", 0))},
46 {"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.FixedZone("", 0))},
47 {"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.FixedZone("", 0))},
48 {"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
49 {"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.FixedZone("", 0))},
50 {"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
51 {"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.FixedZone("", 0))},
52 {"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.FixedZone("", 0))},
53 {"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.FixedZone("", 0))},
54 {"2001-02-03 04:05:06.123-07", time.Date(2001, time.February, 3, 4, 5, 6, 123000000,
55 time.FixedZone("", -7*60*60))},
56 {"2001-02-03 04:05:06-07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
57 time.FixedZone("", -7*60*60))},
58 {"2001-02-03 04:05:06-07:42", time.Date(2001, time.February, 3, 4, 5, 6, 0,
59 time.FixedZone("", -(7*60*60+42*60)))},
60 {"2001-02-03 04:05:06-07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0,
61 time.FixedZone("", -(7*60*60+30*60+9)))},
62 {"2001-02-03 04:05:06+07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0,
63 time.FixedZone("", +(7*60*60+30*60+9)))},
64 {"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
65 time.FixedZone("", 7*60*60))},
66 {"0011-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
67 {"0011-02-03 04:05:06.123 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
68 {"0011-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000,
69 time.FixedZone("", -7*60*60))},
70 {"0001-02-03 04:05:06.123", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
71 {"0001-02-03 04:05:06.123 BC", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
72 {"0001-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
73 {"0002-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
74 {"0002-02-03 04:05:06.123 BC", time.Date(-1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
75 {"12345-02-03 04:05:06.1", time.Date(12345, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
76 {"123456-02-03 04:05:06.1", time.Date(123456, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
77 }
78
79
80 func TestParseTs(t *testing.T) {
81 for i, tt := range timeTests {
82 val, err := ParseTimestamp(nil, tt.str)
83 if err != nil {
84 t.Errorf("%d: got error: %v", i, err)
85 } else if val.String() != tt.timeval.String() {
86 t.Errorf("%d: expected to parse %q into %q; got %q",
87 i, tt.str, tt.timeval, val)
88 }
89 }
90 }
91
92 var timeErrorTests = []string{
93 "BC",
94 " BC",
95 "2001",
96 "2001-2-03",
97 "2001-02-3",
98 "2001-02-03 ",
99 "2001-02-03 B",
100 "2001-02-03 04",
101 "2001-02-03 04:",
102 "2001-02-03 04:05",
103 "2001-02-03 04:05 B",
104 "2001-02-03 04:05 BC",
105 "2001-02-03 04:05:",
106 "2001-02-03 04:05:6",
107 "2001-02-03 04:05:06 B",
108 "2001-02-03 04:05:06BC",
109 "2001-02-03 04:05:06.123 B",
110 }
111
112
113 func TestParseTsErrors(t *testing.T) {
114 for i, tt := range timeErrorTests {
115 _, err := ParseTimestamp(nil, tt)
116 if err == nil {
117 t.Errorf("%d: expected an error from parsing: %v", i, tt)
118 }
119 }
120 }
121
122
123
124 func TestEncodeAndParseTs(t *testing.T) {
125 db, err := openTestConnConninfo("timezone='Etc/UTC'")
126 if err != nil {
127 t.Fatal(err)
128 }
129 defer db.Close()
130
131 for i, tt := range timeTests {
132 var dbstr string
133 err = db.QueryRow("SELECT ($1::timestamptz)::text", tt.timeval).Scan(&dbstr)
134 if err != nil {
135 t.Errorf("%d: could not send value %q to the database: %s", i, tt.timeval, err)
136 continue
137 }
138
139 val, err := ParseTimestamp(nil, dbstr)
140 if err != nil {
141 t.Errorf("%d: could not parse value %q: %s", i, dbstr, err)
142 continue
143 }
144 val = val.In(tt.timeval.Location())
145 if val.String() != tt.timeval.String() {
146 t.Errorf("%d: expected to parse %q into %q; got %q", i, dbstr, tt.timeval, val)
147 }
148 }
149 }
150
151 var formatTimeTests = []struct {
152 time time.Time
153 expected string
154 }{
155 {time.Time{}, "0001-01-01 00:00:00Z"},
156 {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"},
157 {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"},
158 {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"},
159 {time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"},
160
161 {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"},
162 {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"},
163 {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"},
164
165 {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"},
166 {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"},
167 {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"},
168
169 {time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"},
170 {time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"},
171 }
172
173 func TestFormatTs(t *testing.T) {
174 for i, tt := range formatTimeTests {
175 val := string(formatTs(tt.time))
176 if val != tt.expected {
177 t.Errorf("%d: incorrect time format %q, want %q", i, val, tt.expected)
178 }
179 }
180 }
181
182 func TestFormatTsBackend(t *testing.T) {
183 db := openTestConn(t)
184 defer db.Close()
185
186 var str string
187 err := db.QueryRow("SELECT '2001-02-03T04:05:06.007-08:09:10'::time::text").Scan(&str)
188 if err == nil {
189 t.Fatalf("PostgreSQL is accepting an ISO timestamp input for time")
190 }
191
192 for i, tt := range formatTimeTests {
193 for _, typ := range []string{"date", "time", "timetz", "timestamp", "timestamptz"} {
194 err = db.QueryRow("SELECT $1::"+typ+"::text", tt.time).Scan(&str)
195 if err != nil {
196 t.Errorf("%d: incorrect time format for %v on the backend: %v", i, typ, err)
197 }
198 }
199 }
200 }
201
202 func TestTimeWithoutTimezone(t *testing.T) {
203 db := openTestConn(t)
204 defer db.Close()
205
206 tx, err := db.Begin()
207 if err != nil {
208 t.Fatal(err)
209 }
210 defer tx.Rollback()
211
212 for _, tc := range []struct {
213 refTime string
214 expectedTime time.Time
215 }{
216 {"11:59:59", time.Date(0, 1, 1, 11, 59, 59, 0, time.UTC)},
217 {"24:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
218 {"24:00:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
219 {"24:00:00.0", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
220 {"24:00:00.000000", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
221 } {
222 t.Run(
223 fmt.Sprintf("%s => %s", tc.refTime, tc.expectedTime.Format(time.RFC3339)),
224 func(t *testing.T) {
225 var gotTime time.Time
226 row := tx.QueryRow("select $1::time", tc.refTime)
227 err = row.Scan(&gotTime)
228 if err != nil {
229 t.Fatal(err)
230 }
231
232 if !tc.expectedTime.Equal(gotTime) {
233 t.Errorf("timestamps not equal: %s != %s", tc.expectedTime, gotTime)
234 }
235 },
236 )
237 }
238 }
239
240 func TestTimeWithTimezone(t *testing.T) {
241 db := openTestConn(t)
242 defer db.Close()
243
244 tx, err := db.Begin()
245 if err != nil {
246 t.Fatal(err)
247 }
248 defer tx.Rollback()
249
250 for _, tc := range []struct {
251 refTime string
252 expectedTime time.Time
253 }{
254 {"11:59:59+00:00", time.Date(0, 1, 1, 11, 59, 59, 0, time.UTC)},
255 {"11:59:59+04:00", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("+04", 4*60*60))},
256 {"11:59:59+04:01:02", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("+04:01:02", 4*60*60+1*60+2))},
257 {"11:59:59-04:01:02", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("-04:01:02", -(4*60*60+1*60+2)))},
258 {"24:00+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
259 {"24:00Z", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
260 {"24:00-04:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.FixedZone("-04", -4*60*60))},
261 {"24:00:00+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
262 {"24:00:00.0+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
263 {"24:00:00.000000+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
264 } {
265 t.Run(
266 fmt.Sprintf("%s => %s", tc.refTime, tc.expectedTime.Format(time.RFC3339)),
267 func(t *testing.T) {
268 var gotTime time.Time
269 row := tx.QueryRow("select $1::timetz", tc.refTime)
270 err = row.Scan(&gotTime)
271 if err != nil {
272 t.Fatal(err)
273 }
274
275 if !tc.expectedTime.Equal(gotTime) {
276 t.Errorf("timestamps not equal: %s != %s", tc.expectedTime, gotTime)
277 }
278 },
279 )
280 }
281 }
282
283 func TestTimestampWithTimeZone(t *testing.T) {
284 db := openTestConn(t)
285 defer db.Close()
286
287 tx, err := db.Begin()
288 if err != nil {
289 t.Fatal(err)
290 }
291 defer tx.Rollback()
292
293
294 for _, locName := range []string{
295 "UTC",
296 "America/Chicago",
297 "America/New_York",
298 "Australia/Darwin",
299 "Australia/Perth",
300 } {
301 loc, err := time.LoadLocation(locName)
302 if err != nil {
303 t.Logf("Could not load time zone %s - skipping", locName)
304 continue
305 }
306
307
308
309 refTime := time.Date(2012, 11, 6, 10, 23, 42, 123456000, loc)
310
311 for _, pgTimeZone := range []string{"US/Eastern", "Australia/Darwin"} {
312
313 _, err = tx.Exec(fmt.Sprintf("set time zone '%s'", pgTimeZone))
314 if err != nil {
315 t.Fatal(err)
316 }
317
318 var gotTime time.Time
319 row := tx.QueryRow("select $1::timestamp with time zone", refTime)
320 err = row.Scan(&gotTime)
321 if err != nil {
322 t.Fatal(err)
323 }
324
325 if !refTime.Equal(gotTime) {
326 t.Errorf("timestamps not equal: %s != %s", refTime, gotTime)
327 }
328
329
330 pgLoc, err := time.LoadLocation(pgTimeZone)
331 if err != nil {
332 t.Logf("Could not load time zone %s - skipping", pgLoc)
333 continue
334 }
335 translated := refTime.In(pgLoc)
336 if translated.String() != gotTime.String() {
337 t.Errorf("timestamps not equal: %s != %s", translated, gotTime)
338 }
339 }
340 }
341 }
342
343 func TestTimestampWithOutTimezone(t *testing.T) {
344 db := openTestConn(t)
345 defer db.Close()
346
347 test := func(ts, pgts string) {
348 r, err := db.Query("SELECT $1::timestamp", pgts)
349 if err != nil {
350 t.Fatalf("Could not run query: %v", err)
351 }
352
353 if !r.Next() {
354 t.Fatal("Expected at least one row")
355 }
356
357 var result time.Time
358 err = r.Scan(&result)
359 if err != nil {
360 t.Fatalf("Did not expect error scanning row: %v", err)
361 }
362
363 expected, err := time.Parse(time.RFC3339, ts)
364 if err != nil {
365 t.Fatalf("Could not parse test time literal: %v", err)
366 }
367
368 if !result.Equal(expected) {
369 t.Fatalf("Expected time to match %v: got mismatch %v",
370 expected, result)
371 }
372
373 if r.Next() {
374 t.Fatal("Expected only one row")
375 }
376 }
377
378 test("2000-01-01T00:00:00Z", "2000-01-01T00:00:00")
379
380
381 test("2013-01-04T20:14:58.80033Z", "2013-01-04 20:14:58.80033")
382 }
383
384 func TestInfinityTimestamp(t *testing.T) {
385 db := openTestConn(t)
386 defer db.Close()
387 var err error
388 var resultT time.Time
389
390 expectedErrorStrRegexp := regexp.MustCompile(
391 `^sql: Scan error on column index 0(, name "timestamp(tz)?"|): unsupported`)
392
393 type testCases []struct {
394 Query string
395 Param string
396 ExpectedErrorStrRegexp *regexp.Regexp
397 ExpectedVal interface{}
398 }
399 tc := testCases{
400 {"SELECT $1::timestamp", "-infinity", expectedErrorStrRegexp, "-infinity"},
401 {"SELECT $1::timestamptz", "-infinity", expectedErrorStrRegexp, "-infinity"},
402 {"SELECT $1::timestamp", "infinity", expectedErrorStrRegexp, "infinity"},
403 {"SELECT $1::timestamptz", "infinity", expectedErrorStrRegexp, "infinity"},
404 }
405
406 for _, q := range tc {
407 err = db.QueryRow(q.Query, q.Param).Scan(&resultT)
408 if err == nil || !q.ExpectedErrorStrRegexp.MatchString(err.Error()) {
409 t.Errorf("Scanning -/+infinity, expected error to match regexp %q, got %q",
410 q.ExpectedErrorStrRegexp, err)
411 }
412 }
413
414 for _, q := range tc {
415 var resultI interface{}
416 err = db.QueryRow(q.Query, q.Param).Scan(&resultI)
417 if err != nil {
418 t.Errorf("Scanning -/+infinity, expected no error, got %q", err)
419 }
420 result, ok := resultI.([]byte)
421 if !ok {
422 t.Errorf("Scanning -/+infinity, expected []byte, got %#v", resultI)
423 }
424 if string(result) != q.ExpectedVal {
425 t.Errorf("Scanning -/+infinity, expected %q, got %q", q.ExpectedVal, result)
426 }
427 }
428
429 y1500 := time.Date(1500, time.January, 1, 0, 0, 0, 0, time.UTC)
430 y2500 := time.Date(2500, time.January, 1, 0, 0, 0, 0, time.UTC)
431 EnableInfinityTs(y1500, y2500)
432
433 err = db.QueryRow("SELECT $1::timestamp", "infinity").Scan(&resultT)
434 if err != nil {
435 t.Errorf("Scanning infinity, expected no error, got %q", err)
436 }
437 if !resultT.Equal(y2500) {
438 t.Errorf("Scanning infinity, expected %q, got %q", y2500, resultT)
439 }
440
441 err = db.QueryRow("SELECT $1::timestamptz", "infinity").Scan(&resultT)
442 if err != nil {
443 t.Errorf("Scanning infinity, expected no error, got %q", err)
444 }
445 if !resultT.Equal(y2500) {
446 t.Errorf("Scanning Infinity, expected time %q, got %q", y2500, resultT.String())
447 }
448
449 err = db.QueryRow("SELECT $1::timestamp", "-infinity").Scan(&resultT)
450 if err != nil {
451 t.Errorf("Scanning -infinity, expected no error, got %q", err)
452 }
453 if !resultT.Equal(y1500) {
454 t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
455 }
456
457 err = db.QueryRow("SELECT $1::timestamptz", "-infinity").Scan(&resultT)
458 if err != nil {
459 t.Errorf("Scanning -infinity, expected no error, got %q", err)
460 }
461 if !resultT.Equal(y1500) {
462 t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
463 }
464
465 ym1500 := time.Date(-1500, time.January, 1, 0, 0, 0, 0, time.UTC)
466 y11500 := time.Date(11500, time.January, 1, 0, 0, 0, 0, time.UTC)
467 var s string
468 err = db.QueryRow("SELECT $1::timestamp::text", ym1500).Scan(&s)
469 if err != nil {
470 t.Errorf("Encoding -infinity, expected no error, got %q", err)
471 }
472 if s != "-infinity" {
473 t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
474 }
475 err = db.QueryRow("SELECT $1::timestamptz::text", ym1500).Scan(&s)
476 if err != nil {
477 t.Errorf("Encoding -infinity, expected no error, got %q", err)
478 }
479 if s != "-infinity" {
480 t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
481 }
482
483 err = db.QueryRow("SELECT $1::timestamp::text", y11500).Scan(&s)
484 if err != nil {
485 t.Errorf("Encoding infinity, expected no error, got %q", err)
486 }
487 if s != "infinity" {
488 t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
489 }
490 err = db.QueryRow("SELECT $1::timestamptz::text", y11500).Scan(&s)
491 if err != nil {
492 t.Errorf("Encoding infinity, expected no error, got %q", err)
493 }
494 if s != "infinity" {
495 t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
496 }
497
498 disableInfinityTs()
499
500 var panicErrorString string
501 func() {
502 defer func() {
503 panicErrorString, _ = recover().(string)
504 }()
505 EnableInfinityTs(y2500, y1500)
506 }()
507 if panicErrorString != infinityTsNegativeMustBeSmaller {
508 t.Errorf("Expected error, %q, got %q", infinityTsNegativeMustBeSmaller, panicErrorString)
509 }
510 }
511
512 func TestStringWithNul(t *testing.T) {
513 db := openTestConn(t)
514 defer db.Close()
515
516 hello0world := string("hello\x00world")
517 _, err := db.Query("SELECT $1::text", &hello0world)
518 if err == nil {
519 t.Fatal("Postgres accepts a string with nul in it; " +
520 "injection attacks may be plausible")
521 }
522 }
523
524 func TestByteSliceToText(t *testing.T) {
525 db := openTestConn(t)
526 defer db.Close()
527
528 b := []byte("hello world")
529 row := db.QueryRow("SELECT $1::text", b)
530
531 var result []byte
532 err := row.Scan(&result)
533 if err != nil {
534 t.Fatal(err)
535 }
536
537 if string(result) != string(b) {
538 t.Fatalf("expected %v but got %v", b, result)
539 }
540 }
541
542 func TestStringToBytea(t *testing.T) {
543 db := openTestConn(t)
544 defer db.Close()
545
546 b := "hello world"
547 row := db.QueryRow("SELECT $1::bytea", b)
548
549 var result []byte
550 err := row.Scan(&result)
551 if err != nil {
552 t.Fatal(err)
553 }
554
555 if !bytes.Equal(result, []byte(b)) {
556 t.Fatalf("expected %v but got %v", b, result)
557 }
558 }
559
560 func TestTextByteSliceToUUID(t *testing.T) {
561 db := openTestConn(t)
562 defer db.Close()
563
564 b := []byte("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
565 row := db.QueryRow("SELECT $1::uuid", b)
566
567 var result string
568 err := row.Scan(&result)
569 if forceBinaryParameters() {
570 pqErr := err.(*Error)
571 if pqErr == nil {
572 t.Errorf("Expected to get error")
573 } else if pqErr.Code != "22P03" {
574 t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
575 }
576 } else {
577 if err != nil {
578 t.Fatal(err)
579 }
580
581 if result != string(b) {
582 t.Fatalf("expected %v but got %v", b, result)
583 }
584 }
585 }
586
587 func TestBinaryByteSlicetoUUID(t *testing.T) {
588 db := openTestConn(t)
589 defer db.Close()
590
591 b := []byte{'\xa0', '\xee', '\xbc', '\x99',
592 '\x9c', '\x0b',
593 '\x4e', '\xf8',
594 '\xbb', '\x00', '\x6b',
595 '\xb9', '\xbd', '\x38', '\x0a', '\x11'}
596 row := db.QueryRow("SELECT $1::uuid", b)
597
598 var result string
599 err := row.Scan(&result)
600 if forceBinaryParameters() {
601 if err != nil {
602 t.Fatal(err)
603 }
604
605 if result != string("a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11") {
606 t.Fatalf("expected %v but got %v", b, result)
607 }
608 } else {
609 pqErr := err.(*Error)
610 if pqErr == nil {
611 t.Errorf("Expected to get error")
612 } else if pqErr.Code != "22021" {
613 t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
614 }
615 }
616 }
617
618 func TestStringToUUID(t *testing.T) {
619 db := openTestConn(t)
620 defer db.Close()
621
622 s := "a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11"
623 row := db.QueryRow("SELECT $1::uuid", s)
624
625 var result string
626 err := row.Scan(&result)
627 if err != nil {
628 t.Fatal(err)
629 }
630
631 if result != s {
632 t.Fatalf("expected %v but got %v", s, result)
633 }
634 }
635
636 func TestTextByteSliceToInt(t *testing.T) {
637 db := openTestConn(t)
638 defer db.Close()
639
640 expected := 12345678
641 b := []byte(fmt.Sprintf("%d", expected))
642 row := db.QueryRow("SELECT $1::int", b)
643
644 var result int
645 err := row.Scan(&result)
646 if forceBinaryParameters() {
647 pqErr := err.(*Error)
648 if pqErr == nil {
649 t.Errorf("Expected to get error")
650 } else if pqErr.Code != "22P03" {
651 t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
652 }
653 } else {
654 if err != nil {
655 t.Fatal(err)
656 }
657 if result != expected {
658 t.Fatalf("expected %v but got %v", expected, result)
659 }
660 }
661 }
662
663 func TestBinaryByteSliceToInt(t *testing.T) {
664 db := openTestConn(t)
665 defer db.Close()
666
667 expected := 12345678
668 b := []byte{'\x00', '\xbc', '\x61', '\x4e'}
669 row := db.QueryRow("SELECT $1::int", b)
670
671 var result int
672 err := row.Scan(&result)
673 if forceBinaryParameters() {
674 if err != nil {
675 t.Fatal(err)
676 }
677 if result != expected {
678 t.Fatalf("expected %v but got %v", expected, result)
679 }
680 } else {
681 pqErr := err.(*Error)
682 if pqErr == nil {
683 t.Errorf("Expected to get error")
684 } else if pqErr.Code != "22021" {
685 t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
686 }
687 }
688 }
689
690 func TestTextDecodeIntoString(t *testing.T) {
691 input := []byte("hello world")
692 want := string(input)
693 for _, typ := range []oid.Oid{oid.T_char, oid.T_varchar, oid.T_text} {
694 got := decode(¶meterStatus{}, input, typ, formatText)
695 if got != want {
696 t.Errorf("invalid string decoding output for %T(%+v), got %v but expected %v", typ, typ, got, want)
697 }
698 }
699 }
700
701 func TestByteaOutputFormatEncoding(t *testing.T) {
702 input := []byte("\\x\x00\x01\x02\xFF\xFEabcdefg0123")
703 want := []byte("\\x5c78000102fffe6162636465666730313233")
704 got := encode(¶meterStatus{serverVersion: 90000}, input, oid.T_bytea)
705 if !bytes.Equal(want, got) {
706 t.Errorf("invalid hex bytea output, got %v but expected %v", got, want)
707 }
708
709 want = []byte("\\\\x\\000\\001\\002\\377\\376abcdefg0123")
710 got = encode(¶meterStatus{serverVersion: 84000}, input, oid.T_bytea)
711 if !bytes.Equal(want, got) {
712 t.Errorf("invalid escape bytea output, got %v but expected %v", got, want)
713 }
714 }
715
716 func TestByteaOutputFormats(t *testing.T) {
717 db := openTestConn(t)
718 defer db.Close()
719
720 if getServerVersion(t, db) < 90000 {
721
722 return
723 }
724
725 testByteaOutputFormat := func(f string, usePrepared bool) {
726 expectedData := []byte("\x5c\x78\x00\xff\x61\x62\x63\x01\x08")
727 sqlQuery := "SELECT decode('5c7800ff6162630108', 'hex')"
728
729 var data []byte
730
731
732 txn, err := db.Begin()
733 if err != nil {
734 t.Fatal(err)
735 }
736 defer txn.Rollback()
737
738 _, err = txn.Exec("SET LOCAL bytea_output TO " + f)
739 if err != nil {
740 t.Fatal(err)
741 }
742 var rows *sql.Rows
743 var stmt *sql.Stmt
744 if usePrepared {
745 stmt, err = txn.Prepare(sqlQuery)
746 if err != nil {
747 t.Fatal(err)
748 }
749 rows, err = stmt.Query()
750 } else {
751
752 rows, err = txn.Query(sqlQuery)
753 }
754 if err != nil {
755 t.Fatal(err)
756 }
757 if !rows.Next() {
758 if rows.Err() != nil {
759 t.Fatal(rows.Err())
760 }
761 t.Fatal("shouldn't happen")
762 }
763 err = rows.Scan(&data)
764 if err != nil {
765 t.Fatal(err)
766 }
767 err = rows.Close()
768 if err != nil {
769 t.Fatal(err)
770 }
771 if stmt != nil {
772 err = stmt.Close()
773 if err != nil {
774 t.Fatal(err)
775 }
776 }
777 if !bytes.Equal(data, expectedData) {
778 t.Errorf("unexpected bytea value %v for format %s; expected %v", data, f, expectedData)
779 }
780 }
781
782 testByteaOutputFormat("hex", false)
783 testByteaOutputFormat("escape", false)
784 testByteaOutputFormat("hex", true)
785 testByteaOutputFormat("escape", true)
786 }
787
788 func TestAppendEncodedText(t *testing.T) {
789 var buf []byte
790
791 buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, int64(10))
792 buf = append(buf, '\t')
793 buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, 42.0000000001)
794 buf = append(buf, '\t')
795 buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, "hello\tworld")
796 buf = append(buf, '\t')
797 buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, []byte{0, 128, 255})
798
799 if string(buf) != "10\t42.0000000001\thello\\tworld\t\\\\x0080ff" {
800 t.Fatal(string(buf))
801 }
802 }
803
804 func TestAppendEscapedText(t *testing.T) {
805 if esc := appendEscapedText(nil, "hallo\tescape"); string(esc) != "hallo\\tescape" {
806 t.Fatal(string(esc))
807 }
808 if esc := appendEscapedText(nil, "hallo\\tescape\n"); string(esc) != "hallo\\\\tescape\\n" {
809 t.Fatal(string(esc))
810 }
811 if esc := appendEscapedText(nil, "\n\r\t\f"); string(esc) != "\\n\\r\\t\f" {
812 t.Fatal(string(esc))
813 }
814 }
815
816 func TestAppendEscapedTextExistingBuffer(t *testing.T) {
817 buf := []byte("123\t")
818 if esc := appendEscapedText(buf, "hallo\tescape"); string(esc) != "123\thallo\\tescape" {
819 t.Fatal(string(esc))
820 }
821 buf = []byte("123\t")
822 if esc := appendEscapedText(buf, "hallo\\tescape\n"); string(esc) != "123\thallo\\\\tescape\\n" {
823 t.Fatal(string(esc))
824 }
825 buf = []byte("123\t")
826 if esc := appendEscapedText(buf, "\n\r\t\f"); string(esc) != "123\t\\n\\r\\t\f" {
827 t.Fatal(string(esc))
828 }
829 }
830
831 var formatAndParseTimestamp = []struct {
832 time time.Time
833 expected string
834 }{
835 {time.Time{}, "0001-01-01 00:00:00Z"},
836 {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"},
837 {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"},
838 {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"},
839 {time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"},
840
841 {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"},
842 {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"},
843 {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"},
844
845 {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"},
846 {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"},
847 {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"},
848
849 {time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"},
850 {time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"},
851 }
852
853 func TestFormatAndParseTimestamp(t *testing.T) {
854 for _, val := range formatAndParseTimestamp {
855 formattedTime := FormatTimestamp(val.time)
856 parsedTime, err := ParseTimestamp(nil, string(formattedTime))
857
858 if err != nil {
859 t.Errorf("invalid parsing, err: %v", err.Error())
860 }
861
862 if val.time.UTC() != parsedTime.UTC() {
863 t.Errorf("invalid parsing from formatted timestamp, got %v; expected %v", parsedTime.String(), val.time.String())
864 }
865 }
866 }
867
868 func BenchmarkAppendEscapedText(b *testing.B) {
869 longString := ""
870 for i := 0; i < 100; i++ {
871 longString += "123456789\n"
872 }
873 for i := 0; i < b.N; i++ {
874 appendEscapedText(nil, longString)
875 }
876 }
877
878 func BenchmarkAppendEscapedTextNoEscape(b *testing.B) {
879 longString := ""
880 for i := 0; i < 100; i++ {
881 longString += "1234567890"
882 }
883 for i := 0; i < b.N; i++ {
884 appendEscapedText(nil, longString)
885 }
886 }
887
View as plain text