mirror of https://github.com/VinGarcia/ksql.git
1060 lines
25 KiB
Go
1060 lines
25 KiB
Go
package benchmarks
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"strconv"
|
|
"testing"
|
|
|
|
_ "embed"
|
|
|
|
"github.com/jackc/pgx/v4/pgxpool"
|
|
"github.com/jmoiron/sqlx"
|
|
_ "github.com/lib/pq"
|
|
"github.com/vingarcia/ksql"
|
|
"github.com/vingarcia/ksql/adapters/kpgx"
|
|
"github.com/vingarcia/ksql/benchmarks/sqlboilergen"
|
|
"github.com/vingarcia/ksql/benchmarks/sqlcgen"
|
|
"github.com/volatiletech/sqlboiler/v4/boil"
|
|
"github.com/volatiletech/sqlboiler/v4/queries/qm"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var UsersTable = ksql.NewTable("users")
|
|
|
|
func BenchmarkInsert(b *testing.B) {
|
|
ctx := context.Background()
|
|
|
|
driver := "postgres"
|
|
connStr := "host=localhost port=5432 user=postgres password=postgres dbname=ksql sslmode=disable"
|
|
|
|
type User struct {
|
|
ID int `ksql:"id" db:"id"`
|
|
Name string `ksql:"name" db:"name"`
|
|
Age int `ksql:"age" db:"age"`
|
|
}
|
|
|
|
b.Run("ksql/sql-adapter", func(b *testing.B) {
|
|
db, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error connecting to database: %s", err)
|
|
}
|
|
db.SetMaxOpenConns(1)
|
|
ksqlDB, err := ksql.NewWithAdapter(NewSQLAdapter(db), driver)
|
|
if err != nil {
|
|
b.Fatalf("error creating ksql client: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
err := ksqlDB.Insert(ctx, UsersTable, &User{
|
|
Name: strconv.Itoa(i),
|
|
Age: i,
|
|
})
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("ksql/pgx-adapter", func(b *testing.B) {
|
|
kpgxDB, err := kpgx.New(ctx, connStr, ksql.Config{
|
|
MaxOpenConns: 1,
|
|
})
|
|
if err != nil {
|
|
b.Fatalf("error creating kpgx client: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
err := kpgxDB.Insert(ctx, UsersTable, &User{
|
|
Name: strconv.Itoa(i),
|
|
Age: i,
|
|
})
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sql", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
user := User{
|
|
Name: strconv.Itoa(i),
|
|
Age: i,
|
|
}
|
|
rows, err := sqlDB.QueryContext(ctx,
|
|
`INSERT INTO users(name, age) VALUES ($1, $2) RETURNING id`,
|
|
user.Name, user.Age,
|
|
)
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing id from inserted record")
|
|
}
|
|
err = rows.Scan(&user.ID)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sql/prep-stmt", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
insertOne, err := sqlDB.Prepare(`INSERT INTO users(name, age) VALUES ($1, $2) RETURNING id`)
|
|
if err != nil {
|
|
b.Fatalf("could not prepare sql insert query: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
user := User{
|
|
Name: strconv.Itoa(i),
|
|
Age: i,
|
|
}
|
|
rows, err := insertOne.QueryContext(ctx, user.Name, user.Age)
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing id from inserted record")
|
|
}
|
|
err = rows.Scan(&user.ID)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlx", func(b *testing.B) {
|
|
sqlxDB, err := sqlx.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sqlx client: %s", err)
|
|
}
|
|
sqlxDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
user := User{
|
|
Name: strconv.Itoa(i),
|
|
Age: i,
|
|
}
|
|
rows, err := sqlxDB.NamedQueryContext(ctx,
|
|
`INSERT INTO users(name, age) VALUES (:name, :age) RETURNING id`,
|
|
user,
|
|
)
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing id from inserted record")
|
|
}
|
|
err = rows.Scan(&user.ID)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlx/prep-stmt", func(b *testing.B) {
|
|
sqlxDB, err := sqlx.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sqlx client: %s", err)
|
|
}
|
|
sqlxDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
insertOne, err := sqlxDB.Prepare(`INSERT INTO users(name, age) VALUES ($1, $2) RETURNING id`)
|
|
if err != nil {
|
|
b.Fatalf("could not prepare sql insert query: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
user := User{
|
|
Name: strconv.Itoa(i),
|
|
Age: i,
|
|
}
|
|
rows, err := insertOne.QueryContext(ctx, user.Name, user.Age)
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing id from inserted record")
|
|
}
|
|
err = rows.Scan(&user.ID)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("pgxpool", func(b *testing.B) {
|
|
pgxConf, err := pgxpool.ParseConfig(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error parsing pgx client configs: %s", err)
|
|
}
|
|
|
|
pgxConf.MaxConns = 1
|
|
pgxDB, err := pgxpool.ConnectConfig(ctx, pgxConf)
|
|
if err != nil {
|
|
b.Fatalf("error creating pgx client: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
user := User{
|
|
Name: strconv.Itoa(i),
|
|
Age: i,
|
|
}
|
|
rows, err := pgxDB.Query(ctx,
|
|
`INSERT INTO users(name, age) VALUES ($1, $2) RETURNING id`,
|
|
user.Name, user.Age,
|
|
)
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing id from inserted record")
|
|
}
|
|
err = rows.Scan(&user.ID)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
rows.Close()
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("gorm", func(b *testing.B) {
|
|
gormDB, err := gorm.Open(postgres.Open(connStr), &gorm.Config{})
|
|
if err != nil {
|
|
b.Fatalf("error creating gorm client: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err)
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
err := gormDB.Table("users").WithContext(ctx).Create(&User{
|
|
Name: strconv.Itoa(i),
|
|
Age: i,
|
|
}).Error
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlc", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
sqlcDB := sqlcgen.New(sqlDB)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
user := sqlcgen.InsertUserParams{
|
|
Name: strconv.Itoa(i),
|
|
Age: int32(i),
|
|
}
|
|
_, err := sqlcDB.InsertUser(ctx, user)
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlc/prep-stmt", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
sqlcDB, err := sqlcgen.Prepare(ctx, sqlDB)
|
|
if err != nil {
|
|
b.Fatalf("error preparing sqlc statements: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
user := sqlcgen.InsertUserParams{
|
|
Name: strconv.Itoa(i),
|
|
Age: int32(i),
|
|
}
|
|
_, err := sqlcDB.InsertUser(ctx, user)
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlboiler", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
b.Run("insert-one", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
user := sqlboilergen.User{
|
|
Name: strconv.Itoa(i),
|
|
Age: i,
|
|
}
|
|
err := user.Insert(ctx, sqlDB, boil.Infer())
|
|
if err != nil {
|
|
b.Fatalf("insert error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func BenchmarkQuery(b *testing.B) {
|
|
ctx := context.Background()
|
|
|
|
driver := "postgres"
|
|
connStr := "host=localhost port=5432 user=postgres password=postgres dbname=ksql sslmode=disable"
|
|
|
|
type User struct {
|
|
ID int `ksql:"id" db:"id"`
|
|
Name string `ksql:"name" db:"name"`
|
|
Age int `ksql:"age" db:"age"`
|
|
}
|
|
|
|
b.Run("ksql/sql-adapter", func(b *testing.B) {
|
|
db, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error connecting to database: %s", err)
|
|
}
|
|
db.SetMaxOpenConns(1)
|
|
ksqlDB, err := ksql.NewWithAdapter(NewSQLAdapter(db), driver)
|
|
if err != nil {
|
|
b.Fatalf("error creating ksql client: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
err := ksqlDB.QueryOne(ctx, &user, `FROM users OFFSET $1 LIMIT 1`, i%100)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var users []User
|
|
err := ksqlDB.Query(ctx, &users, `FROM users OFFSET $1 LIMIT 10`, i%90)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
if len(users) < 10 {
|
|
b.Fatalf("expected 10 scanned users, but got: %d", len(users))
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("ksql/pgx-adapter", func(b *testing.B) {
|
|
kpgxDB, err := kpgx.New(ctx, connStr, ksql.Config{
|
|
MaxOpenConns: 1,
|
|
})
|
|
if err != nil {
|
|
b.Fatalf("error creating kpgx client: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
err := kpgxDB.QueryOne(ctx, &user, `FROM users OFFSET $1 LIMIT 1`, i%100)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var users []User
|
|
err := kpgxDB.Query(ctx, &users, `FROM users OFFSET $1 LIMIT 10`, i%90)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
if len(users) < 10 {
|
|
b.Fatalf("expected 10 scanned users, but got: %d", len(users))
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sql", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
rows, err := sqlDB.QueryContext(ctx, `SELECT id, name, age FROM users OFFSET $1 LIMIT 1`, i%100)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%100)
|
|
}
|
|
err = rows.Scan(&user.ID, &user.Name, &user.Age)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var users []User
|
|
rows, err := sqlDB.QueryContext(ctx, `SELECT id, name, age FROM users OFFSET $1 LIMIT 10`, i%90)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
for j := 0; j < 10; j++ {
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%90)
|
|
}
|
|
var user User
|
|
err = rows.Scan(&user.ID, &user.Name, &user.Age)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
if len(users) < 10 {
|
|
b.Fatalf("expected 10 scanned users, but got: %d", len(users))
|
|
}
|
|
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sql/prep-stmt", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
singleRow, err := sqlDB.Prepare(`SELECT id, name, age FROM users OFFSET $1 LIMIT 1`)
|
|
if err != nil {
|
|
b.Fatalf("error preparing sql statement for single row: %s", err.Error())
|
|
}
|
|
|
|
multipleRows, err := sqlDB.Prepare(`SELECT id, name, age FROM users OFFSET $1 LIMIT 10`)
|
|
if err != nil {
|
|
b.Fatalf("error preparing sql statement for multiple rows: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
rows, err := singleRow.QueryContext(ctx, i%100)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%100)
|
|
}
|
|
err = rows.Scan(&user.ID, &user.Name, &user.Age)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var users []User
|
|
rows, err := multipleRows.QueryContext(ctx, i%90)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
for j := 0; j < 10; j++ {
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%90)
|
|
}
|
|
var user User
|
|
err = rows.Scan(&user.ID, &user.Name, &user.Age)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
if len(users) < 10 {
|
|
b.Fatalf("expected 10 scanned users, but got: %d", len(users))
|
|
}
|
|
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlx", func(b *testing.B) {
|
|
sqlxDB, err := sqlx.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sqlx client: %s", err)
|
|
}
|
|
sqlxDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
rows, err := sqlxDB.QueryxContext(ctx, `SELECT id, name, age FROM users OFFSET $1 LIMIT 1`, i%100)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%100)
|
|
}
|
|
err = rows.StructScan(&user)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var users []User
|
|
rows, err := sqlxDB.QueryxContext(ctx, `SELECT id, name, age FROM users OFFSET $1 LIMIT 10`, i%90)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
for j := 0; j < 10; j++ {
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%90)
|
|
}
|
|
var user User
|
|
rows.StructScan(&user)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
if len(users) < 10 {
|
|
b.Fatalf("expected 10 scanned users, but got: %d", len(users))
|
|
}
|
|
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlx/prep-stmt", func(b *testing.B) {
|
|
sqlxDB, err := sqlx.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sqlx client: %s", err)
|
|
}
|
|
sqlxDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
singleRow, err := sqlxDB.Preparex(`SELECT id, name, age FROM users OFFSET $1 LIMIT 1`)
|
|
if err != nil {
|
|
b.Fatalf("error preparing sql statement for single row: %s", err.Error())
|
|
}
|
|
|
|
multipleRows, err := sqlxDB.Preparex(`SELECT id, name, age FROM users OFFSET $1 LIMIT 10`)
|
|
if err != nil {
|
|
b.Fatalf("error preparing sql statement for multiple rows: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
rows, err := singleRow.QueryxContext(ctx, i%100)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%100)
|
|
}
|
|
err = rows.StructScan(&user)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var users []User
|
|
rows, err := multipleRows.QueryxContext(ctx, i%90)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
for j := 0; j < 10; j++ {
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%90)
|
|
}
|
|
var user User
|
|
rows.StructScan(&user)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
if len(users) < 10 {
|
|
b.Fatalf("expected 10 scanned users, but got: %d", len(users))
|
|
}
|
|
|
|
err = rows.Close()
|
|
if err != nil {
|
|
b.Fatalf("error closing rows")
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("pgxpool", func(b *testing.B) {
|
|
pgxConf, err := pgxpool.ParseConfig(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error parsing pgx client configs: %s", err)
|
|
}
|
|
|
|
pgxConf.MaxConns = 1
|
|
pgxDB, err := pgxpool.ConnectConfig(ctx, pgxConf)
|
|
if err != nil {
|
|
b.Fatalf("error creating pgx client: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
rows, err := pgxDB.Query(ctx, `SELECT id, name, age FROM users OFFSET $1 LIMIT 1`, i%100)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%100)
|
|
}
|
|
err = rows.Scan(&user.ID, &user.Name, &user.Age)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
rows.Close()
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var users []User
|
|
rows, err := pgxDB.Query(ctx, `SELECT id, name, age FROM users OFFSET $1 LIMIT 10`, i%90)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
for j := 0; j < 10; j++ {
|
|
if !rows.Next() {
|
|
b.Fatalf("missing user from inserted record, offset: %d", i%90)
|
|
}
|
|
var user User
|
|
err = rows.Scan(&user.ID, &user.Name, &user.Age)
|
|
if err != nil {
|
|
b.Fatalf("error scanning rows")
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
if len(users) < 10 {
|
|
b.Fatalf("expected 10 scanned users, but got: %d", len(users))
|
|
}
|
|
|
|
rows.Close()
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("gorm", func(b *testing.B) {
|
|
gormDB, err := gorm.Open(postgres.Open(connStr), &gorm.Config{})
|
|
if err != nil {
|
|
b.Fatalf("error creating gorm client: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
err := gormDB.Table("users").WithContext(ctx).Offset(i % 100).Take(&user).Error
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var users []User
|
|
err := gormDB.Table("users").WithContext(ctx).Offset(i % 90).Limit(10).Find(&users).Error
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
if len(users) < 10 {
|
|
b.Fatalf("expected 10 scanned users, but got: %d", len(users))
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlc", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
sqlcDB := sqlcgen.New(sqlDB)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := sqlcDB.GetUser(ctx, int32(i%100))
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := sqlcDB.List10Users(ctx, int32(i%90))
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlc/prep-stmt", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
sqlcDB, err := sqlcgen.Prepare(ctx, sqlDB)
|
|
if err != nil {
|
|
b.Fatalf("error preparing sqlc statements: %s", err)
|
|
}
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := sqlcDB.GetUser(ctx, int32(i%100))
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := sqlcDB.List10Users(ctx, int32(i%90))
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
b.Run("sqlboiler", func(b *testing.B) {
|
|
sqlDB, err := sql.Open(driver, connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating sql client: %s", err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
|
|
err = recreateTable(connStr)
|
|
if err != nil {
|
|
b.Fatalf("error creating table: %s", err.Error())
|
|
}
|
|
|
|
err = insertUsers(connStr, 100)
|
|
if err != nil {
|
|
b.Fatalf("error inserting users: %s", err.Error())
|
|
}
|
|
|
|
b.Run("single-row", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := sqlboilergen.Users(qm.Select("id", "name", "age"), qm.Offset(i%100), qm.Limit(1)).One(ctx, sqlDB)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("multiple-rows", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := sqlboilergen.Users(qm.Select("id", "name", "age"), qm.Offset(i%90), qm.Limit(10)).One(ctx, sqlDB)
|
|
if err != nil {
|
|
b.Fatalf("query error: %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
//go:embed schema.sql
|
|
var createTablesSQL string
|
|
|
|
func recreateTable(connStr string) error {
|
|
db, err := sql.Open("postgres", connStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
|
|
db.Exec(`DROP TABLE users`)
|
|
|
|
_, err = db.Exec(createTablesSQL)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create new users table: %s", err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func insertUsers(connStr string, numUsers int) error {
|
|
db, err := sql.Open("postgres", connStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
|
|
for i := 0; i < numUsers; i++ {
|
|
_, err = db.Exec(`INSERT INTO users (name, age) VALUES ($1, $2)`, strconv.Itoa(i), i)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert new user: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|