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 }