1package pq 2 3import ( 4 "database/sql" 5 "database/sql/driver" 6 "fmt" 7 "io" 8 "net" 9 "os" 10 "reflect" 11 "strings" 12 "testing" 13 "time" 14) 15 16type Fatalistic interface { 17 Fatal(args ...interface{}) 18} 19 20func forceBinaryParameters() bool { 21 bp := os.Getenv("PQTEST_BINARY_PARAMETERS") 22 if bp == "yes" { 23 return true 24 } else if bp == "" || bp == "no" { 25 return false 26 } else { 27 panic("unexpected value for PQTEST_BINARY_PARAMETERS") 28 } 29} 30 31func openTestConnConninfo(conninfo string) (*sql.DB, error) { 32 defaultTo := func(envvar string, value string) { 33 if os.Getenv(envvar) == "" { 34 os.Setenv(envvar, value) 35 } 36 } 37 defaultTo("PGDATABASE", "pqgotest") 38 defaultTo("PGSSLMODE", "disable") 39 defaultTo("PGCONNECT_TIMEOUT", "20") 40 41 if forceBinaryParameters() && 42 !strings.HasPrefix(conninfo, "postgres://") && 43 !strings.HasPrefix(conninfo, "postgresql://") { 44 conninfo = conninfo + " binary_parameters=yes" 45 } 46 47 return sql.Open("postgres", conninfo) 48} 49 50func openTestConn(t Fatalistic) *sql.DB { 51 conn, err := openTestConnConninfo("") 52 if err != nil { 53 t.Fatal(err) 54 } 55 56 return conn 57} 58 59func getServerVersion(t *testing.T, db *sql.DB) int { 60 var version int 61 err := db.QueryRow("SHOW server_version_num").Scan(&version) 62 if err != nil { 63 t.Fatal(err) 64 } 65 return version 66} 67 68func TestReconnect(t *testing.T) { 69 db1 := openTestConn(t) 70 defer db1.Close() 71 tx, err := db1.Begin() 72 if err != nil { 73 t.Fatal(err) 74 } 75 var pid1 int 76 err = tx.QueryRow("SELECT pg_backend_pid()").Scan(&pid1) 77 if err != nil { 78 t.Fatal(err) 79 } 80 db2 := openTestConn(t) 81 defer db2.Close() 82 _, err = db2.Exec("SELECT pg_terminate_backend($1)", pid1) 83 if err != nil { 84 t.Fatal(err) 85 } 86 // The rollback will probably "fail" because we just killed 87 // its connection above 88 _ = tx.Rollback() 89 90 const expected int = 42 91 var result int 92 err = db1.QueryRow(fmt.Sprintf("SELECT %d", expected)).Scan(&result) 93 if err != nil { 94 t.Fatal(err) 95 } 96 if result != expected { 97 t.Errorf("got %v; expected %v", result, expected) 98 } 99} 100 101func TestCommitInFailedTransaction(t *testing.T) { 102 db := openTestConn(t) 103 defer db.Close() 104 105 txn, err := db.Begin() 106 if err != nil { 107 t.Fatal(err) 108 } 109 rows, err := txn.Query("SELECT error") 110 if err == nil { 111 rows.Close() 112 t.Fatal("expected failure") 113 } 114 err = txn.Commit() 115 if err != ErrInFailedTransaction { 116 t.Fatalf("expected ErrInFailedTransaction; got %#v", err) 117 } 118} 119 120func TestOpenURL(t *testing.T) { 121 testURL := func(url string) { 122 db, err := openTestConnConninfo(url) 123 if err != nil { 124 t.Fatal(err) 125 } 126 defer db.Close() 127 // database/sql might not call our Open at all unless we do something with 128 // the connection 129 txn, err := db.Begin() 130 if err != nil { 131 t.Fatal(err) 132 } 133 txn.Rollback() 134 } 135 testURL("postgres://") 136 testURL("postgresql://") 137} 138 139const pgpass_file = "/tmp/pqgotest_pgpass" 140 141func TestPgpass(t *testing.T) { 142 if os.Getenv("TRAVIS") != "true" { 143 t.Skip("not running under Travis, skipping pgpass tests") 144 } 145 146 testAssert := func(conninfo string, expected string, reason string) { 147 conn, err := openTestConnConninfo(conninfo) 148 if err != nil { 149 t.Fatal(err) 150 } 151 defer conn.Close() 152 153 txn, err := conn.Begin() 154 if err != nil { 155 if expected != "fail" { 156 t.Fatalf(reason, err) 157 } 158 return 159 } 160 rows, err := txn.Query("SELECT USER") 161 if err != nil { 162 txn.Rollback() 163 rows.Close() 164 if expected != "fail" { 165 t.Fatalf(reason, err) 166 } 167 } else { 168 if expected != "ok" { 169 t.Fatalf(reason, err) 170 } 171 } 172 txn.Rollback() 173 } 174 testAssert("", "ok", "missing .pgpass, unexpected error %#v") 175 os.Setenv("PGPASSFILE", pgpass_file) 176 testAssert("host=/tmp", "fail", ", unexpected error %#v") 177 os.Remove(pgpass_file) 178 pgpass, err := os.OpenFile(pgpass_file, os.O_RDWR|os.O_CREATE, 0644) 179 if err != nil { 180 t.Fatalf("Unexpected error writing pgpass file %#v", err) 181 } 182 _, err = pgpass.WriteString(`# comment 183server:5432:some_db:some_user:pass_A 184*:5432:some_db:some_user:pass_B 185localhost:*:*:*:pass_C 186*:*:*:*:pass_fallback 187`) 188 if err != nil { 189 t.Fatalf("Unexpected error writing pgpass file %#v", err) 190 } 191 pgpass.Close() 192 193 assertPassword := func(extra values, expected string) { 194 o := values{ 195 "host": "localhost", 196 "sslmode": "disable", 197 "connect_timeout": "20", 198 "user": "majid", 199 "port": "5432", 200 "extra_float_digits": "2", 201 "dbname": "pqgotest", 202 "client_encoding": "UTF8", 203 "datestyle": "ISO, MDY", 204 } 205 for k, v := range extra { 206 o[k] = v 207 } 208 (&conn{}).handlePgpass(o) 209 if pw := o["password"]; pw != expected { 210 t.Fatalf("For %v expected %s got %s", extra, expected, pw) 211 } 212 } 213 // wrong permissions for the pgpass file means it should be ignored 214 assertPassword(values{"host": "example.com", "user": "foo"}, "") 215 // fix the permissions and check if it has taken effect 216 os.Chmod(pgpass_file, 0600) 217 assertPassword(values{"host": "server", "dbname": "some_db", "user": "some_user"}, "pass_A") 218 assertPassword(values{"host": "example.com", "user": "foo"}, "pass_fallback") 219 assertPassword(values{"host": "example.com", "dbname": "some_db", "user": "some_user"}, "pass_B") 220 // localhost also matches the default "" and UNIX sockets 221 assertPassword(values{"host": "", "user": "some_user"}, "pass_C") 222 assertPassword(values{"host": "/tmp", "user": "some_user"}, "pass_C") 223 // cleanup 224 os.Remove(pgpass_file) 225 os.Setenv("PGPASSFILE", "") 226} 227 228func TestExec(t *testing.T) { 229 db := openTestConn(t) 230 defer db.Close() 231 232 _, err := db.Exec("CREATE TEMP TABLE temp (a int)") 233 if err != nil { 234 t.Fatal(err) 235 } 236 237 r, err := db.Exec("INSERT INTO temp VALUES (1)") 238 if err != nil { 239 t.Fatal(err) 240 } 241 242 if n, _ := r.RowsAffected(); n != 1 { 243 t.Fatalf("expected 1 row affected, not %d", n) 244 } 245 246 r, err = db.Exec("INSERT INTO temp VALUES ($1), ($2), ($3)", 1, 2, 3) 247 if err != nil { 248 t.Fatal(err) 249 } 250 251 if n, _ := r.RowsAffected(); n != 3 { 252 t.Fatalf("expected 3 rows affected, not %d", n) 253 } 254 255 // SELECT doesn't send the number of returned rows in the command tag 256 // before 9.0 257 if getServerVersion(t, db) >= 90000 { 258 r, err = db.Exec("SELECT g FROM generate_series(1, 2) g") 259 if err != nil { 260 t.Fatal(err) 261 } 262 if n, _ := r.RowsAffected(); n != 2 { 263 t.Fatalf("expected 2 rows affected, not %d", n) 264 } 265 266 r, err = db.Exec("SELECT g FROM generate_series(1, $1) g", 3) 267 if err != nil { 268 t.Fatal(err) 269 } 270 if n, _ := r.RowsAffected(); n != 3 { 271 t.Fatalf("expected 3 rows affected, not %d", n) 272 } 273 } 274} 275 276func TestStatment(t *testing.T) { 277 db := openTestConn(t) 278 defer db.Close() 279 280 st, err := db.Prepare("SELECT 1") 281 if err != nil { 282 t.Fatal(err) 283 } 284 285 st1, err := db.Prepare("SELECT 2") 286 if err != nil { 287 t.Fatal(err) 288 } 289 290 r, err := st.Query() 291 if err != nil { 292 t.Fatal(err) 293 } 294 defer r.Close() 295 296 if !r.Next() { 297 t.Fatal("expected row") 298 } 299 300 var i int 301 err = r.Scan(&i) 302 if err != nil { 303 t.Fatal(err) 304 } 305 306 if i != 1 { 307 t.Fatalf("expected 1, got %d", i) 308 } 309 310 // st1 311 312 r1, err := st1.Query() 313 if err != nil { 314 t.Fatal(err) 315 } 316 defer r1.Close() 317 318 if !r1.Next() { 319 if r.Err() != nil { 320 t.Fatal(r1.Err()) 321 } 322 t.Fatal("expected row") 323 } 324 325 err = r1.Scan(&i) 326 if err != nil { 327 t.Fatal(err) 328 } 329 330 if i != 2 { 331 t.Fatalf("expected 2, got %d", i) 332 } 333} 334 335func TestRowsCloseBeforeDone(t *testing.T) { 336 db := openTestConn(t) 337 defer db.Close() 338 339 r, err := db.Query("SELECT 1") 340 if err != nil { 341 t.Fatal(err) 342 } 343 344 err = r.Close() 345 if err != nil { 346 t.Fatal(err) 347 } 348 349 if r.Next() { 350 t.Fatal("unexpected row") 351 } 352 353 if r.Err() != nil { 354 t.Fatal(r.Err()) 355 } 356} 357 358func TestParameterCountMismatch(t *testing.T) { 359 db := openTestConn(t) 360 defer db.Close() 361 362 var notused int 363 err := db.QueryRow("SELECT false", 1).Scan(¬used) 364 if err == nil { 365 t.Fatal("expected err") 366 } 367 // make sure we clean up correctly 368 err = db.QueryRow("SELECT 1").Scan(¬used) 369 if err != nil { 370 t.Fatal(err) 371 } 372 373 err = db.QueryRow("SELECT $1").Scan(¬used) 374 if err == nil { 375 t.Fatal("expected err") 376 } 377 // make sure we clean up correctly 378 err = db.QueryRow("SELECT 1").Scan(¬used) 379 if err != nil { 380 t.Fatal(err) 381 } 382} 383 384// Test that EmptyQueryResponses are handled correctly. 385func TestEmptyQuery(t *testing.T) { 386 db := openTestConn(t) 387 defer db.Close() 388 389 res, err := db.Exec("") 390 if err != nil { 391 t.Fatal(err) 392 } 393 if _, err := res.RowsAffected(); err != errNoRowsAffected { 394 t.Fatalf("expected %s, got %v", errNoRowsAffected, err) 395 } 396 if _, err := res.LastInsertId(); err != errNoLastInsertId { 397 t.Fatalf("expected %s, got %v", errNoLastInsertId, err) 398 } 399 rows, err := db.Query("") 400 if err != nil { 401 t.Fatal(err) 402 } 403 cols, err := rows.Columns() 404 if err != nil { 405 t.Fatal(err) 406 } 407 if len(cols) != 0 { 408 t.Fatalf("unexpected number of columns %d in response to an empty query", len(cols)) 409 } 410 if rows.Next() { 411 t.Fatal("unexpected row") 412 } 413 if rows.Err() != nil { 414 t.Fatal(rows.Err()) 415 } 416 417 stmt, err := db.Prepare("") 418 if err != nil { 419 t.Fatal(err) 420 } 421 res, err = stmt.Exec() 422 if err != nil { 423 t.Fatal(err) 424 } 425 if _, err := res.RowsAffected(); err != errNoRowsAffected { 426 t.Fatalf("expected %s, got %v", errNoRowsAffected, err) 427 } 428 if _, err := res.LastInsertId(); err != errNoLastInsertId { 429 t.Fatalf("expected %s, got %v", errNoLastInsertId, err) 430 } 431 rows, err = stmt.Query() 432 if err != nil { 433 t.Fatal(err) 434 } 435 cols, err = rows.Columns() 436 if err != nil { 437 t.Fatal(err) 438 } 439 if len(cols) != 0 { 440 t.Fatalf("unexpected number of columns %d in response to an empty query", len(cols)) 441 } 442 if rows.Next() { 443 t.Fatal("unexpected row") 444 } 445 if rows.Err() != nil { 446 t.Fatal(rows.Err()) 447 } 448} 449 450// Test that rows.Columns() is correct even if there are no result rows. 451func TestEmptyResultSetColumns(t *testing.T) { 452 db := openTestConn(t) 453 defer db.Close() 454 455 rows, err := db.Query("SELECT 1 AS a, text 'bar' AS bar WHERE FALSE") 456 if err != nil { 457 t.Fatal(err) 458 } 459 cols, err := rows.Columns() 460 if err != nil { 461 t.Fatal(err) 462 } 463 if len(cols) != 2 { 464 t.Fatalf("unexpected number of columns %d in response to an empty query", len(cols)) 465 } 466 if rows.Next() { 467 t.Fatal("unexpected row") 468 } 469 if rows.Err() != nil { 470 t.Fatal(rows.Err()) 471 } 472 if cols[0] != "a" || cols[1] != "bar" { 473 t.Fatalf("unexpected Columns result %v", cols) 474 } 475 476 stmt, err := db.Prepare("SELECT $1::int AS a, text 'bar' AS bar WHERE FALSE") 477 if err != nil { 478 t.Fatal(err) 479 } 480 rows, err = stmt.Query(1) 481 if err != nil { 482 t.Fatal(err) 483 } 484 cols, err = rows.Columns() 485 if err != nil { 486 t.Fatal(err) 487 } 488 if len(cols) != 2 { 489 t.Fatalf("unexpected number of columns %d in response to an empty query", len(cols)) 490 } 491 if rows.Next() { 492 t.Fatal("unexpected row") 493 } 494 if rows.Err() != nil { 495 t.Fatal(rows.Err()) 496 } 497 if cols[0] != "a" || cols[1] != "bar" { 498 t.Fatalf("unexpected Columns result %v", cols) 499 } 500 501} 502 503func TestEncodeDecode(t *testing.T) { 504 db := openTestConn(t) 505 defer db.Close() 506 507 q := ` 508 SELECT 509 E'\\000\\001\\002'::bytea, 510 'foobar'::text, 511 NULL::integer, 512 '2000-1-1 01:02:03.04-7'::timestamptz, 513 0::boolean, 514 123, 515 -321, 516 3.14::float8 517 WHERE 518 E'\\000\\001\\002'::bytea = $1 519 AND 'foobar'::text = $2 520 AND $3::integer is NULL 521 ` 522 // AND '2000-1-1 12:00:00.000000-7'::timestamp = $3 523 524 exp1 := []byte{0, 1, 2} 525 exp2 := "foobar" 526 527 r, err := db.Query(q, exp1, exp2, nil) 528 if err != nil { 529 t.Fatal(err) 530 } 531 defer r.Close() 532 533 if !r.Next() { 534 if r.Err() != nil { 535 t.Fatal(r.Err()) 536 } 537 t.Fatal("expected row") 538 } 539 540 var got1 []byte 541 var got2 string 542 var got3 = sql.NullInt64{Valid: true} 543 var got4 time.Time 544 var got5, got6, got7, got8 interface{} 545 546 err = r.Scan(&got1, &got2, &got3, &got4, &got5, &got6, &got7, &got8) 547 if err != nil { 548 t.Fatal(err) 549 } 550 551 if !reflect.DeepEqual(exp1, got1) { 552 t.Errorf("expected %q byte: %q", exp1, got1) 553 } 554 555 if !reflect.DeepEqual(exp2, got2) { 556 t.Errorf("expected %q byte: %q", exp2, got2) 557 } 558 559 if got3.Valid { 560 t.Fatal("expected invalid") 561 } 562 563 if got4.Year() != 2000 { 564 t.Fatal("wrong year") 565 } 566 567 if got5 != false { 568 t.Fatalf("expected false, got %q", got5) 569 } 570 571 if got6 != int64(123) { 572 t.Fatalf("expected 123, got %d", got6) 573 } 574 575 if got7 != int64(-321) { 576 t.Fatalf("expected -321, got %d", got7) 577 } 578 579 if got8 != float64(3.14) { 580 t.Fatalf("expected 3.14, got %f", got8) 581 } 582} 583 584func TestNoData(t *testing.T) { 585 db := openTestConn(t) 586 defer db.Close() 587 588 st, err := db.Prepare("SELECT 1 WHERE true = false") 589 if err != nil { 590 t.Fatal(err) 591 } 592 defer st.Close() 593 594 r, err := st.Query() 595 if err != nil { 596 t.Fatal(err) 597 } 598 defer r.Close() 599 600 if r.Next() { 601 if r.Err() != nil { 602 t.Fatal(r.Err()) 603 } 604 t.Fatal("unexpected row") 605 } 606 607 _, err = db.Query("SELECT * FROM nonexistenttable WHERE age=$1", 20) 608 if err == nil { 609 t.Fatal("Should have raised an error on non existent table") 610 } 611 612 _, err = db.Query("SELECT * FROM nonexistenttable") 613 if err == nil { 614 t.Fatal("Should have raised an error on non existent table") 615 } 616} 617 618func TestErrorDuringStartup(t *testing.T) { 619 // Don't use the normal connection setup, this is intended to 620 // blow up in the startup packet from a non-existent user. 621 db, err := openTestConnConninfo("user=thisuserreallydoesntexist") 622 if err != nil { 623 t.Fatal(err) 624 } 625 defer db.Close() 626 627 _, err = db.Begin() 628 if err == nil { 629 t.Fatal("expected error") 630 } 631 632 e, ok := err.(*Error) 633 if !ok { 634 t.Fatalf("expected Error, got %#v", err) 635 } else if e.Code.Name() != "invalid_authorization_specification" && e.Code.Name() != "invalid_password" { 636 t.Fatalf("expected invalid_authorization_specification or invalid_password, got %s (%+v)", e.Code.Name(), err) 637 } 638} 639 640func TestBadConn(t *testing.T) { 641 var err error 642 643 cn := conn{} 644 func() { 645 defer cn.errRecover(&err) 646 panic(io.EOF) 647 }() 648 if err != driver.ErrBadConn { 649 t.Fatalf("expected driver.ErrBadConn, got: %#v", err) 650 } 651 if !cn.bad { 652 t.Fatalf("expected cn.bad") 653 } 654 655 cn = conn{} 656 func() { 657 defer cn.errRecover(&err) 658 e := &Error{Severity: Efatal} 659 panic(e) 660 }() 661 if err != driver.ErrBadConn { 662 t.Fatalf("expected driver.ErrBadConn, got: %#v", err) 663 } 664 if !cn.bad { 665 t.Fatalf("expected cn.bad") 666 } 667} 668 669// TestCloseBadConn tests that the underlying connection can be closed with 670// Close after an error. 671func TestCloseBadConn(t *testing.T) { 672 nc, err := net.Dial("tcp", "localhost:5432") 673 if err != nil { 674 t.Fatal(err) 675 } 676 cn := conn{c: nc} 677 func() { 678 defer cn.errRecover(&err) 679 panic(io.EOF) 680 }() 681 // Verify we can write before closing. 682 if _, err := nc.Write(nil); err != nil { 683 t.Fatal(err) 684 } 685 // First close should close the connection. 686 if err := cn.Close(); err != nil { 687 t.Fatal(err) 688 } 689 690 // During the Go 1.9 cycle, https://github.com/golang/go/commit/3792db5 691 // changed this error from 692 // 693 // net.errClosing = errors.New("use of closed network connection") 694 // 695 // to 696 // 697 // internal/poll.ErrClosing = errors.New("use of closed file or network connection") 698 const errClosing = "use of closed" 699 700 // Verify write after closing fails. 701 if _, err := nc.Write(nil); err == nil { 702 t.Fatal("expected error") 703 } else if !strings.Contains(err.Error(), errClosing) { 704 t.Fatalf("expected %s error, got %s", errClosing, err) 705 } 706 // Verify second close fails. 707 if err := cn.Close(); err == nil { 708 t.Fatal("expected error") 709 } else if !strings.Contains(err.Error(), errClosing) { 710 t.Fatalf("expected %s error, got %s", errClosing, err) 711 } 712} 713 714func TestErrorOnExec(t *testing.T) { 715 db := openTestConn(t) 716 defer db.Close() 717 718 txn, err := db.Begin() 719 if err != nil { 720 t.Fatal(err) 721 } 722 defer txn.Rollback() 723 724 _, err = txn.Exec("CREATE TEMPORARY TABLE foo(f1 int PRIMARY KEY)") 725 if err != nil { 726 t.Fatal(err) 727 } 728 729 _, err = txn.Exec("INSERT INTO foo VALUES (0), (0)") 730 if err == nil { 731 t.Fatal("Should have raised error") 732 } 733 734 e, ok := err.(*Error) 735 if !ok { 736 t.Fatalf("expected Error, got %#v", err) 737 } else if e.Code.Name() != "unique_violation" { 738 t.Fatalf("expected unique_violation, got %s (%+v)", e.Code.Name(), err) 739 } 740} 741 742func TestErrorOnQuery(t *testing.T) { 743 db := openTestConn(t) 744 defer db.Close() 745 746 txn, err := db.Begin() 747 if err != nil { 748 t.Fatal(err) 749 } 750 defer txn.Rollback() 751 752 _, err = txn.Exec("CREATE TEMPORARY TABLE foo(f1 int PRIMARY KEY)") 753 if err != nil { 754 t.Fatal(err) 755 } 756 757 _, err = txn.Query("INSERT INTO foo VALUES (0), (0)") 758 if err == nil { 759 t.Fatal("Should have raised error") 760 } 761 762 e, ok := err.(*Error) 763 if !ok { 764 t.Fatalf("expected Error, got %#v", err) 765 } else if e.Code.Name() != "unique_violation" { 766 t.Fatalf("expected unique_violation, got %s (%+v)", e.Code.Name(), err) 767 } 768} 769 770func TestErrorOnQueryRowSimpleQuery(t *testing.T) { 771 db := openTestConn(t) 772 defer db.Close() 773 774 txn, err := db.Begin() 775 if err != nil { 776 t.Fatal(err) 777 } 778 defer txn.Rollback() 779 780 _, err = txn.Exec("CREATE TEMPORARY TABLE foo(f1 int PRIMARY KEY)") 781 if err != nil { 782 t.Fatal(err) 783 } 784 785 var v int 786 err = txn.QueryRow("INSERT INTO foo VALUES (0), (0)").Scan(&v) 787 if err == nil { 788 t.Fatal("Should have raised error") 789 } 790 791 e, ok := err.(*Error) 792 if !ok { 793 t.Fatalf("expected Error, got %#v", err) 794 } else if e.Code.Name() != "unique_violation" { 795 t.Fatalf("expected unique_violation, got %s (%+v)", e.Code.Name(), err) 796 } 797} 798 799// Test the QueryRow bug workarounds in stmt.exec() and simpleQuery() 800func TestQueryRowBugWorkaround(t *testing.T) { 801 db := openTestConn(t) 802 defer db.Close() 803 804 // stmt.exec() 805 _, err := db.Exec("CREATE TEMP TABLE notnulltemp (a varchar(10) not null)") 806 if err != nil { 807 t.Fatal(err) 808 } 809 810 var a string 811 err = db.QueryRow("INSERT INTO notnulltemp(a) values($1) RETURNING a", nil).Scan(&a) 812 if err == sql.ErrNoRows { 813 t.Fatalf("expected constraint violation error; got: %v", err) 814 } 815 pge, ok := err.(*Error) 816 if !ok { 817 t.Fatalf("expected *Error; got: %#v", err) 818 } 819 if pge.Code.Name() != "not_null_violation" { 820 t.Fatalf("expected not_null_violation; got: %s (%+v)", pge.Code.Name(), err) 821 } 822 823 // Test workaround in simpleQuery() 824 tx, err := db.Begin() 825 if err != nil { 826 t.Fatalf("unexpected error %s in Begin", err) 827 } 828 defer tx.Rollback() 829 830 _, err = tx.Exec("SET LOCAL check_function_bodies TO FALSE") 831 if err != nil { 832 t.Fatalf("could not disable check_function_bodies: %s", err) 833 } 834 _, err = tx.Exec(` 835CREATE OR REPLACE FUNCTION bad_function() 836RETURNS integer 837-- hack to prevent the function from being inlined 838SET check_function_bodies TO TRUE 839AS $$ 840 SELECT text 'bad' 841$$ LANGUAGE sql`) 842 if err != nil { 843 t.Fatalf("could not create function: %s", err) 844 } 845 846 err = tx.QueryRow("SELECT * FROM bad_function()").Scan(&a) 847 if err == nil { 848 t.Fatalf("expected error") 849 } 850 pge, ok = err.(*Error) 851 if !ok { 852 t.Fatalf("expected *Error; got: %#v", err) 853 } 854 if pge.Code.Name() != "invalid_function_definition" { 855 t.Fatalf("expected invalid_function_definition; got: %s (%+v)", pge.Code.Name(), err) 856 } 857 858 err = tx.Rollback() 859 if err != nil { 860 t.Fatalf("unexpected error %s in Rollback", err) 861 } 862 863 // Also test that simpleQuery()'s workaround works when the query fails 864 // after a row has been received. 865 rows, err := db.Query(` 866select 867 (select generate_series(1, ss.i)) 868from (select gs.i 869 from generate_series(1, 2) gs(i) 870 order by gs.i limit 2) ss`) 871 if err != nil { 872 t.Fatalf("query failed: %s", err) 873 } 874 if !rows.Next() { 875 t.Fatalf("expected at least one result row; got %s", rows.Err()) 876 } 877 var i int 878 err = rows.Scan(&i) 879 if err != nil { 880 t.Fatalf("rows.Scan() failed: %s", err) 881 } 882 if i != 1 { 883 t.Fatalf("unexpected value for i: %d", i) 884 } 885 if rows.Next() { 886 t.Fatalf("unexpected row") 887 } 888 pge, ok = rows.Err().(*Error) 889 if !ok { 890 t.Fatalf("expected *Error; got: %#v", err) 891 } 892 if pge.Code.Name() != "cardinality_violation" { 893 t.Fatalf("expected cardinality_violation; got: %s (%+v)", pge.Code.Name(), rows.Err()) 894 } 895} 896 897func TestSimpleQuery(t *testing.T) { 898 db := openTestConn(t) 899 defer db.Close() 900 901 r, err := db.Query("select 1") 902 if err != nil { 903 t.Fatal(err) 904 } 905 defer r.Close() 906 907 if !r.Next() { 908 t.Fatal("expected row") 909 } 910} 911 912func TestBindError(t *testing.T) { 913 db := openTestConn(t) 914 defer db.Close() 915 916 _, err := db.Exec("create temp table test (i integer)") 917 if err != nil { 918 t.Fatal(err) 919 } 920 921 _, err = db.Query("select * from test where i=$1", "hhh") 922 if err == nil { 923 t.Fatal("expected an error") 924 } 925 926 // Should not get error here 927 r, err := db.Query("select * from test where i=$1", 1) 928 if err != nil { 929 t.Fatal(err) 930 } 931 defer r.Close() 932} 933 934func TestParseErrorInExtendedQuery(t *testing.T) { 935 db := openTestConn(t) 936 defer db.Close() 937 938 rows, err := db.Query("PARSE_ERROR $1", 1) 939 if err == nil { 940 t.Fatal("expected error") 941 } 942 943 rows, err = db.Query("SELECT 1") 944 if err != nil { 945 t.Fatal(err) 946 } 947 rows.Close() 948} 949 950// TestReturning tests that an INSERT query using the RETURNING clause returns a row. 951func TestReturning(t *testing.T) { 952 db := openTestConn(t) 953 defer db.Close() 954 955 _, err := db.Exec("CREATE TEMP TABLE distributors (did integer default 0, dname text)") 956 if err != nil { 957 t.Fatal(err) 958 } 959 960 rows, err := db.Query("INSERT INTO distributors (did, dname) VALUES (DEFAULT, 'XYZ Widgets') " + 961 "RETURNING did;") 962 if err != nil { 963 t.Fatal(err) 964 } 965 if !rows.Next() { 966 t.Fatal("no rows") 967 } 968 var did int 969 err = rows.Scan(&did) 970 if err != nil { 971 t.Fatal(err) 972 } 973 if did != 0 { 974 t.Fatalf("bad value for did: got %d, want %d", did, 0) 975 } 976 977 if rows.Next() { 978 t.Fatal("unexpected next row") 979 } 980 err = rows.Err() 981 if err != nil { 982 t.Fatal(err) 983 } 984} 985 986func TestIssue186(t *testing.T) { 987 db := openTestConn(t) 988 defer db.Close() 989 990 // Exec() a query which returns results 991 _, err := db.Exec("VALUES (1), (2), (3)") 992 if err != nil { 993 t.Fatal(err) 994 } 995 996 _, err = db.Exec("VALUES ($1), ($2), ($3)", 1, 2, 3) 997 if err != nil { 998 t.Fatal(err) 999 } 1000 1001 // Query() a query which doesn't return any results 1002 txn, err := db.Begin() 1003 if err != nil { 1004 t.Fatal(err) 1005 } 1006 defer txn.Rollback() 1007 1008 rows, err := txn.Query("CREATE TEMP TABLE foo(f1 int)") 1009 if err != nil { 1010 t.Fatal(err) 1011 } 1012 if err = rows.Close(); err != nil { 1013 t.Fatal(err) 1014 } 1015 1016 // small trick to get NoData from a parameterized query 1017 _, err = txn.Exec("CREATE RULE nodata AS ON INSERT TO foo DO INSTEAD NOTHING") 1018 if err != nil { 1019 t.Fatal(err) 1020 } 1021 rows, err = txn.Query("INSERT INTO foo VALUES ($1)", 1) 1022 if err != nil { 1023 t.Fatal(err) 1024 } 1025 if err = rows.Close(); err != nil { 1026 t.Fatal(err) 1027 } 1028} 1029 1030func TestIssue196(t *testing.T) { 1031 db := openTestConn(t) 1032 defer db.Close() 1033 1034 row := db.QueryRow("SELECT float4 '0.10000122' = $1, float8 '35.03554004971999' = $2", 1035 float32(0.10000122), float64(35.03554004971999)) 1036 1037 var float4match, float8match bool 1038 err := row.Scan(&float4match, &float8match) 1039 if err != nil { 1040 t.Fatal(err) 1041 } 1042 if !float4match { 1043 t.Errorf("Expected float4 fidelity to be maintained; got no match") 1044 } 1045 if !float8match { 1046 t.Errorf("Expected float8 fidelity to be maintained; got no match") 1047 } 1048} 1049 1050// Test that any CommandComplete messages sent before the query results are 1051// ignored. 1052func TestIssue282(t *testing.T) { 1053 db := openTestConn(t) 1054 defer db.Close() 1055 1056 var search_path string 1057 err := db.QueryRow(` 1058 SET LOCAL search_path TO pg_catalog; 1059 SET LOCAL search_path TO pg_catalog; 1060 SHOW search_path`).Scan(&search_path) 1061 if err != nil { 1062 t.Fatal(err) 1063 } 1064 if search_path != "pg_catalog" { 1065 t.Fatalf("unexpected search_path %s", search_path) 1066 } 1067} 1068 1069func TestReadFloatPrecision(t *testing.T) { 1070 db := openTestConn(t) 1071 defer db.Close() 1072 1073 row := db.QueryRow("SELECT float4 '0.10000122', float8 '35.03554004971999'") 1074 var float4val float32 1075 var float8val float64 1076 err := row.Scan(&float4val, &float8val) 1077 if err != nil { 1078 t.Fatal(err) 1079 } 1080 if float4val != float32(0.10000122) { 1081 t.Errorf("Expected float4 fidelity to be maintained; got no match") 1082 } 1083 if float8val != float64(35.03554004971999) { 1084 t.Errorf("Expected float8 fidelity to be maintained; got no match") 1085 } 1086} 1087 1088func TestXactMultiStmt(t *testing.T) { 1089 // minified test case based on bug reports from 1090 // pico303@gmail.com and rangelspam@gmail.com 1091 t.Skip("Skipping failing test") 1092 db := openTestConn(t) 1093 defer db.Close() 1094 1095 tx, err := db.Begin() 1096 if err != nil { 1097 t.Fatal(err) 1098 } 1099 defer tx.Commit() 1100 1101 rows, err := tx.Query("select 1") 1102 if err != nil { 1103 t.Fatal(err) 1104 } 1105 1106 if rows.Next() { 1107 var val int32 1108 if err = rows.Scan(&val); err != nil { 1109 t.Fatal(err) 1110 } 1111 } else { 1112 t.Fatal("Expected at least one row in first query in xact") 1113 } 1114 1115 rows2, err := tx.Query("select 2") 1116 if err != nil { 1117 t.Fatal(err) 1118 } 1119 1120 if rows2.Next() { 1121 var val2 int32 1122 if err := rows2.Scan(&val2); err != nil { 1123 t.Fatal(err) 1124 } 1125 } else { 1126 t.Fatal("Expected at least one row in second query in xact") 1127 } 1128 1129 if err = rows.Err(); err != nil { 1130 t.Fatal(err) 1131 } 1132 1133 if err = rows2.Err(); err != nil { 1134 t.Fatal(err) 1135 } 1136 1137 if err = tx.Commit(); err != nil { 1138 t.Fatal(err) 1139 } 1140} 1141 1142var envParseTests = []struct { 1143 Expected map[string]string 1144 Env []string 1145}{ 1146 { 1147 Env: []string{"PGDATABASE=hello", "PGUSER=goodbye"}, 1148 Expected: map[string]string{"dbname": "hello", "user": "goodbye"}, 1149 }, 1150 { 1151 Env: []string{"PGDATESTYLE=ISO, MDY"}, 1152 Expected: map[string]string{"datestyle": "ISO, MDY"}, 1153 }, 1154 { 1155 Env: []string{"PGCONNECT_TIMEOUT=30"}, 1156 Expected: map[string]string{"connect_timeout": "30"}, 1157 }, 1158} 1159 1160func TestParseEnviron(t *testing.T) { 1161 for i, tt := range envParseTests { 1162 results := parseEnviron(tt.Env) 1163 if !reflect.DeepEqual(tt.Expected, results) { 1164 t.Errorf("%d: Expected: %#v Got: %#v", i, tt.Expected, results) 1165 } 1166 } 1167} 1168 1169func TestParseComplete(t *testing.T) { 1170 tpc := func(commandTag string, command string, affectedRows int64, shouldFail bool) { 1171 defer func() { 1172 if p := recover(); p != nil { 1173 if !shouldFail { 1174 t.Error(p) 1175 } 1176 } 1177 }() 1178 cn := &conn{} 1179 res, c := cn.parseComplete(commandTag) 1180 if c != command { 1181 t.Errorf("Expected %v, got %v", command, c) 1182 } 1183 n, err := res.RowsAffected() 1184 if err != nil { 1185 t.Fatal(err) 1186 } 1187 if n != affectedRows { 1188 t.Errorf("Expected %d, got %d", affectedRows, n) 1189 } 1190 } 1191 1192 tpc("ALTER TABLE", "ALTER TABLE", 0, false) 1193 tpc("INSERT 0 1", "INSERT", 1, false) 1194 tpc("UPDATE 100", "UPDATE", 100, false) 1195 tpc("SELECT 100", "SELECT", 100, false) 1196 tpc("FETCH 100", "FETCH", 100, false) 1197 // allow COPY (and others) without row count 1198 tpc("COPY", "COPY", 0, false) 1199 // don't fail on command tags we don't recognize 1200 tpc("UNKNOWNCOMMANDTAG", "UNKNOWNCOMMANDTAG", 0, false) 1201 1202 // failure cases 1203 tpc("INSERT 1", "", 0, true) // missing oid 1204 tpc("UPDATE 0 1", "", 0, true) // too many numbers 1205 tpc("SELECT foo", "", 0, true) // invalid row count 1206} 1207 1208func TestExecerInterface(t *testing.T) { 1209 // Gin up a straw man private struct just for the type check 1210 cn := &conn{c: nil} 1211 var cni interface{} = cn 1212 1213 _, ok := cni.(driver.Execer) 1214 if !ok { 1215 t.Fatal("Driver doesn't implement Execer") 1216 } 1217} 1218 1219func TestNullAfterNonNull(t *testing.T) { 1220 db := openTestConn(t) 1221 defer db.Close() 1222 1223 r, err := db.Query("SELECT 9::integer UNION SELECT NULL::integer") 1224 if err != nil { 1225 t.Fatal(err) 1226 } 1227 1228 var n sql.NullInt64 1229 1230 if !r.Next() { 1231 if r.Err() != nil { 1232 t.Fatal(err) 1233 } 1234 t.Fatal("expected row") 1235 } 1236 1237 if err := r.Scan(&n); err != nil { 1238 t.Fatal(err) 1239 } 1240 1241 if n.Int64 != 9 { 1242 t.Fatalf("expected 2, not %d", n.Int64) 1243 } 1244 1245 if !r.Next() { 1246 if r.Err() != nil { 1247 t.Fatal(err) 1248 } 1249 t.Fatal("expected row") 1250 } 1251 1252 if err := r.Scan(&n); err != nil { 1253 t.Fatal(err) 1254 } 1255 1256 if n.Valid { 1257 t.Fatal("expected n to be invalid") 1258 } 1259 1260 if n.Int64 != 0 { 1261 t.Fatalf("expected n to 2, not %d", n.Int64) 1262 } 1263} 1264 1265func Test64BitErrorChecking(t *testing.T) { 1266 defer func() { 1267 if err := recover(); err != nil { 1268 t.Fatal("panic due to 0xFFFFFFFF != -1 " + 1269 "when int is 64 bits") 1270 } 1271 }() 1272 1273 db := openTestConn(t) 1274 defer db.Close() 1275 1276 r, err := db.Query(`SELECT * 1277FROM (VALUES (0::integer, NULL::text), (1, 'test string')) AS t;`) 1278 1279 if err != nil { 1280 t.Fatal(err) 1281 } 1282 1283 defer r.Close() 1284 1285 for r.Next() { 1286 } 1287} 1288 1289func TestCommit(t *testing.T) { 1290 db := openTestConn(t) 1291 defer db.Close() 1292 1293 _, err := db.Exec("CREATE TEMP TABLE temp (a int)") 1294 if err != nil { 1295 t.Fatal(err) 1296 } 1297 sqlInsert := "INSERT INTO temp VALUES (1)" 1298 sqlSelect := "SELECT * FROM temp" 1299 tx, err := db.Begin() 1300 if err != nil { 1301 t.Fatal(err) 1302 } 1303 _, err = tx.Exec(sqlInsert) 1304 if err != nil { 1305 t.Fatal(err) 1306 } 1307 err = tx.Commit() 1308 if err != nil { 1309 t.Fatal(err) 1310 } 1311 var i int 1312 err = db.QueryRow(sqlSelect).Scan(&i) 1313 if err != nil { 1314 t.Fatal(err) 1315 } 1316 if i != 1 { 1317 t.Fatalf("expected 1, got %d", i) 1318 } 1319} 1320 1321func TestErrorClass(t *testing.T) { 1322 db := openTestConn(t) 1323 defer db.Close() 1324 1325 _, err := db.Query("SELECT int 'notint'") 1326 if err == nil { 1327 t.Fatal("expected error") 1328 } 1329 pge, ok := err.(*Error) 1330 if !ok { 1331 t.Fatalf("expected *pq.Error, got %#+v", err) 1332 } 1333 if pge.Code.Class() != "22" { 1334 t.Fatalf("expected class 28, got %v", pge.Code.Class()) 1335 } 1336 if pge.Code.Class().Name() != "data_exception" { 1337 t.Fatalf("expected data_exception, got %v", pge.Code.Class().Name()) 1338 } 1339} 1340 1341func TestParseOpts(t *testing.T) { 1342 tests := []struct { 1343 in string 1344 expected values 1345 valid bool 1346 }{ 1347 {"dbname=hello user=goodbye", values{"dbname": "hello", "user": "goodbye"}, true}, 1348 {"dbname=hello user=goodbye ", values{"dbname": "hello", "user": "goodbye"}, true}, 1349 {"dbname = hello user=goodbye", values{"dbname": "hello", "user": "goodbye"}, true}, 1350 {"dbname=hello user =goodbye", values{"dbname": "hello", "user": "goodbye"}, true}, 1351 {"dbname=hello user= goodbye", values{"dbname": "hello", "user": "goodbye"}, true}, 1352 {"host=localhost password='correct horse battery staple'", values{"host": "localhost", "password": "correct horse battery staple"}, true}, 1353 {"dbname=データベース password=パスワード", values{"dbname": "データベース", "password": "パスワード"}, true}, 1354 {"dbname=hello user=''", values{"dbname": "hello", "user": ""}, true}, 1355 {"user='' dbname=hello", values{"dbname": "hello", "user": ""}, true}, 1356 // The last option value is an empty string if there's no non-whitespace after its = 1357 {"dbname=hello user= ", values{"dbname": "hello", "user": ""}, true}, 1358 1359 // The parser ignores spaces after = and interprets the next set of non-whitespace characters as the value. 1360 {"user= password=foo", values{"user": "password=foo"}, true}, 1361 1362 // Backslash escapes next char 1363 {`user=a\ \'\\b`, values{"user": `a '\b`}, true}, 1364 {`user='a \'b'`, values{"user": `a 'b`}, true}, 1365 1366 // Incomplete escape 1367 {`user=x\`, values{}, false}, 1368 1369 // No '=' after the key 1370 {"postgre://marko@internet", values{}, false}, 1371 {"dbname user=goodbye", values{}, false}, 1372 {"user=foo blah", values{}, false}, 1373 {"user=foo blah ", values{}, false}, 1374 1375 // Unterminated quoted value 1376 {"dbname=hello user='unterminated", values{}, false}, 1377 } 1378 1379 for _, test := range tests { 1380 o := make(values) 1381 err := parseOpts(test.in, o) 1382 1383 switch { 1384 case err != nil && test.valid: 1385 t.Errorf("%q got unexpected error: %s", test.in, err) 1386 case err == nil && test.valid && !reflect.DeepEqual(test.expected, o): 1387 t.Errorf("%q got: %#v want: %#v", test.in, o, test.expected) 1388 case err == nil && !test.valid: 1389 t.Errorf("%q expected an error", test.in) 1390 } 1391 } 1392} 1393 1394func TestRuntimeParameters(t *testing.T) { 1395 type RuntimeTestResult int 1396 const ( 1397 ResultUnknown RuntimeTestResult = iota 1398 ResultSuccess 1399 ResultError // other error 1400 ) 1401 1402 tests := []struct { 1403 conninfo string 1404 param string 1405 expected string 1406 expectedOutcome RuntimeTestResult 1407 }{ 1408 // invalid parameter 1409 {"DOESNOTEXIST=foo", "", "", ResultError}, 1410 // we can only work with a specific value for these two 1411 {"client_encoding=SQL_ASCII", "", "", ResultError}, 1412 {"datestyle='ISO, YDM'", "", "", ResultError}, 1413 // "options" should work exactly as it does in libpq 1414 {"options='-c search_path=pqgotest'", "search_path", "pqgotest", ResultSuccess}, 1415 // pq should override client_encoding in this case 1416 {"options='-c client_encoding=SQL_ASCII'", "client_encoding", "UTF8", ResultSuccess}, 1417 // allow client_encoding to be set explicitly 1418 {"client_encoding=UTF8", "client_encoding", "UTF8", ResultSuccess}, 1419 // test a runtime parameter not supported by libpq 1420 {"work_mem='139kB'", "work_mem", "139kB", ResultSuccess}, 1421 // test fallback_application_name 1422 {"application_name=foo fallback_application_name=bar", "application_name", "foo", ResultSuccess}, 1423 {"application_name='' fallback_application_name=bar", "application_name", "", ResultSuccess}, 1424 {"fallback_application_name=bar", "application_name", "bar", ResultSuccess}, 1425 } 1426 1427 for _, test := range tests { 1428 db, err := openTestConnConninfo(test.conninfo) 1429 if err != nil { 1430 t.Fatal(err) 1431 } 1432 1433 // application_name didn't exist before 9.0 1434 if test.param == "application_name" && getServerVersion(t, db) < 90000 { 1435 db.Close() 1436 continue 1437 } 1438 1439 tryGetParameterValue := func() (value string, outcome RuntimeTestResult) { 1440 defer db.Close() 1441 row := db.QueryRow("SELECT current_setting($1)", test.param) 1442 err = row.Scan(&value) 1443 if err != nil { 1444 return "", ResultError 1445 } 1446 return value, ResultSuccess 1447 } 1448 1449 value, outcome := tryGetParameterValue() 1450 if outcome != test.expectedOutcome && outcome == ResultError { 1451 t.Fatalf("%v: unexpected error: %v", test.conninfo, err) 1452 } 1453 if outcome != test.expectedOutcome { 1454 t.Fatalf("unexpected outcome %v (was expecting %v) for conninfo \"%s\"", 1455 outcome, test.expectedOutcome, test.conninfo) 1456 } 1457 if value != test.expected { 1458 t.Fatalf("bad value for %s: got %s, want %s with conninfo \"%s\"", 1459 test.param, value, test.expected, test.conninfo) 1460 } 1461 } 1462} 1463 1464func TestIsUTF8(t *testing.T) { 1465 var cases = []struct { 1466 name string 1467 want bool 1468 }{ 1469 {"unicode", true}, 1470 {"utf-8", true}, 1471 {"utf_8", true}, 1472 {"UTF-8", true}, 1473 {"UTF8", true}, 1474 {"utf8", true}, 1475 {"u n ic_ode", true}, 1476 {"ut_f%8", true}, 1477 {"ubf8", false}, 1478 {"punycode", false}, 1479 } 1480 1481 for _, test := range cases { 1482 if g := isUTF8(test.name); g != test.want { 1483 t.Errorf("isUTF8(%q) = %v want %v", test.name, g, test.want) 1484 } 1485 } 1486} 1487 1488func TestQuoteIdentifier(t *testing.T) { 1489 var cases = []struct { 1490 input string 1491 want string 1492 }{ 1493 {`foo`, `"foo"`}, 1494 {`foo bar baz`, `"foo bar baz"`}, 1495 {`foo"bar`, `"foo""bar"`}, 1496 {"foo\x00bar", `"foo"`}, 1497 {"\x00foo", `""`}, 1498 } 1499 1500 for _, test := range cases { 1501 got := QuoteIdentifier(test.input) 1502 if got != test.want { 1503 t.Errorf("QuoteIdentifier(%q) = %v want %v", test.input, got, test.want) 1504 } 1505 } 1506} 1507 1508func TestRowsResultTag(t *testing.T) { 1509 type ResultTag interface { 1510 Result() driver.Result 1511 Tag() string 1512 } 1513 1514 tests := []struct { 1515 query string 1516 tag string 1517 ra int64 1518 }{ 1519 { 1520 query: "CREATE TEMP TABLE temp (a int)", 1521 tag: "CREATE TABLE", 1522 }, 1523 { 1524 query: "INSERT INTO temp VALUES (1), (2)", 1525 tag: "INSERT", 1526 ra: 2, 1527 }, 1528 { 1529 query: "SELECT 1", 1530 }, 1531 // A SELECT anywhere should take precedent. 1532 { 1533 query: "SELECT 1; INSERT INTO temp VALUES (1), (2)", 1534 }, 1535 { 1536 query: "INSERT INTO temp VALUES (1), (2); SELECT 1", 1537 }, 1538 // Multiple statements that don't return rows should return the last tag. 1539 { 1540 query: "CREATE TEMP TABLE t (a int); DROP TABLE t", 1541 tag: "DROP TABLE", 1542 }, 1543 // Ensure a rows-returning query in any position among various tags-returing 1544 // statements will prefer the rows. 1545 { 1546 query: "SELECT 1; CREATE TEMP TABLE t (a int); DROP TABLE t", 1547 }, 1548 { 1549 query: "CREATE TEMP TABLE t (a int); SELECT 1; DROP TABLE t", 1550 }, 1551 { 1552 query: "CREATE TEMP TABLE t (a int); DROP TABLE t; SELECT 1", 1553 }, 1554 // Verify that an no-results query doesn't set the tag. 1555 { 1556 query: "CREATE TEMP TABLE t (a int); SELECT 1 WHERE FALSE; DROP TABLE t;", 1557 }, 1558 } 1559 1560 // If this is the only test run, this will correct the connection string. 1561 openTestConn(t).Close() 1562 1563 conn, err := Open("") 1564 if err != nil { 1565 t.Fatal(err) 1566 } 1567 defer conn.Close() 1568 q := conn.(driver.Queryer) 1569 1570 for _, test := range tests { 1571 if rows, err := q.Query(test.query, nil); err != nil { 1572 t.Fatalf("%s: %s", test.query, err) 1573 } else { 1574 r := rows.(ResultTag) 1575 if tag := r.Tag(); tag != test.tag { 1576 t.Fatalf("%s: unexpected tag %q", test.query, tag) 1577 } 1578 res := r.Result() 1579 if ra, _ := res.RowsAffected(); ra != test.ra { 1580 t.Fatalf("%s: unexpected rows affected: %d", test.query, ra) 1581 } 1582 rows.Close() 1583 } 1584 } 1585} 1586