| // +build go1.8 |
| |
| package pq |
| |
| import ( |
| "context" |
| "database/sql" |
| "runtime" |
| "strings" |
| "testing" |
| "time" |
| ) |
| |
| func TestMultipleSimpleQuery(t *testing.T) { |
| db := openTestConn(t) |
| defer db.Close() |
| |
| rows, err := db.Query("select 1; set time zone default; select 2; select 3") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer rows.Close() |
| |
| var i int |
| for rows.Next() { |
| if err := rows.Scan(&i); err != nil { |
| t.Fatal(err) |
| } |
| if i != 1 { |
| t.Fatalf("expected 1, got %d", i) |
| } |
| } |
| if !rows.NextResultSet() { |
| t.Fatal("expected more result sets", rows.Err()) |
| } |
| for rows.Next() { |
| if err := rows.Scan(&i); err != nil { |
| t.Fatal(err) |
| } |
| if i != 2 { |
| t.Fatalf("expected 2, got %d", i) |
| } |
| } |
| |
| // Make sure that if we ignore a result we can still query. |
| |
| rows, err = db.Query("select 4; select 5") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer rows.Close() |
| |
| for rows.Next() { |
| if err := rows.Scan(&i); err != nil { |
| t.Fatal(err) |
| } |
| if i != 4 { |
| t.Fatalf("expected 4, got %d", i) |
| } |
| } |
| if !rows.NextResultSet() { |
| t.Fatal("expected more result sets", rows.Err()) |
| } |
| for rows.Next() { |
| if err := rows.Scan(&i); err != nil { |
| t.Fatal(err) |
| } |
| if i != 5 { |
| t.Fatalf("expected 5, got %d", i) |
| } |
| } |
| if rows.NextResultSet() { |
| t.Fatal("unexpected result set") |
| } |
| } |
| |
| const contextRaceIterations = 100 |
| |
| func TestContextCancelExec(t *testing.T) { |
| db := openTestConn(t) |
| defer db.Close() |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| |
| // Delay execution for just a bit until db.ExecContext has begun. |
| defer time.AfterFunc(time.Millisecond*10, cancel).Stop() |
| |
| // Not canceled until after the exec has started. |
| if _, err := db.ExecContext(ctx, "select pg_sleep(1)"); err == nil { |
| t.Fatal("expected error") |
| } else if err.Error() != "pq: canceling statement due to user request" { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| // Context is already canceled, so error should come before execution. |
| if _, err := db.ExecContext(ctx, "select pg_sleep(1)"); err == nil { |
| t.Fatal("expected error") |
| } else if err.Error() != "context canceled" { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| for i := 0; i < contextRaceIterations; i++ { |
| func() { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| if _, err := db.ExecContext(ctx, "select 1"); err != nil { |
| t.Fatal(err) |
| } |
| }() |
| |
| if _, err := db.Exec("select 1"); err != nil { |
| t.Fatal(err) |
| } |
| } |
| } |
| |
| func TestContextCancelQuery(t *testing.T) { |
| db := openTestConn(t) |
| defer db.Close() |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| |
| // Delay execution for just a bit until db.QueryContext has begun. |
| defer time.AfterFunc(time.Millisecond*10, cancel).Stop() |
| |
| // Not canceled until after the exec has started. |
| if _, err := db.QueryContext(ctx, "select pg_sleep(1)"); err == nil { |
| t.Fatal("expected error") |
| } else if err.Error() != "pq: canceling statement due to user request" { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| // Context is already canceled, so error should come before execution. |
| if _, err := db.QueryContext(ctx, "select pg_sleep(1)"); err == nil { |
| t.Fatal("expected error") |
| } else if err.Error() != "context canceled" { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| for i := 0; i < contextRaceIterations; i++ { |
| func() { |
| ctx, cancel := context.WithCancel(context.Background()) |
| rows, err := db.QueryContext(ctx, "select 1") |
| cancel() |
| if err != nil { |
| t.Fatal(err) |
| } else if err := rows.Close(); err != nil { |
| t.Fatal(err) |
| } |
| }() |
| |
| if rows, err := db.Query("select 1"); err != nil { |
| t.Fatal(err) |
| } else if err := rows.Close(); err != nil { |
| t.Fatal(err) |
| } |
| } |
| } |
| |
| // TestIssue617 tests that a failed query in QueryContext doesn't lead to a |
| // goroutine leak. |
| func TestIssue617(t *testing.T) { |
| db := openTestConn(t) |
| defer db.Close() |
| |
| const N = 10 |
| |
| numGoroutineStart := runtime.NumGoroutine() |
| for i := 0; i < N; i++ { |
| func() { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| _, err := db.QueryContext(ctx, `SELECT * FROM DOESNOTEXIST`) |
| pqErr, _ := err.(*Error) |
| // Expecting "pq: relation \"doesnotexist\" does not exist" error. |
| if err == nil || pqErr == nil || pqErr.Code != "42P01" { |
| t.Fatalf("expected undefined table error, got %v", err) |
| } |
| }() |
| } |
| numGoroutineFinish := runtime.NumGoroutine() |
| |
| // We use N/2 and not N because the GC and other actors may increase or |
| // decrease the number of goroutines. |
| if numGoroutineFinish-numGoroutineStart >= N/2 { |
| t.Errorf("goroutine leak detected, was %d, now %d", numGoroutineStart, numGoroutineFinish) |
| } |
| } |
| |
| func TestContextCancelBegin(t *testing.T) { |
| db := openTestConn(t) |
| defer db.Close() |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| tx, err := db.BeginTx(ctx, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Delay execution for just a bit until tx.Exec has begun. |
| defer time.AfterFunc(time.Millisecond*10, cancel).Stop() |
| |
| // Not canceled until after the exec has started. |
| if _, err := tx.Exec("select pg_sleep(1)"); err == nil { |
| t.Fatal("expected error") |
| } else if err.Error() != "pq: canceling statement due to user request" { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| // Transaction is canceled, so expect an error. |
| if _, err := tx.Query("select pg_sleep(1)"); err == nil { |
| t.Fatal("expected error") |
| } else if err != sql.ErrTxDone { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| // Context is canceled, so cannot begin a transaction. |
| if _, err := db.BeginTx(ctx, nil); err == nil { |
| t.Fatal("expected error") |
| } else if err.Error() != "context canceled" { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| for i := 0; i < contextRaceIterations; i++ { |
| func() { |
| ctx, cancel := context.WithCancel(context.Background()) |
| tx, err := db.BeginTx(ctx, nil) |
| cancel() |
| if err != nil { |
| t.Fatal(err) |
| } else if err := tx.Rollback(); err != nil && err != sql.ErrTxDone { |
| t.Fatal(err) |
| } |
| }() |
| |
| if tx, err := db.Begin(); err != nil { |
| t.Fatal(err) |
| } else if err := tx.Rollback(); err != nil { |
| t.Fatal(err) |
| } |
| } |
| } |
| |
| func TestTxOptions(t *testing.T) { |
| db := openTestConn(t) |
| defer db.Close() |
| ctx := context.Background() |
| |
| tests := []struct { |
| level sql.IsolationLevel |
| isolation string |
| }{ |
| { |
| level: sql.LevelDefault, |
| isolation: "", |
| }, |
| { |
| level: sql.LevelReadUncommitted, |
| isolation: "read uncommitted", |
| }, |
| { |
| level: sql.LevelReadCommitted, |
| isolation: "read committed", |
| }, |
| { |
| level: sql.LevelRepeatableRead, |
| isolation: "repeatable read", |
| }, |
| { |
| level: sql.LevelSerializable, |
| isolation: "serializable", |
| }, |
| } |
| |
| for _, test := range tests { |
| for _, ro := range []bool{true, false} { |
| tx, err := db.BeginTx(ctx, &sql.TxOptions{ |
| Isolation: test.level, |
| ReadOnly: ro, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var isolation string |
| err = tx.QueryRow("select current_setting('transaction_isolation')").Scan(&isolation) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if test.isolation != "" && isolation != test.isolation { |
| t.Errorf("wrong isolation level: %s != %s", isolation, test.isolation) |
| } |
| |
| var isRO string |
| err = tx.QueryRow("select current_setting('transaction_read_only')").Scan(&isRO) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if ro != (isRO == "on") { |
| t.Errorf("read/[write,only] not set: %t != %s for level %s", |
| ro, isRO, test.isolation) |
| } |
| |
| tx.Rollback() |
| } |
| } |
| |
| _, err := db.BeginTx(ctx, &sql.TxOptions{ |
| Isolation: sql.LevelLinearizable, |
| }) |
| if err == nil { |
| t.Fatal("expected LevelLinearizable to fail") |
| } |
| if !strings.Contains(err.Error(), "isolation level not supported") { |
| t.Errorf("Expected error to mention isolation level, got %q", err) |
| } |
| } |