2021-04-18 15:41:13 -04:00
|
|
|
// 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/>.
|
2019-01-28 20:59:48 -05:00
|
|
|
|
|
|
|
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"),
|
2019-03-09 11:13:37 -05:00
|
|
|
participle.CaseInsensitive("Timeword"),
|
2019-01-28 20:59:48 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
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"),
|
2019-03-09 11:13:37 -05:00
|
|
|
participle.CaseInsensitive("Timeword"),
|
2019-01-28 20:59:48 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
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",
|
2021-11-02 22:14:46 -04:00
|
|
|
"select * from s3object[*].books[*] limit 1",
|
2019-01-28 20:59:48 -05:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
// }
|
|
|
|
}
|