Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pgxmock batch example in Go #212

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions examples/batch/batch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package main

import (
"context"
"errors"
"fmt"

pgx "github.com/jackc/pgx/v5"
pgconn "github.com/jackc/pgx/v5/pgconn"
pgxpool "github.com/jackc/pgx/v5/pgxpool"
)

type PgxPoolInterface interface {
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
SendBatch(context.Context, *pgx.Batch) pgx.BatchResults
Close()
}

type exampleBatch struct {
batch *pgx.Batch // batch pointer
results pgx.BatchResults // query results
db PgxPoolInterface // database connection
}
Comment on lines +19 to +23
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the meaning of this.


type metadata struct {
title string
authors string
subject string
description string
}

func NewExample(db PgxPoolInterface) (example exampleBatch, err error) {

example = exampleBatch{
db: db, // database
batch: &pgx.Batch{}, // batch struct address
}

sql := `CREATE TABLE IF NOT EXISTS metadata (
id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
title TEXT NOT NULL,
authors TEXT NOT NULL,
subject TEXT NOT NULL,
description TEXT NOT NULL);
DELETE FROM metadata ;`
_, err = example.db.Exec(context.Background(), sql) // Execute sql

return example, err
}

func (ex *exampleBatch) BulkInsertMetadata(data []metadata) error {
query := `INSERT INTO metadata (title, authors, subject, description)
VALUES
(@title, @authors, @subject, @description)
`
batch := &pgx.Batch{}
for _, m := range data {
args := pgx.NamedArgs{
"title": m.title,
"authors": m.authors,
"subject": m.subject,
"description": m.description,
}
batch.Queue(query, args)
}

results := ex.db.SendBatch(context.Background(), batch)
defer results.Close()

for index := range data {
_, err := results.Exec()
if err != nil {
return fmt.Errorf("unable to insert row %d: %w", index, err)
}
}

return results.Close()
}

func (ex *exampleBatch) SendCustomBatch(queries []string) (err error) {

// Add SQL queries to the queue for a custom batch
for _, query := range queries {
ex.batch.Queue(query)
}

// Efficiently transmits queued queries as a single transaction.
// After the queries are run, a BatchResults object is returned.
ex.results = ex.db.SendBatch(context.Background(), ex.batch)
if ex.results == nil {
return errors.New("SendBatch returns a NIL object")
}

return err
}

func (ex *exampleBatch) TestCustomResults() (err error) {

for index := range ex.batch.QueuedQueries {
_, err := ex.results.Exec()
if err != nil {
return fmt.Errorf("unable to run query %d: %w", index, err)
}
}

return ex.results.Close()
}

func (ex *exampleBatch) Close() (err error) {

// Close batch results object
ex.results.Close()

// Close connection to database
ex.db.Close()

return err
}

func main() {

// @NOTE: the real connection is not required for tests
db, _ := pgxpool.New(context.Background(), "postgres://postgres:password@localhost/batch")

// Create a new instance of example struct
example, err := NewExample(db)
if err != nil {
panic(err)
}
defer example.Close()

// Insert multiple rows into the database
example.BulkInsertMetadata([]metadata{
{`A Journey Through the Alps`, `John Doe, Jane Smith`, `Hiking, Nature, Travel`, `Explore the breathtaking beauty of the Alps on a scenic hiking adventure. Discover hidden trails, alpine lakes, and charming mountain villages.`},
{`The Art of Baking: A Beginner's Guide`, `Emily Baker`, `Cooking, Baking, Food`, `Learn the fundamentals of baking and create delicious treats from scratch. Master techniques like kneading dough, frosting cakes, and piping cookies.`},
{`The History of Ancient Rome`, `David Johnson`, `History, Ancient Rome, Civilization`, `Delve into the fascinating history of the Roman Empire. Explore its rise to power, its cultural achievements, and its eventual decline.`},
})

// Send a custom batch operation to database
example.SendCustomBatch([]string{
"SELECT title FROM metadata",
"SELECT authors FROM metadata",
"SELECT subject, description FROM metadata",
})
Comment on lines +140 to +144
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't buy it. It should be one query

SELECT title, authors, subject, description FROM metadata

This is bad example.


// Print batch results
for _, query := range example.batch.QueuedQueries {
fmt.Println(query.SQL)
rows, _ := example.results.Query()
for rows.Next() {
values, _ := rows.Values()
fmt.Println(values)
}
}
}
Comment on lines +147 to +155
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless piece of code. No purpose. No meaning.

96 changes: 96 additions & 0 deletions examples/batch/batch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package main

import (
"testing"

pgxmock "github.com/pashagolub/pgxmock/v4"
)

func TestNewExample(t *testing.T) {
mock, err := pgxmock.NewPool()
if err != nil {
t.Fatal(err)
}
defer mock.Close()

// Expect a call to Exec
mock.ExpectExec(`^CREATE TABLE IF NOT EXISTS (.+)`).
WillReturnResult(pgxmock.NewResult("CREATE TABLE", 0))

_, err = NewExample(mock)
if err != nil {
t.Errorf("creating new example error: %s", err)
}

// We make sure that all expectations were met
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}

func TestSendCustomBatch(t *testing.T) {
mock, err := pgxmock.NewPool()
if err != nil {
t.Fatal(err)
}
defer mock.Close()

// Expect a call to Exec and pgx.Batch
mock.ExpectExec(`^CREATE TABLE IF NOT EXISTS (.+)`).
WillReturnResult(pgxmock.NewResult("CREATE TABLE", 0))
mock.ExpectBatch()

example, err := NewExample(mock)
if err != nil {
t.Errorf("creating new example error: %s", err)
}

err = example.SendCustomBatch([]string{
"SELECT title FROM metadata",
"SELECT authors FROM metadata",
"SELECT subject, description FROM metadata",
})
if err != nil {
t.Errorf("SendCustomBatch error: %s", err)
}

err = example.TestCustomResults()
if err != nil {
t.Errorf("TestCustomResults error: %s", err)
}

// We make sure that all expectations were met
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
func TestBulkInsert(t *testing.T) {
mock, err := pgxmock.NewPool()
if err != nil {
t.Fatal(err)
}
defer mock.Close()

// Expect a call to Exec and pgx.Batch
mock.ExpectExec(`^CREATE TABLE IF NOT EXISTS (.+)`).
WillReturnResult(pgxmock.NewResult("CREATE TABLE", 0))
mock.ExpectBatch()

example, err := NewExample(mock)
if err != nil {
t.Errorf("creating new example error: %s", err)
}

// Insert multiple rows into the database
err = example.BulkInsertMetadata([]metadata{
{`title`, `author`, `subject`, `description`},
})
if err != nil {
t.Errorf("bulk insert error: %s", err)
}

// We make sure that all expectations were met
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}