github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/sql_util_test.go (about)

     1  // Copyright 2015 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  	"net/url"
    18  	"reflect"
    19  	"testing"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/security"
    22  	"github.com/cockroachdb/cockroach/pkg/testutils"
    23  	"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
    24  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    25  	"github.com/cockroachdb/errors"
    26  )
    27  
    28  func TestConnRecover(t *testing.T) {
    29  	defer leaktest.AfterTest(t)()
    30  
    31  	p := cliTestParams{t: t}
    32  	c := newCLITest(p)
    33  	defer c.cleanup()
    34  
    35  	url, cleanup := sqlutils.PGUrl(t, c.ServingSQLAddr(), t.Name(), url.User(security.RootUser))
    36  	defer cleanup()
    37  
    38  	conn := makeSQLConn(url.String())
    39  	defer conn.Close()
    40  
    41  	// Sanity check to establish baseline.
    42  	rows, err := conn.Query(`SELECT 1`, nil)
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	if err := rows.Close(); err != nil {
    47  		t.Fatal(err)
    48  	}
    49  
    50  	// Check that Query detects a connection close.
    51  	defer simulateServerRestart(&c, p, conn)()
    52  
    53  	// When the server restarts, the next Query() attempt may encounter a
    54  	// TCP reset error before the SQL driver realizes there is a problem
    55  	// and starts delivering ErrBadConn. We don't know the timing of
    56  	// this however.
    57  	testutils.SucceedsSoon(t, func() error {
    58  		if sqlRows, err := conn.Query(`SELECT 1`, nil); !errors.Is(err, driver.ErrBadConn) {
    59  			return fmt.Errorf("expected ErrBadConn, got %v", err)
    60  		} else if err == nil {
    61  			if closeErr := sqlRows.Close(); closeErr != nil {
    62  				t.Fatal(closeErr)
    63  			}
    64  		}
    65  		return nil
    66  	})
    67  
    68  	// Check that Query recovers from a connection close by re-connecting.
    69  	rows, err = conn.Query(`SELECT 1`, nil)
    70  	if err != nil {
    71  		t.Fatalf("conn.Query(): expected no error after reconnect, got %v", err)
    72  	}
    73  	if err := rows.Close(); err != nil {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	// Check that Exec detects a connection close.
    78  	defer simulateServerRestart(&c, p, conn)()
    79  
    80  	// Ditto from Query().
    81  	testutils.SucceedsSoon(t, func() error {
    82  		if err := conn.Exec(`SELECT 1`, nil); !errors.Is(err, driver.ErrBadConn) {
    83  			return fmt.Errorf("expected ErrBadConn, got %v", err)
    84  		}
    85  		return nil
    86  	})
    87  
    88  	// Check that Exec recovers from a connection close by re-connecting.
    89  	if err := conn.Exec(`SELECT 1`, nil); err != nil {
    90  		t.Fatalf("conn.Exec(): expected no error after reconnect, got %v", err)
    91  	}
    92  }
    93  
    94  // simulateServerRestart restarts the test server and reconfigures the connection
    95  // to use the new test server's port number. This is necessary because the port
    96  // number is selected randomly.
    97  func simulateServerRestart(c *cliTest, p cliTestParams, conn *sqlConn) func() {
    98  	c.restartServer(p)
    99  	url2, cleanup2 := sqlutils.PGUrl(c.t, c.ServingSQLAddr(), c.t.Name(), url.User(security.RootUser))
   100  	conn.url = url2.String()
   101  	return cleanup2
   102  }
   103  
   104  func TestRunQuery(t *testing.T) {
   105  	defer leaktest.AfterTest(t)()
   106  
   107  	c := newCLITest(cliTestParams{t: t})
   108  	defer c.cleanup()
   109  
   110  	url, cleanup := sqlutils.PGUrl(t, c.ServingSQLAddr(), t.Name(), url.User(security.RootUser))
   111  	defer cleanup()
   112  
   113  	conn := makeSQLConn(url.String())
   114  	defer conn.Close()
   115  
   116  	setCLIDefaultsForTests()
   117  
   118  	var b bytes.Buffer
   119  
   120  	// Non-query statement.
   121  	if err := runQueryAndFormatResults(conn, &b, makeQuery(`SET DATABASE=system`)); err != nil {
   122  		t.Fatal(err)
   123  	}
   124  
   125  	expected := `
   126  SET
   127  `
   128  	if a, e := b.String(), expected[1:]; a != e {
   129  		t.Fatalf("expected output:\n%s\ngot:\n%s", e, a)
   130  	}
   131  	b.Reset()
   132  
   133  	// Use system database for sample query/output as they are fairly fixed.
   134  	cols, rows, err := runQuery(conn, makeQuery(`SHOW COLUMNS FROM system.namespace`), false)
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  
   139  	expectedCols := []string{
   140  		"column_name",
   141  		"data_type",
   142  		"is_nullable",
   143  		"column_default",
   144  		"generation_expression",
   145  		"indices",
   146  		"is_hidden",
   147  	}
   148  	if !reflect.DeepEqual(expectedCols, cols) {
   149  		t.Fatalf("expected:\n%v\ngot:\n%v", expectedCols, cols)
   150  	}
   151  
   152  	expectedRows := [][]string{
   153  		{`parentID`, `INT8`, `false`, `NULL`, ``, `{primary}`, `false`},
   154  		{`name`, `STRING`, `false`, `NULL`, ``, `{primary}`, `false`},
   155  		{`id`, `INT8`, `true`, `NULL`, ``, `{}`, `false`},
   156  	}
   157  	if !reflect.DeepEqual(expectedRows, rows) {
   158  		t.Fatalf("expected:\n%v\ngot:\n%v", expectedRows, rows)
   159  	}
   160  
   161  	if err := runQueryAndFormatResults(conn, &b,
   162  		makeQuery(`SHOW COLUMNS FROM system.namespace`)); err != nil {
   163  		t.Fatal(err)
   164  	}
   165  
   166  	expected = `
   167    column_name | data_type | is_nullable | column_default | generation_expression |  indices  | is_hidden
   168  --------------+-----------+-------------+----------------+-----------------------+-----------+------------
   169    parentID    | INT8      |    false    | NULL           |                       | {primary} |   false
   170    name        | STRING    |    false    | NULL           |                       | {primary} |   false
   171    id          | INT8      |    true     | NULL           |                       | {}        |   false
   172  (3 rows)
   173  `
   174  
   175  	if a, e := b.String(), expected[1:]; a != e {
   176  		t.Fatalf("expected output:\n%s\ngot:\n%s", e, a)
   177  	}
   178  	b.Reset()
   179  
   180  	// Test placeholders.
   181  	if err := runQueryAndFormatResults(conn, &b,
   182  		makeQuery(`SELECT * FROM system.namespace WHERE name=$1`, "descriptor")); err != nil {
   183  		t.Fatal(err)
   184  	}
   185  
   186  	expected = `
   187    parentID | parentSchemaID |    name    | id
   188  -----------+----------------+------------+-----
   189           1 |             29 | descriptor |  3
   190  (1 row)
   191  `
   192  	if a, e := b.String(), expected[1:]; a != e {
   193  		t.Fatalf("expected output:\n%s\ngot:\n%s", e, a)
   194  	}
   195  	b.Reset()
   196  
   197  	// Test multiple results.
   198  	if err := runQueryAndFormatResults(conn, &b,
   199  		makeQuery(`SELECT 1 AS "1"; SELECT 2 AS "2", 3 AS "3"; SELECT 'hello' AS "'hello'"`)); err != nil {
   200  		t.Fatal(err)
   201  	}
   202  
   203  	expected = `
   204    1
   205  -----
   206    1
   207  (1 row)
   208    2 | 3
   209  ----+----
   210    2 | 3
   211  (1 row)
   212    'hello'
   213  -----------
   214    hello
   215  (1 row)
   216  `
   217  
   218  	if a, e := b.String(), expected[1:]; a != e {
   219  		t.Fatalf("expected output:\n%s\ngot:\n%s", e, a)
   220  	}
   221  	b.Reset()
   222  }
   223  
   224  func TestTransactionRetry(t *testing.T) {
   225  	defer leaktest.AfterTest(t)()
   226  
   227  	p := cliTestParams{t: t}
   228  	c := newCLITest(p)
   229  	defer c.cleanup()
   230  
   231  	url, cleanup := sqlutils.PGUrl(t, c.ServingSQLAddr(), t.Name(), url.User(security.RootUser))
   232  	defer cleanup()
   233  
   234  	conn := makeSQLConn(url.String())
   235  	defer conn.Close()
   236  
   237  	var tries int
   238  	err := conn.ExecTxn(func(conn *sqlConn) error {
   239  		tries++
   240  		if tries > 2 {
   241  			return nil
   242  		}
   243  
   244  		// Prevent automatic server-side retries.
   245  		rows, err := conn.Query(`SELECT now()`, nil)
   246  		if err != nil {
   247  			return err
   248  		}
   249  		if err := rows.Close(); err != nil {
   250  			return err
   251  		}
   252  
   253  		// Force a client-side retry.
   254  		rows, err = conn.Query(`SELECT crdb_internal.force_retry('1h')`, nil)
   255  		if err != nil {
   256  			return err
   257  		}
   258  		return rows.Close()
   259  	})
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  	if tries <= 2 {
   264  		t.Fatalf("expected transaction to require at least two tries, but it only required %d", tries)
   265  	}
   266  }