github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/workload/sql_runner.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package workload 12 13 import ( 14 "context" 15 "fmt" 16 "strings" 17 18 "github.com/cockroachdb/errors" 19 "github.com/jackc/pgx" 20 ) 21 22 // SQLRunner is a helper for issuing SQL statements; it supports multiple 23 // methods for issuing queries. 24 // 25 // Queries need to first be defined using calls to Define. Then the runner 26 // must be initialized, after which we can use the handles returned by Define. 27 // 28 // Sample usage: 29 // sr := &workload.SQLRunner{} 30 // 31 // sel:= sr.Define("SELECT x FROM t WHERE y = $1") 32 // ins:= sr.Define("INSERT INTO t(x, y) VALUES ($1, $2)") 33 // 34 // err := sr.Init(ctx, conn, flags) 35 // // [handle err] 36 // 37 // row := sel.QueryRow(1) 38 // // [use row] 39 // 40 // _, err := ins.Exec(5, 6) 41 // // [handle err] 42 // 43 // A runner should typically be associated with a single worker. 44 type SQLRunner struct { 45 // The fields below are used by Define. 46 stmts []*stmt 47 48 // The fields below are set by Init. 49 initialized bool 50 method method 51 mcp *MultiConnPool 52 } 53 54 type method int 55 56 const ( 57 prepare method = iota 58 noprepare 59 simple 60 ) 61 62 var stringToMethod = map[string]method{ 63 "prepare": prepare, 64 "noprepare": noprepare, 65 "simple": simple, 66 } 67 68 // Define creates a handle for the given statement. The handle can be used after 69 // Init is called. 70 func (sr *SQLRunner) Define(sql string) StmtHandle { 71 if sr.initialized { 72 panic("Define can't be called after Init") 73 } 74 s := &stmt{sr: sr, sql: sql} 75 sr.stmts = append(sr.stmts, s) 76 return StmtHandle{s: s} 77 } 78 79 // Init initializes the runner; must be called after calls to Define and before 80 // the StmtHandles are used. 81 // 82 // The name is used for naming prepared statements. Multiple workers that use 83 // the same set of defined queries can and should use the same name. 84 // 85 // The way we issue queries is set by flags.Method: 86 // 87 // - "prepare": we prepare the query once during Init, then we reuse it for 88 // each execution. This results in a Bind and Execute on the server each time 89 // we run a query (on the given connection). Note that it's important to 90 // prepare on separate connections if there are many parallel workers; this 91 // avoids lock contention in the sql.Rows objects they produce. See #30811. 92 // 93 // - "noprepare": each query is issued separately (on the given connection). 94 // This results in Parse, Bind, Execute on the server each time we run a 95 // query. 96 // 97 // - "simple": each query is issued in a single string; parameters are 98 // rendered inside the string. This results in a single SimpleExecute 99 // request to the server for each query. Note that only a few parameter types 100 // are supported. 101 // 102 func (sr *SQLRunner) Init( 103 ctx context.Context, name string, mcp *MultiConnPool, flags *ConnFlags, 104 ) error { 105 if sr.initialized { 106 panic("already initialized") 107 } 108 109 var ok bool 110 sr.method, ok = stringToMethod[strings.ToLower(flags.Method)] 111 if !ok { 112 return errors.Errorf("unknown method %s", flags.Method) 113 } 114 115 if sr.method == prepare { 116 for i, s := range sr.stmts { 117 stmtName := fmt.Sprintf("%s-%d", name, i+1) 118 var err error 119 s.prepared, err = mcp.PrepareEx(ctx, stmtName, s.sql, nil /* opts */) 120 if err != nil { 121 return errors.Wrapf(err, "preparing %s", s.sql) 122 } 123 } 124 } 125 126 sr.mcp = mcp 127 sr.initialized = true 128 return nil 129 } 130 131 func (h StmtHandle) check() { 132 if !h.s.sr.initialized { 133 panic("SQLRunner.Init not called") 134 } 135 } 136 137 var simpleProtocolOpt = &pgx.QueryExOptions{SimpleProtocol: true} 138 139 type stmt struct { 140 sr *SQLRunner 141 sql string 142 // prepared is only used for the prepare method. 143 prepared *pgx.PreparedStatement 144 } 145 146 // StmtHandle is associated with a (possibly prepared) statement; created by 147 // SQLRunner.Define. 148 type StmtHandle struct { 149 s *stmt 150 } 151 152 // Exec executes a query that doesn't return rows. The query is executed on the 153 // connection that was passed to SQLRunner.Init. 154 // 155 // See pgx.Conn.Exec. 156 func (h StmtHandle) Exec(ctx context.Context, args ...interface{}) (pgx.CommandTag, error) { 157 h.check() 158 p := h.s.sr.mcp.Get() 159 switch h.s.sr.method { 160 case prepare: 161 return p.ExecEx(ctx, h.s.prepared.Name, nil /* options */, args...) 162 163 case noprepare: 164 return p.ExecEx(ctx, h.s.sql, nil /* options */, args...) 165 166 case simple: 167 return p.ExecEx(ctx, h.s.sql, simpleProtocolOpt, args...) 168 169 default: 170 panic("invalid method") 171 } 172 } 173 174 // ExecTx executes a query that doesn't return rows, inside a transaction. 175 // 176 // See pgx.Conn.Exec. 177 func (h StmtHandle) ExecTx( 178 ctx context.Context, tx *pgx.Tx, args ...interface{}, 179 ) (pgx.CommandTag, error) { 180 h.check() 181 switch h.s.sr.method { 182 case prepare: 183 return tx.ExecEx(ctx, h.s.prepared.Name, nil /* options */, args...) 184 185 case noprepare: 186 return tx.ExecEx(ctx, h.s.sql, nil /* options */, args...) 187 188 case simple: 189 return tx.ExecEx(ctx, h.s.sql, simpleProtocolOpt, args...) 190 191 default: 192 panic("invalid method") 193 } 194 } 195 196 // Query executes a query that returns rows. 197 // 198 // See pgx.Conn.Query. 199 func (h StmtHandle) Query(ctx context.Context, args ...interface{}) (*pgx.Rows, error) { 200 h.check() 201 p := h.s.sr.mcp.Get() 202 switch h.s.sr.method { 203 case prepare: 204 return p.QueryEx(ctx, h.s.prepared.Name, nil /* options */, args...) 205 206 case noprepare: 207 return p.QueryEx(ctx, h.s.sql, nil /* options */, args...) 208 209 case simple: 210 return p.QueryEx(ctx, h.s.sql, simpleProtocolOpt, args...) 211 212 default: 213 panic("invalid method") 214 } 215 } 216 217 // QueryTx executes a query that returns rows, inside a transaction. 218 // 219 // See pgx.Tx.Query. 220 func (h StmtHandle) QueryTx( 221 ctx context.Context, tx *pgx.Tx, args ...interface{}, 222 ) (*pgx.Rows, error) { 223 h.check() 224 switch h.s.sr.method { 225 case prepare: 226 return tx.QueryEx(ctx, h.s.prepared.Name, nil /* options */, args...) 227 228 case noprepare: 229 return tx.QueryEx(ctx, h.s.sql, nil /* options */, args...) 230 231 case simple: 232 return tx.QueryEx(ctx, h.s.sql, simpleProtocolOpt, args...) 233 234 default: 235 panic("invalid method") 236 } 237 } 238 239 // QueryRow executes a query that is expected to return at most one row. 240 // 241 // See pgx.Conn.QueryRow. 242 func (h StmtHandle) QueryRow(ctx context.Context, args ...interface{}) *pgx.Row { 243 h.check() 244 p := h.s.sr.mcp.Get() 245 switch h.s.sr.method { 246 case prepare: 247 return p.QueryRowEx(ctx, h.s.prepared.Name, nil /* options */, args...) 248 249 case noprepare: 250 return p.QueryRowEx(ctx, h.s.sql, nil /* options */, args...) 251 252 case simple: 253 return p.QueryRowEx(ctx, h.s.sql, simpleProtocolOpt, args...) 254 255 default: 256 panic("invalid method") 257 } 258 } 259 260 // QueryRowTx executes a query that is expected to return at most one row, 261 // inside a transaction. 262 // 263 // See pgx.Conn.QueryRow. 264 func (h StmtHandle) QueryRowTx(ctx context.Context, tx *pgx.Tx, args ...interface{}) *pgx.Row { 265 h.check() 266 switch h.s.sr.method { 267 case prepare: 268 return tx.QueryRowEx(ctx, h.s.prepared.Name, nil /* options */, args...) 269 270 case noprepare: 271 return tx.QueryRowEx(ctx, h.s.sql, nil /* options */, args...) 272 273 case simple: 274 return tx.QueryRowEx(ctx, h.s.sql, simpleProtocolOpt, args...) 275 276 default: 277 panic("invalid method") 278 } 279 } 280 281 // Appease the linter. 282 var _ = StmtHandle.QueryRow