github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/dump_test.go (about) 1 // Copyright 2016 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 cli 12 13 import ( 14 "bytes" 15 "database/sql/driver" 16 "fmt" 17 "io" 18 "net/url" 19 "path/filepath" 20 "reflect" 21 "strings" 22 "testing" 23 "time" 24 "unicode/utf8" 25 26 "github.com/cockroachdb/apd" 27 "github.com/cockroachdb/cockroach/pkg/security" 28 "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" 29 "github.com/cockroachdb/cockroach/pkg/util/duration" 30 "github.com/cockroachdb/cockroach/pkg/util/ipaddr" 31 "github.com/cockroachdb/cockroach/pkg/util/json" 32 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 33 "github.com/cockroachdb/cockroach/pkg/util/randutil" 34 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 35 "github.com/cockroachdb/cockroach/pkg/util/timeutil/pgdate" 36 "github.com/cockroachdb/cockroach/pkg/util/uuid" 37 "github.com/cockroachdb/datadriven" 38 "github.com/spf13/pflag" 39 ) 40 41 // TestDumpData uses the testdata/dump directory to execute SQL statements 42 // and compare dump output with expected output. File format is from the 43 // datadriven package. 44 // 45 // The commands supported in the data files are: 46 // 47 // sql: execute the commands in the input section; no arguments supported. 48 // 49 // dump: runs the CLI dump command with the given arguments, using its 50 // output as the expected result. Then loads the data back into an empty 51 // server and dumps it again to ensure the dump is roundtrippable. If the 52 // input section is equal to `noroundtrip` the roundtrip step is skipped 53 // (i.e., only the first dump is done). After a roundtripped dump, the tmp 54 // database may be examined to verify correctness. 55 func TestDumpData(t *testing.T) { 56 defer leaktest.AfterTest(t)() 57 58 datadriven.Walk(t, filepath.Join("testdata", "dump"), func(t *testing.T, path string) { 59 c := newCLITest(cliTestParams{t: t}) 60 c.omitArgs = true 61 defer c.cleanup() 62 63 datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string { 64 args := []string{d.Cmd} 65 switch d.Cmd { 66 case "sql": 67 args = append(args, "-e", d.Input) 68 case "dump": 69 for _, a := range d.CmdArgs { 70 args = append(args, a.String()) 71 } 72 default: 73 d.Fatalf(t, "unknown command: %s", d.Cmd) 74 } 75 s, err := c.RunWithCaptureArgs(args) 76 if err != nil { 77 d.Fatalf(t, "%v", err) 78 } 79 if d.Cmd == "dump" && d.Input != "noroundtrip" { 80 if s != d.Expected { 81 return s 82 } 83 84 c.RunWithArgs([]string{"sql", "-e", "drop database if exists tmp; create database tmp"}) 85 if out, err := c.RunWithCaptureArgs([]string{"sql", "-d", "tmp", "-e", s}); err != nil { 86 d.Fatalf(t, "%v", err) 87 } else { 88 t.Logf("executed SQL: %s\nresult: %s", s, out) 89 } 90 args[1] = "tmp" 91 roundtrip, err := c.RunWithCaptureArgs(args) 92 if err != nil { 93 d.Fatalf(t, "%v", err) 94 } 95 if roundtrip != s { 96 d.Fatalf(t, "roundtrip results unexpected: %s, expected: %s", roundtrip, s) 97 } 98 } 99 return s 100 }) 101 }) 102 } 103 104 func dumpSingleTable(w io.Writer, conn *sqlConn, dbName string, tName string) error { 105 mds, err := getDumpMetadata(conn, dbName, []string{tName}, "") 106 if err != nil { 107 return err 108 } 109 if err := dumpCreateTable(w, mds[0]); err != nil { 110 return err 111 } 112 return dumpTableData(w, conn, mds[0]) 113 } 114 115 func TestDumpBytes(t *testing.T) { 116 defer leaktest.AfterTest(t)() 117 118 c := newCLITest(cliTestParams{t: t}) 119 defer c.cleanup() 120 121 url, cleanup := sqlutils.PGUrl(t, c.ServingSQLAddr(), t.Name(), url.User(security.RootUser)) 122 defer cleanup() 123 124 conn := makeSQLConn(url.String()) 125 defer conn.Close() 126 127 if err := conn.Exec(` 128 CREATE DATABASE d; 129 SET DATABASE = d; 130 CREATE TABLE t (b BYTES PRIMARY KEY); 131 `, nil); err != nil { 132 t.Fatal(err) 133 } 134 135 for i := int64(0); i < 256; i++ { 136 if err := conn.Exec("INSERT INTO t VALUES ($1)", []driver.Value{[]byte{byte(i)}}); err != nil { 137 t.Fatal(err) 138 } 139 } 140 141 var b bytes.Buffer 142 if err := dumpSingleTable(&b, conn, "d", "t"); err != nil { 143 t.Fatal(err) 144 } 145 dump := b.String() 146 b.Reset() 147 148 if err := conn.Exec(` 149 CREATE DATABASE o; 150 SET DATABASE = o; 151 `, nil); err != nil { 152 t.Fatal(err) 153 } 154 if err := conn.Exec(dump, nil); err != nil { 155 t.Fatal(err) 156 } 157 if err := dumpSingleTable(&b, conn, "o", "t"); err != nil { 158 t.Fatal(err) 159 } 160 dump2 := b.String() 161 if dump != dump2 { 162 t.Fatalf("unmatching dumps:\n%s\n%s", dump, dump2) 163 } 164 } 165 166 const durationRandom = "duration-random" 167 168 var randomTestTime = pflag.Duration(durationRandom, time.Second, "duration for randomized dump test to run") 169 170 func init() { 171 pflag.Lookup(durationRandom).Hidden = true 172 } 173 174 // TestDumpRandom generates a random number of random rows with all data 175 // types. This data is dumped, inserted, and dumped again. The two dumps 176 // are compared for exactness. The data from the inserted dump is then 177 // SELECT'd and compared to the original generated data to ensure it is 178 // round-trippable. 179 func TestDumpRandom(t *testing.T) { 180 defer leaktest.AfterTest(t)() 181 182 c := newCLITest(cliTestParams{t: t}) 183 defer c.cleanup() 184 185 url, cleanup := sqlutils.PGUrl(t, c.ServingSQLAddr(), t.Name(), url.User(security.RootUser)) 186 defer cleanup() 187 188 conn := makeSQLConn(url.String()) 189 defer conn.Close() 190 191 if err := conn.Exec(` 192 CREATE DATABASE d; 193 CREATE DATABASE o; 194 CREATE TABLE d.t ( 195 rowid int, 196 i int, 197 si smallint, 198 bi bigint, 199 f float, 200 fr real, 201 d date, 202 m timestamp, 203 mtz timestamptz, 204 n interval, 205 o bool, 206 e decimal, 207 s string, 208 b bytes, 209 u uuid, 210 ip inet, 211 j json, 212 PRIMARY KEY (rowid, i, si, bi, f, fr, d, m, mtz, n, o, e, s, b, u, ip) 213 ); 214 SET extra_float_digits = 3; 215 `, nil); err != nil { 216 t.Fatal(err) 217 } 218 219 rnd, seed := randutil.NewPseudoRand() 220 t.Logf("random seed: %v", seed) 221 222 start := timeutil.Now() 223 224 for iteration := 0; timeutil.Since(start) < *randomTestTime; iteration++ { 225 if err := conn.Exec(`DELETE FROM d.t`, nil); err != nil { 226 t.Fatal(err) 227 } 228 var generatedRows [][]driver.Value 229 count := rnd.Int63n(500) 230 t.Logf("random iteration %v: %v rows", iteration, count) 231 for _i := int64(0); _i < count; _i++ { 232 // Generate a random number of random inserts. 233 i := rnd.Int63() 234 f := rnd.Float64() 235 d, _ := pgdate.MakeCompatibleDateFromDisk(rnd.Int63n(10000)).ToTime() 236 m := timeutil.Unix(0, rnd.Int63()).Round(time.Microsecond) 237 sign := 1 - rnd.Int63n(2)*2 238 dur := duration.MakeDuration(sign*rnd.Int63(), sign*rnd.Int63n(1000), sign*rnd.Int63n(1000)) 239 n := dur.String() 240 o := rnd.Intn(2) == 1 241 e := apd.New(rnd.Int63(), rnd.Int31n(20)-10).String() 242 sr := make([]byte, rnd.Intn(500)) 243 if _, err := rnd.Read(sr); err != nil { 244 t.Fatal(err) 245 } 246 s := make([]byte, 0, len(sr)) 247 for _, b := range sr { 248 r := rune(b) 249 if !utf8.ValidRune(r) { 250 continue 251 } 252 s = append(s, []byte(string(r))...) 253 } 254 b := make([]byte, rnd.Intn(500)) 255 if _, err := rnd.Read(b); err != nil { 256 t.Fatal(err) 257 } 258 259 uuidBytes := make([]byte, 16) 260 if _, err := rnd.Read(b); err != nil { 261 t.Fatal(err) 262 } 263 u, err := uuid.FromBytes(uuidBytes) 264 if err != nil { 265 t.Fatal(err) 266 } 267 268 ip := ipaddr.RandIPAddr(rnd) 269 j, err := json.Random(20, rnd) 270 if err != nil { 271 t.Fatal(err) 272 } 273 274 vals := []driver.Value{ 275 _i, 276 i, 277 i & 0x7fff, // si 278 i, // bi 279 f, 280 f, // fr 281 d, 282 m, 283 m, 284 []byte(n), // intervals come out as `[]byte`s 285 o, 286 []byte(e), // decimals come out as `[]byte`s 287 string(s), 288 b, 289 []byte(u.String()), 290 []byte(ip.String()), 291 []byte(j.String()), 292 } 293 if err := conn.Exec("INSERT INTO d.t VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)", vals); err != nil { 294 t.Fatal(err) 295 } 296 generatedRows = append(generatedRows, vals[1:]) 297 } 298 299 check := func(table string) { 300 q := fmt.Sprintf("SELECT i, si, bi, f, fr, d, m, mtz, n, o, e, s, b, u, ip, j FROM %s ORDER BY rowid", table) 301 nrows, err := conn.Query(q, nil) 302 if err != nil { 303 t.Fatal(err) 304 } 305 defer func() { 306 if err := nrows.Close(); err != nil { 307 t.Fatal(err) 308 } 309 }() 310 for gi, generatedRow := range generatedRows { 311 fetched := make([]driver.Value, len(nrows.Columns())) 312 if err := nrows.Next(fetched); err != nil { 313 t.Fatal(err) 314 } 315 316 for i, fetchedVal := range fetched { 317 generatedVal := generatedRow[i] 318 if t, ok := fetchedVal.(time.Time); ok { 319 // dates and timestamps come out with offset zero (but 320 // not UTC specifically). 321 fetchedVal = t.UTC() 322 } 323 if !reflect.DeepEqual(fetchedVal, generatedVal) { 324 t.Errorf("NOT EQUAL: table %s, row %d, col %d\ngenerated (%T): %v (%s)\nselected (%T): %v (%s)\n", table, gi, i, generatedVal, generatedVal, generatedVal, fetchedVal, fetchedVal, fetchedVal) 325 } 326 } 327 if t.Failed() { 328 t.FailNow() 329 } 330 } 331 } 332 333 check("d.t") 334 335 var buf bytes.Buffer 336 if err := dumpSingleTable(&buf, conn, "d", "t"); err != nil { 337 t.Fatal(err) 338 } 339 dump := buf.String() 340 buf.Reset() 341 342 if err := conn.Exec(` 343 SET DATABASE = o; 344 DROP TABLE IF EXISTS t; 345 `, nil); err != nil { 346 t.Fatal(err) 347 } 348 if err := conn.Exec(dump, nil); err != nil { 349 t.Fatal(err) 350 } 351 352 check("o.t") 353 354 if err := dumpSingleTable(&buf, conn, "o", "t"); err != nil { 355 t.Fatal(err) 356 } 357 dump2 := buf.String() 358 if dump != dump2 { 359 t.Fatalf("unmatching dumps:\nFIRST:\n%s\n\nSECOND:\n%s", dump, dump2) 360 } 361 } 362 } 363 364 func TestDumpAsOf(t *testing.T) { 365 defer leaktest.AfterTest(t)() 366 367 c := newCLITest(cliTestParams{t: t}) 368 defer c.cleanup() 369 370 const create = ` 371 CREATE DATABASE d; 372 CREATE TABLE d.t (i int8); 373 INSERT INTO d.t VALUES (1); 374 SELECT now(); 375 ` 376 377 out, err := c.RunWithCaptureArgs([]string{"sql", "-e", create}) 378 if err != nil { 379 t.Fatal(err) 380 } 381 382 // Last line is the timestamp. 383 fs := strings.Split(strings.TrimSpace(out), "\n") 384 ts := fs[len(fs)-1] 385 386 dump1, err := c.RunWithCaptureArgs([]string{"dump", "d", "t"}) 387 if err != nil { 388 t.Fatal(err) 389 } 390 391 const want1 = `dump d t 392 CREATE TABLE t ( 393 i INT8 NULL, 394 FAMILY "primary" (i, rowid) 395 ); 396 397 INSERT INTO t (i) VALUES 398 (1); 399 ` 400 if dump1 != want1 { 401 t.Fatalf("expected: %s\ngot: %s", want1, dump1) 402 } 403 404 c.RunWithArgs([]string{"sql", "-e", ` 405 ALTER TABLE d.t ADD COLUMN j int8 DEFAULT 2; 406 INSERT INTO d.t VALUES (3, 4); 407 `}) 408 409 dump2, err := c.RunWithCaptureArgs([]string{"dump", "d", "t"}) 410 if err != nil { 411 t.Fatal(err) 412 } 413 const want2 = `dump d t 414 CREATE TABLE t ( 415 i INT8 NULL, 416 j INT8 NULL DEFAULT 2:::INT8, 417 FAMILY "primary" (i, rowid, j) 418 ); 419 420 INSERT INTO t (i, j) VALUES 421 (1, 2), 422 (3, 4); 423 ` 424 if dump2 != want2 { 425 t.Fatalf("expected: %s\ngot: %s", want2, dump2) 426 } 427 428 dumpAsOf, err := c.RunWithCaptureArgs([]string{"dump", "d", "t", "--as-of", ts}) 429 if err != nil { 430 t.Fatal(err) 431 } 432 // Remove the timestamp from the first line. 433 dumpAsOf = fmt.Sprintf("dump d t\n%s", strings.SplitN(dumpAsOf, "\n", 2)[1]) 434 if dumpAsOf != want1 { 435 t.Fatalf("expected: %s\ngot: %s", want1, dumpAsOf) 436 } 437 438 if out, err := c.RunWithCaptureArgs([]string{"dump", "d", "t", "--as-of", "2000-01-01 00:00:00"}); err != nil { 439 t.Fatal(err) 440 } else if !strings.Contains(out, `relation d.public.t does not exist`) { 441 t.Fatalf("unexpected output: %s", out) 442 } 443 } 444 445 func TestDumpInterleavedTables(t *testing.T) { 446 defer leaktest.AfterTest(t)() 447 448 c := newCLITest(cliTestParams{t: t}) 449 defer c.cleanup() 450 451 const create = ` 452 CREATE DATABASE d; 453 CREATE TABLE d.customers (id INT PRIMARY KEY, name STRING(50)); 454 CREATE TABLE d.orders ( 455 customer INT, 456 id INT, 457 total DECIMAL(20, 5), 458 PRIMARY KEY (customer, id), 459 CONSTRAINT fk_customer FOREIGN KEY (customer) REFERENCES d.customers 460 ) INTERLEAVE IN PARENT d.customers (customer); 461 CREATE INDEX i ON d.orders (customer, total) INTERLEAVE IN PARENT d.customers (customer); 462 ` 463 464 _, err := c.RunWithCaptureArgs([]string{"sql", "-e", create}) 465 if err != nil { 466 t.Fatal(err) 467 } 468 469 dump1, err := c.RunWithCaptureArgs([]string{"dump", "d", "orders"}) 470 if err != nil { 471 t.Fatal(err) 472 } 473 474 const want1 = `dump d orders 475 CREATE TABLE orders ( 476 customer INT8 NOT NULL, 477 id INT8 NOT NULL, 478 total DECIMAL(20,5) NULL, 479 CONSTRAINT "primary" PRIMARY KEY (customer ASC, id ASC), 480 FAMILY "primary" (customer, id, total) 481 ) INTERLEAVE IN PARENT customers (customer); 482 483 ALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer) REFERENCES customers(id); 484 CREATE INDEX i ON orders (customer ASC, total ASC) INTERLEAVE IN PARENT customers (customer); 485 486 -- Validate foreign key constraints. These can fail if there was unvalidated data during the dump. 487 ALTER TABLE orders VALIDATE CONSTRAINT fk_customer; 488 ` 489 490 if dump1 != want1 { 491 t.Fatalf("expected: %s\ngot: %s", want1, dump1) 492 } 493 } 494 495 func TestDatabaseDumpCommand(t *testing.T) { 496 defer leaktest.AfterTest(t)() 497 498 tests := []struct { 499 name string 500 create string 501 expected string 502 }{ 503 { 504 name: "columnsless_table", 505 create: ` 506 CREATE DATABASE bar; 507 USE bar; 508 CREATE TABLE foo (); 509 `, 510 expected: `CREATE TABLE foo (FAMILY "primary" (rowid) 511 ); 512 `, 513 }, 514 { 515 name: "table_with_columns", 516 create: ` 517 CREATE DATABASE bar; 518 USE bar; 519 CREATE TABLE foo (id int primary key, text string not null); 520 `, 521 expected: `CREATE TABLE foo ( 522 id INT8 NOT NULL, 523 text STRING NOT NULL, 524 CONSTRAINT "primary" PRIMARY KEY (id ASC), 525 FAMILY "primary" (id, text) 526 ); 527 `, 528 }, 529 { 530 name: "autogenerate_hidden_colum", 531 create: ` 532 CREATE DATABASE bar; 533 USE bar; 534 CREATE TABLE foo(id int); 535 `, 536 expected: `CREATE TABLE foo ( 537 id INT8 NULL, 538 FAMILY "primary" (id, rowid) 539 ); 540 `, 541 }, 542 { 543 name: "columns_less_table_with_data", 544 create: ` 545 CREATE DATABASE bar; 546 USE bar; 547 CREATE TABLE foo(id int); 548 549 INSERT INTO foo(id) VALUES(1); 550 INSERT INTO foo(id) VALUES(2); 551 INSERT INTO foo(id) VALUES(3); 552 553 ALTER TABLE foo DROP COLUMN id; 554 `, 555 expected: `CREATE TABLE foo (FAMILY "primary" (rowid) 556 ); 557 `, 558 }, 559 } 560 for _, test := range tests { 561 t.Run(test.name, func(t *testing.T) { 562 c := newCLITest(cliTestParams{t: t}) 563 c.omitArgs = true 564 defer c.cleanup() 565 566 _, err := c.RunWithCaptureArgs([]string{"sql", "-e", test.create}) 567 if err != nil { 568 t.Fatal(err) 569 } 570 571 dump, err := c.RunWithCaptureArgs([]string{"dump", "bar", "--dump-mode=schema"}) 572 if err != nil { 573 t.Fatal(err) 574 } 575 576 if dump != test.expected { 577 t.Fatalf("expected: %s\ngot: %s", test.expected, dump) 578 } 579 580 dumpWithData, err := c.RunWithCaptureArgs([]string{"dump", "bar", "--dump-mode=data"}) 581 if err != nil { 582 t.Fatal(err) 583 } 584 585 // check we can actually reuse dump output 586 _, err = c.RunWithCaptureArgs([]string{"sql", "-e", fmt.Sprintf(`CREATE DATABASE TEST; 587 USE TEST; 588 %s 589 %s`, dump, dumpWithData)}) 590 if err != nil { 591 t.Fatal(err) 592 } 593 594 result1, err := c.RunWithCaptureArgs([]string{"sql", "-e", "select * from bar.foo"}) 595 if err != nil { 596 t.Fatal(err) 597 } 598 599 result2, err := c.RunWithCaptureArgs([]string{"sql", "-e", "select * from test.foo"}) 600 if err != nil { 601 t.Fatal(err) 602 } 603 604 if result1 != result2 { 605 t.Fatalf("expected: %s\ngot: %s", test.expected, dump) 606 } 607 }) 608 } 609 } 610 611 func TestDumpAllTables(t *testing.T) { 612 defer leaktest.AfterTest(t)() 613 614 tests := []struct { 615 name string 616 args []string 617 recreate bool 618 create string 619 expected string 620 clean string 621 }{ 622 { 623 name: " dump_all", 624 create: ` 625 CREATE DATABASE db1; 626 USE db1; 627 CREATE TABLE t1(id INT NOT NULL, pkey STRING PRIMARY KEY); 628 629 INSERT INTO t1(id, pkey) VALUES(1, 'db1-aaaa'); 630 INSERT INTO t1(id, pkey) VALUES(2, 'db1-bbbb'); 631 632 CREATE DATABASE db2; 633 USE db2; 634 CREATE TABLE t2(id INT NOT NULL, pkey STRING PRIMARY KEY); 635 636 INSERT INTO t2(id, pkey) VALUES(1, 'db2-aaaa'); 637 INSERT INTO t2(id, pkey) VALUES(2, 'db2-bbbb'); 638 `, 639 expected: ` 640 CREATE DATABASE IF NOT EXISTS db1; 641 USE db1; 642 643 CREATE TABLE t1 ( 644 id INT8 NOT NULL, 645 pkey STRING NOT NULL, 646 CONSTRAINT "primary" PRIMARY KEY (pkey ASC), 647 FAMILY "primary" (id, pkey) 648 ); 649 650 INSERT INTO t1 (id, pkey) VALUES 651 (1, 'db1-aaaa'), 652 (2, 'db1-bbbb'); 653 654 CREATE DATABASE IF NOT EXISTS db2; 655 USE db2; 656 657 CREATE TABLE t2 ( 658 id INT8 NOT NULL, 659 pkey STRING NOT NULL, 660 CONSTRAINT "primary" PRIMARY KEY (pkey ASC), 661 FAMILY "primary" (id, pkey) 662 ); 663 664 INSERT INTO t2 (id, pkey) VALUES 665 (1, 'db2-aaaa'), 666 (2, 'db2-bbbb'); 667 `, 668 }, 669 { 670 name: " dump_all_only_data", 671 args: []string{"--dump-mode=data"}, 672 create: ` 673 CREATE DATABASE db1; 674 USE db1; 675 CREATE TABLE t1(id INT NOT NULL, pkey STRING PRIMARY KEY); 676 677 INSERT INTO t1(id, pkey) VALUES(1, 'db1-aaaa'); 678 INSERT INTO t1(id, pkey) VALUES(2, 'db1-bbbb'); 679 680 CREATE DATABASE db2; 681 USE db2; 682 CREATE TABLE t2(id INT NOT NULL, pkey STRING PRIMARY KEY); 683 684 INSERT INTO t2(id, pkey) VALUES(1, 'db2-aaaa'); 685 INSERT INTO t2(id, pkey) VALUES(2, 'db2-bbbb'); 686 `, 687 expected: ` 688 INSERT INTO t1 (id, pkey) VALUES 689 (1, 'db1-aaaa'), 690 (2, 'db1-bbbb'); 691 692 INSERT INTO t2 (id, pkey) VALUES 693 (1, 'db2-aaaa'), 694 (2, 'db2-bbbb'); 695 `, 696 }, 697 { 698 name: "dump_cross_references", 699 recreate: true, 700 create: ` 701 CREATE DATABASE dbB; 702 USE dbB; 703 704 CREATE TABLE person( 705 id int PRIMARY KEY, 706 name string NOT NULL); 707 708 INSERT INTO person(id, name) VALUES(1, 'John Smith'); 709 INSERT INTO person(id, name) VALUES(2, 'Joe Dow'); 710 711 CREATE DATABASE dbA; 712 USE dbA; 713 714 CREATE TABLE account( 715 id int PRIMARY KEY, 716 person_id int REFERENCES dbB.person(id), 717 accountNo int NOT NULL); 718 719 INSERT INTO account(id, person_id, accountNo) VALUES(1, 1, 1111); 720 INSERT INTO account(id, person_id, accountNo) VALUES(2, 2, 2222); 721 `, 722 expected: ` 723 CREATE DATABASE IF NOT EXISTS dba; 724 USE dba; 725 726 CREATE TABLE account ( 727 id INT8 NOT NULL, 728 person_id INT8 NULL, 729 accountno INT8 NOT NULL, 730 CONSTRAINT "primary" PRIMARY KEY (id ASC), 731 INDEX account_auto_index_fk_person_id_ref_person (person_id ASC), 732 FAMILY "primary" (id, person_id, accountno) 733 ); 734 735 INSERT INTO account (id, person_id, accountno) VALUES 736 (1, 1, 1111), 737 (2, 2, 2222); 738 739 CREATE DATABASE IF NOT EXISTS dbb; 740 USE dbb; 741 742 CREATE TABLE person ( 743 id INT8 NOT NULL, 744 name STRING NOT NULL, 745 CONSTRAINT "primary" PRIMARY KEY (id ASC), 746 FAMILY "primary" (id, name) 747 ); 748 749 INSERT INTO person (id, name) VALUES 750 (1, 'John Smith'), 751 (2, 'Joe Dow'); 752 753 ALTER TABLE account ADD CONSTRAINT fk_person_id_ref_person FOREIGN KEY (person_id) REFERENCES dbb.public.person(id); 754 755 -- Validate foreign key constraints. These can fail if there was unvalidated data during the dump. 756 ALTER TABLE account VALIDATE CONSTRAINT fk_person_id_ref_person; 757 `, 758 clean: ` 759 DROP DATABASE dba; 760 DROP DATABASE dbb; 761 `, 762 }, 763 { 764 name: "verify_defaultdb_dump", 765 create: ` 766 CREATE TABLE foo(id INT NOT NULL); 767 768 INSERT INTO foo(id) VALUES(1); 769 INSERT INTO foo(id) VALUES(2); 770 INSERT INTO foo(id) VALUES(3); 771 772 CREATE DATABASE dba; 773 USE dba; 774 775 CREATE TABLE bar(id INT NOT NULL); 776 777 INSERT INTO bar(id) VALUES(1); 778 INSERT INTO bar(id) VALUES(2); 779 `, 780 clean: ` 781 USE defaultdb; 782 DROP TABLE foo; 783 DROP DATABASE dba; 784 `, 785 recreate: true, 786 expected: ` 787 CREATE DATABASE IF NOT EXISTS dba; 788 USE dba; 789 790 CREATE TABLE bar ( 791 id INT8 NOT NULL, 792 FAMILY "primary" (id, rowid) 793 ); 794 795 INSERT INTO bar (id) VALUES 796 (1), 797 (2); 798 799 CREATE DATABASE IF NOT EXISTS defaultdb; 800 USE defaultdb; 801 802 CREATE TABLE foo ( 803 id INT8 NOT NULL, 804 FAMILY "primary" (id, rowid) 805 ); 806 807 INSERT INTO foo (id) VALUES 808 (1), 809 (2), 810 (3); 811 `, 812 }, 813 } 814 for _, test := range tests { 815 tt := test 816 t.Run(tt.name, func(t *testing.T) { 817 818 c := newCLITest(cliTestParams{t: t}) 819 c.omitArgs = true 820 defer c.cleanup() 821 822 _, err := c.RunWithCaptureArgs([]string{"sql", "-e", tt.create}) 823 if err != nil { 824 t.Fatal(err) 825 } 826 827 args := []string{"dump", "--dump-all"} 828 args = append(args, tt.args...) 829 dump, err := c.RunWithCaptureArgs(args) 830 if err != nil { 831 t.Fatal(err) 832 } 833 834 if dump != tt.expected { 835 t.Fatalf("expected: %s\ngot: %s", tt.expected, dump) 836 } 837 838 // attempt to recreate from dump if test case defines 839 //clean up procedure 840 if tt.recreate { 841 _, err := c.RunWithCaptureArgs([]string{"sql", "-e", tt.clean}) 842 if err != nil { 843 t.Fatal(err) 844 } 845 846 _, err = c.RunWithCaptureArgs([]string{"sql", "-e", dump}) 847 if err != nil { 848 t.Fatal(err) 849 } 850 } 851 }) 852 } 853 }