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 }