github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/s3select/sql/parser_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package sql
    19  
    20  import (
    21  	"bytes"
    22  	"testing"
    23  
    24  	"github.com/alecthomas/participle"
    25  	"github.com/alecthomas/participle/lexer"
    26  )
    27  
    28  func TestJSONPathElement(t *testing.T) {
    29  	p := participle.MustBuild(
    30  		&JSONPathElement{},
    31  		participle.Lexer(sqlLexer),
    32  		participle.CaseInsensitive("Keyword"),
    33  		participle.CaseInsensitive("Timeword"),
    34  	)
    35  
    36  	j := JSONPathElement{}
    37  	cases := []string{
    38  		// Key
    39  		"['name']", ".name", `."name"`,
    40  
    41  		// Index
    42  		"[2]", "[0]", "[100]",
    43  
    44  		// Object wildcard
    45  		".*",
    46  
    47  		// array wildcard
    48  		"[*]",
    49  	}
    50  	for i, tc := range cases {
    51  		err := p.ParseString(tc, &j)
    52  		if err != nil {
    53  			t.Fatalf("%d: %v", i, err)
    54  		}
    55  		// repr.Println(j, repr.Indent("  "), repr.OmitEmpty(true))
    56  	}
    57  }
    58  
    59  func TestJSONPath(t *testing.T) {
    60  	p := participle.MustBuild(
    61  		&JSONPath{},
    62  		participle.Lexer(sqlLexer),
    63  		participle.CaseInsensitive("Keyword"),
    64  		participle.CaseInsensitive("Timeword"),
    65  	)
    66  
    67  	j := JSONPath{}
    68  	cases := []string{
    69  		"S3Object",
    70  		"S3Object.id",
    71  		"S3Object.book.title",
    72  		"S3Object.id[1]",
    73  		"S3Object.id['abc']",
    74  		"S3Object.id['ab']",
    75  		"S3Object.words.*.id",
    76  		"S3Object.words.name[*].val",
    77  		"S3Object.words.name[*].val[*]",
    78  		"S3Object.words.name[*].val.*",
    79  	}
    80  	for i, tc := range cases {
    81  		err := p.ParseString(tc, &j)
    82  		if err != nil {
    83  			t.Fatalf("%d: %v", i, err)
    84  		}
    85  		// repr.Println(j, repr.Indent("  "), repr.OmitEmpty(true))
    86  	}
    87  }
    88  
    89  func TestIdentifierParsing(t *testing.T) {
    90  	p := participle.MustBuild(
    91  		&Identifier{},
    92  		participle.Lexer(sqlLexer),
    93  		participle.CaseInsensitive("Keyword"),
    94  	)
    95  
    96  	id := Identifier{}
    97  	validCases := []string{
    98  		"a",
    99  		"_a",
   100  		"abc_a",
   101  		"a2",
   102  		`"abc"`,
   103  		`"abc\a""ac"`,
   104  	}
   105  	for i, tc := range validCases {
   106  		err := p.ParseString(tc, &id)
   107  		if err != nil {
   108  			t.Fatalf("%d: %v", i, err)
   109  		}
   110  		// repr.Println(id, repr.Indent("  "), repr.OmitEmpty(true))
   111  	}
   112  
   113  	invalidCases := []string{
   114  		"+a",
   115  		"-a",
   116  		"1a",
   117  		`"ab`,
   118  		`abc"`,
   119  		`aa""a`,
   120  		`"a"a"`,
   121  	}
   122  	for i, tc := range invalidCases {
   123  		err := p.ParseString(tc, &id)
   124  		if err == nil {
   125  			t.Fatalf("%d: %v", i, err)
   126  		}
   127  		// fmt.Println(tc, err)
   128  	}
   129  }
   130  
   131  func TestLiteralStringParsing(t *testing.T) {
   132  	var k ObjectKey
   133  	p := participle.MustBuild(
   134  		&ObjectKey{},
   135  		participle.Lexer(sqlLexer),
   136  		participle.CaseInsensitive("Keyword"),
   137  	)
   138  
   139  	validCases := []string{
   140  		"['abc']",
   141  		"['ab''c']",
   142  		"['a''b''c']",
   143  		"['abc-x_1##@(*&(#*))/\\']",
   144  	}
   145  	for i, tc := range validCases {
   146  		err := p.ParseString(tc, &k)
   147  		if err != nil {
   148  			t.Fatalf("%d: %v", i, err)
   149  		}
   150  		if string(*k.Lit) == "" {
   151  			t.Fatalf("Incorrect parse %#v", k)
   152  		}
   153  		// repr.Println(k, repr.Indent("  "), repr.OmitEmpty(true))
   154  	}
   155  
   156  	invalidCases := []string{
   157  		"['abc'']",
   158  		"['-abc'sc']",
   159  		"[abc']",
   160  		"['ac]",
   161  	}
   162  	for i, tc := range invalidCases {
   163  		err := p.ParseString(tc, &k)
   164  		if err == nil {
   165  			t.Fatalf("%d: %v", i, err)
   166  		}
   167  		// fmt.Println(tc, err)
   168  	}
   169  }
   170  
   171  func TestFunctionParsing(t *testing.T) {
   172  	var fex FuncExpr
   173  	p := participle.MustBuild(
   174  		&FuncExpr{},
   175  		participle.Lexer(sqlLexer),
   176  		participle.CaseInsensitive("Keyword"),
   177  		participle.CaseInsensitive("Timeword"),
   178  	)
   179  
   180  	validCases := []string{
   181  		"count(*)",
   182  		"sum(2 + s.id)",
   183  		"sum(t)",
   184  		"avg(s.id[1])",
   185  		"coalesce(s.id[1], 2, 2 + 3)",
   186  
   187  		"cast(s as string)",
   188  		"cast(s AS INT)",
   189  		"cast(s as DECIMAL)",
   190  		"extract(YEAR from '2018-01-09')",
   191  		"extract(month from '2018-01-09')",
   192  
   193  		"extract(hour from '2018-01-09')",
   194  		"extract(day from '2018-01-09')",
   195  		"substring('abcd' from 2 for 2)",
   196  		"substring('abcd' from 2)",
   197  		"substring('abcd' , 2 , 2)",
   198  
   199  		"substring('abcd' , 22 )",
   200  		"trim('  aab  ')",
   201  		"trim(leading from '  aab  ')",
   202  		"trim(trailing from '  aab  ')",
   203  		"trim(both from '  aab  ')",
   204  
   205  		"trim(both '12' from '  aab  ')",
   206  		"trim(leading '12' from '  aab  ')",
   207  		"trim(trailing '12' from '  aab  ')",
   208  		"count(23)",
   209  	}
   210  	for i, tc := range validCases {
   211  		err := p.ParseString(tc, &fex)
   212  		if err != nil {
   213  			t.Fatalf("%d: %v", i, err)
   214  		}
   215  		// repr.Println(fex, repr.Indent("  "), repr.OmitEmpty(true))
   216  	}
   217  }
   218  
   219  func TestSqlLexer(t *testing.T) {
   220  	// s := bytes.NewBuffer([]byte("s.['name'].*.[*].abc.[\"abc\"]"))
   221  	s := bytes.NewBuffer([]byte("S3Object.words.*.id"))
   222  	// s := bytes.NewBuffer([]byte("COUNT(Id)"))
   223  	lex, err := sqlLexer.Lex(s)
   224  	if err != nil {
   225  		t.Fatal(err)
   226  	}
   227  	tokens, err := lexer.ConsumeAll(lex)
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  	// for i, t := range tokens {
   232  	// 	fmt.Printf("%d: %#v\n", i, t)
   233  	// }
   234  	if len(tokens) != 7 {
   235  		t.Fatalf("Expected 7 got %d", len(tokens))
   236  	}
   237  }
   238  
   239  func TestSelectWhere(t *testing.T) {
   240  	p := participle.MustBuild(
   241  		&Select{},
   242  		participle.Lexer(sqlLexer),
   243  		participle.CaseInsensitive("Keyword"),
   244  	)
   245  
   246  	s := Select{}
   247  	cases := []string{
   248  		"select * from s3object",
   249  		"select a, b from s3object s",
   250  		"select a, b from s3object as s",
   251  		"select a, b from s3object as s where a = 1",
   252  		"select a, b from s3object s where a = 1",
   253  		"select a, b from s3object where a = 1",
   254  	}
   255  	for i, tc := range cases {
   256  		err := p.ParseString(tc, &s)
   257  		if err != nil {
   258  			t.Fatalf("%d: %v", i, err)
   259  		}
   260  
   261  		// repr.Println(s, repr.Indent("  "), repr.OmitEmpty(true))
   262  	}
   263  }
   264  
   265  func TestLikeClause(t *testing.T) {
   266  	p := participle.MustBuild(
   267  		&Select{},
   268  		participle.Lexer(sqlLexer),
   269  		participle.CaseInsensitive("Keyword"),
   270  	)
   271  
   272  	s := Select{}
   273  	cases := []string{
   274  		`select * from s3object where Name like 'abcd'`,
   275  		`select Name like 'abc' from s3object`,
   276  		`select * from s3object where Name not like 'abc'`,
   277  		`select * from s3object where Name like 'abc' escape 't'`,
   278  		`select * from s3object where Name like 'a\%' escape '?'`,
   279  		`select * from s3object where Name not like 'abc\' escape '?'`,
   280  		`select * from s3object where Name like 'a\%' escape LOWER('?')`,
   281  		`select * from s3object where Name not like LOWER('Bc\') escape '?'`,
   282  	}
   283  	for i, tc := range cases {
   284  		err := p.ParseString(tc, &s)
   285  		if err != nil {
   286  			t.Errorf("%d: %v", i, err)
   287  		}
   288  	}
   289  }
   290  
   291  func TestBetweenClause(t *testing.T) {
   292  	p := participle.MustBuild(
   293  		&Select{},
   294  		participle.Lexer(sqlLexer),
   295  		participle.CaseInsensitive("Keyword"),
   296  	)
   297  
   298  	s := Select{}
   299  	cases := []string{
   300  		`select * from s3object where Id between 1 and 2`,
   301  		`select * from s3object where Id between 1 and 2 and name = 'Ab'`,
   302  		`select * from s3object where Id not between 1 and 2`,
   303  		`select * from s3object where Id not between 1 and 2 and name = 'Bc'`,
   304  	}
   305  	for i, tc := range cases {
   306  		err := p.ParseString(tc, &s)
   307  		if err != nil {
   308  			t.Errorf("%d: %v", i, err)
   309  		}
   310  	}
   311  }
   312  
   313  func TestFromClauseJSONPath(t *testing.T) {
   314  	p := participle.MustBuild(
   315  		&Select{},
   316  		participle.Lexer(sqlLexer),
   317  		participle.CaseInsensitive("Keyword"),
   318  	)
   319  
   320  	s := Select{}
   321  	cases := []string{
   322  		"select * from s3object",
   323  		"select * from s3object[*].name",
   324  		"select * from s3object[*].books[*]",
   325  		"select * from s3object[*].books[*].name",
   326  		"select * from s3object where name > 2",
   327  		"select * from s3object[*].name where name > 2",
   328  		"select * from s3object[*].books[*] where name > 2",
   329  		"select * from s3object[*].books[*].name where name > 2",
   330  		"select * from s3object[*].books[*] s",
   331  		"select * from s3object[*].books[*].name as s",
   332  		"select * from s3object s where name > 2",
   333  		"select * from s3object[*].name as s where name > 2",
   334  		"select * from s3object[*].books[*] limit 1",
   335  	}
   336  	for i, tc := range cases {
   337  		err := p.ParseString(tc, &s)
   338  		if err != nil {
   339  			t.Fatalf("%d: %v", i, err)
   340  		}
   341  
   342  		// repr.Println(s, repr.Indent("  "), repr.OmitEmpty(true))
   343  	}
   344  }
   345  
   346  func TestSelectParsing(t *testing.T) {
   347  	p := participle.MustBuild(
   348  		&Select{},
   349  		participle.Lexer(sqlLexer),
   350  		participle.CaseInsensitive("Keyword"),
   351  	)
   352  
   353  	s := Select{}
   354  	cases := []string{
   355  		"select * from s3object where name > 2 or value > 1 or word > 2",
   356  		"select s.word.id + 2 from s3object s",
   357  		"select 1-2-3 from s3object s limit 1",
   358  	}
   359  	for i, tc := range cases {
   360  		err := p.ParseString(tc, &s)
   361  		if err != nil {
   362  			t.Fatalf("%d: %v", i, err)
   363  		}
   364  
   365  		// repr.Println(s, repr.Indent("  "), repr.OmitEmpty(true))
   366  	}
   367  }
   368  
   369  func TestSqlLexerArithOps(t *testing.T) {
   370  	s := bytes.NewBuffer([]byte("year from select month hour distinct"))
   371  	lex, err := sqlLexer.Lex(s)
   372  	if err != nil {
   373  		t.Fatal(err)
   374  	}
   375  	tokens, err := lexer.ConsumeAll(lex)
   376  	if err != nil {
   377  		t.Fatal(err)
   378  	}
   379  	if len(tokens) != 7 {
   380  		t.Errorf("Expected 7 got %d", len(tokens))
   381  	}
   382  	// for i, t := range tokens {
   383  	// 	fmt.Printf("%d: %#v\n", i, t)
   384  	// }
   385  }
   386  
   387  func TestParseSelectStatement(t *testing.T) {
   388  	exp, err := ParseSelectStatement("select _3,_1,_2 as 'mytest'  from S3object")
   389  	if err != nil {
   390  		t.Fatalf("parse alias sql error: %v", err)
   391  	}
   392  	if exp.selectAST.Expression.Expressions[2].As != "mytest" {
   393  		t.Fatalf("parse alias sql error: %s not equal %s", exp.selectAST.Expression.Expressions[2].As, err)
   394  	}
   395  }