-
-
Notifications
You must be signed in to change notification settings - Fork 50
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
Closed
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
3a09c76
Batch example draft.
giuliocn 8e2db26
Working example with pgx.Batch
giuliocn d36e83e
Create README.md
giuliocn 1762d12
Added batch example with comments.
giuliocn 1a89fce
Resolved comments from author.
giuliocn b3539db
Resolved comments from author.
giuliocn a36b504
Update README.md
giuliocn 3255207
Update batch_test.go
giuliocn 886f321
Reads results and print rows to stdout.
giuliocn 49f014d
Fix typos and update README.
giuliocn 4841976
Create OUTPUT.md and formatting.
giuliocn b0d1e2a
bump to v4
pashagolub e8cf2a8
one-statement queries are automatically executed in the transaction
pashagolub 0768aaf
Merge branch 'pashagolub:master' into pull-request-batch
giuliocn bd3d7cd
Delete .md files from example tree.
giuliocn 8a03813
Example Batch new struct added and sample tests.
giuliocn 84a0ec5
Merge branch 'pashagolub:master' into pr-batch-example
giuliocn ad1dad8
Batch results at localhost in raw text format.
giuliocn 2f03bb6
Merge branch 'master' into pr-batch-example
pashagolub 117f4c9
Merge branch 'pashagolub:master' into pr-batch-example
giuliocn efee7a3
Complete example with revised structure.
giuliocn 4a4e1d9
Failing tests due to expected queries.
giuliocn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Useless piece of code. No purpose. No meaning. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.