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 }