409 lines
8.3 KiB
Go
409 lines
8.3 KiB
Go
package parser
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/kashav/fsql/query"
|
|
"github.com/kashav/fsql/tokenizer"
|
|
)
|
|
|
|
func TestConditionParser_ExpectCorrectCondition(t *testing.T) {
|
|
type Expected struct {
|
|
condition *query.Condition
|
|
err error
|
|
}
|
|
|
|
type Case struct {
|
|
input string
|
|
expected Expected
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
input: "name LIKE foo%",
|
|
expected: Expected{
|
|
condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.Like,
|
|
Value: "foo%",
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "size = 10",
|
|
expected: Expected{
|
|
condition: &query.Condition{
|
|
Attribute: "size",
|
|
Operator: tokenizer.Equals,
|
|
Value: "10",
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "mode IS dir",
|
|
expected: Expected{
|
|
condition: &query.Condition{
|
|
Attribute: "mode",
|
|
Operator: tokenizer.Is,
|
|
Value: "dir",
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "format(time, iso) >= 2017-05-28T16:37:18Z",
|
|
expected: Expected{
|
|
condition: &query.Condition{
|
|
Attribute: "time",
|
|
AttributeModifiers: []query.Modifier{
|
|
{
|
|
Name: "FORMAT",
|
|
Arguments: []string{"iso"},
|
|
},
|
|
},
|
|
Operator: tokenizer.GreaterThanEquals,
|
|
Value: "2017-05-28T16:37:18Z",
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "upper(name) != FOO",
|
|
expected: Expected{
|
|
condition: &query.Condition{
|
|
Attribute: "name",
|
|
AttributeModifiers: []query.Modifier{
|
|
{
|
|
Name: "UPPER",
|
|
Arguments: []string{},
|
|
},
|
|
},
|
|
Operator: tokenizer.NotEquals,
|
|
Value: "FOO",
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "NOT name IN [foo,bar,baz]",
|
|
expected: Expected{
|
|
condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.In,
|
|
Value: []string{"foo", "bar", "baz"},
|
|
Negate: true,
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
// No attribute-operator validation yet (these 3 should /eventually/ throw
|
|
// some error)!
|
|
{
|
|
input: "time RLIKE '.*'",
|
|
expected: Expected{
|
|
condition: &query.Condition{
|
|
Attribute: "time",
|
|
Operator: tokenizer.RLike,
|
|
Value: ".*",
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "size LIKE foo",
|
|
expected: Expected{
|
|
condition: &query.Condition{
|
|
Attribute: "size",
|
|
Operator: tokenizer.Like,
|
|
Value: "foo",
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "time <> now",
|
|
expected: Expected{
|
|
condition: &query.Condition{
|
|
Attribute: "time",
|
|
Operator: tokenizer.NotEquals,
|
|
Value: "now",
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "name =",
|
|
expected: Expected{err: io.ErrUnexpectedEOF},
|
|
},
|
|
|
|
{
|
|
input: "file IS dir",
|
|
expected: Expected{err: &ErrUnknownToken{"file"}},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
p := &parser{tokenizer: tokenizer.NewTokenizer(c.input)}
|
|
actual, err := p.parseCondition()
|
|
|
|
if c.expected.err == nil {
|
|
if err != nil {
|
|
t.Fatalf("\nExpected no error\n Got %v", err)
|
|
}
|
|
if !reflect.DeepEqual(c.expected.condition, actual) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.condition, actual)
|
|
}
|
|
} else if !reflect.DeepEqual(c.expected.err, err) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.err, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConditionParser_ExpectCorrectConditionTree(t *testing.T) {
|
|
type Expected struct {
|
|
node *query.ConditionNode
|
|
err error
|
|
}
|
|
|
|
type Case struct {
|
|
input string
|
|
expected Expected
|
|
}
|
|
|
|
// Not sure why, but compiler throws when attempting to take the memory
|
|
// address of any tokenizer.TokenType.
|
|
var (
|
|
tmpAnd = tokenizer.And
|
|
tmpOr = tokenizer.Or
|
|
)
|
|
|
|
cases := []Case{
|
|
{
|
|
input: "name LIKE foo%",
|
|
expected: Expected{
|
|
node: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.Like,
|
|
Value: "foo%",
|
|
},
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "upper(name) = MAIN",
|
|
expected: Expected{
|
|
node: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
AttributeModifiers: []query.Modifier{
|
|
{
|
|
Name: "UPPER",
|
|
Arguments: []string{},
|
|
},
|
|
},
|
|
Operator: tokenizer.Equals,
|
|
Value: "MAIN",
|
|
},
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "name LIKE %foo AND name <> bar.foo",
|
|
expected: Expected{
|
|
node: &query.ConditionNode{
|
|
Type: &tmpAnd,
|
|
Left: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.Like,
|
|
Value: "%foo",
|
|
},
|
|
},
|
|
Right: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.NotEquals,
|
|
Value: "bar.foo",
|
|
},
|
|
},
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "size <= 10 OR NOT mode IS dir",
|
|
expected: Expected{
|
|
node: &query.ConditionNode{
|
|
Type: &tmpOr,
|
|
Left: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "size",
|
|
Operator: tokenizer.LessThanEquals,
|
|
Value: "10",
|
|
},
|
|
},
|
|
Right: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "mode",
|
|
Operator: tokenizer.Is,
|
|
Value: "dir",
|
|
Negate: true,
|
|
},
|
|
},
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "size = 5 AND name = foo",
|
|
expected: Expected{
|
|
node: &query.ConditionNode{
|
|
Type: &tmpAnd,
|
|
Left: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "size",
|
|
Operator: tokenizer.Equals,
|
|
Value: "5",
|
|
},
|
|
},
|
|
Right: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.Equals,
|
|
Value: "foo",
|
|
},
|
|
},
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "format(size, mb) <= 2 AND (name = foo OR name = bar)",
|
|
expected: Expected{
|
|
node: &query.ConditionNode{
|
|
Type: &tmpAnd,
|
|
Left: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "size",
|
|
AttributeModifiers: []query.Modifier{
|
|
{
|
|
Name: "FORMAT",
|
|
Arguments: []string{"mb"},
|
|
},
|
|
},
|
|
Operator: tokenizer.LessThanEquals,
|
|
Value: "2",
|
|
},
|
|
},
|
|
Right: &query.ConditionNode{
|
|
Type: &tmpOr,
|
|
Left: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.Equals,
|
|
Value: "foo",
|
|
},
|
|
},
|
|
Right: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.Equals,
|
|
Value: "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "name = foo AND NOT (name = bar OR name = baz)",
|
|
expected: Expected{
|
|
node: &query.ConditionNode{},
|
|
err: &ErrUnexpectedToken{
|
|
Expected: tokenizer.Identifier,
|
|
Actual: tokenizer.OpenParen,
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "size = 5 AND ()",
|
|
expected: Expected{err: errors.New("failed to parse conditions")},
|
|
},
|
|
|
|
// FIXME: The following case /should/ throw EOF (it doesn't right now).
|
|
// Case{input: "name = foo AND", expected: Expected{err: io.ErrUnexpectedEOF}},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
p := &parser{tokenizer: tokenizer.NewTokenizer(c.input)}
|
|
actual, err := p.parseConditionTree()
|
|
|
|
if c.expected.err == nil {
|
|
if err != nil {
|
|
t.Fatalf("\nExpected no error\n Got %v", err)
|
|
}
|
|
if !reflect.DeepEqual(c.expected.node, actual) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.node, actual)
|
|
}
|
|
} else if !reflect.DeepEqual(c.expected.err, err) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.err, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConditionParser_ExpectCorrectSubquery(t *testing.T) {
|
|
type Expected struct {
|
|
condition *query.Condition
|
|
err error
|
|
}
|
|
|
|
type Case struct {
|
|
input *query.Condition
|
|
expected Expected
|
|
}
|
|
|
|
// TODO: Complete these cases. This test relies on testdata fixtures.
|
|
cases := []Case{}
|
|
|
|
for _, c := range cases {
|
|
p := &parser{tokenizer: tokenizer.NewTokenizer("")}
|
|
err := p.parseSubquery(c.input)
|
|
|
|
if c.expected.err == nil {
|
|
if err != nil {
|
|
t.Fatalf("\nExpected no error\n Got %v", err)
|
|
}
|
|
if !reflect.DeepEqual(c.expected.condition, c.input) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.condition, c.input)
|
|
}
|
|
} else if !reflect.DeepEqual(c.expected.err, err) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.err, err)
|
|
}
|
|
}
|
|
}
|