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  }