// Copyright (c) 2015-2021 MinIO, Inc.//// This file is part of MinIO Object Storage stack//// This program is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// This program is distributed in the hope that it will be useful// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with this program. If not, see <http://www.gnu.org/licenses/>. package sql import ( "bytes" "testing" "github.com/alecthomas/participle" "github.com/alecthomas/participle/lexer") func TestJSONPathElement(t *testing.T) { p := participle.MustBuild( &JSONPathElement{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), participle.CaseInsensitive("Timeword"), ) j := JSONPathElement{} cases := []string{ // Key "['name']", ".name", `."name"`, // Index "[2]", "[0]", "[100]", // Object wilcard ".*", // array wildcard "[*]", } for i, tc := range cases { err := p.ParseString(tc, &j) if err != nil { t.Fatalf("%d: %v", i, err) } // repr.Println(j, repr.Indent(" "), repr.OmitEmpty(true)) }} func TestJSONPath(t *testing.T) { p := participle.MustBuild( &JSONPath{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), participle.CaseInsensitive("Timeword"), ) j := JSONPath{} cases := []string{ "S3Object", "S3Object.id", "S3Object.book.title", "S3Object.id[1]", "S3Object.id['abc']", "S3Object.id['ab']", "S3Object.words.*.id", "S3Object.words.name[*].val", "S3Object.words.name[*].val[*]", "S3Object.words.name[*].val.*", } for i, tc := range cases { err := p.ParseString(tc, &j) if err != nil { t.Fatalf("%d: %v", i, err) } // repr.Println(j, repr.Indent(" "), repr.OmitEmpty(true)) }} func TestIdentifierParsing(t *testing.T) { p := participle.MustBuild( &Identifier{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), ) id := Identifier{} validCases := []string{ "a", "_a", "abc_a", "a2", `"abc"`, `"abc\a""ac"`, } for i, tc := range validCases { err := p.ParseString(tc, &id) if err != nil { t.Fatalf("%d: %v", i, err) } // repr.Println(id, repr.Indent(" "), repr.OmitEmpty(true)) } invalidCases := []string{ "+a", "-a", "1a", `"ab`, `abc"`, `aa""a`, `"a"a"`, } for i, tc := range invalidCases { err := p.ParseString(tc, &id) if err == nil { t.Fatalf("%d: %v", i, err) } // fmt.Println(tc, err) }} func TestLiteralStringParsing(t *testing.T) { var k ObjectKey p := participle.MustBuild( &ObjectKey{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), ) validCases := []string{ "['abc']", "['ab''c']", "['a''b''c']", "['abc-x_1##@(*&(#*))/\\']", } for i, tc := range validCases { err := p.ParseString(tc, &k) if err != nil { t.Fatalf("%d: %v", i, err) } if string(*k.Lit) == "" { t.Fatalf("Incorrect parse %#v", k) } // repr.Println(k, repr.Indent(" "), repr.OmitEmpty(true)) } invalidCases := []string{ "['abc'']", "['-abc'sc']", "[abc']", "['ac]", } for i, tc := range invalidCases { err := p.ParseString(tc, &k) if err == nil { t.Fatalf("%d: %v", i, err) } // fmt.Println(tc, err) }} func TestFunctionParsing(t *testing.T) { var fex FuncExpr p := participle.MustBuild( &FuncExpr{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), participle.CaseInsensitive("Timeword"), ) validCases := []string{ "count(*)", "sum(2 + s.id)", "sum(t)", "avg(s.id[1])", "coalesce(s.id[1], 2, 2 + 3)", "cast(s as string)", "cast(s AS INT)", "cast(s as DECIMAL)", "extract(YEAR from '2018-01-09')", "extract(month from '2018-01-09')", "extract(hour from '2018-01-09')", "extract(day from '2018-01-09')", "substring('abcd' from 2 for 2)", "substring('abcd' from 2)", "substring('abcd' , 2 , 2)", "substring('abcd' , 22 )", "trim(' aab ')", "trim(leading from ' aab ')", "trim(trailing from ' aab ')", "trim(both from ' aab ')", "trim(both '12' from ' aab ')", "trim(leading '12' from ' aab ')", "trim(trailing '12' from ' aab ')", "count(23)", } for i, tc := range validCases { err := p.ParseString(tc, &fex) if err != nil { t.Fatalf("%d: %v", i, err) } // repr.Println(fex, repr.Indent(" "), repr.OmitEmpty(true)) }} func TestSqlLexer(t *testing.T) { // s := bytes.NewBuffer([]byte("s.['name'].*.[*].abc.[\"abc\"]")) s := bytes.NewBuffer([]byte("S3Object.words.*.id")) // s := bytes.NewBuffer([]byte("COUNT(Id)")) lex, err := sqlLexer.Lex(s) if err != nil { t.Fatal(err) } tokens, err := lexer.ConsumeAll(lex) if err != nil { t.Fatal(err) } // for i, t := range tokens { // fmt.Printf("%d: %#v\n", i, t) // } if len(tokens) != 7 { t.Fatalf("Expected 7 got %d", len(tokens)) }} func TestSelectWhere(t *testing.T) { p := participle.MustBuild( &Select{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), ) s := Select{} cases := []string{ "select * from s3object", "select a, b from s3object s", "select a, b from s3object as s", "select a, b from s3object as s where a = 1", "select a, b from s3object s where a = 1", "select a, b from s3object where a = 1", } for i, tc := range cases { err := p.ParseString(tc, &s) if err != nil { t.Fatalf("%d: %v", i, err) } // repr.Println(s, repr.Indent(" "), repr.OmitEmpty(true)) }} func TestLikeClause(t *testing.T) { p := participle.MustBuild( &Select{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), ) s := Select{} cases := []string{ `select * from s3object where Name like 'abcd'`, `select Name like 'abc' from s3object`, `select * from s3object where Name not like 'abc'`, `select * from s3object where Name like 'abc' escape 't'`, `select * from s3object where Name like 'a\%' escape '?'`, `select * from s3object where Name not like 'abc\' escape '?'`, `select * from s3object where Name like 'a\%' escape LOWER('?')`, `select * from s3object where Name not like LOWER('Bc\') escape '?'`, } for i, tc := range cases { err := p.ParseString(tc, &s) if err != nil { t.Errorf("%d: %v", i, err) } }} func TestBetweenClause(t *testing.T) { p := participle.MustBuild( &Select{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), ) s := Select{} cases := []string{ `select * from s3object where Id between 1 and 2`, `select * from s3object where Id between 1 and 2 and name = 'Ab'`, `select * from s3object where Id not between 1 and 2`, `select * from s3object where Id not between 1 and 2 and name = 'Bc'`, } for i, tc := range cases { err := p.ParseString(tc, &s) if err != nil { t.Errorf("%d: %v", i, err) } }} func TestFromClauseJSONPath(t *testing.T) { p := participle.MustBuild( &Select{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), ) s := Select{} cases := []string{ "select * from s3object", "select * from s3object[*].name", "select * from s3object[*].books[*]", "select * from s3object[*].books[*].name", "select * from s3object where name > 2", "select * from s3object[*].name where name > 2", "select * from s3object[*].books[*] where name > 2", "select * from s3object[*].books[*].name where name > 2", "select * from s3object[*].books[*] s", "select * from s3object[*].books[*].name as s", "select * from s3object s where name > 2", "select * from s3object[*].name as s where name > 2", "select * from s3object[*].books[*] limit 1", } for i, tc := range cases { err := p.ParseString(tc, &s) if err != nil { t.Fatalf("%d: %v", i, err) } // repr.Println(s, repr.Indent(" "), repr.OmitEmpty(true)) }} func TestSelectParsing(t *testing.T) { p := participle.MustBuild( &Select{}, participle.Lexer(sqlLexer), participle.CaseInsensitive("Keyword"), ) s := Select{} cases := []string{ "select * from s3object where name > 2 or value > 1 or word > 2", "select s.word.id + 2 from s3object s", "select 1-2-3 from s3object s limit 1", } for i, tc := range cases { err := p.ParseString(tc, &s) if err != nil { t.Fatalf("%d: %v", i, err) } // repr.Println(s, repr.Indent(" "), repr.OmitEmpty(true)) }} func TestSqlLexerArithOps(t *testing.T) { s := bytes.NewBuffer([]byte("year from select month hour distinct")) lex, err := sqlLexer.Lex(s) if err != nil { t.Fatal(err) } tokens, err := lexer.ConsumeAll(lex) if err != nil { t.Fatal(err) } if len(tokens) != 7 { t.Errorf("Expected 7 got %d", len(tokens)) } // for i, t := range tokens { // fmt.Printf("%d: %#v\n", i, t) // }}
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package sql
import (
"bytes"
"testing"
"github.com/alecthomas/participle"
"github.com/alecthomas/participle/lexer"
)
func TestJSONPathElement(t *testing.T) {
p := participle.MustBuild(
&JSONPathElement{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
participle.CaseInsensitive("Timeword"),
)
j := JSONPathElement{}
cases := []string{
// Key
"['name']", ".name", `."name"`,
// Index
"[2]", "[0]", "[100]",
// Object wilcard
".*",
// array wildcard
"[*]",
}
for i, tc := range cases {
err := p.ParseString(tc, &j)
if err != nil {
t.Fatalf("%d: %v", i, err)
}
// repr.Println(j, repr.Indent(" "), repr.OmitEmpty(true))
}
}
func TestJSONPath(t *testing.T) {
p := participle.MustBuild(
&JSONPath{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
participle.CaseInsensitive("Timeword"),
)
j := JSONPath{}
cases := []string{
"S3Object",
"S3Object.id",
"S3Object.book.title",
"S3Object.id[1]",
"S3Object.id['abc']",
"S3Object.id['ab']",
"S3Object.words.*.id",
"S3Object.words.name[*].val",
"S3Object.words.name[*].val[*]",
"S3Object.words.name[*].val.*",
}
for i, tc := range cases {
err := p.ParseString(tc, &j)
if err != nil {
t.Fatalf("%d: %v", i, err)
}
// repr.Println(j, repr.Indent(" "), repr.OmitEmpty(true))
}
}
func TestIdentifierParsing(t *testing.T) {
p := participle.MustBuild(
&Identifier{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
)
id := Identifier{}
validCases := []string{
"a",
"_a",
"abc_a",
"a2",
`"abc"`,
`"abc\a""ac"`,
}
for i, tc := range validCases {
err := p.ParseString(tc, &id)
if err != nil {
t.Fatalf("%d: %v", i, err)
}
// repr.Println(id, repr.Indent(" "), repr.OmitEmpty(true))
}
invalidCases := []string{
"+a",
"-a",
"1a",
`"ab`,
`abc"`,
`aa""a`,
`"a"a"`,
}
for i, tc := range invalidCases {
err := p.ParseString(tc, &id)
if err == nil {
t.Fatalf("%d: %v", i, err)
}
// fmt.Println(tc, err)
}
}
func TestLiteralStringParsing(t *testing.T) {
var k ObjectKey
p := participle.MustBuild(
&ObjectKey{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
)
validCases := []string{
"['abc']",
"['ab''c']",
"['a''b''c']",
"['abc-x_1##@(*&(#*))/\\']",
}
for i, tc := range validCases {
err := p.ParseString(tc, &k)
if err != nil {
t.Fatalf("%d: %v", i, err)
}
if string(*k.Lit) == "" {
t.Fatalf("Incorrect parse %#v", k)
}
// repr.Println(k, repr.Indent(" "), repr.OmitEmpty(true))
}
invalidCases := []string{
"['abc'']",
"['-abc'sc']",
"[abc']",
"['ac]",
}
for i, tc := range invalidCases {
err := p.ParseString(tc, &k)
if err == nil {
t.Fatalf("%d: %v", i, err)
}
// fmt.Println(tc, err)
}
}
func TestFunctionParsing(t *testing.T) {
var fex FuncExpr
p := participle.MustBuild(
&FuncExpr{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
participle.CaseInsensitive("Timeword"),
)
validCases := []string{
"count(*)",
"sum(2 + s.id)",
"sum(t)",
"avg(s.id[1])",
"coalesce(s.id[1], 2, 2 + 3)",
"cast(s as string)",
"cast(s AS INT)",
"cast(s as DECIMAL)",
"extract(YEAR from '2018-01-09')",
"extract(month from '2018-01-09')",
"extract(hour from '2018-01-09')",
"extract(day from '2018-01-09')",
"substring('abcd' from 2 for 2)",
"substring('abcd' from 2)",
"substring('abcd' , 2 , 2)",
"substring('abcd' , 22 )",
"trim(' aab ')",
"trim(leading from ' aab ')",
"trim(trailing from ' aab ')",
"trim(both from ' aab ')",
"trim(both '12' from ' aab ')",
"trim(leading '12' from ' aab ')",
"trim(trailing '12' from ' aab ')",
"count(23)",
}
for i, tc := range validCases {
err := p.ParseString(tc, &fex)
if err != nil {
t.Fatalf("%d: %v", i, err)
}
// repr.Println(fex, repr.Indent(" "), repr.OmitEmpty(true))
}
}
func TestSqlLexer(t *testing.T) {
// s := bytes.NewBuffer([]byte("s.['name'].*.[*].abc.[\"abc\"]"))
s := bytes.NewBuffer([]byte("S3Object.words.*.id"))
// s := bytes.NewBuffer([]byte("COUNT(Id)"))
lex, err := sqlLexer.Lex(s)
if err != nil {
t.Fatal(err)
}
tokens, err := lexer.ConsumeAll(lex)
if err != nil {
t.Fatal(err)
}
// for i, t := range tokens {
// fmt.Printf("%d: %#v\n", i, t)
// }
if len(tokens) != 7 {
t.Fatalf("Expected 7 got %d", len(tokens))
}
}
func TestSelectWhere(t *testing.T) {
p := participle.MustBuild(
&Select{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
)
s := Select{}
cases := []string{
"select * from s3object",
"select a, b from s3object s",
"select a, b from s3object as s",
"select a, b from s3object as s where a = 1",
"select a, b from s3object s where a = 1",
"select a, b from s3object where a = 1",
}
for i, tc := range cases {
err := p.ParseString(tc, &s)
if err != nil {
t.Fatalf("%d: %v", i, err)
}
// repr.Println(s, repr.Indent(" "), repr.OmitEmpty(true))
}
}
func TestLikeClause(t *testing.T) {
p := participle.MustBuild(
&Select{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
)
s := Select{}
cases := []string{
`select * from s3object where Name like 'abcd'`,
`select Name like 'abc' from s3object`,
`select * from s3object where Name not like 'abc'`,
`select * from s3object where Name like 'abc' escape 't'`,
`select * from s3object where Name like 'a\%' escape '?'`,
`select * from s3object where Name not like 'abc\' escape '?'`,
`select * from s3object where Name like 'a\%' escape LOWER('?')`,
`select * from s3object where Name not like LOWER('Bc\') escape '?'`,
}
for i, tc := range cases {
err := p.ParseString(tc, &s)
if err != nil {
t.Errorf("%d: %v", i, err)
}
}
}
func TestBetweenClause(t *testing.T) {
p := participle.MustBuild(
&Select{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
)
s := Select{}
cases := []string{
`select * from s3object where Id between 1 and 2`,
`select * from s3object where Id between 1 and 2 and name = 'Ab'`,
`select * from s3object where Id not between 1 and 2`,
`select * from s3object where Id not between 1 and 2 and name = 'Bc'`,
}
for i, tc := range cases {
err := p.ParseString(tc, &s)
if err != nil {
t.Errorf("%d: %v", i, err)
}
}
}
func TestFromClauseJSONPath(t *testing.T) {
p := participle.MustBuild(
&Select{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
)
s := Select{}
cases := []string{
"select * from s3object",
"select * from s3object[*].name",
"select * from s3object[*].books[*]",
"select * from s3object[*].books[*].name",
"select * from s3object where name > 2",
"select * from s3object[*].name where name > 2",
"select * from s3object[*].books[*] where name > 2",
"select * from s3object[*].books[*].name where name > 2",
"select * from s3object[*].books[*] s",
"select * from s3object[*].books[*].name as s",
"select * from s3object s where name > 2",
"select * from s3object[*].name as s where name > 2",
"select * from s3object[*].books[*] limit 1",
}
for i, tc := range cases {
err := p.ParseString(tc, &s)
if err != nil {
t.Fatalf("%d: %v", i, err)
}
// repr.Println(s, repr.Indent(" "), repr.OmitEmpty(true))
}
}
func TestSelectParsing(t *testing.T) {
p := participle.MustBuild(
&Select{},
participle.Lexer(sqlLexer),
participle.CaseInsensitive("Keyword"),
)
s := Select{}
cases := []string{
"select * from s3object where name > 2 or value > 1 or word > 2",
"select s.word.id + 2 from s3object s",
"select 1-2-3 from s3object s limit 1",
}
for i, tc := range cases {
err := p.ParseString(tc, &s)
if err != nil {
t.Fatalf("%d: %v", i, err)
}
// repr.Println(s, repr.Indent(" "), repr.OmitEmpty(true))
}
}
func TestSqlLexerArithOps(t *testing.T) {
s := bytes.NewBuffer([]byte("year from select month hour distinct"))
lex, err := sqlLexer.Lex(s)
if err != nil {
t.Fatal(err)
}
tokens, err := lexer.ConsumeAll(lex)
if err != nil {
t.Fatal(err)
}
if len(tokens) != 7 {
t.Errorf("Expected 7 got %d", len(tokens))
}
// for i, t := range tokens {
// fmt.Printf("%d: %#v\n", i, t)
// }
}