You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
228 lines
5.6 KiB
228 lines
5.6 KiB
package pgx |
|
|
|
import ( |
|
"context" |
|
"errors" |
|
"fmt" |
|
|
|
"github.com/jackc/pgconn" |
|
) |
|
|
|
type batchItem struct { |
|
query string |
|
arguments []interface{} |
|
} |
|
|
|
// Batch queries are a way of bundling multiple queries together to avoid |
|
// unnecessary network round trips. |
|
type Batch struct { |
|
items []*batchItem |
|
} |
|
|
|
// Queue queues a query to batch b. query can be an SQL query or the name of a prepared statement. |
|
func (b *Batch) Queue(query string, arguments ...interface{}) { |
|
b.items = append(b.items, &batchItem{ |
|
query: query, |
|
arguments: arguments, |
|
}) |
|
} |
|
|
|
// Len returns number of queries that have been queued so far. |
|
func (b *Batch) Len() int { |
|
return len(b.items) |
|
} |
|
|
|
type BatchResults interface { |
|
// Exec reads the results from the next query in the batch as if the query has been sent with Conn.Exec. |
|
Exec() (pgconn.CommandTag, error) |
|
|
|
// Query reads the results from the next query in the batch as if the query has been sent with Conn.Query. |
|
Query() (Rows, error) |
|
|
|
// QueryRow reads the results from the next query in the batch as if the query has been sent with Conn.QueryRow. |
|
QueryRow() Row |
|
|
|
// QueryFunc reads the results from the next query in the batch as if the query has been sent with Conn.QueryFunc. |
|
QueryFunc(scans []interface{}, f func(QueryFuncRow) error) (pgconn.CommandTag, error) |
|
|
|
// Close closes the batch operation. This must be called before the underlying connection can be used again. Any error |
|
// that occurred during a batch operation may have made it impossible to resyncronize the connection with the server. |
|
// In this case the underlying connection will have been closed. Close is safe to call multiple times. |
|
Close() error |
|
} |
|
|
|
type batchResults struct { |
|
ctx context.Context |
|
conn *Conn |
|
mrr *pgconn.MultiResultReader |
|
err error |
|
b *Batch |
|
ix int |
|
closed bool |
|
} |
|
|
|
// Exec reads the results from the next query in the batch as if the query has been sent with Exec. |
|
func (br *batchResults) Exec() (pgconn.CommandTag, error) { |
|
if br.err != nil { |
|
return nil, br.err |
|
} |
|
if br.closed { |
|
return nil, fmt.Errorf("batch already closed") |
|
} |
|
|
|
query, arguments, _ := br.nextQueryAndArgs() |
|
|
|
if !br.mrr.NextResult() { |
|
err := br.mrr.Close() |
|
if err == nil { |
|
err = errors.New("no result") |
|
} |
|
if br.conn.shouldLog(LogLevelError) { |
|
br.conn.log(br.ctx, LogLevelError, "BatchResult.Exec", map[string]interface{}{ |
|
"sql": query, |
|
"args": logQueryArgs(arguments), |
|
"err": err, |
|
}) |
|
} |
|
return nil, err |
|
} |
|
|
|
commandTag, err := br.mrr.ResultReader().Close() |
|
|
|
if err != nil { |
|
if br.conn.shouldLog(LogLevelError) { |
|
br.conn.log(br.ctx, LogLevelError, "BatchResult.Exec", map[string]interface{}{ |
|
"sql": query, |
|
"args": logQueryArgs(arguments), |
|
"err": err, |
|
}) |
|
} |
|
} else if br.conn.shouldLog(LogLevelInfo) { |
|
br.conn.log(br.ctx, LogLevelInfo, "BatchResult.Exec", map[string]interface{}{ |
|
"sql": query, |
|
"args": logQueryArgs(arguments), |
|
"commandTag": commandTag, |
|
}) |
|
} |
|
|
|
return commandTag, err |
|
} |
|
|
|
// Query reads the results from the next query in the batch as if the query has been sent with Query. |
|
func (br *batchResults) Query() (Rows, error) { |
|
query, arguments, ok := br.nextQueryAndArgs() |
|
if !ok { |
|
query = "batch query" |
|
} |
|
|
|
if br.err != nil { |
|
return &connRows{err: br.err, closed: true}, br.err |
|
} |
|
|
|
if br.closed { |
|
alreadyClosedErr := fmt.Errorf("batch already closed") |
|
return &connRows{err: alreadyClosedErr, closed: true}, alreadyClosedErr |
|
} |
|
|
|
rows := br.conn.getRows(br.ctx, query, arguments) |
|
|
|
if !br.mrr.NextResult() { |
|
rows.err = br.mrr.Close() |
|
if rows.err == nil { |
|
rows.err = errors.New("no result") |
|
} |
|
rows.closed = true |
|
|
|
if br.conn.shouldLog(LogLevelError) { |
|
br.conn.log(br.ctx, LogLevelError, "BatchResult.Query", map[string]interface{}{ |
|
"sql": query, |
|
"args": logQueryArgs(arguments), |
|
"err": rows.err, |
|
}) |
|
} |
|
|
|
return rows, rows.err |
|
} |
|
|
|
rows.resultReader = br.mrr.ResultReader() |
|
return rows, nil |
|
} |
|
|
|
// QueryFunc reads the results from the next query in the batch as if the query has been sent with Conn.QueryFunc. |
|
func (br *batchResults) QueryFunc(scans []interface{}, f func(QueryFuncRow) error) (pgconn.CommandTag, error) { |
|
if br.closed { |
|
return nil, fmt.Errorf("batch already closed") |
|
} |
|
|
|
rows, err := br.Query() |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer rows.Close() |
|
|
|
for rows.Next() { |
|
err = rows.Scan(scans...) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
err = f(rows) |
|
if err != nil { |
|
return nil, err |
|
} |
|
} |
|
|
|
if err := rows.Err(); err != nil { |
|
return nil, err |
|
} |
|
|
|
return rows.CommandTag(), nil |
|
} |
|
|
|
// QueryRow reads the results from the next query in the batch as if the query has been sent with QueryRow. |
|
func (br *batchResults) QueryRow() Row { |
|
rows, _ := br.Query() |
|
return (*connRow)(rows.(*connRows)) |
|
|
|
} |
|
|
|
// Close closes the batch operation. Any error that occurred during a batch operation may have made it impossible to |
|
// resyncronize the connection with the server. In this case the underlying connection will have been closed. |
|
func (br *batchResults) Close() error { |
|
if br.err != nil { |
|
return br.err |
|
} |
|
|
|
if br.closed { |
|
return nil |
|
} |
|
br.closed = true |
|
|
|
// log any queries that haven't yet been logged by Exec or Query |
|
for { |
|
query, args, ok := br.nextQueryAndArgs() |
|
if !ok { |
|
break |
|
} |
|
|
|
if br.conn.shouldLog(LogLevelInfo) { |
|
br.conn.log(br.ctx, LogLevelInfo, "BatchResult.Close", map[string]interface{}{ |
|
"sql": query, |
|
"args": logQueryArgs(args), |
|
}) |
|
} |
|
} |
|
|
|
return br.mrr.Close() |
|
} |
|
|
|
func (br *batchResults) nextQueryAndArgs() (query string, args []interface{}, ok bool) { |
|
if br.b != nil && br.ix < len(br.b.items) { |
|
bi := br.b.items[br.ix] |
|
query = bi.query |
|
args = bi.arguments |
|
ok = true |
|
br.ix++ |
|
} |
|
return |
|
}
|
|
|