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