vitess.io/vitess@v0.16.2/go/vt/sqlparser/parse_next_test.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package sqlparser
    18  
    19  import (
    20  	"bytes"
    21  	"io"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  // TestParseNextValid concatenates all the valid SQL test cases and check it can read
    30  // them as one long string.
    31  func TestParseNextValid(t *testing.T) {
    32  	var sql bytes.Buffer
    33  	for _, tcase := range validSQL {
    34  		sql.WriteString(strings.TrimSuffix(tcase.input, ";"))
    35  		sql.WriteRune(';')
    36  	}
    37  
    38  	tokens := NewStringTokenizer(sql.String())
    39  	for i, tcase := range validSQL {
    40  		input := tcase.input + ";"
    41  		want := tcase.output
    42  		if want == "" {
    43  			want = tcase.input
    44  		}
    45  
    46  		tree, err := ParseNext(tokens)
    47  		require.NoError(t, err)
    48  
    49  		if got := String(tree); got != want {
    50  			t.Fatalf("[%d] ParseNext(%q) = %q, want %q", i, input, got, want)
    51  		}
    52  	}
    53  
    54  	// Read once more and it should be EOF.
    55  	if tree, err := ParseNext(tokens); err != io.EOF {
    56  		t.Errorf("ParseNext(tokens) = (%q, %v) want io.EOF", String(tree), err)
    57  	}
    58  }
    59  
    60  func TestIgnoreSpecialComments(t *testing.T) {
    61  	input := `SELECT 1;/*! ALTER TABLE foo DISABLE KEYS */;SELECT 2;`
    62  
    63  	tokenizer := NewStringTokenizer(input)
    64  	tokenizer.SkipSpecialComments = true
    65  	one, err := ParseNextStrictDDL(tokenizer)
    66  	require.NoError(t, err)
    67  	require.Equal(t, "select 1 from dual", String(one))
    68  	two, err := ParseNextStrictDDL(tokenizer)
    69  	require.NoError(t, err)
    70  	require.Equal(t, "select 2 from dual", String(two))
    71  }
    72  
    73  // TestParseNextErrors tests all the error cases, and ensures a valid
    74  // SQL statement can be passed afterwards.
    75  func TestParseNextErrors(t *testing.T) {
    76  	for _, tcase := range invalidSQL {
    77  		if tcase.excludeMulti {
    78  			// Skip tests which leave unclosed strings, or comments.
    79  			continue
    80  		}
    81  		t.Run(tcase.input, func(t *testing.T) {
    82  			sql := tcase.input + "; select 1 from t"
    83  			tokens := NewStringTokenizer(sql)
    84  
    85  			// The first statement should be an error
    86  			_, err := ParseNextStrictDDL(tokens)
    87  			require.EqualError(t, err, tcase.output)
    88  
    89  			// The second should be valid
    90  			tree, err := ParseNextStrictDDL(tokens)
    91  			require.NoError(t, err)
    92  
    93  			want := "select 1 from t"
    94  			assert.Equal(t, want, String(tree))
    95  
    96  			// Read once more and it should be EOF.
    97  			_, err = ParseNextStrictDDL(tokens)
    98  			require.Same(t, io.EOF, err)
    99  		})
   100  	}
   101  }
   102  
   103  // TestParseNextEdgeCases tests various ParseNext edge cases.
   104  func TestParseNextEdgeCases(t *testing.T) {
   105  	tests := []struct {
   106  		name  string
   107  		input string
   108  		want  []string
   109  	}{{
   110  		name:  "Trailing ;",
   111  		input: "select 1 from a; update a set b = 2;",
   112  		want:  []string{"select 1 from a", "update a set b = 2"},
   113  	}, {
   114  		name:  "No trailing ;",
   115  		input: "select 1 from a; update a set b = 2",
   116  		want:  []string{"select 1 from a", "update a set b = 2"},
   117  	}, {
   118  		name:  "Trailing whitespace",
   119  		input: "select 1 from a; update a set b = 2    ",
   120  		want:  []string{"select 1 from a", "update a set b = 2"},
   121  	}, {
   122  		name:  "Trailing whitespace and ;",
   123  		input: "select 1 from a; update a set b = 2   ;   ",
   124  		want:  []string{"select 1 from a", "update a set b = 2"},
   125  	}, {
   126  		name:  "Handle SkipToEnd statements",
   127  		input: "set character set utf8; select 1 from a",
   128  		want:  []string{"set charset 'utf8'", "select 1 from a"},
   129  	}, {
   130  		name:  "Semicolin inside a string",
   131  		input: "set character set ';'; select 1 from a",
   132  		want:  []string{"set charset ';'", "select 1 from a"},
   133  	}, {
   134  		name:  "Partial DDL",
   135  		input: "create table a; select 1 from a",
   136  		want:  []string{"create table a", "select 1 from a"},
   137  	}, {
   138  		name:  "Partial DDL",
   139  		input: "create table a ignore me this is garbage; select 1 from a",
   140  		want:  []string{"create table a", "select 1 from a"},
   141  	}}
   142  
   143  	for _, test := range tests {
   144  		tokens := NewStringTokenizer(test.input)
   145  
   146  		for i, want := range test.want {
   147  			tree, err := ParseNext(tokens)
   148  			require.NoError(t, err)
   149  
   150  			if got := String(tree); got != want {
   151  				t.Fatalf("[%d] ParseNext(%q) = %q, want %q", i, test.input, got, want)
   152  			}
   153  		}
   154  
   155  		// Read once more and it should be EOF.
   156  		if tree, err := ParseNext(tokens); err != io.EOF {
   157  			t.Errorf("ParseNext(%q) = (%q, %v) want io.EOF", test.input, String(tree), err)
   158  		}
   159  
   160  		// And again, once more should be EOF.
   161  		if tree, err := ParseNext(tokens); err != io.EOF {
   162  			t.Errorf("ParseNext(%q) = (%q, %v) want io.EOF", test.input, String(tree), err)
   163  		}
   164  	}
   165  }
   166  
   167  // TestParseNextEdgeCases tests various ParseNext edge cases.
   168  func TestParseNextStrictNonStrict(t *testing.T) {
   169  	// This is one of the edge cases above.
   170  	input := "create table a ignore me this is garbage; select 1 from a"
   171  	want := []string{"create table a", "select 1 from a"}
   172  
   173  	// First go through as expected with non-strict DDL parsing.
   174  	tokens := NewStringTokenizer(input)
   175  	for i, want := range want {
   176  		tree, err := ParseNext(tokens)
   177  		if err != nil {
   178  			t.Fatalf("[%d] ParseNext(%q) err = %q, want nil", i, input, err)
   179  		}
   180  		if got := String(tree); got != want {
   181  			t.Fatalf("[%d] ParseNext(%q) = %q, want %q", i, input, got, want)
   182  		}
   183  	}
   184  
   185  	// Now try again with strict parsing and observe the expected error.
   186  	tokens = NewStringTokenizer(input)
   187  	_, err := ParseNextStrictDDL(tokens)
   188  	if err == nil || !strings.Contains(err.Error(), "ignore") {
   189  		t.Fatalf("ParseNext(%q) err = %q, want ignore", input, err)
   190  	}
   191  	tree, err := ParseNextStrictDDL(tokens)
   192  	if err != nil {
   193  		t.Fatalf("ParseNext(%q) err = %q, want nil", input, err)
   194  	}
   195  	if got := String(tree); got != want[1] {
   196  		t.Fatalf("ParseNext(%q) = %q, want %q", input, got, want)
   197  	}
   198  }