good morning!!!!

Skip to content
Snippets Groups Projects
Commit bd61cf84 authored by José Carlos Nieto's avatar José Carlos Nieto
Browse files

Adding benchmarks for Find(), Remove(), Update() and equivalent SQL queries.

parent f10e0a82
Branches
Tags
No related merge requests found
......@@ -11,4 +11,4 @@ test: reset-db
$(MAKE) -C _example
bench: reset-db
go test -v -test.bench=.
go test -v -test.bench=. -test.benchtime=10s -benchmem
......@@ -11,9 +11,17 @@ import (
)
const (
testRows = 10000
testRows = 1000
)
func updatedArtistN(i int) string {
return fmt.Sprintf("Updated Artist %d", i%testRows)
}
func artistN(i int) string {
return fmt.Sprintf("Artist %d", i%testRows)
}
func connectAndAddFakeRows() (db.Database, error) {
var err error
var sess db.Database
......@@ -24,12 +32,12 @@ func connectAndAddFakeRows() (db.Database, error) {
driver := sess.Driver().(*sqlx.DB)
if _, err = driver.Exec(`TRUNCATE TABLE "artist"`); err != nil {
if _, err = driver.Exec(`TRUNCATE TABLE "artist" RESTART IDENTITY`); err != nil {
return nil, err
}
for i := 0; i < testRows; i++ {
if _, err = driver.Exec(fmt.Sprintf(`INSERT INTO "artist" ("name") VALUES('Artist %d')`, i)); err != nil {
if _, err = driver.Exec(`INSERT INTO "artist" ("name") VALUES($1)`, artistN(i)); err != nil {
return nil, err
}
}
......@@ -97,9 +105,9 @@ func BenchmarkSQLAppendWithArgs(b *testing.B) {
}
}
// BenchmarkSQLPrepareAppend benchmarks raw INSERT SQL queries using prepared
// BenchmarkSQLPreparedAppend benchmarks raw INSERT SQL queries using prepared
// statements but no arguments.
func BenchmarkSQLPrepareAppend(b *testing.B) {
func BenchmarkSQLPreparedAppend(b *testing.B) {
var err error
var sess db.Database
......@@ -131,7 +139,7 @@ func BenchmarkSQLPrepareAppend(b *testing.B) {
// BenchmarkSQLAppendWithArgs benchmarks raw INSERT SQL queries with arguments
// using prepared statements. The SQL query looks like the one that is
// generated by upper.io/db.
func BenchmarkSQLPrepareAppendWithArgs(b *testing.B) {
func BenchmarkSQLPreparedAppendWithArgs(b *testing.B) {
var err error
var sess db.Database
......@@ -171,7 +179,7 @@ func BenchmarkSQLPrepareAppendWithArgs(b *testing.B) {
// BenchmarkSQLAppendWithVariableArgs benchmarks raw INSERT SQL queries with
// arguments using prepared statements. The SQL query looks like the one that
// is generated by upper.io/db.
func BenchmarkSQLPrepareAppendWithVariableArgs(b *testing.B) {
func BenchmarkSQLPreparedAppendWithVariableArgs(b *testing.B) {
var err error
var sess db.Database
......@@ -207,10 +215,10 @@ func BenchmarkSQLPrepareAppendWithVariableArgs(b *testing.B) {
}
}
// BenchmarkSQLPrepareAppendTransactionWithArgs benchmarks raw INSERT queries
// BenchmarkSQLPreparedAppendTransactionWithArgs benchmarks raw INSERT queries
// within a transaction block with arguments and prepared statements. SQL
// queries look like those generated by upper.io/db.
func BenchmarkSQLPrepareAppendTransactionWithArgs(b *testing.B) {
func BenchmarkSQLPreparedAppendTransactionWithArgs(b *testing.B) {
var err error
var sess db.Database
var tx *sql.Tx
......@@ -397,7 +405,7 @@ func BenchmarkUpperAppendTransactionWithMap(b *testing.B) {
}
}
// BenchmarkSQLSelect benchmarks SQL select queries.
// BenchmarkSQLSelect benchmarks SQL SELECT queries.
func BenchmarkSQLSelect(b *testing.B) {
var err error
var sess db.Database
......@@ -414,7 +422,7 @@ func BenchmarkSQLSelect(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
if res, err = driver.Query(`SELECT * FROM "artist" WHERE "name" = $1`, fmt.Sprintf("Artist %d", i%testRows)); err != nil {
if res, err = driver.Query(`SELECT * FROM "artist" WHERE "name" = $1`, artistN(i)); err != nil {
b.Fatal(err)
}
res.Close()
......@@ -443,7 +451,7 @@ func BenchmarkSQLPreparedSelect(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
if res, err = stmt.Query(fmt.Sprintf("Artist %d", i%testRows)); err != nil {
if res, err = stmt.Query(artistN(i)); err != nil {
b.Fatal(err)
}
res.Close()
......@@ -466,27 +474,204 @@ func BenchmarkUpperFind(b *testing.B) {
b.Fatal(err)
}
artist.Truncate()
type artistType struct {
Name string `db:"name"`
}
var item artistType
for i := 0; i < testRows; i++ {
item = artistType{
Name: fmt.Sprintf("Artist %d", i),
b.ResetTimer()
for i := 0; i < b.N; i++ {
res := artist.Find(db.Cond{"name": artistN(i)})
if err = res.One(&item); err != nil {
b.Fatal(err)
}
if _, err = artist.Append(item); err != nil {
}
}
// BenchmarkUpperFindAll benchmarks upper.io/db's All method.
func BenchmarkUpperFindAll(b *testing.B) {
var err error
var sess db.Database
if sess, err = connectAndAddFakeRows(); err != nil {
b.Fatal(err)
}
defer sess.Close()
artist, err := sess.Collection("artist")
if err != nil {
b.Fatal(err)
}
type artistType struct {
Name string `db:"name"`
}
var items []artistType
b.ResetTimer()
for i := 0; i < b.N; i++ {
res := artist.Find(db.Cond{"name": fmt.Sprintf("Artist %d", i%testRows)})
if err = res.One(&item); err != nil {
res := artist.Find(db.Or{
db.Cond{"name": artistN(i)},
db.Cond{"name": artistN(i + 1)},
db.Cond{"name": artistN(i + 2)},
})
if err = res.All(&items); err != nil {
b.Fatal(err)
}
if len(items) != 3 {
b.Fatal("Expecting 3 results.")
}
}
}
// BenchmarkSQLUpdate benchmarks SQL UPDATE queries.
func BenchmarkSQLUpdate(b *testing.B) {
var err error
var sess db.Database
if sess, err = connectAndAddFakeRows(); err != nil {
b.Fatal(err)
}
defer sess.Close()
driver := sess.Driver().(*sqlx.DB)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err = driver.Exec(`UPDATE "artist" SET "name" = $1 WHERE "name" = $2`, updatedArtistN(i), artistN(i)); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkSQLPreparedUpdate benchmarks SQL UPDATE queries.
func BenchmarkSQLPreparedUpdate(b *testing.B) {
var err error
var sess db.Database
if sess, err = connectAndAddFakeRows(); err != nil {
b.Fatal(err)
}
defer sess.Close()
driver := sess.Driver().(*sqlx.DB)
stmt, err := driver.Prepare(`UPDATE "artist" SET "name" = $1 WHERE "name" = $2`)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err = stmt.Exec(updatedArtistN(i), artistN(i)); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkUpperUpdate benchmarks upper.io/db's Update method.
func BenchmarkUpperUpdate(b *testing.B) {
var err error
var sess db.Database
if sess, err = connectAndAddFakeRows(); err != nil {
b.Fatal(err)
}
defer sess.Close()
artist, err := sess.Collection("artist")
if err != nil {
b.Fatal(err)
}
type artistType struct {
Name string `db:"name"`
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
newValue := artistType{
Name: updatedArtistN(i),
}
res := artist.Find(db.Cond{"name": artistN(i)})
if err = res.Update(newValue); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkSQLDelete benchmarks SQL DELETE queries.
func BenchmarkSQLDelete(b *testing.B) {
var err error
var sess db.Database
if sess, err = connectAndAddFakeRows(); err != nil {
b.Fatal(err)
}
defer sess.Close()
driver := sess.Driver().(*sqlx.DB)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err = driver.Exec(`DELETE FROM "artist" WHERE "name" = $1`, artistN(i)); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkSQLPreparedDelete benchmarks SQL DELETE queries.
func BenchmarkSQLPreparedDelete(b *testing.B) {
var err error
var sess db.Database
if sess, err = connectAndAddFakeRows(); err != nil {
b.Fatal(err)
}
defer sess.Close()
driver := sess.Driver().(*sqlx.DB)
stmt, err := driver.Prepare(`DELETE FROM "artist" WHERE "name" = $1`)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err = stmt.Exec(artistN(i)); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkUpperRemove benchmarks
func BenchmarkUpperRemove(b *testing.B) {
var err error
var sess db.Database
if sess, err = connectAndAddFakeRows(); err != nil {
b.Fatal(err)
}
defer sess.Close()
artist, err := sess.Collection("artist")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
res := artist.Find(db.Cond{"name": artistN(i)})
if err = res.Remove(); err != nil {
b.Fatal(err)
}
}
......
......@@ -116,12 +116,12 @@ func (t *table) Append(item interface{}) (interface{}, error) {
return nil, err
}
defer rows.Close()
keyMap := map[string]interface{}{}
if err := sqlutil.FetchRow(rows, &keyMap); err != nil {
rows.Close()
return nil, err
}
rows.Close()
// Does the item satisfy the db.IDSetter interface?
if setter, ok := item.(db.IDSetter); ok {
......
......@@ -23,7 +23,6 @@ package postgresql
import (
"database/sql"
"fmt"
"strconv"
"strings"
"time"
......@@ -65,6 +64,50 @@ type columnSchemaT struct {
DataType string `db:"data_type"`
}
func (d *database) prepareStatement(stmt *sqlgen.Statement) (p *sqlx.Stmt, query string, err error) {
if d.session == nil {
return nil, "", db.ErrNotConnected
}
pc, ok := d.cachedStatements.ReadRaw(stmt)
if ok {
p = pc.(*sqlx.Stmt)
} else {
query = compileAndReplacePlaceholders(stmt)
if d.tx != nil {
p, err = d.tx.Preparex(query)
} else {
p, err = d.session.Preparex(query)
}
if err != nil {
return nil, "", err
}
d.cachedStatements.Write(stmt, p)
}
return p, query, nil
}
func compileAndReplacePlaceholders(stmt *sqlgen.Statement) (query string) {
buf := stmt.Compile(template.Template)
j := 1
for i := range buf {
if buf[i] == '?' {
query = query + "$" + strconv.Itoa(j)
j++
} else {
query = query + string(buf[i])
}
}
return query
}
// Driver returns the underlying *sqlx.DB instance.
func (d *database) Driver() interface{} {
return d.session
......@@ -211,8 +254,6 @@ func (d *database) Collections() (collections []string, err error) {
return nil, err
}
defer rows.Close()
collections = []string{}
var name string
......@@ -220,6 +261,7 @@ func (d *database) Collections() (collections []string, err error) {
for rows.Next() {
// Getting table name.
if err = rows.Scan(&name); err != nil {
rows.Close()
return nil, err
}
......@@ -291,124 +333,70 @@ func (d *database) Transaction() (db.Tx, error) {
// Exec compiles and executes a statement that does not return any rows.
func (d *database) Exec(stmt *sqlgen.Statement, args ...interface{}) (sql.Result, error) {
var query string
var res sql.Result
var p *sqlx.Stmt
var err error
var start, end int64
if db.Debug {
var start, end int64
start = time.Now().UnixNano()
defer func() {
end = time.Now().UnixNano()
sqlutil.Log(query, args, err, start, end)
}()
if d.session == nil {
return nil, db.ErrNotConnected
}
query = stmt.Compile(template.Template)
l := len(args)
for i := 0; i < l; i++ {
query = strings.Replace(query, `?`, fmt.Sprintf(`$%d`, i+1), 1)
}
if d.tx != nil {
res, err = d.tx.Exec(query, args...)
} else {
res, err = d.session.Exec(query, args...)
if p, query, err = d.prepareStatement(stmt); err != nil {
return nil, err
}
return res, err
return p.Exec(args...)
}
// Query compiles and executes a statement that returns rows.
func (d *database) Query(stmt *sqlgen.Statement, args ...interface{}) (*sqlx.Rows, error) {
var rows *sqlx.Rows
var query string
var p *sqlx.Stmt
var err error
var start, end int64
if db.Debug {
var start, end int64
start = time.Now().UnixNano()
defer func() {
end = time.Now().UnixNano()
sqlutil.Log(query, args, err, start, end)
}()
if d.session == nil {
return nil, db.ErrNotConnected
}
var p *sqlx.Stmt
pc, ok := d.cachedStatements.ReadRaw(stmt)
if ok {
p = pc.(*sqlx.Stmt)
} else {
buf := stmt.Compile(template.Template)
j := 1
for i := range buf {
if buf[i] == '?' {
query = query + "$" + strconv.Itoa(j)
j++
} else {
query = query + string(buf[i])
}
}
if d.tx != nil {
p, err = d.tx.Preparex(query)
} else {
p, err = d.session.Preparex(query)
}
if err != nil {
if p, query, err = d.prepareStatement(stmt); err != nil {
return nil, err
}
d.cachedStatements.Write(stmt, p)
}
rows, err = p.Queryx(args...)
return rows, err
return p.Queryx(args...)
}
// QueryRow compiles and executes a statement that returns at most one row.
func (d *database) QueryRow(stmt *sqlgen.Statement, args ...interface{}) (*sqlx.Row, error) {
var query string
var row *sqlx.Row
var p *sqlx.Stmt
var err error
var start, end int64
if db.Debug {
var start, end int64
start = time.Now().UnixNano()
defer func() {
end = time.Now().UnixNano()
sqlutil.Log(query, args, err, start, end)
}()
if d.session == nil {
return nil, db.ErrNotConnected
}
query = stmt.Compile(template.Template)
l := len(args)
for i := 0; i < l; i++ {
query = strings.Replace(query, `?`, `$`+strconv.Itoa(i+1), 1)
}
if d.tx != nil {
row = d.tx.QueryRowx(query, args...)
} else {
row = d.session.QueryRowx(query, args...)
if p, query, err = d.prepareStatement(stmt); err != nil {
return nil, err
}
return row, err
return p.QueryRowx(args...), nil
}
// populateSchema looks up for the table info in the database and populates its
......@@ -485,9 +473,8 @@ func (d *database) tableExists(names ...string) error {
return db.ErrCollectionDoesNotExist
}
defer rows.Close()
if !rows.Next() {
rows.Close()
return db.ErrCollectionDoesNotExist
}
}
......@@ -532,14 +519,15 @@ func (d *database) tableColumns(tableName string) ([]string, error) {
return nil, err
}
defer rows.Close()
tableFields := []columnSchemaT{}
if err = sqlutil.FetchRows(rows, &tableFields); err != nil {
rows.Close()
return nil, err
}
rows.Close()
d.schema.TableInfo[tableName].Columns = make([]string, 0, len(tableFields))
for i := range tableFields {
......@@ -587,13 +575,12 @@ func (d *database) getPrimaryKey(tableName string) ([]string, error) {
return nil, err
}
defer rows.Close()
tableSchema.PrimaryKey = make([]string, 0, 1)
for rows.Next() {
var key string
if err = rows.Scan(&key); err != nil {
rows.Close()
return nil, err
}
tableSchema.PrimaryKey = append(tableSchema.PrimaryKey, key)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment