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  }