66625e6489
Also remove PrepareEx. It's primary usage was for context. Supplying parameter OIDs is unnecessary when you can type cast in the query SQL. If it does become necessary or desirable to add options back it can be added in a backwards compatible way by adding a varargs as last argument.
657 lines
14 KiB
Go
657 lines
14 KiB
Go
package pgx_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx"
|
|
"github.com/jackc/pgx/pgtype"
|
|
)
|
|
|
|
func BenchmarkPointerPointerWithNullValues(b *testing.B) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
_, err := conn.Prepare(context.Background(), "selectNulls", "select 1::int4, 'johnsmith', null::text, null::text, null::text, null::date, null::timestamptz")
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
var record struct {
|
|
id int32
|
|
userName string
|
|
email *string
|
|
name *string
|
|
sex *string
|
|
birthDate *time.Time
|
|
lastLoginTime *time.Time
|
|
}
|
|
|
|
err = conn.QueryRow(context.Background(), "selectNulls").Scan(
|
|
&record.id,
|
|
&record.userName,
|
|
&record.email,
|
|
&record.name,
|
|
&record.sex,
|
|
&record.birthDate,
|
|
&record.lastLoginTime,
|
|
)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
// These checks both ensure that the correct data was returned
|
|
// and provide a benchmark of accessing the returned values.
|
|
if record.id != 1 {
|
|
b.Fatalf("bad value for id: %v", record.id)
|
|
}
|
|
if record.userName != "johnsmith" {
|
|
b.Fatalf("bad value for userName: %v", record.userName)
|
|
}
|
|
if record.email != nil {
|
|
b.Fatalf("bad value for email: %v", record.email)
|
|
}
|
|
if record.name != nil {
|
|
b.Fatalf("bad value for name: %v", record.name)
|
|
}
|
|
if record.sex != nil {
|
|
b.Fatalf("bad value for sex: %v", record.sex)
|
|
}
|
|
if record.birthDate != nil {
|
|
b.Fatalf("bad value for birthDate: %v", record.birthDate)
|
|
}
|
|
if record.lastLoginTime != nil {
|
|
b.Fatalf("bad value for lastLoginTime: %v", record.lastLoginTime)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkPointerPointerWithPresentValues(b *testing.B) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
_, err := conn.Prepare(context.Background(), "selectNulls", "select 1::int4, 'johnsmith', 'johnsmith@example.com', 'John Smith', 'male', '1970-01-01'::date, '2015-01-01 00:00:00'::timestamptz")
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
var record struct {
|
|
id int32
|
|
userName string
|
|
email *string
|
|
name *string
|
|
sex *string
|
|
birthDate *time.Time
|
|
lastLoginTime *time.Time
|
|
}
|
|
|
|
err = conn.QueryRow(context.Background(), "selectNulls").Scan(
|
|
&record.id,
|
|
&record.userName,
|
|
&record.email,
|
|
&record.name,
|
|
&record.sex,
|
|
&record.birthDate,
|
|
&record.lastLoginTime,
|
|
)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
// These checks both ensure that the correct data was returned
|
|
// and provide a benchmark of accessing the returned values.
|
|
if record.id != 1 {
|
|
b.Fatalf("bad value for id: %v", record.id)
|
|
}
|
|
if record.userName != "johnsmith" {
|
|
b.Fatalf("bad value for userName: %v", record.userName)
|
|
}
|
|
if record.email == nil || *record.email != "johnsmith@example.com" {
|
|
b.Fatalf("bad value for email: %v", record.email)
|
|
}
|
|
if record.name == nil || *record.name != "John Smith" {
|
|
b.Fatalf("bad value for name: %v", record.name)
|
|
}
|
|
if record.sex == nil || *record.sex != "male" {
|
|
b.Fatalf("bad value for sex: %v", record.sex)
|
|
}
|
|
if record.birthDate == nil || *record.birthDate != time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local) {
|
|
b.Fatalf("bad value for birthDate: %v", record.birthDate)
|
|
}
|
|
if record.lastLoginTime == nil || *record.lastLoginTime != time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local) {
|
|
b.Fatalf("bad value for lastLoginTime: %v", record.lastLoginTime)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkSelectWithoutLogging(b *testing.B) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
benchmarkSelectWithLog(b, conn)
|
|
}
|
|
|
|
type discardLogger struct{}
|
|
|
|
func (dl discardLogger) Log(level pgx.LogLevel, msg string, data map[string]interface{}) {}
|
|
|
|
func BenchmarkSelectWithLoggingTraceDiscard(b *testing.B) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
var logger discardLogger
|
|
conn.SetLogger(logger)
|
|
conn.SetLogLevel(pgx.LogLevelTrace)
|
|
|
|
benchmarkSelectWithLog(b, conn)
|
|
}
|
|
|
|
func BenchmarkSelectWithLoggingDebugWithDiscard(b *testing.B) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
var logger discardLogger
|
|
conn.SetLogger(logger)
|
|
conn.SetLogLevel(pgx.LogLevelDebug)
|
|
|
|
benchmarkSelectWithLog(b, conn)
|
|
}
|
|
|
|
func BenchmarkSelectWithLoggingInfoWithDiscard(b *testing.B) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
var logger discardLogger
|
|
conn.SetLogger(logger)
|
|
conn.SetLogLevel(pgx.LogLevelInfo)
|
|
|
|
benchmarkSelectWithLog(b, conn)
|
|
}
|
|
|
|
func BenchmarkSelectWithLoggingErrorWithDiscard(b *testing.B) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
var logger discardLogger
|
|
conn.SetLogger(logger)
|
|
conn.SetLogLevel(pgx.LogLevelError)
|
|
|
|
benchmarkSelectWithLog(b, conn)
|
|
}
|
|
|
|
func benchmarkSelectWithLog(b *testing.B, conn *pgx.Conn) {
|
|
_, err := conn.Prepare(context.Background(), "test", "select 1::int4, 'johnsmith', 'johnsmith@example.com', 'John Smith', 'male', '1970-01-01'::date, '2015-01-01 00:00:00'::timestamptz")
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
var record struct {
|
|
id int32
|
|
userName string
|
|
email string
|
|
name string
|
|
sex string
|
|
birthDate time.Time
|
|
lastLoginTime time.Time
|
|
}
|
|
|
|
err = conn.QueryRow(context.Background(), "test").Scan(
|
|
&record.id,
|
|
&record.userName,
|
|
&record.email,
|
|
&record.name,
|
|
&record.sex,
|
|
&record.birthDate,
|
|
&record.lastLoginTime,
|
|
)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
// These checks both ensure that the correct data was returned
|
|
// and provide a benchmark of accessing the returned values.
|
|
if record.id != 1 {
|
|
b.Fatalf("bad value for id: %v", record.id)
|
|
}
|
|
if record.userName != "johnsmith" {
|
|
b.Fatalf("bad value for userName: %v", record.userName)
|
|
}
|
|
if record.email != "johnsmith@example.com" {
|
|
b.Fatalf("bad value for email: %v", record.email)
|
|
}
|
|
if record.name != "John Smith" {
|
|
b.Fatalf("bad value for name: %v", record.name)
|
|
}
|
|
if record.sex != "male" {
|
|
b.Fatalf("bad value for sex: %v", record.sex)
|
|
}
|
|
if record.birthDate != time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local) {
|
|
b.Fatalf("bad value for birthDate: %v", record.birthDate)
|
|
}
|
|
if record.lastLoginTime != time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local) {
|
|
b.Fatalf("bad value for lastLoginTime: %v", record.lastLoginTime)
|
|
}
|
|
}
|
|
}
|
|
|
|
const benchmarkWriteTableCreateSQL = `drop table if exists t;
|
|
|
|
create table t(
|
|
varchar_1 varchar not null,
|
|
varchar_2 varchar not null,
|
|
varchar_null_1 varchar,
|
|
date_1 date not null,
|
|
date_null_1 date,
|
|
int4_1 int4 not null,
|
|
int4_2 int4 not null,
|
|
int4_null_1 int4,
|
|
tstz_1 timestamptz not null,
|
|
tstz_2 timestamptz,
|
|
bool_1 bool not null,
|
|
bool_2 bool not null,
|
|
bool_3 bool not null
|
|
);
|
|
`
|
|
|
|
const benchmarkWriteTableInsertSQL = `insert into t(
|
|
varchar_1,
|
|
varchar_2,
|
|
varchar_null_1,
|
|
date_1,
|
|
date_null_1,
|
|
int4_1,
|
|
int4_2,
|
|
int4_null_1,
|
|
tstz_1,
|
|
tstz_2,
|
|
bool_1,
|
|
bool_2,
|
|
bool_3
|
|
) values (
|
|
$1::varchar,
|
|
$2::varchar,
|
|
$3::varchar,
|
|
$4::date,
|
|
$5::date,
|
|
$6::int4,
|
|
$7::int4,
|
|
$8::int4,
|
|
$9::timestamptz,
|
|
$10::timestamptz,
|
|
$11::bool,
|
|
$12::bool,
|
|
$13::bool
|
|
)`
|
|
|
|
type benchmarkWriteTableCopyFromSrc struct {
|
|
count int
|
|
idx int
|
|
row []interface{}
|
|
}
|
|
|
|
func (s *benchmarkWriteTableCopyFromSrc) Next() bool {
|
|
s.idx++
|
|
return s.idx < s.count
|
|
}
|
|
|
|
func (s *benchmarkWriteTableCopyFromSrc) Values() ([]interface{}, error) {
|
|
return s.row, nil
|
|
}
|
|
|
|
func (s *benchmarkWriteTableCopyFromSrc) Err() error {
|
|
return nil
|
|
}
|
|
|
|
func newBenchmarkWriteTableCopyFromSrc(count int) pgx.CopyFromSource {
|
|
return &benchmarkWriteTableCopyFromSrc{
|
|
count: count,
|
|
row: []interface{}{
|
|
"varchar_1",
|
|
"varchar_2",
|
|
pgtype.Text{},
|
|
time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local),
|
|
pgtype.Date{},
|
|
1,
|
|
2,
|
|
pgtype.Int4{},
|
|
time.Date(2001, 1, 1, 0, 0, 0, 0, time.Local),
|
|
time.Date(2002, 1, 1, 0, 0, 0, 0, time.Local),
|
|
true,
|
|
false,
|
|
true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func benchmarkWriteNRowsViaInsert(b *testing.B, n int) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
mustExec(b, conn, benchmarkWriteTableCreateSQL)
|
|
_, err := conn.Prepare(context.Background(), "insert_t", benchmarkWriteTableInsertSQL)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
src := newBenchmarkWriteTableCopyFromSrc(n)
|
|
|
|
tx, err := conn.Begin()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
for src.Next() {
|
|
values, _ := src.Values()
|
|
if _, err = tx.Exec(context.Background(), "insert_t", values...); err != nil {
|
|
b.Fatalf("Exec unexpectedly failed with: %v", err)
|
|
}
|
|
}
|
|
|
|
err = tx.Commit(context.Background())
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// note this function is only used for benchmarks -- it doesn't escape tableName
|
|
// or columnNames
|
|
func multiInsert(conn *pgx.Conn, tableName string, columnNames []string, rowSrc pgx.CopyFromSource) (int, error) {
|
|
maxRowsPerInsert := 65535 / len(columnNames)
|
|
rowsThisInsert := 0
|
|
rowCount := 0
|
|
|
|
sqlBuf := &bytes.Buffer{}
|
|
args := make(pgx.QueryArgs, 0)
|
|
|
|
resetQuery := func() {
|
|
sqlBuf.Reset()
|
|
fmt.Fprintf(sqlBuf, "insert into %s(%s) values", tableName, strings.Join(columnNames, ", "))
|
|
|
|
args = args[0:0]
|
|
|
|
rowsThisInsert = 0
|
|
}
|
|
resetQuery()
|
|
|
|
tx, err := conn.Begin()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer tx.Rollback(context.Background())
|
|
|
|
for rowSrc.Next() {
|
|
if rowsThisInsert > 0 {
|
|
sqlBuf.WriteByte(',')
|
|
}
|
|
|
|
sqlBuf.WriteByte('(')
|
|
|
|
values, err := rowSrc.Values()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
for i, val := range values {
|
|
if i > 0 {
|
|
sqlBuf.WriteByte(',')
|
|
}
|
|
sqlBuf.WriteString(args.Append(val))
|
|
}
|
|
|
|
sqlBuf.WriteByte(')')
|
|
|
|
rowsThisInsert++
|
|
|
|
if rowsThisInsert == maxRowsPerInsert {
|
|
_, err := tx.Exec(context.Background(), sqlBuf.String(), args...)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
rowCount += rowsThisInsert
|
|
resetQuery()
|
|
}
|
|
}
|
|
|
|
if rowsThisInsert > 0 {
|
|
_, err := tx.Exec(context.Background(), sqlBuf.String(), args...)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
rowCount += rowsThisInsert
|
|
}
|
|
|
|
if err := tx.Commit(context.Background()); err != nil {
|
|
return 0, nil
|
|
}
|
|
|
|
return rowCount, nil
|
|
|
|
}
|
|
|
|
func benchmarkWriteNRowsViaMultiInsert(b *testing.B, n int) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
mustExec(b, conn, benchmarkWriteTableCreateSQL)
|
|
_, err := conn.Prepare(context.Background(), "insert_t", benchmarkWriteTableInsertSQL)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
src := newBenchmarkWriteTableCopyFromSrc(n)
|
|
|
|
_, err := multiInsert(conn, "t",
|
|
[]string{"varchar_1",
|
|
"varchar_2",
|
|
"varchar_null_1",
|
|
"date_1",
|
|
"date_null_1",
|
|
"int4_1",
|
|
"int4_2",
|
|
"int4_null_1",
|
|
"tstz_1",
|
|
"tstz_2",
|
|
"bool_1",
|
|
"bool_2",
|
|
"bool_3"},
|
|
src)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func benchmarkWriteNRowsViaCopy(b *testing.B, n int) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
mustExec(b, conn, benchmarkWriteTableCreateSQL)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
src := newBenchmarkWriteTableCopyFromSrc(n)
|
|
|
|
_, err := conn.CopyFrom(context.Background(),
|
|
pgx.Identifier{"t"},
|
|
[]string{"varchar_1",
|
|
"varchar_2",
|
|
"varchar_null_1",
|
|
"date_1",
|
|
"date_null_1",
|
|
"int4_1",
|
|
"int4_2",
|
|
"int4_null_1",
|
|
"tstz_1",
|
|
"tstz_2",
|
|
"bool_1",
|
|
"bool_2",
|
|
"bool_3"},
|
|
src)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkWrite5RowsViaInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaInsert(b, 5)
|
|
}
|
|
|
|
func BenchmarkWrite5RowsViaMultiInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaMultiInsert(b, 5)
|
|
}
|
|
|
|
func BenchmarkWrite5RowsViaCopy(b *testing.B) {
|
|
benchmarkWriteNRowsViaCopy(b, 5)
|
|
}
|
|
|
|
func BenchmarkWrite10RowsViaInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaInsert(b, 10)
|
|
}
|
|
|
|
func BenchmarkWrite10RowsViaMultiInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaMultiInsert(b, 10)
|
|
}
|
|
|
|
func BenchmarkWrite10RowsViaCopy(b *testing.B) {
|
|
benchmarkWriteNRowsViaCopy(b, 10)
|
|
}
|
|
|
|
func BenchmarkWrite100RowsViaInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaInsert(b, 100)
|
|
}
|
|
|
|
func BenchmarkWrite100RowsViaMultiInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaMultiInsert(b, 100)
|
|
}
|
|
|
|
func BenchmarkWrite100RowsViaCopy(b *testing.B) {
|
|
benchmarkWriteNRowsViaCopy(b, 100)
|
|
}
|
|
|
|
func BenchmarkWrite1000RowsViaInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaInsert(b, 1000)
|
|
}
|
|
|
|
func BenchmarkWrite1000RowsViaMultiInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaMultiInsert(b, 1000)
|
|
}
|
|
|
|
func BenchmarkWrite1000RowsViaCopy(b *testing.B) {
|
|
benchmarkWriteNRowsViaCopy(b, 1000)
|
|
}
|
|
|
|
func BenchmarkWrite10000RowsViaInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaInsert(b, 10000)
|
|
}
|
|
|
|
func BenchmarkWrite10000RowsViaMultiInsert(b *testing.B) {
|
|
benchmarkWriteNRowsViaMultiInsert(b, 10000)
|
|
}
|
|
|
|
func BenchmarkWrite10000RowsViaCopy(b *testing.B) {
|
|
benchmarkWriteNRowsViaCopy(b, 10000)
|
|
}
|
|
|
|
func BenchmarkMultipleQueriesNonBatch(b *testing.B) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
queryCount := 3
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
for j := 0; j < queryCount; j++ {
|
|
rows, err := conn.Query(context.Background(), "select n from generate_series(0, 5) n")
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
for k := 0; rows.Next(); k++ {
|
|
var n int
|
|
if err := rows.Scan(&n); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
if n != k {
|
|
b.Fatalf("n => %v, want %v", n, k)
|
|
}
|
|
}
|
|
|
|
if rows.Err() != nil {
|
|
b.Fatal(rows.Err())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkMultipleQueriesBatch(b *testing.B) {
|
|
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
|
|
defer closeConn(b, conn)
|
|
|
|
queryCount := 3
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
batch := conn.BeginBatch()
|
|
for j := 0; j < queryCount; j++ {
|
|
batch.Queue("select n from generate_series(0,5) n",
|
|
nil,
|
|
nil,
|
|
[]int16{pgx.BinaryFormatCode},
|
|
)
|
|
}
|
|
|
|
err := batch.Send(context.Background())
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
for j := 0; j < queryCount; j++ {
|
|
rows, err := batch.QueryResults()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
for k := 0; rows.Next(); k++ {
|
|
var n int
|
|
if err := rows.Scan(&n); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
if n != k {
|
|
b.Fatalf("n => %v, want %v", n, k)
|
|
}
|
|
}
|
|
|
|
if rows.Err() != nil {
|
|
b.Fatal(rows.Err())
|
|
}
|
|
}
|
|
|
|
err = batch.Close()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|