github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/sql_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 "fmt" 15 "io/ioutil" 16 "os" 17 "testing" 18 19 "github.com/cockroachdb/cockroach/pkg/security" 20 "github.com/cockroachdb/cockroach/pkg/sql/parser" 21 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 22 "github.com/stretchr/testify/assert" 23 ) 24 25 // Example_sql_lex tests the usage of the lexer in the sql subcommand. 26 func Example_sql_lex() { 27 c := newCLITest(cliTestParams{insecure: true}) 28 defer c.cleanup() 29 30 conn := makeSQLConn(fmt.Sprintf("postgres://%s@%s/?sslmode=disable", 31 security.RootUser, c.ServingSQLAddr())) 32 defer conn.Close() 33 34 tests := []string{` 35 select ' 36 \? 37 ; 38 '; 39 `, 40 ` 41 select '''' 42 ; 43 44 select ''' 45 ; 46 '''; 47 `, 48 `select 1 as "1"; 49 -- just a comment without final semicolon`, 50 } 51 52 setCLIDefaultsForTests() 53 54 // We need a temporary file with a name guaranteed to be available. 55 // So open a dummy file. 56 f, err := ioutil.TempFile("", "input") 57 if err != nil { 58 fmt.Fprintln(stderr, err) 59 return 60 } 61 // Get the name and close it. 62 fname := f.Name() 63 f.Close() 64 65 // At every point below, when t.Fatal is called we should ensure the 66 // file is closed and removed. 67 f = nil 68 defer func() { 69 if f != nil { 70 f.Close() 71 } 72 _ = os.Remove(fname) 73 stdin = os.Stdin 74 }() 75 76 for _, test := range tests { 77 // Populate the test input. 78 if f, err = os.OpenFile(fname, os.O_WRONLY, 0644); err != nil { 79 fmt.Fprintln(stderr, err) 80 return 81 } 82 if _, err := f.WriteString(test); err != nil { 83 fmt.Fprintln(stderr, err) 84 return 85 } 86 f.Close() 87 // Make it available for reading. 88 if f, err = os.Open(fname); err != nil { 89 fmt.Fprintln(stderr, err) 90 return 91 } 92 // Override the standard input for runInteractive(). 93 stdin = f 94 95 err := runInteractive(conn) 96 if err != nil { 97 fmt.Fprintln(stderr, err) 98 } 99 } 100 101 // Output: 102 // ?column? 103 // ------------ 104 // 105 // \? 106 // ; 107 // 108 // (1 row) 109 // ?column? 110 // ------------ 111 // ' 112 // (1 row) 113 // ?column? 114 // ------------ 115 // ' 116 // ; 117 // ' 118 // (1 row) 119 // 1 120 // ----- 121 // 1 122 // (1 row) 123 } 124 125 func TestIsEndOfStatement(t *testing.T) { 126 defer leaktest.AfterTest(t)() 127 128 tests := []struct { 129 in string 130 isEnd bool 131 isNotEmpty bool 132 }{ 133 { 134 in: ";", 135 isEnd: true, 136 isNotEmpty: true, 137 }, 138 { 139 in: "; /* comment */", 140 isEnd: true, 141 isNotEmpty: true, 142 }, 143 { 144 in: "; SELECT", 145 isNotEmpty: true, 146 }, 147 { 148 in: "SELECT", 149 isNotEmpty: true, 150 }, 151 { 152 in: "SET; SELECT 1;", 153 isEnd: true, 154 isNotEmpty: true, 155 }, 156 { 157 in: "SELECT ''''; SET;", 158 isEnd: true, 159 isNotEmpty: true, 160 }, 161 { 162 in: " -- hello", 163 }, 164 { 165 in: "select 'abc", // invalid token 166 isNotEmpty: true, 167 }, 168 { 169 in: "'abc", // invalid token 170 isNotEmpty: true, 171 }, 172 { 173 in: `SELECT e'\xaa';`, // invalid token but last token is semicolon 174 isEnd: true, 175 isNotEmpty: true, 176 }, 177 } 178 179 for _, test := range tests { 180 lastTok, isNotEmpty := parser.LastLexicalToken(test.in) 181 if isNotEmpty != test.isNotEmpty { 182 t.Errorf("%q: isNotEmpty expected %v, got %v", test.in, test.isNotEmpty, isNotEmpty) 183 } 184 isEnd := isEndOfStatement(lastTok) 185 if isEnd != test.isEnd { 186 t.Errorf("%q: isEnd expected %v, got %v", test.in, test.isEnd, isEnd) 187 } 188 } 189 } 190 191 // Test handleCliCmd cases for client-side commands that are aliases for sql 192 // statements. 193 func TestHandleCliCmdSqlAlias(t *testing.T) { 194 defer leaktest.AfterTest(t)() 195 clientSideCommandTestsTable := []struct { 196 commandString string 197 wantSQLStmt string 198 }{ 199 {`\l`, `SHOW DATABASES`}, 200 {`\dt`, `SHOW TABLES`}, 201 {`\du`, `SHOW USERS`}, 202 {`\d mytable`, `SHOW COLUMNS FROM mytable`}, 203 {`\d`, `SHOW TABLES`}, 204 } 205 206 var c cliState 207 for _, tt := range clientSideCommandTestsTable { 208 c = setupTestCliState() 209 c.lastInputLine = tt.commandString 210 gotState := c.doHandleCliCmd(cliStateEnum(0), cliStateEnum(1)) 211 212 assert.Equal(t, cliRunStatement, gotState) 213 assert.Equal(t, tt.wantSQLStmt, c.concatLines) 214 } 215 } 216 217 func TestHandleCliCmdSlashDInvalidSyntax(t *testing.T) { 218 defer leaktest.AfterTest(t)() 219 220 clientSideCommandTests := []string{`\d goodarg badarg`, `\dz`} 221 222 var c cliState 223 for _, tt := range clientSideCommandTests { 224 c = setupTestCliState() 225 c.lastInputLine = tt 226 gotState := c.doHandleCliCmd(cliStateEnum(0), cliStateEnum(1)) 227 228 assert.Equal(t, cliStateEnum(0), gotState) 229 assert.Equal(t, errInvalidSyntax, c.exitErr) 230 } 231 } 232 233 func setupTestCliState() cliState { 234 c := cliState{} 235 c.ins = noLineEditor 236 return c 237 }