2
0

Prepare takes context

Also remove PrepareEx. It's primary usage was for context. Supplying
parameter OIDs is unnecessary when you can type cast in the query SQL.
If it does become necessary or desirable to add options back it can be
added in a backwards compatible way by adding a varargs as last
argument.
This commit is contained in:
Jack Christensen
2019-04-20 11:47:16 -05:00
parent dc699cefc7
commit 66625e6489
11 changed files with 25 additions and 82 deletions
+1 -1
View File
@@ -165,7 +165,7 @@ func TestConnBeginBatchWithPreparedStatement(t *testing.T) {
conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE")) conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(t, conn) defer closeConn(t, conn)
_, err := conn.Prepare("ps1", "select n from generate_series(0,$1::int) n") _, err := conn.Prepare(context.Background(), "ps1", "select n from generate_series(0,$1::int) n")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+2 -2
View File
@@ -10,7 +10,7 @@ func BenchmarkPgtypeInt4ParseBinary(b *testing.B) {
conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE")) conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(b, conn) defer closeConn(b, conn)
_, err := conn.Prepare("selectBinary", "select n::int4 from generate_series(1, 100) n") _, err := conn.Prepare(context.Background(), "selectBinary", "select n::int4 from generate_series(1, 100) n")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -41,7 +41,7 @@ func BenchmarkPgtypeInt4EncodeBinary(b *testing.B) {
conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE")) conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(b, conn) defer closeConn(b, conn)
_, err := conn.Prepare("encodeBinary", "select $1::int4, $2::int4, $3::int4, $4::int4, $5::int4, $6::int4, $7::int4") _, err := conn.Prepare(context.Background(), "encodeBinary", "select $1::int4, $2::int4, $3::int4, $4::int4, $5::int4, $6::int4, $7::int4")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
+5 -5
View File
@@ -17,7 +17,7 @@ func BenchmarkPointerPointerWithNullValues(b *testing.B) {
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE"))) conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
defer closeConn(b, conn) defer closeConn(b, conn)
_, err := conn.Prepare("selectNulls", "select 1::int4, 'johnsmith', null::text, null::text, null::text, null::date, null::timestamptz") _, err := conn.Prepare(context.Background(), "selectNulls", "select 1::int4, 'johnsmith', null::text, null::text, null::text, null::date, null::timestamptz")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -77,7 +77,7 @@ func BenchmarkPointerPointerWithPresentValues(b *testing.B) {
conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE"))) conn := mustConnect(b, mustParseConfig(b, os.Getenv("PGX_TEST_DATABASE")))
defer closeConn(b, conn) defer closeConn(b, conn)
_, err := conn.Prepare("selectNulls", "select 1::int4, 'johnsmith', 'johnsmith@example.com', 'John Smith', 'male', '1970-01-01'::date, '2015-01-01 00:00:00'::timestamptz") _, err := conn.Prepare(context.Background(), "selectNulls", "select 1::int4, 'johnsmith', 'johnsmith@example.com', 'John Smith', 'male', '1970-01-01'::date, '2015-01-01 00:00:00'::timestamptz")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -189,7 +189,7 @@ func BenchmarkSelectWithLoggingErrorWithDiscard(b *testing.B) {
} }
func benchmarkSelectWithLog(b *testing.B, conn *pgx.Conn) { func benchmarkSelectWithLog(b *testing.B, conn *pgx.Conn) {
_, err := conn.Prepare("test", "select 1::int4, 'johnsmith', 'johnsmith@example.com', 'John Smith', 'male', '1970-01-01'::date, '2015-01-01 00:00:00'::timestamptz") _, err := conn.Prepare(context.Background(), "test", "select 1::int4, 'johnsmith', 'johnsmith@example.com', 'John Smith', 'male', '1970-01-01'::date, '2015-01-01 00:00:00'::timestamptz")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -339,7 +339,7 @@ func benchmarkWriteNRowsViaInsert(b *testing.B, n int) {
defer closeConn(b, conn) defer closeConn(b, conn)
mustExec(b, conn, benchmarkWriteTableCreateSQL) mustExec(b, conn, benchmarkWriteTableCreateSQL)
_, err := conn.Prepare("insert_t", benchmarkWriteTableInsertSQL) _, err := conn.Prepare(context.Background(), "insert_t", benchmarkWriteTableInsertSQL)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -450,7 +450,7 @@ func benchmarkWriteNRowsViaMultiInsert(b *testing.B, n int) {
defer closeConn(b, conn) defer closeConn(b, conn)
mustExec(b, conn, benchmarkWriteTableCreateSQL) mustExec(b, conn, benchmarkWriteTableCreateSQL)
_, err := conn.Prepare("insert_t", benchmarkWriteTableInsertSQL) _, err := conn.Prepare(context.Background(), "insert_t", benchmarkWriteTableInsertSQL)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
+3 -27
View File
@@ -216,18 +216,7 @@ func (c *Conn) ParameterStatus(key string) string {
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same // Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same
// name and sql arguments. This allows a code path to Prepare and Query/Exec without // name and sql arguments. This allows a code path to Prepare and Query/Exec without
// concern for if the statement has already been prepared. // concern for if the statement has already been prepared.
func (c *Conn) Prepare(name, sql string) (ps *PreparedStatement, err error) { func (c *Conn) Prepare(ctx context.Context, name, sql string) (ps *PreparedStatement, err error) {
return c.PrepareEx(context.Background(), name, sql, nil)
}
// PrepareEx creates a prepared statement with name and sql. sql can contain placeholders
// for bound parameters. These placeholders are referenced positional as $1, $2, etc.
// It differs from Prepare as it allows additional options (such as parameter OIDs) to be passed via struct
//
// PrepareEx is idempotent; i.e. it is safe to call PrepareEx multiple times with the same
// name and sql arguments. This allows a code path to PrepareEx and Query/Exec without
// concern for if the statement has already been prepared.
func (c *Conn) PrepareEx(ctx context.Context, name, sql string, opts *PrepareExOptions) (ps *PreparedStatement, err error) {
if name != "" { if name != "" {
if ps, ok := c.preparedStatements[name]; ok && ps.SQL == sql { if ps, ok := c.preparedStatements[name]; ok && ps.SQL == sql {
return ps, nil return ps, nil
@@ -237,25 +226,12 @@ func (c *Conn) PrepareEx(ctx context.Context, name, sql string, opts *PrepareExO
if c.shouldLog(LogLevelError) { if c.shouldLog(LogLevelError) {
defer func() { defer func() {
if err != nil { if err != nil {
c.log(LogLevelError, "prepareEx failed", map[string]interface{}{"err": err, "name": name, "sql": sql}) c.log(LogLevelError, "Prepare failed", map[string]interface{}{"err": err, "name": name, "sql": sql})
} }
}() }()
} }
if opts == nil { psd, err := c.pgConn.Prepare(context.TODO(), name, sql, nil)
opts = &PrepareExOptions{}
}
if len(opts.ParameterOIDs) > 65535 {
return nil, errors.Errorf("Number of PrepareExOptions ParameterOIDs must be between 0 and 65535, received %d", len(opts.ParameterOIDs))
}
var paramOIDs []uint32
for _, oid := range opts.ParameterOIDs {
paramOIDs = append(paramOIDs, uint32(oid))
}
psd, err := c.pgConn.Prepare(context.TODO(), name, sql, paramOIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+5 -33
View File
@@ -281,7 +281,7 @@ func TestPrepare(t *testing.T) {
conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE")) conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(t, conn) defer closeConn(t, conn)
_, err := conn.Prepare("test", "select $1::varchar") _, err := conn.Prepare(context.Background(), "test", "select $1::varchar")
if err != nil { if err != nil {
t.Errorf("Unable to prepare statement: %v", err) t.Errorf("Unable to prepare statement: %v", err)
return return
@@ -305,7 +305,7 @@ func TestPrepare(t *testing.T) {
// Create another prepared statement to ensure Deallocate left the connection // Create another prepared statement to ensure Deallocate left the connection
// in a working state and that we can reuse the prepared statement name. // in a working state and that we can reuse the prepared statement name.
_, err = conn.Prepare("test", "select $1::integer") _, err = conn.Prepare(context.Background(), "test", "select $1::integer")
if err != nil { if err != nil {
t.Errorf("Unable to prepare statement: %v", err) t.Errorf("Unable to prepare statement: %v", err)
return return
@@ -333,7 +333,7 @@ func TestPrepareBadSQLFailure(t *testing.T) {
conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE")) conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(t, conn) defer closeConn(t, conn)
if _, err := conn.Prepare("badSQL", "select foo"); err == nil { if _, err := conn.Prepare(context.Background(), "badSQL", "select foo"); err == nil {
t.Fatal("Prepare should have failed with syntax error") t.Fatal("Prepare should have failed with syntax error")
} }
@@ -347,7 +347,7 @@ func TestPrepareIdempotency(t *testing.T) {
defer closeConn(t, conn) defer closeConn(t, conn)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
_, err := conn.Prepare("test", "select 42::integer") _, err := conn.Prepare(context.Background(), "test", "select 42::integer")
if err != nil { if err != nil {
t.Fatalf("%d. Unable to prepare statement: %v", i, err) t.Fatalf("%d. Unable to prepare statement: %v", i, err)
} }
@@ -363,41 +363,13 @@ func TestPrepareIdempotency(t *testing.T) {
} }
} }
_, err := conn.Prepare("test", "select 'fail'::varchar") _, err := conn.Prepare(context.Background(), "test", "select 'fail'::varchar")
if err == nil { if err == nil {
t.Fatalf("Prepare statement with same name but different SQL should have failed but it didn't") t.Fatalf("Prepare statement with same name but different SQL should have failed but it didn't")
return return
} }
} }
func TestPrepareEx(t *testing.T) {
t.Parallel()
conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(t, conn)
_, err := conn.PrepareEx(context.Background(), "test", "select $1", &pgx.PrepareExOptions{ParameterOIDs: []pgtype.OID{pgtype.TextOID}})
if err != nil {
t.Errorf("Unable to prepare statement: %v", err)
return
}
var s string
err = conn.QueryRow(context.Background(), "test", "hello").Scan(&s)
if err != nil {
t.Errorf("Executing prepared statement failed: %v", err)
}
if s != "hello" {
t.Errorf("Prepared statement did not return expected value: %v", s)
}
err = conn.Deallocate(context.Background(), "test")
if err != nil {
t.Errorf("conn.Deallocate failed: %v", err)
}
}
func TestFatalRxError(t *testing.T) { func TestFatalRxError(t *testing.T) {
t.Parallel() t.Parallel()
+1 -1
View File
@@ -68,7 +68,7 @@ func (ct *copyFrom) run(ctx context.Context) (int, error) {
} }
quotedColumnNames := cbuf.String() quotedColumnNames := cbuf.String()
ps, err := ct.conn.Prepare("", fmt.Sprintf("select %s from %s", quotedColumnNames, quotedTableName)) ps, err := ct.conn.Prepare(ctx, "", fmt.Sprintf("select %s from %s", quotedColumnNames, quotedTableName))
if err != nil { if err != nil {
return 0, err return 0, err
} }
+1 -1
View File
@@ -70,7 +70,7 @@ func TestHstoreArrayTranscode(t *testing.T) {
Status: pgtype.Present, Status: pgtype.Present,
} }
ps, err := conn.Prepare("test", "select $1::hstore[]") ps, err := conn.Prepare(context.Background(), "test", "select $1::hstore[]")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+1 -1
View File
@@ -85,7 +85,7 @@ func TestRecordTranscode(t *testing.T) {
for i, tt := range tests { for i, tt := range tests {
psName := fmt.Sprintf("test%d", i) psName := fmt.Sprintf("test%d", i)
ps, err := conn.Prepare(psName, tt.sql) ps, err := conn.Prepare(context.Background(), psName, tt.sql)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+2 -2
View File
@@ -107,7 +107,7 @@ func TestPgxSuccessfulTranscodeEqFunc(t testing.TB, pgTypeName string, values []
conn := MustConnectPgx(t) conn := MustConnectPgx(t)
defer MustCloseContext(t, conn) defer MustCloseContext(t, conn)
_, err := conn.Prepare("test", fmt.Sprintf("select $1::%s", pgTypeName)) _, err := conn.Prepare(context.Background(), "test", fmt.Sprintf("select $1::%s", pgTypeName))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -225,7 +225,7 @@ func TestPgxSuccessfulNormalizeEqFunc(t testing.TB, tests []NormalizeTest, eqFun
for i, tt := range tests { for i, tt := range tests {
for _, fc := range formats { for _, fc := range formats {
psName := fmt.Sprintf("test%d", i) psName := fmt.Sprintf("test%d", i)
ps, err := conn.Prepare(psName, tt.SQL) ps, err := conn.Prepare(context.Background(), psName, tt.SQL)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+2 -2
View File
@@ -162,7 +162,7 @@ func (c *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e
name := fmt.Sprintf("pgx_%d", c.psCount) name := fmt.Sprintf("pgx_%d", c.psCount)
c.psCount++ c.psCount++
ps, err := c.conn.PrepareEx(ctx, name, query, nil) ps, err := c.conn.Prepare(ctx, name, query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -242,7 +242,7 @@ func (c *Conn) QueryContext(ctx context.Context, query string, argsV []driver.Na
// TODO - remove hack that creates a new prepared statement for every query -- put in place because of problem preparing empty statement name // TODO - remove hack that creates a new prepared statement for every query -- put in place because of problem preparing empty statement name
psname := fmt.Sprintf("stdlibpx%v", &argsV) psname := fmt.Sprintf("stdlibpx%v", &argsV)
ps, err := c.conn.PrepareEx(ctx, psname, query, nil) ps, err := c.conn.Prepare(ctx, psname, query)
if err != nil { if err != nil {
// since PrepareEx failed, we didn't actually get to send the values, so // since PrepareEx failed, we didn't actually get to send the values, so
// we can safely retry // we can safely retry
+2 -7
View File
@@ -158,17 +158,12 @@ func (tx *Tx) Exec(ctx context.Context, sql string, arguments ...interface{}) (c
} }
// Prepare delegates to the underlying *Conn // Prepare delegates to the underlying *Conn
func (tx *Tx) Prepare(name, sql string) (*PreparedStatement, error) { func (tx *Tx) Prepare(ctx context.Context, name, sql string) (*PreparedStatement, error) {
return tx.PrepareEx(context.Background(), name, sql, nil)
}
// PrepareEx delegates to the underlying *Conn
func (tx *Tx) PrepareEx(ctx context.Context, name, sql string, opts *PrepareExOptions) (*PreparedStatement, error) {
if tx.status != TxStatusInProgress { if tx.status != TxStatusInProgress {
return nil, ErrTxClosed return nil, ErrTxClosed
} }
return tx.conn.PrepareEx(ctx, name, sql, opts) return tx.conn.Prepare(ctx, name, sql)
} }
// Query delegates to the underlying *Conn // Query delegates to the underlying *Conn