github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/workload/tpcc/delivery.go (about)

     1  // Copyright 2017 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 tpcc
    12  
    13  import (
    14  	"context"
    15  	gosql "database/sql"
    16  	"fmt"
    17  	"strings"
    18  	"sync/atomic"
    19  
    20  	"github.com/cockroachdb/cockroach-go/crdb"
    21  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    22  	"github.com/cockroachdb/cockroach/pkg/workload"
    23  	"github.com/cockroachdb/errors"
    24  	"golang.org/x/exp/rand"
    25  )
    26  
    27  // 2.7 The Delivery Transaction
    28  
    29  // The Delivery business transaction consists of processing a batch of 10 new
    30  // (not yet delivered) orders. Each order is processed (delivered) in full
    31  // within the scope of a read-write database transaction. The number of orders
    32  // delivered as a group (or batched) within the same database transaction is
    33  // implementation specific. The business transaction, comprised of one or more
    34  // (up to 10) database transactions, has a low frequency of execution and must
    35  // complete within a relaxed response time requirement.
    36  
    37  // The Delivery transaction is intended to be executed in deferred mode through
    38  // a queuing mechanism, rather than interactively, with terminal response
    39  // indicating transaction completion. The result of the deferred execution is
    40  // recorded into a result file.
    41  
    42  type delivery struct {
    43  	config *tpcc
    44  	mcp    *workload.MultiConnPool
    45  	sr     workload.SQLRunner
    46  
    47  	selectNewOrder workload.StmtHandle
    48  	sumAmount      workload.StmtHandle
    49  }
    50  
    51  var _ tpccTx = &delivery{}
    52  
    53  func createDelivery(
    54  	ctx context.Context, config *tpcc, mcp *workload.MultiConnPool,
    55  ) (tpccTx, error) {
    56  	del := &delivery{
    57  		config: config,
    58  		mcp:    mcp,
    59  	}
    60  
    61  	del.selectNewOrder = del.sr.Define(`
    62  		SELECT no_o_id
    63  		FROM new_order
    64  		WHERE no_w_id = $1 AND no_d_id = $2
    65  		ORDER BY no_o_id ASC
    66  		LIMIT 1`,
    67  	)
    68  
    69  	del.sumAmount = del.sr.Define(`
    70  		SELECT sum(ol_amount) FROM order_line
    71  		WHERE ol_w_id = $1 AND ol_d_id = $2 AND ol_o_id = $3`,
    72  	)
    73  
    74  	if err := del.sr.Init(ctx, "delivery", mcp, config.connFlags); err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	return del, nil
    79  }
    80  
    81  func (del *delivery) run(ctx context.Context, wID int) (interface{}, error) {
    82  	atomic.AddUint64(&del.config.auditor.deliveryTransactions, 1)
    83  
    84  	rng := rand.New(rand.NewSource(uint64(timeutil.Now().UnixNano())))
    85  
    86  	oCarrierID := rng.Intn(10) + 1
    87  	olDeliveryD := timeutil.Now()
    88  
    89  	tx, err := del.mcp.Get().BeginEx(ctx, del.config.txOpts)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	err = crdb.ExecuteInTx(
    94  		ctx, (*workload.PgxTx)(tx),
    95  		func() error {
    96  			// 2.7.4.2. For each district:
    97  			dIDoIDPairs := make(map[int]int)
    98  			dIDolTotalPairs := make(map[int]float64)
    99  			for dID := 1; dID <= 10; dID++ {
   100  				var oID int
   101  				if err := del.selectNewOrder.QueryRowTx(ctx, tx, wID, dID).Scan(&oID); err != nil {
   102  					// If no matching order is found, the delivery of this order is skipped.
   103  					if !errors.Is(err, gosql.ErrNoRows) {
   104  						atomic.AddUint64(&del.config.auditor.skippedDelivieries, 1)
   105  						return err
   106  					}
   107  					continue
   108  				}
   109  				dIDoIDPairs[dID] = oID
   110  
   111  				var olTotal float64
   112  				if err := del.sumAmount.QueryRowTx(
   113  					ctx, tx, wID, dID, oID,
   114  				).Scan(&olTotal); err != nil {
   115  					return err
   116  				}
   117  				dIDolTotalPairs[dID] = olTotal
   118  			}
   119  			dIDoIDPairsStr := makeInTuples(dIDoIDPairs)
   120  
   121  			rows, err := tx.QueryEx(
   122  				ctx,
   123  				fmt.Sprintf(`
   124  					UPDATE "order"
   125  					SET o_carrier_id = %d
   126  					WHERE o_w_id = %d AND (o_d_id, o_id) IN (%s)
   127  					RETURNING o_d_id, o_c_id`,
   128  					oCarrierID, wID, dIDoIDPairsStr,
   129  				),
   130  				nil, /* options */
   131  			)
   132  			if err != nil {
   133  				return err
   134  			}
   135  			dIDcIDPairs := make(map[int]int)
   136  			for rows.Next() {
   137  				var dID, oCID int
   138  				if err := rows.Scan(&dID, &oCID); err != nil {
   139  					rows.Close()
   140  					return err
   141  				}
   142  				dIDcIDPairs[dID] = oCID
   143  			}
   144  			if err := rows.Err(); err != nil {
   145  				return err
   146  			}
   147  			rows.Close()
   148  
   149  			if err := checkSameKeys(dIDoIDPairs, dIDcIDPairs); err != nil {
   150  				return err
   151  			}
   152  			dIDcIDPairsStr := makeInTuples(dIDcIDPairs)
   153  			dIDToOlTotalStr := makeWhereCases(dIDolTotalPairs)
   154  
   155  			if _, err := tx.ExecEx(
   156  				ctx,
   157  				fmt.Sprintf(`
   158  					UPDATE customer
   159  					SET c_delivery_cnt = c_delivery_cnt + 1,
   160  						c_balance = c_balance + CASE c_d_id %s END
   161  					WHERE c_w_id = %d AND (c_d_id, c_id) IN (%s)`,
   162  					dIDToOlTotalStr, wID, dIDcIDPairsStr,
   163  				),
   164  				nil, /* options */
   165  			); err != nil {
   166  				return err
   167  			}
   168  			if _, err := tx.ExecEx(
   169  				ctx,
   170  				fmt.Sprintf(`
   171  					DELETE FROM new_order
   172  					WHERE no_w_id = %d AND (no_d_id, no_o_id) IN (%s)`,
   173  					wID, dIDoIDPairsStr,
   174  				),
   175  				nil, /* options */
   176  			); err != nil {
   177  				return err
   178  			}
   179  
   180  			_, err = tx.ExecEx(
   181  				ctx,
   182  				fmt.Sprintf(`
   183  					UPDATE order_line
   184  					SET ol_delivery_d = '%s'
   185  					WHERE ol_w_id = %d AND (ol_d_id, ol_o_id) IN (%s)`,
   186  					olDeliveryD.Format("2006-01-02 15:04:05"), wID, dIDoIDPairsStr,
   187  				),
   188  				nil, /* options */
   189  			)
   190  			return err
   191  		})
   192  	return nil, err
   193  }
   194  
   195  func makeInTuples(pairs map[int]int) string {
   196  	tupleStrs := make([]string, 0, len(pairs))
   197  	for k, v := range pairs {
   198  		tupleStrs = append(tupleStrs, fmt.Sprintf("(%d, %d)", k, v))
   199  	}
   200  	return strings.Join(tupleStrs, ", ")
   201  }
   202  
   203  func makeWhereCases(cases map[int]float64) string {
   204  	casesStrs := make([]string, 0, len(cases))
   205  	for k, v := range cases {
   206  		casesStrs = append(casesStrs, fmt.Sprintf("WHEN %d THEN %f", k, v))
   207  	}
   208  	return strings.Join(casesStrs, " ")
   209  }
   210  
   211  func checkSameKeys(a, b map[int]int) error {
   212  	if len(a) != len(b) {
   213  		return errors.Errorf("different number of keys")
   214  	}
   215  	for k := range a {
   216  		if _, ok := b[k]; !ok {
   217  			return errors.Errorf("missing key %v", k)
   218  		}
   219  	}
   220  	return nil
   221  }