diff --git a/.gitignore b/.gitignore index 39175a965..7500e87e8 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ _testmain.go *.exe .envrc +.idea diff --git a/conn.go b/conn.go index 4bc5db6bb..5f365771b 100644 --- a/conn.go +++ b/conn.go @@ -37,6 +37,12 @@ type ConnConfig struct { // QueryExOptions.SimpleProtocol. PreferSimpleProtocol bool + // PreferExecParams disables the describe statement round trip when there is no statement cache in use. Instead, pgx + // will execute the statement directly in extended mode by sending the messages Parse-Bind-Execute directly. This + // setting only has effect if statement caching has been disabled, either by setting BuildStatementCache to nil or + // by setting 'statement_cache_capacity=0' in the connection string. + PreferExecParams bool + createdByParseConfig bool // Used to enforce created by ParseConfig rule. } @@ -127,6 +133,9 @@ func ConnectConfig(ctx context.Context, connConfig *ConnConfig) (*Conn, error) { // // prefer_simple_protocol // Possible values: "true" and "false". Use the simple protocol instead of extended protocol. Default: false +// +// prefer_exec_params +// Possible values: "true" and "false". Skip DescribeStatement when statement cache is disabled. Default: true func ParseConfig(connString string) (*ConnConfig, error) { config, err := pgconn.ParseConfig(connString) if err != nil { @@ -173,12 +182,23 @@ func ParseConfig(connString string) (*ConnConfig, error) { } } + preferExecParams := true + if s, ok := config.RuntimeParams["prefer_exec_params"]; ok { + delete(config.RuntimeParams, "prefer_exec_params") + if b, err := strconv.ParseBool(s); err == nil { + preferExecParams = b + } else { + return nil, fmt.Errorf("invalid prefer_exec_params: %v", err) + } + } + connConfig := &ConnConfig{ Config: *config, createdByParseConfig: true, LogLevel: LogLevelInfo, BuildStatementCache: buildStatementCache, PreferSimpleProtocol: preferSimpleProtocol, + PreferExecParams: preferExecParams, connString: connString, } @@ -440,6 +460,14 @@ optionLoop: return c.execPrepared(ctx, sd, arguments) } + if c.config.PreferExecParams { + sd := &pgconn.StatementDescription{ + SQL: sql, + ParamOIDs: c.paramOIDsFromArguments(arguments), + } + return c.execParams(ctx, sd, arguments) + } + sd, err := c.Prepare(ctx, "", sql) if err != nil { return nil, err @@ -447,6 +475,16 @@ optionLoop: return c.execPrepared(ctx, sd, arguments) } +func (c *Conn) paramOIDsFromArguments(arguments []interface{}) []uint32 { + paramOIDs := make([]uint32, len(arguments)) + for i, arg := range arguments { + if dt, ok := c.ConnInfo().DataTypeForValue(arg); ok { + paramOIDs[i] = dt.OID + } + } + return paramOIDs +} + func (c *Conn) execSimpleProtocol(ctx context.Context, sql string, arguments []interface{}) (commandTag pgconn.CommandTag, err error) { if len(arguments) > 0 { sql, err = c.sanitizeForSimpleQuery(sql, arguments...) @@ -600,6 +638,11 @@ optionLoop: rows.fatal(err) return rows, rows.err } + } else if c.config.PreferExecParams { + sd = &pgconn.StatementDescription{ + SQL: sql, + ParamOIDs: c.paramOIDsFromArguments(args), + } } else { sd, err = c.pgConn.Prepare(ctx, "", sql, nil) if err != nil { @@ -644,7 +687,7 @@ optionLoop: resultFormats = c.eqb.resultFormats } - if c.stmtcache != nil && c.stmtcache.Mode() == stmtcache.ModeDescribe { + if (c.stmtcache == nil && c.config.PreferExecParams) || (c.stmtcache != nil && c.stmtcache.Mode() == stmtcache.ModeDescribe) { rows.resultReader = c.pgConn.ExecParams(ctx, sql, c.eqb.paramValues, sd.ParamOIDs, c.eqb.paramFormats, resultFormats) } else { rows.resultReader = c.pgConn.ExecPrepared(ctx, sd.Name, c.eqb.paramValues, c.eqb.paramFormats, resultFormats) diff --git a/conn_test.go b/conn_test.go index beddcdcd5..74377f8f0 100644 --- a/conn_test.go +++ b/conn_test.go @@ -328,10 +328,17 @@ func TestExecStatementCacheModes(t *testing.T) { tests := []struct { name string buildStatementCache pgx.BuildStatementCacheFunc + preferExecParams bool }{ { - name: "disabled", + name: "disabled - execPrepared", buildStatementCache: nil, + preferExecParams: false, + }, + { + name: "disabled - execParams", + buildStatementCache: nil, + preferExecParams: true, }, { name: "prepare", @@ -344,12 +351,14 @@ func TestExecStatementCacheModes(t *testing.T) { buildStatementCache: func(conn *pgconn.PgConn) stmtcache.Cache { return stmtcache.New(conn, stmtcache.ModeDescribe, 32) }, + preferExecParams: false, }, } for _, tt := range tests { func() { config.BuildStatementCache = tt.buildStatementCache + config.PreferExecParams = tt.preferExecParams conn := mustConnect(t, config) defer closeConn(t, conn)