github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/smithcmp/main.go (about)

     1  // Copyright 2019 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  // smithcmp is a tool to execute random queries on a database. A TOML
    12  // file provides configuration for which databases to connect to. If there
    13  // is more than one, only non-mutating statements are generated, and the
    14  // output is compared, exiting if there is a difference. If there is only
    15  // one database, mutating and non-mutating statements are generated. A
    16  // flag in the TOML controls whether Postgres-compatible output is generated.
    17  //
    18  // Explicit SQL statements can be specified (skipping sqlsmith generation)
    19  // using the top-level SQL array. Placeholders (`$1`, etc.) are
    20  // supported. Random datums of the correct type will be filled in.
    21  package main
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"log"
    28  	"math/rand"
    29  	"os"
    30  	"strings"
    31  	"time"
    32  
    33  	"github.com/BurntSushi/toml"
    34  	"github.com/cockroachdb/cockroach/pkg/cmd/cmpconn"
    35  	"github.com/cockroachdb/cockroach/pkg/internal/sqlsmith"
    36  	"github.com/cockroachdb/cockroach/pkg/sql/mutations"
    37  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    38  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    39  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    40  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    41  	"github.com/lib/pq/oid"
    42  )
    43  
    44  func usage() {
    45  	const use = `Usage of %s:
    46  	%[1]s config.toml
    47  `
    48  
    49  	fmt.Printf(use, os.Args[0])
    50  	os.Exit(1)
    51  }
    52  
    53  type options struct {
    54  	Postgres        bool
    55  	InitSQL         string
    56  	Smither         string
    57  	Seed            int64
    58  	TimeoutMins     int
    59  	StmtTimeoutSecs int
    60  	SQL             []string
    61  
    62  	Databases map[string]struct {
    63  		Addr           string
    64  		InitSQL        string
    65  		AllowMutations bool
    66  	}
    67  }
    68  
    69  var sqlMutators = []sqlbase.Mutator{mutations.ColumnFamilyMutator}
    70  
    71  func enableMutations(shouldEnable bool, mutations []sqlbase.Mutator) []sqlbase.Mutator {
    72  	if shouldEnable {
    73  		return mutations
    74  	}
    75  	return nil
    76  }
    77  
    78  func main() {
    79  	args := os.Args[1:]
    80  	if len(args) != 1 {
    81  		usage()
    82  	}
    83  
    84  	tomlData, err := ioutil.ReadFile(args[0])
    85  	if err != nil {
    86  		log.Fatal(err)
    87  	}
    88  
    89  	var opts options
    90  	if err := toml.Unmarshal(tomlData, &opts); err != nil {
    91  		log.Fatal(err)
    92  	}
    93  	timeout := time.Duration(opts.TimeoutMins) * time.Minute
    94  	if timeout <= 0 {
    95  		timeout = 15 * time.Minute
    96  	}
    97  	stmtTimeout := time.Duration(opts.StmtTimeoutSecs) * time.Second
    98  	if stmtTimeout <= 0 {
    99  		stmtTimeout = time.Minute
   100  	}
   101  
   102  	rng := rand.New(rand.NewSource(opts.Seed))
   103  	conns := map[string]cmpconn.Conn{}
   104  	for name, db := range opts.Databases {
   105  		var err error
   106  		mutators := enableMutations(opts.Databases[name].AllowMutations, sqlMutators)
   107  		if opts.Postgres {
   108  			mutators = append(mutators, mutations.PostgresMutator)
   109  		}
   110  		conns[name], err = cmpconn.NewConnWithMutators(
   111  			db.Addr, rng, mutators, db.InitSQL, opts.InitSQL)
   112  		if err != nil {
   113  			log.Fatalf("%s (%s): %+v", name, db.Addr, err)
   114  		}
   115  	}
   116  	compare := len(conns) > 1
   117  
   118  	if opts.Seed < 0 {
   119  		opts.Seed = timeutil.Now().UnixNano()
   120  		fmt.Println("seed:", opts.Seed)
   121  	}
   122  	smithOpts := []sqlsmith.SmitherOption{
   123  		sqlsmith.AvoidConsts(),
   124  	}
   125  	if opts.Postgres {
   126  		smithOpts = append(smithOpts, sqlsmith.PostgresMode())
   127  	} else if compare {
   128  		smithOpts = append(smithOpts,
   129  			sqlsmith.CompareMode(),
   130  			sqlsmith.DisableCRDBFns(),
   131  		)
   132  	}
   133  	if _, ok := conns[opts.Smither]; !ok {
   134  		log.Fatalf("Smither option not present in databases: %s", opts.Smither)
   135  	}
   136  	var smither *sqlsmith.Smither
   137  	var stmts []statement
   138  	if len(opts.SQL) == 0 {
   139  		smither, err = sqlsmith.NewSmither(conns[opts.Smither].DB(), rng, smithOpts...)
   140  		if err != nil {
   141  			log.Fatal(err)
   142  		}
   143  	} else {
   144  		stmts = make([]statement, len(opts.SQL))
   145  		for i, stmt := range opts.SQL {
   146  			ps, err := conns[opts.Smither].PGX().Prepare("", stmt)
   147  			if err != nil {
   148  				log.Fatalf("bad SQL statement on %s: %v\nSQL:\n%s", opts.Smither, stmt, err)
   149  			}
   150  			var placeholders []*types.T
   151  			for _, param := range ps.ParameterOIDs {
   152  				typ, ok := types.OidToType[oid.Oid(param)]
   153  				if !ok {
   154  					log.Fatalf("unknown oid: %v", param)
   155  				}
   156  				placeholders = append(placeholders, typ)
   157  			}
   158  			stmts[i] = statement{
   159  				stmt:         stmt,
   160  				placeholders: placeholders,
   161  			}
   162  		}
   163  	}
   164  
   165  	var prep, exec string
   166  	ctx := context.Background()
   167  	done := time.After(timeout)
   168  	for i := 0; true; i++ {
   169  		select {
   170  		case <-done:
   171  			return
   172  		default:
   173  		}
   174  		fmt.Printf("stmt: %d\n", i)
   175  		if smither != nil {
   176  			exec = smither.Generate()
   177  		} else {
   178  			randStatement := stmts[rng.Intn(len(stmts))]
   179  			name := fmt.Sprintf("s%d", i)
   180  			prep = fmt.Sprintf("PREPARE %s AS\n%s;", name, randStatement.stmt)
   181  			var sb strings.Builder
   182  			fmt.Fprintf(&sb, "EXECUTE %s", name)
   183  			for i, typ := range randStatement.placeholders {
   184  				if i > 0 {
   185  					sb.WriteString(", ")
   186  				} else {
   187  					sb.WriteString(" (")
   188  				}
   189  				d := sqlbase.RandDatum(rng, typ, true)
   190  				fmt.Println(i, typ, d, tree.Serialize(d))
   191  				sb.WriteString(tree.Serialize(d))
   192  			}
   193  			if len(randStatement.placeholders) > 0 {
   194  				fmt.Fprintf(&sb, ")")
   195  			}
   196  			fmt.Fprintf(&sb, ";")
   197  			exec = sb.String()
   198  			fmt.Println(exec)
   199  		}
   200  		if compare {
   201  			if err := cmpconn.CompareConns(
   202  				ctx, stmtTimeout, conns, prep, exec, true, /* ignoreSQLErrors */
   203  			); err != nil {
   204  				fmt.Printf("prep:\n%s;\nexec:\n%s;\nERR: %s\n\n", prep, exec, err)
   205  				os.Exit(1)
   206  			}
   207  		} else {
   208  			for _, conn := range conns {
   209  				if err := conn.Exec(ctx, prep+exec); err != nil {
   210  					fmt.Println(err)
   211  				}
   212  			}
   213  		}
   214  
   215  		// Make sure the servers are alive.
   216  		for name, conn := range conns {
   217  			start := timeutil.Now()
   218  			fmt.Printf("pinging %s...", name)
   219  			if err := conn.Ping(); err != nil {
   220  				fmt.Printf("\n%s: ping failure: %v\nprevious SQL:\n%s;\n%s;\n", name, err, prep, exec)
   221  				// Try to reconnect.
   222  				db := opts.Databases[name]
   223  				newConn, err := cmpconn.NewConnWithMutators(
   224  					db.Addr, rng, enableMutations(db.AllowMutations, sqlMutators),
   225  					db.InitSQL, opts.InitSQL,
   226  				)
   227  				if err != nil {
   228  					log.Fatalf("tried to reconnect: %v\n", err)
   229  				}
   230  				conns[name] = newConn
   231  			}
   232  			fmt.Printf(" %s\n", timeutil.Since(start))
   233  		}
   234  	}
   235  }
   236  
   237  type statement struct {
   238  	stmt         string
   239  	placeholders []*types.T
   240  }