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

feat: add prefer execParams option #1172

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ _testmain.go
*.exe

.envrc
.idea
45 changes: 44 additions & 1 deletion conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -440,13 +460,31 @@ 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
}
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...)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 10 additions & 1 deletion conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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)

Expand Down