github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/workload/ledger/ops_util.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 ledger
    12  
    13  import (
    14  	gosql "database/sql"
    15  	"database/sql/driver"
    16  	"fmt"
    17  	"math/rand"
    18  	"regexp"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    23  	"golang.org/x/sync/syncmap"
    24  )
    25  
    26  // querier is the common interface to execute queries on a DB, Tx, or Conn.
    27  type querier interface {
    28  	Exec(query string, args ...interface{}) (gosql.Result, error)
    29  	Query(query string, args ...interface{}) (*gosql.Rows, error)
    30  	QueryRow(query string, args ...interface{}) *gosql.Row
    31  }
    32  
    33  var sqlParamRE = regexp.MustCompile(`\$(\d+)`)
    34  var replacedSQLParams syncmap.Map
    35  
    36  func replaceSQLParams(s string) string {
    37  	// Memoize the result.
    38  	if res, ok := replacedSQLParams.Load(s); ok {
    39  		return res.(string)
    40  	}
    41  
    42  	res := sqlParamRE.ReplaceAllString(s, "'%[${1}]v'")
    43  	replacedSQLParams.Store(s, res)
    44  	return res
    45  }
    46  
    47  func maybeInlineStmtArgs(
    48  	config *ledger, query string, args ...interface{},
    49  ) (string, []interface{}) {
    50  	if !config.inlineArgs {
    51  		return query, args
    52  	}
    53  	queryFmt := replaceSQLParams(query)
    54  	for i, arg := range args {
    55  		if v, ok := arg.(driver.Valuer); ok {
    56  			val, err := v.Value()
    57  			if err != nil {
    58  				panic(err)
    59  			}
    60  			args[i] = val
    61  		}
    62  	}
    63  	return strings.Replace(
    64  			strings.Replace(
    65  				fmt.Sprintf(queryFmt, args...),
    66  				" UTC", "", -1), // remove UTC suffix from timestamps.
    67  			`'<nil>'`, `NULL`, -1), // fix NULL values.
    68  		nil
    69  }
    70  
    71  type customer struct {
    72  	id               int
    73  	identifier       string
    74  	name             gosql.NullString
    75  	currencyCode     string
    76  	isSystemCustomer bool
    77  	isActive         bool
    78  	created          time.Time
    79  	balance          float64
    80  	creditLimit      gosql.NullFloat64
    81  	sequence         int
    82  }
    83  
    84  func getBalance(q querier, config *ledger, id int, historical bool) (customer, error) {
    85  	aostSpec := ""
    86  	if historical {
    87  		aostSpec = " AS OF SYSTEM TIME '-10s'"
    88  	}
    89  	stmt, args := maybeInlineStmtArgs(config, `
    90  		SELECT
    91  			id,
    92  			identifier,
    93  			"name",
    94  			currency_code,
    95  			is_system_customer,
    96  			is_active,
    97  			created,
    98  			balance,
    99  			credit_limit,
   100  			sequence_number
   101  		FROM customer`+
   102  		aostSpec+`
   103  		WHERE id = $1 AND IS_ACTIVE = true`,
   104  		id,
   105  	)
   106  	rows, err := q.Query(stmt, args...)
   107  	if err != nil {
   108  		return customer{}, err
   109  	}
   110  	defer rows.Close()
   111  
   112  	var c customer
   113  	for rows.Next() {
   114  		if err := rows.Scan(
   115  			&c.id,
   116  			&c.identifier,
   117  			&c.name,
   118  			&c.currencyCode,
   119  			&c.isSystemCustomer,
   120  			&c.isActive,
   121  			&c.created,
   122  			&c.balance,
   123  			&c.creditLimit,
   124  			&c.sequence,
   125  		); err != nil {
   126  			return customer{}, err
   127  		}
   128  	}
   129  	return c, rows.Err()
   130  }
   131  
   132  func updateBalance(q querier, config *ledger, c customer) error {
   133  	stmt, args := maybeInlineStmtArgs(config, `
   134  		UPDATE customer SET
   135  			balance         = $1,
   136  			credit_limit    = $2,
   137  			is_active       = $3,
   138  			name            = $4,
   139  			sequence_number = $5
   140  		WHERE id = $6`,
   141  		c.balance, c.creditLimit, c.isActive, c.name, c.sequence, c.id,
   142  	)
   143  	_, err := q.Exec(stmt, args...)
   144  	return err
   145  }
   146  
   147  func insertTransaction(q querier, config *ledger, rng *rand.Rand, username string) (string, error) {
   148  	tID := randPaymentID(rng)
   149  
   150  	stmt, args := maybeInlineStmtArgs(config, `
   151  		INSERT INTO transaction (
   152  			tcomment, context, response, reversed_by, created_ts, 
   153  			transaction_type_reference, username, external_id
   154  		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
   155  		nil, randContext(rng), randResponse(rng), nil,
   156  		timeutil.Now(), txnTypeReference, username, tID,
   157  	)
   158  	_, err := q.Exec(stmt, args...)
   159  	return tID, err
   160  }
   161  
   162  func insertEntries(q querier, config *ledger, rng *rand.Rand, cIDs [2]int, tID string) error {
   163  	amount1 := randAmount(rng)
   164  	sysAmount := 88.433571
   165  	ts := timeutil.Now()
   166  
   167  	stmt, args := maybeInlineStmtArgs(config, `
   168  		INSERT INTO entry (
   169  			amount, system_amount, created_ts, transaction_id, customer_id, money_type
   170  		) VALUES
   171  			($1 , $2 , $3 , $4 , $5 , $6 ),
   172  			($7 , $8 , $9 , $10, $11, $12)`,
   173  		amount1, sysAmount, ts, tID, cIDs[0], cashMoneyType,
   174  		-amount1, -sysAmount, ts, tID, cIDs[1], cashMoneyType,
   175  	)
   176  	_, err := q.Exec(stmt, args...)
   177  	return err
   178  }
   179  
   180  func getSession(q querier, config *ledger, rng *rand.Rand) error {
   181  	stmt, args := maybeInlineStmtArgs(config, `
   182  		SELECT
   183  			session_id,
   184  			expiry_timestamp,
   185  			data,
   186  			last_update
   187  		FROM session
   188  		WHERE session_id >= $1
   189  		LIMIT 1`,
   190  		randSessionID(rng),
   191  	)
   192  	rows, err := q.Query(stmt, args...)
   193  	if err != nil {
   194  		return err
   195  	}
   196  	defer rows.Close()
   197  
   198  	for rows.Next() {
   199  		// No-op.
   200  	}
   201  	return rows.Err()
   202  }
   203  
   204  func insertSession(q querier, config *ledger, rng *rand.Rand) error {
   205  	stmt, args := maybeInlineStmtArgs(config, `
   206  		INSERT INTO session (
   207  			data, expiry_timestamp, last_update, session_id
   208  		) VALUES ($1, $2, $3, $4)`,
   209  		randSessionData(rng), randTimestamp(rng), timeutil.Now(), randSessionID(rng),
   210  	)
   211  	_, err := q.Exec(stmt, args...)
   212  	return err
   213  }