github.com/square/finch@v0.0.0-20240412205204-6530c03e2b96/finch.go (about)

     1  // Copyright 2024 Block, Inc.
     2  
     3  package finch
     4  
     5  import (
     6  	"database/sql"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"net/http"
    11  	"os"
    12  	"path"
    13  	"regexp"
    14  	"runtime"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  )
    19  
    20  const (
    21  	VERSION = "1.0.0"
    22  
    23  	DEFAULT_SERVER_PORT = "33075"
    24  
    25  	COPY_NUMBER = `/*!copy-number*/`
    26  
    27  	NOOP_COLUMN = "_"
    28  
    29  	ROWS = 100000
    30  )
    31  
    32  type RunLevel struct {
    33  	Stage         uint
    34  	StageName     string
    35  	ExecGroup     uint
    36  	ExecGroupName string
    37  	ClientGroup   uint
    38  	Client        uint
    39  	Trx           uint
    40  	TrxName       string
    41  	Query         uint64
    42  }
    43  
    44  func (s RunLevel) String() string {
    45  	return fmt.Sprintf("%d(%s)/e%d(%s)/g%d/c%d/t%d(%s)/q%d",
    46  		s.Stage, s.StageName, s.ExecGroup, s.ExecGroupName, s.ClientGroup, s.Client, s.Trx, s.TrxName, s.Query)
    47  }
    48  
    49  func (s RunLevel) ClientId() string {
    50  	return fmt.Sprintf("%d(%s)/e%d(%s)/g%d/c%d",
    51  		s.Stage, s.StageName, s.ExecGroup, s.ExecGroupName, s.ClientGroup, s.Client)
    52  }
    53  
    54  const (
    55  	SCOPE_GLOBAL       = "global"
    56  	SCOPE_STAGE        = "stage"
    57  	SCOPE_WORKLOAD     = "workload"
    58  	SCOPE_EXEC_GROUP   = "exec-group"
    59  	SCOPE_CLIENT_GROUP = "client-group"
    60  	SCOPE_CLIENT       = "client"
    61  	SCOPE_ITER         = "iter"
    62  	SCOPE_TRX          = "trx"
    63  	SCOPE_STATEMENT    = "statement"
    64  	SCOPE_ROW          = "row" // special: INSERT INTO t VALUES (@d), (@d), ...
    65  	SCOPE_VALUE        = "value"
    66  )
    67  
    68  func (rl RunLevel) array() []uint {
    69  	return []uint{
    70  		uint(rl.Query), // 0
    71  		rl.Trx,         // 1
    72  		0,              // 2 ITER (data scope)
    73  		rl.Client,      // 3
    74  		rl.ClientGroup, // 4
    75  		rl.ExecGroup,   // 5
    76  		1,              // 6 WORKLOAD not counted
    77  		rl.Stage,       // 7 STAGE
    78  		1,              // 8 GLOBAL
    79  	}
    80  }
    81  
    82  func (rl RunLevel) GreaterThan(prev RunLevel, s string) bool {
    83  	switch s {
    84  	case SCOPE_VALUE:
    85  		return true // all value scoped @d are unique
    86  	case SCOPE_ROW:
    87  		s = SCOPE_STATEMENT // row scoped @d per statement
    88  	case SCOPE_ITER:
    89  		s = SCOPE_CLIENT // iter scoped @d per client
    90  	}
    91  	n := RunLevelNumber(s)
    92  	now := rl.array()
    93  	before := prev.array()
    94  	for i := n; i < uint(len(now)); i++ {
    95  		if now[i] > before[i] {
    96  			return true
    97  		}
    98  	}
    99  	return false
   100  }
   101  
   102  var runlevelNumber = map[string]uint{
   103  	SCOPE_VALUE:        0,
   104  	SCOPE_ROW:          0,
   105  	SCOPE_STATEMENT:    0, // data.STATEMENT | These four must
   106  	SCOPE_TRX:          1, // data.TRX       | match, else
   107  	SCOPE_ITER:         2, // data.ITER      | NewScopedGenerator
   108  	SCOPE_CLIENT:       3, // data.CONN      | needs to be updated
   109  	SCOPE_CLIENT_GROUP: 4,
   110  	SCOPE_EXEC_GROUP:   5,
   111  	SCOPE_WORKLOAD:     6,
   112  	SCOPE_STAGE:        7,
   113  	SCOPE_GLOBAL:       8,
   114  }
   115  
   116  func RunLevelNumber(s string) uint {
   117  	n, ok := runlevelNumber[s]
   118  	if !ok {
   119  		panic("invalid run level: " + s)
   120  	}
   121  	return n
   122  }
   123  
   124  func RunLevelIsValid(s string) bool {
   125  	_, ok := runlevelNumber[s]
   126  	return ok
   127  }
   128  
   129  var (
   130  	CPUProfile io.Writer // --cpu-profile FILE
   131  	Debugging  = false
   132  	debugLog   = log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds)
   133  )
   134  
   135  func Debug(msg string, v ...interface{}) {
   136  	if !Debugging {
   137  		return
   138  	}
   139  	_, file, line, _ := runtime.Caller(1)
   140  	msg = fmt.Sprintf("DEBUG %s:%d %s", path.Base(file), line, msg)
   141  	debugLog.Printf(msg, v...)
   142  }
   143  
   144  var MakeHTTPClient func() *http.Client = func() *http.Client {
   145  	tr := &http.Transport{
   146  		MaxIdleConns:    1,
   147  		IdleConnTimeout: 1 * time.Hour,
   148  	}
   149  	return &http.Client{Transport: tr}
   150  }
   151  
   152  func Bool(s string) bool {
   153  	v := strings.ToLower(s)
   154  	return v == "true" || v == "yes" || v == "on" || v == "aye"
   155  }
   156  
   157  func BoolString(b bool) string {
   158  	if b {
   159  		return "true"
   160  	}
   161  	return "false"
   162  }
   163  
   164  var portRe = regexp.MustCompile(`:\d+$`)
   165  
   166  // WithPort return s:p if s doesn't have port suffix p.
   167  // p must not have a colon prefix.
   168  func WithPort(s, p string) string {
   169  	if portRe.MatchString(s) {
   170  		return s
   171  	}
   172  	return s + ":" + p
   173  }
   174  
   175  // Uint returns s as a unit presuming s has already been validated.
   176  func Uint(s string) uint {
   177  	i, _ := strconv.ParseUint(s, 10, 64)
   178  	return uint(i)
   179  }
   180  
   181  var SystemParams = map[string]string{}
   182  
   183  func init() {
   184  	SystemParams["CPU_CORES"] = strconv.Itoa(runtime.NumCPU())
   185  }
   186  
   187  const (
   188  	Eabort     = 0         // default (set to clear default flags)
   189  	Ereconnect = 1 << iota // reconnect, next iter
   190  	Econtinue              // don't reconnect, continue next iter
   191  	Esilent                // don't repot error or reconnect
   192  	Erollback              // execute ROLLBACK if in trx
   193  )
   194  
   195  var MySQLErrorHandling = map[uint16]byte{
   196  	1046: Eabort,                // no database selected
   197  	1062: Eabort,                // duplicate key
   198  	1064: Eabort,                // You have an error in your SQL syntax
   199  	1146: Eabort,                // table doesn't exist
   200  	1205: Erollback | Econtinue, // lock wait timeout; no automatic rollback (innodb_rollback_on_timeout=OFF by default)
   201  	1213: Econtinue,             // deadlock; automatic rollback
   202  	1290: Erollback | Econtinue, // read-only (server is running with the --read-only option so it cannot execute this statement)
   203  	1317: Econtinue,             // query killed (Query execution was interrupted)
   204  	1836: Erollback | Econtinue, // read-only (Running in read-only mode)
   205  }
   206  
   207  var ModifyDB func(*sql.DB, RunLevel)