github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/workload/schemachange/schemachange.go (about) 1 // Copyright 2020 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 schemachange 12 13 import ( 14 "context" 15 gosql "database/sql" 16 "fmt" 17 "math/rand" 18 "runtime" 19 "strings" 20 "sync/atomic" 21 22 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 23 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 24 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 25 "github.com/cockroachdb/cockroach/pkg/workload" 26 "github.com/cockroachdb/cockroach/pkg/workload/histogram" 27 "github.com/cockroachdb/errors" 28 "github.com/jackc/pgx" 29 "github.com/spf13/pflag" 30 ) 31 32 // This workload executes batches of schema changes asynchronously. Each 33 // batch is executed in a separate transaction and transactions run in 34 // parallel. Batches are drawn from a pre-defined distribution. 35 // Currently all schema change ops are equally likely to be chosen. This 36 // includes table creation but note that the tables contain no data. 37 // 38 // Example usage: 39 // `bin/workload run schemachange --init --concurrency=2 --verbose=false --max-ops=1000` 40 // will execute up to 1000 schema change operations per txn in two concurrent txns. 41 // 42 // TODO(peter): This is still work in progress, we need to 43 // - support more than 1 database 44 // - reference sequences in column defaults 45 // - create foreign keys 46 // - support `ADD CONSTRAINT` 47 // - support `SET COLUMN DEFAULT` 48 // 49 // TODO(spaskob): introspect errors returned from the workload and determine 50 // whether they're expected or unexpected. Flag `tolerate-errors` should be 51 // added to tolerate unexpected errors and then unexpected errors should fail 52 // the workload. 53 // 54 //For example, an attempt to do something we don't support should be swallowed (though if we can detect that maybe we should just not do it, e.g). It will be hard to use this test for anything more than liveness detection until we go through the tedious process of classifying errors.: 55 56 const ( 57 defaultMaxOpsPerWorker = 5 58 defaultExistingPct = 10 59 ) 60 61 type schemaChange struct { 62 flags workload.Flags 63 dbOverride string 64 concurrency int 65 maxOpsPerWorker int 66 existingPct int 67 verbose int 68 dryRun bool 69 } 70 71 var schemaChangeMeta = workload.Meta{ 72 Name: `schemachange`, 73 Description: `schemachange randomly generates concurrent schema changes`, 74 Version: `1.0.0`, 75 New: func() workload.Generator { 76 s := &schemaChange{} 77 s.flags.FlagSet = pflag.NewFlagSet(`schemachange`, pflag.ContinueOnError) 78 s.flags.StringVar(&s.dbOverride, `db`, ``, 79 `Override for the SQL database to use. If empty, defaults to the generator name`) 80 s.flags.IntVar(&s.concurrency, `concurrency`, 2*runtime.NumCPU(), /* TODO(spaskob): sensible default? */ 81 `Number of concurrent workers`) 82 s.flags.IntVar(&s.maxOpsPerWorker, `max-ops-per-worker`, defaultMaxOpsPerWorker, 83 `Number of operations to execute in a single transaction`) 84 s.flags.IntVar(&s.existingPct, `existing-pct`, defaultExistingPct, 85 `Percentage of times to use existing name`) 86 s.flags.IntVarP(&s.verbose, `verbose`, `v`, 0, ``) 87 s.flags.BoolVarP(&s.dryRun, `dry-run`, `n`, false, ``) 88 return s 89 }, 90 } 91 92 func init() { 93 workload.Register(schemaChangeMeta) 94 } 95 96 //go:generate stringer -type=opType 97 type opType int 98 99 const ( 100 addColumn opType = iota // ALTER TABLE <table> ADD [COLUMN] <column> <type> 101 addConstraint // ALTER TABLE <table> ADD CONSTRAINT <constraint> <def> 102 103 createIndex // CREATE INDEX <index> ON <table> <def> 104 createSequence // CREATE SEQUENCE <sequence> <def> 105 createTable // CREATE TABLE <table> <def> 106 createTableAs // CREATE TABLE <table> AS <def> 107 createView // CREATE VIEW <view> AS <def> 108 109 dropColumn // ALTER TABLE <table> DROP COLUMN <column> 110 dropColumnDefault // ALTER TABLE <table> ALTER [COLUMN] <column> DROP DEFAULT 111 dropColumnNotNull // ALTER TABLE <table> ALTER [COLUMN] <column> DROP NOT NULL 112 dropColumnStored // ALTER TABLE <table> ALTER [COLUMN] <column> DROP STORED 113 dropConstraint // ALTER TABLE <table> DROP CONSTRAINT <constraint> 114 dropIndex // DROP INDEX <index>@<table> 115 dropSequence // DROP SEQUENCE <sequence> 116 dropTable // DROP TABLE <table> 117 dropView // DROP VIEW <view> 118 119 renameColumn // ALTER TABLE <table> RENAME [COLUMN] <column> TO <column> 120 renameIndex // ALTER TABLE <table> RENAME CONSTRAINT <constraint> TO <constraint> 121 renameSequence // ALTER SEQUENCE <sequence> RENAME TO <sequence> 122 renameTable // ALTER TABLE <table> RENAME TO <table> 123 renameView // ALTER VIEW <view> RENAME TO <view> 124 125 setColumnDefault // ALTER TABLE <table> ALTER [COLUMN] <column> SET DEFAULT <expr> 126 setColumnNotNull // ALTER TABLE <table> ALTER [COLUMN] <column> SET NOT NULL 127 setColumnType // ALTER TABLE <table> ALTER [COLUMN] <column> [SET DATA] TYPE <type> 128 ) 129 130 var opWeights = []int{ 131 addColumn: 1, 132 addConstraint: 0, // TODO(spaskob): unimplemented 133 createIndex: 1, 134 createSequence: 1, 135 createTable: 1, 136 createTableAs: 1, 137 createView: 1, 138 dropColumn: 1, 139 dropColumnDefault: 1, 140 dropColumnNotNull: 1, 141 dropColumnStored: 1, 142 dropConstraint: 1, 143 dropIndex: 1, 144 dropSequence: 1, 145 dropTable: 1, 146 dropView: 1, 147 renameColumn: 1, 148 renameIndex: 1, 149 renameSequence: 1, 150 renameTable: 1, 151 renameView: 1, 152 setColumnDefault: 0, // TODO(spaskob): unimplemented 153 setColumnNotNull: 1, 154 setColumnType: 1, 155 } 156 157 // Meta implements the workload.Generator interface. 158 func (s *schemaChange) Meta() workload.Meta { 159 return schemaChangeMeta 160 } 161 162 // Flags implements the workload.Flagser interface. 163 func (s *schemaChange) Flags() workload.Flags { 164 return s.flags 165 } 166 167 // Tables implements the workload.Generator interface. 168 func (s *schemaChange) Tables() []workload.Table { 169 return nil 170 } 171 172 // Tables implements the workload.Opser interface. 173 func (s *schemaChange) Ops(urls []string, reg *histogram.Registry) (workload.QueryLoad, error) { 174 sqlDatabase, err := workload.SanitizeUrls(s, s.dbOverride, urls) 175 if err != nil { 176 return workload.QueryLoad{}, err 177 } 178 cfg := workload.MultiConnPoolCfg{ 179 MaxTotalConnections: s.concurrency * 2, //TODO(spaskob): pick a sensible default. 180 } 181 pool, err := workload.NewMultiConnPool(cfg, urls...) 182 if err != nil { 183 return workload.QueryLoad{}, err 184 } 185 186 seqNum, err := s.initSeqNum(pool) 187 if err != nil { 188 return workload.QueryLoad{}, err 189 } 190 191 ops := newDeck(rand.New(rand.NewSource(timeutil.Now().UnixNano())), opWeights...) 192 ql := workload.QueryLoad{SQLDatabase: sqlDatabase} 193 for i := 0; i < s.concurrency; i++ { 194 w := &schemaChangeWorker{ 195 verbose: s.verbose, 196 dryRun: s.dryRun, 197 maxOpsPerWorker: s.maxOpsPerWorker, 198 existingPct: s.existingPct, 199 rng: rand.New(rand.NewSource(timeutil.Now().UnixNano())), 200 ops: ops, 201 pool: pool, 202 hists: reg.GetHandle(), 203 seqNum: seqNum, 204 } 205 ql.WorkerFns = append(ql.WorkerFns, w.run) 206 } 207 return ql, nil 208 } 209 210 // initSeqName returns the smallest available sequence number to be 211 // used to generate new unique names. Note that this assumes that no 212 // other workload is being run at the same time. 213 // TODO(spaskob): Do we need to protect from workloads running concurrently. 214 // It's not obvious how the workloads will behave when accessing the same 215 // cluster. 216 func (s *schemaChange) initSeqNum(pool *workload.MultiConnPool) (*int64, error) { 217 seqNum := new(int64) 218 219 const q = ` 220 SELECT max(regexp_extract(name, '[0-9]+$')::int) 221 FROM ((SELECT table_name FROM [SHOW TABLES]) UNION (SELECT sequence_name FROM [SHOW SEQUENCES])) AS obj(name) 222 WHERE name ~ '^(table|view|seq)[0-9]+$'; 223 ` 224 var max gosql.NullInt64 225 if err := pool.Get().QueryRow(q).Scan(&max); err != nil { 226 return nil, err 227 } 228 if max.Valid { 229 *seqNum = max.Int64 + 1 230 } 231 232 return seqNum, nil 233 } 234 235 type schemaChangeWorker struct { 236 verbose int 237 dryRun bool 238 maxOpsPerWorker int 239 existingPct int 240 rng *rand.Rand 241 ops *deck 242 pool *workload.MultiConnPool 243 hists *histogram.Histograms 244 seqNum *int64 245 } 246 247 // handleOpError returns an error if the op error is considered serious and 248 // we should terminate the workload. 249 func handleOpError(err error) error { 250 if err == nil { 251 return nil 252 } 253 if pgErr := (pgx.PgError{}); errors.As(err, &pgErr) { 254 sqlstate := pgErr.SQLState() 255 class := sqlstate[0:2] 256 switch class { 257 case "09": 258 return errors.Wrap(err, "Class 09 - Triggered Action Exception") 259 case "XX": 260 return errors.Wrap(err, "Class XX - Internal Error") 261 } 262 } else { 263 return errors.Wrapf(err, "unexpected error %v", err) 264 } 265 return nil 266 } 267 268 var ( 269 errRunInTxnFatalSentinel = errors.New("fatal error when running txn") 270 errRunInTxnRbkSentinel = errors.New("txn needs to rollback") 271 ) 272 273 func (w *schemaChangeWorker) runInTxn(tx *pgx.Tx, opsNum int) (string, error) { 274 var log strings.Builder 275 for i := 0; i < opsNum; i++ { 276 op, noops, err := w.randOp(tx) 277 if err != nil { 278 return noops, errors.Mark( 279 errors.Wrap(err, "could not generate a random operation"), 280 errRunInTxnFatalSentinel, 281 ) 282 } 283 if w.verbose >= 2 { 284 // Print the failed attempts to produce a random operation. 285 log.WriteString(noops) 286 } 287 log.WriteString(fmt.Sprintf(" %s;\n", op)) 288 if !w.dryRun { 289 histBin := "opOk" 290 start := timeutil.Now() 291 if _, err = tx.Exec(op); err != nil { 292 histBin = "txnRbk" 293 log.WriteString(fmt.Sprintf("***FAIL: %v\n", err)) 294 log.WriteString("ROLLBACK;\n") 295 return log.String(), errors.Mark(err, errRunInTxnRbkSentinel) 296 } 297 elapsed := timeutil.Since(start) 298 w.hists.Get(histBin).Record(elapsed) 299 } 300 } 301 return log.String(), nil 302 } 303 304 func (w *schemaChangeWorker) run(_ context.Context) error { 305 tx, err := w.pool.Get().Begin() 306 if err != nil { 307 return errors.Wrap(err, "cannot get a connection and begin a txn") 308 } 309 opsNum := 1 + w.rng.Intn(w.maxOpsPerWorker) 310 311 // Run between 1 and maxOpsPerWorker schema change operations. 312 start := timeutil.Now() 313 logs, err := w.runInTxn(tx, opsNum) 314 logs = "BEGIN\n" + logs 315 defer func() { 316 if w.verbose >= 1 { 317 fmt.Print(logs) 318 } 319 }() 320 321 if err != nil { 322 // Rollback in all cases to release the txn object and its conn pool. 323 if rbkErr := tx.Rollback(); rbkErr != nil { 324 return errors.Wrapf(err, "Could not rollback %v", rbkErr) 325 } 326 switch { 327 case errors.Is(err, errRunInTxnFatalSentinel): 328 return err 329 case errors.Is(err, errRunInTxnRbkSentinel): 330 if seriousErr := handleOpError(err); seriousErr != nil { 331 return seriousErr 332 } 333 return nil 334 default: 335 return errors.Wrapf(err, "Unexpected error") 336 } 337 } 338 339 // If there were no errors commit the txn. 340 histBin := "txnOk" 341 cmtErrMsg := "" 342 if err = tx.Commit(); err != nil { 343 histBin = "txnCmtErr" 344 cmtErrMsg = fmt.Sprintf("***FAIL: %v", err) 345 } 346 w.hists.Get(histBin).Record(timeutil.Since(start)) 347 logs = logs + fmt.Sprintf("COMMIT; %s\n", cmtErrMsg) 348 return nil 349 } 350 351 // randOp attempts to produce a random schema change operation. It returns a 352 // triple `(randOp, log, error)`. On success `randOp` is the random schema 353 // change constructed. Constructing a random schema change may require a few 354 // stochastic attempts and if verbosity is >= 2 the unsuccessful attempts are 355 // recorded in `log` to help with debugging of the workload. 356 func (w *schemaChangeWorker) randOp(tx *pgx.Tx) (string, string, error) { 357 var log strings.Builder 358 for { 359 var stmt string 360 var err error 361 op := opType(w.ops.Int()) 362 switch op { 363 case addColumn: 364 stmt, err = w.addColumn(tx) 365 366 case addConstraint: 367 stmt, err = w.addConstraint(tx) 368 369 case createIndex: 370 stmt, err = w.createIndex(tx) 371 372 case createSequence: 373 stmt, err = w.createSequence(tx) 374 375 case createTable: 376 stmt, err = w.createTable(tx) 377 378 case createTableAs: 379 stmt, err = w.createTableAs(tx) 380 381 case createView: 382 stmt, err = w.createView(tx) 383 384 case dropColumn: 385 stmt, err = w.dropColumn(tx) 386 387 case dropColumnDefault: 388 stmt, err = w.dropColumnDefault(tx) 389 390 case dropColumnNotNull: 391 stmt, err = w.dropColumnNotNull(tx) 392 393 case dropColumnStored: 394 stmt, err = w.dropColumnStored(tx) 395 396 case dropConstraint: 397 stmt, err = w.dropConstraint(tx) 398 399 case dropIndex: 400 stmt, err = w.dropIndex(tx) 401 402 case dropSequence: 403 stmt, err = w.dropSequence(tx) 404 405 case dropTable: 406 stmt, err = w.dropTable(tx) 407 408 case dropView: 409 stmt, err = w.dropView(tx) 410 411 case renameColumn: 412 stmt, err = w.renameColumn(tx) 413 414 case renameIndex: 415 stmt, err = w.renameIndex(tx) 416 417 case renameSequence: 418 stmt, err = w.renameSequence(tx) 419 420 case renameTable: 421 stmt, err = w.renameTable(tx) 422 423 case renameView: 424 stmt, err = w.renameView(tx) 425 426 case setColumnDefault: 427 stmt, err = w.setColumnDefault(tx) 428 429 case setColumnNotNull: 430 stmt, err = w.setColumnNotNull(tx) 431 432 case setColumnType: 433 stmt, err = w.setColumnType(tx) 434 } 435 436 // TODO(spaskob): use more fine-grained error reporting. 437 if stmt == "" || errors.Is(err, pgx.ErrNoRows) { 438 log.WriteString(fmt.Sprintf("NOOP: %s -> %v\n", op, err)) 439 continue 440 } 441 return stmt, log.String(), err 442 } 443 } 444 445 func (w *schemaChangeWorker) addColumn(tx *pgx.Tx) (string, error) { 446 tableName, err := w.randTable(tx, 100) 447 if err != nil { 448 return "", err 449 } 450 451 columnName, err := w.randColumn(tx, tableName, w.existingPct) 452 if err != nil { 453 return "", err 454 } 455 456 def := &tree.ColumnTableDef{ 457 Name: tree.Name(columnName), 458 Type: sqlbase.RandSortingType(w.rng), 459 } 460 def.Nullable.Nullability = tree.Nullability(rand.Intn(1 + int(tree.SilentNull))) 461 return fmt.Sprintf(`ALTER TABLE "%s" ADD COLUMN %s`, tableName, tree.Serialize(def)), nil 462 } 463 464 func (w *schemaChangeWorker) addConstraint(tx *pgx.Tx) (string, error) { 465 // TODO(peter): unimplemented 466 // - Export sqlbase.randColumnTableDef. 467 return "", nil 468 } 469 470 func (w *schemaChangeWorker) createIndex(tx *pgx.Tx) (string, error) { 471 tableName, err := w.randTable(tx, 100) 472 if err != nil { 473 return "", err 474 } 475 476 columnNames, err := w.tableColumnsShuffled(tx, tableName) 477 if err != nil { 478 return "", err 479 } 480 481 indexName, err := w.randIndex(tx, tableName, w.existingPct) 482 if err != nil { 483 return "", err 484 } 485 486 def := &tree.CreateIndex{ 487 Name: tree.Name(indexName), 488 Table: tree.MakeUnqualifiedTableName(tree.Name(tableName)), 489 Unique: w.rng.Intn(4) == 0, // 25% UNIQUE 490 Inverted: w.rng.Intn(10) == 0, // 10% INVERTED 491 IfNotExists: w.rng.Intn(2) == 0, // 50% IF NOT EXISTS 492 Columns: make(tree.IndexElemList, 1+w.rng.Intn(len(columnNames))), 493 } 494 495 for i := range def.Columns { 496 def.Columns[i].Column = tree.Name(columnNames[i]) 497 def.Columns[i].Direction = tree.Direction(w.rng.Intn(1 + int(tree.Descending))) 498 } 499 columnNames = columnNames[len(def.Columns):] 500 501 if n := len(columnNames); n > 0 { 502 def.Storing = make(tree.NameList, w.rng.Intn(1+n)) 503 for i := range def.Storing { 504 def.Storing[i] = tree.Name(columnNames[i]) 505 } 506 } 507 508 return tree.Serialize(def), nil 509 } 510 511 func (w *schemaChangeWorker) createSequence(tx *pgx.Tx) (string, error) { 512 return fmt.Sprintf(`CREATE SEQUENCE "seq%d"`, atomic.AddInt64(w.seqNum, 1)), nil 513 } 514 515 func (w *schemaChangeWorker) createTable(tx *pgx.Tx) (string, error) { 516 tableName, err := w.randTable(tx, 10) 517 if err != nil { 518 return "", err 519 } 520 521 stmt := sqlbase.RandCreateTable(w.rng, "table", int(atomic.AddInt64(w.seqNum, 1))) 522 stmt.Table = tree.MakeUnqualifiedTableName(tree.Name(tableName)) 523 stmt.IfNotExists = w.rng.Intn(2) == 0 524 return tree.Serialize(stmt), nil 525 } 526 527 func (w *schemaChangeWorker) createTableAs(tx *pgx.Tx) (string, error) { 528 tableName, err := w.randTable(tx, 100) 529 if err != nil { 530 return "", err 531 } 532 533 columnNames, err := w.tableColumnsShuffled(tx, tableName) 534 if err != nil { 535 return "", err 536 } 537 columnNames = columnNames[:1+w.rng.Intn(len(columnNames))] 538 539 names := make(tree.NameList, len(columnNames)) 540 for i := range names { 541 names[i] = tree.Name(columnNames[i]) 542 } 543 544 destTableName, err := w.randTable(tx, 10) 545 if err != nil { 546 return "", err 547 } 548 549 return fmt.Sprintf(`CREATE TABLE "%s" AS SELECT %s FROM "%s"`, 550 destTableName, tree.Serialize(&names), tableName), nil 551 } 552 553 func (w *schemaChangeWorker) createView(tx *pgx.Tx) (string, error) { 554 tableName, err := w.randTable(tx, 100) 555 if err != nil { 556 return "", err 557 } 558 559 columnNames, err := w.tableColumnsShuffled(tx, tableName) 560 if err != nil { 561 return "", err 562 } 563 columnNames = columnNames[:1+w.rng.Intn(len(columnNames))] 564 565 names := make(tree.NameList, len(columnNames)) 566 for i := range names { 567 names[i] = tree.Name(columnNames[i]) 568 } 569 570 destViewName, err := w.randView(tx, w.existingPct) 571 if err != nil { 572 return "", err 573 } 574 575 // TODO(peter): Create views that are dependent on multiple tables. 576 return fmt.Sprintf(`CREATE VIEW "%s" AS SELECT %s FROM "%s"`, 577 destViewName, tree.Serialize(&names), tableName), nil 578 } 579 580 func (w *schemaChangeWorker) dropColumn(tx *pgx.Tx) (string, error) { 581 tableName, err := w.randTable(tx, 100) 582 if err != nil { 583 return "", err 584 } 585 columnName, err := w.randColumn(tx, tableName, 100) 586 if err != nil { 587 return "", err 588 } 589 return fmt.Sprintf(`ALTER TABLE "%s" DROP COLUMN "%s"`, tableName, columnName), nil 590 } 591 592 func (w *schemaChangeWorker) dropColumnDefault(tx *pgx.Tx) (string, error) { 593 tableName, err := w.randTable(tx, 100) 594 if err != nil { 595 return "", err 596 } 597 columnName, err := w.randColumn(tx, tableName, 100) 598 if err != nil { 599 return "", err 600 } 601 return fmt.Sprintf(`ALTER TABLE "%s" ALTER COLUMN "%s" DROP DEFAULT`, tableName, columnName), nil 602 } 603 604 func (w *schemaChangeWorker) dropColumnNotNull(tx *pgx.Tx) (string, error) { 605 tableName, err := w.randTable(tx, 100) 606 if err != nil { 607 return "", err 608 } 609 columnName, err := w.randColumn(tx, tableName, 100) 610 if err != nil { 611 return "", err 612 } 613 return fmt.Sprintf(`ALTER TABLE "%s" ALTER COLUMN "%s" DROP NOT NULL`, tableName, columnName), nil 614 } 615 616 func (w *schemaChangeWorker) dropColumnStored(tx *pgx.Tx) (string, error) { 617 tableName, err := w.randTable(tx, 100) 618 if err != nil { 619 return "", err 620 } 621 columnName, err := w.randColumn(tx, tableName, 100) 622 if err != nil { 623 return "", err 624 } 625 return fmt.Sprintf(`ALTER TABLE "%s" ALTER COLUMN "%s" DROP STORED`, tableName, columnName), nil 626 } 627 628 func (w *schemaChangeWorker) dropConstraint(tx *pgx.Tx) (string, error) { 629 tableName, err := w.randTable(tx, 100) 630 if err != nil { 631 return "", err 632 } 633 constraintName, err := w.randConstraint(tx, tableName) 634 if err != nil { 635 return "", err 636 } 637 return fmt.Sprintf(`ALTER TABLE "%s" DROP CONSTRAINT "%s"`, tableName, constraintName), nil 638 } 639 640 func (w *schemaChangeWorker) dropIndex(tx *pgx.Tx) (string, error) { 641 tableName, err := w.randTable(tx, 100) 642 if err != nil { 643 return "", err 644 } 645 indexName, err := w.randIndex(tx, tableName, 100) 646 if err != nil { 647 return "", err 648 } 649 return fmt.Sprintf(`DROP INDEX "%s"@"%s"`, tableName, indexName), nil 650 } 651 652 func (w *schemaChangeWorker) dropSequence(tx *pgx.Tx) (string, error) { 653 sequenceName, err := w.randSequence(tx, 100) 654 if err != nil { 655 return "", err 656 } 657 return fmt.Sprintf(`DROP SEQUENCE "%s"`, sequenceName), nil 658 } 659 660 func (w *schemaChangeWorker) dropTable(tx *pgx.Tx) (string, error) { 661 tableName, err := w.randTable(tx, 100) 662 if err != nil { 663 return "", err 664 } 665 return fmt.Sprintf(`DROP TABLE "%s"`, tableName), nil 666 } 667 668 func (w *schemaChangeWorker) dropView(tx *pgx.Tx) (string, error) { 669 viewName, err := w.randView(tx, 100) 670 if err != nil { 671 return "", err 672 } 673 return fmt.Sprintf(`DROP VIEW "%s"`, viewName), nil 674 } 675 676 func (w *schemaChangeWorker) renameColumn(tx *pgx.Tx) (string, error) { 677 tableName, err := w.randTable(tx, 100) 678 if err != nil { 679 return "", err 680 } 681 682 srcColumnName, err := w.randColumn(tx, tableName, 100) 683 if err != nil { 684 return "", err 685 } 686 687 destColumnName, err := w.randColumn(tx, tableName, 50) 688 if err != nil { 689 return "", err 690 } 691 692 return fmt.Sprintf(`ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"`, 693 tableName, srcColumnName, destColumnName), nil 694 } 695 696 func (w *schemaChangeWorker) renameIndex(tx *pgx.Tx) (string, error) { 697 tableName, err := w.randTable(tx, 100) 698 if err != nil { 699 return "", err 700 } 701 702 srcIndexName, err := w.randIndex(tx, tableName, w.existingPct) 703 if err != nil { 704 return "", err 705 } 706 707 destIndexName, err := w.randIndex(tx, tableName, 50) 708 if err != nil { 709 return "", err 710 } 711 712 return fmt.Sprintf(`ALTER TABLE "%s" RENAME CONSTRAINT "%s" TO "%s"`, 713 tableName, srcIndexName, destIndexName), nil 714 } 715 716 func (w *schemaChangeWorker) renameSequence(tx *pgx.Tx) (string, error) { 717 srcSequenceName, err := w.randSequence(tx, 100) 718 if err != nil { 719 return "", err 720 } 721 722 destSequenceName, err := w.randSequence(tx, 50) 723 if err != nil { 724 return "", err 725 } 726 727 return fmt.Sprintf(`ALTER SEQUENCE "%s" RENAME TO "%s"`, srcSequenceName, destSequenceName), nil 728 } 729 730 func (w *schemaChangeWorker) renameTable(tx *pgx.Tx) (string, error) { 731 srcTableName, err := w.randTable(tx, 100) 732 if err != nil { 733 return "", err 734 } 735 736 destTableName, err := w.randTable(tx, 50) 737 if err != nil { 738 return "", err 739 } 740 741 return fmt.Sprintf(`ALTER TABLE "%s" RENAME TO "%s"`, srcTableName, destTableName), nil 742 } 743 744 func (w *schemaChangeWorker) renameView(tx *pgx.Tx) (string, error) { 745 srcViewName, err := w.randView(tx, 100) 746 if err != nil { 747 return "", err 748 } 749 750 destViewName, err := w.randView(tx, 50) 751 if err != nil { 752 return "", err 753 } 754 755 return fmt.Sprintf(`ALTER VIEW "%s" RENAME TO "%s"`, srcViewName, destViewName), nil 756 } 757 758 func (w *schemaChangeWorker) setColumnDefault(tx *pgx.Tx) (string, error) { 759 // TODO(peter): unimplemented 760 return "", nil 761 } 762 763 func (w *schemaChangeWorker) setColumnNotNull(tx *pgx.Tx) (string, error) { 764 tableName, err := w.randTable(tx, 100) 765 if err != nil { 766 return "", err 767 } 768 columnName, err := w.randColumn(tx, tableName, 100) 769 if err != nil { 770 return "", err 771 } 772 return fmt.Sprintf(`ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL`, tableName, columnName), nil 773 } 774 775 func (w *schemaChangeWorker) setColumnType(tx *pgx.Tx) (string, error) { 776 tableName, err := w.randTable(tx, 100) 777 if err != nil { 778 return "", err 779 } 780 columnName, err := w.randColumn(tx, tableName, 100) 781 if err != nil { 782 return "", err 783 } 784 return fmt.Sprintf(`ALTER TABLE "%s" ALTER COLUMN "%s" SET DATA TYPE %s`, 785 tableName, columnName, sqlbase.RandSortingType(w.rng)), nil 786 } 787 788 func (w *schemaChangeWorker) randColumn( 789 tx *pgx.Tx, tableName string, pctExisting int, 790 ) (string, error) { 791 if w.rng.Intn(100) >= pctExisting { 792 // We make a unique name for all columns by prefixing them with the table 793 // index to make it easier to reference columns from different tables. 794 return fmt.Sprintf("col%s_%d", 795 strings.TrimPrefix(tableName, "table"), atomic.AddInt64(w.seqNum, 1)), nil 796 } 797 q := fmt.Sprintf(` 798 SELECT column_name 799 FROM [SHOW COLUMNS FROM "%s"] 800 ORDER BY random() 801 LIMIT 1; 802 `, tableName) 803 var name string 804 if err := tx.QueryRow(q).Scan(&name); err != nil { 805 return "", err 806 } 807 return name, nil 808 } 809 810 func (w *schemaChangeWorker) randConstraint(tx *pgx.Tx, tableName string) (string, error) { 811 q := fmt.Sprintf(` 812 SELECT constraint_name 813 FROM [SHOW CONSTRAINTS FROM "%s"] 814 ORDER BY random() 815 LIMIT 1; 816 `, tableName) 817 var name string 818 err := tx.QueryRow(q).Scan(&name) 819 if err != nil { 820 return "", err 821 } 822 return name, nil 823 } 824 825 func (w *schemaChangeWorker) randIndex( 826 tx *pgx.Tx, tableName string, pctExisting int, 827 ) (string, error) { 828 if w.rng.Intn(100) >= pctExisting { 829 // We make a unique name for all indices by prefixing them with the table 830 // index to make it easier to reference columns from different tables. 831 return fmt.Sprintf("index%s_%d", 832 strings.TrimPrefix(tableName, "table"), atomic.AddInt64(w.seqNum, 1)), nil 833 } 834 q := fmt.Sprintf(` 835 SELECT index_name 836 FROM [SHOW INDEXES FROM "%s"] 837 ORDER BY random() 838 LIMIT 1; 839 `, tableName) 840 var name string 841 if err := tx.QueryRow(q).Scan(&name); err != nil { 842 return "", err 843 } 844 return name, nil 845 } 846 847 func (w *schemaChangeWorker) randSequence(tx *pgx.Tx, pctExisting int) (string, error) { 848 if w.rng.Intn(100) >= pctExisting { 849 return fmt.Sprintf(`seq%d`, atomic.AddInt64(w.seqNum, 1)), nil 850 } 851 const q = ` 852 SELECT sequence_name 853 FROM [SHOW SEQUENCES] 854 WHERE sequence_name LIKE 'seq%' 855 ORDER BY random() 856 LIMIT 1; 857 ` 858 var name string 859 if err := tx.QueryRow(q).Scan(&name); err != nil { 860 return "", err 861 } 862 return name, nil 863 } 864 865 func (w *schemaChangeWorker) randTable(tx *pgx.Tx, pctExisting int) (string, error) { 866 if w.rng.Intn(100) >= pctExisting { 867 return fmt.Sprintf("table%d", atomic.AddInt64(w.seqNum, 1)), nil 868 } 869 const q = ` 870 SELECT table_name 871 FROM [SHOW TABLES] 872 WHERE table_name LIKE 'table%' 873 ORDER BY random() 874 LIMIT 1; 875 ` 876 var name string 877 if err := tx.QueryRow(q).Scan(&name); err != nil { 878 return "", err 879 } 880 return name, nil 881 } 882 883 func (w *schemaChangeWorker) randView(tx *pgx.Tx, pctExisting int) (string, error) { 884 if w.rng.Intn(100) >= pctExisting { 885 return fmt.Sprintf("view%d", atomic.AddInt64(w.seqNum, 1)), nil 886 } 887 const q = ` 888 SELECT table_name 889 FROM [SHOW TABLES] 890 WHERE table_name LIKE 'view%' 891 ORDER BY random() 892 LIMIT 1; 893 ` 894 var name string 895 if err := tx.QueryRow(q).Scan(&name); err != nil { 896 return "", err 897 } 898 return name, nil 899 } 900 901 func (w *schemaChangeWorker) tableColumnsShuffled(tx *pgx.Tx, tableName string) ([]string, error) { 902 q := fmt.Sprintf(` 903 SELECT column_name 904 FROM [SHOW COLUMNS FROM "%s"]; 905 `, tableName) 906 907 rows, err := tx.Query(q) 908 if err != nil { 909 return nil, err 910 } 911 defer rows.Close() 912 913 var columnNames []string 914 for rows.Next() { 915 var name string 916 if err := rows.Scan(&name); err != nil { 917 return nil, err 918 } 919 columnNames = append(columnNames, name) 920 } 921 if rows.Err() != nil { 922 return nil, rows.Err() 923 } 924 925 w.rng.Shuffle(len(columnNames), func(i, j int) { 926 columnNames[i], columnNames[j] = columnNames[j], columnNames[i] 927 }) 928 929 if len(columnNames) <= 0 { 930 return nil, errors.Errorf("table %s has no columns", tableName) 931 } 932 return columnNames, nil 933 }