438 lines
9.2 KiB
Go
438 lines
9.2 KiB
Go
package parser
|
|
|
|
import (
|
|
"io"
|
|
"os/user"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/kashav/fsql/query"
|
|
"github.com/kashav/fsql/tokenizer"
|
|
)
|
|
|
|
func TestParser_ParseSelect(t *testing.T) {
|
|
type Expected struct {
|
|
attributes []string
|
|
modifiers map[string][]query.Modifier
|
|
err error
|
|
}
|
|
|
|
type Case struct {
|
|
input string
|
|
expected Expected
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
input: "all",
|
|
expected: Expected{
|
|
attributes: allAttributes,
|
|
modifiers: map[string][]query.Modifier{},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "SELECT",
|
|
expected: Expected{
|
|
attributes: allAttributes,
|
|
modifiers: map[string][]query.Modifier{},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "FROM",
|
|
expected: Expected{
|
|
attributes: allAttributes,
|
|
modifiers: map[string][]query.Modifier{},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "SELECT name",
|
|
expected: Expected{
|
|
attributes: []string{"name"},
|
|
modifiers: map[string][]query.Modifier{"name": {}},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "SELECT format(size, kb)",
|
|
expected: Expected{
|
|
attributes: []string{"size"},
|
|
modifiers: map[string][]query.Modifier{
|
|
"size": {
|
|
{
|
|
Name: "FORMAT",
|
|
Arguments: []string{"kb"},
|
|
},
|
|
},
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "",
|
|
expected: Expected{err: io.ErrUnexpectedEOF},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
q := query.NewQuery()
|
|
err := (&parser{tokenizer: tokenizer.NewTokenizer(c.input)}).parseSelectClause(q)
|
|
|
|
if c.expected.err == nil {
|
|
if err != nil {
|
|
t.Fatalf("\nExpected no error\n Got %v", err)
|
|
}
|
|
if !reflect.DeepEqual(c.expected.attributes, q.Attributes) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.attributes, q.Attributes)
|
|
}
|
|
if !reflect.DeepEqual(c.expected.modifiers, q.Modifiers) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.modifiers, q.Modifiers)
|
|
}
|
|
} else if !reflect.DeepEqual(c.expected.err, err) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.err, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParser_ParseFrom(t *testing.T) {
|
|
type Expected struct {
|
|
sources map[string][]string
|
|
aliases map[string]string
|
|
err error
|
|
}
|
|
|
|
type Case struct {
|
|
input string
|
|
expected Expected
|
|
}
|
|
|
|
u, err := user.Current()
|
|
if err != nil {
|
|
// TODO: If we can't get the current user, should we fatal or just return?
|
|
return
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
input: "WHERE",
|
|
expected: Expected{
|
|
sources: map[string][]string{
|
|
"include": {"."},
|
|
"exclude": {},
|
|
},
|
|
aliases: map[string]string{},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "FROM .",
|
|
expected: Expected{
|
|
sources: map[string][]string{
|
|
"include": {"."},
|
|
"exclude": {},
|
|
},
|
|
aliases: map[string]string{},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "FROM ~/foo, -./.git/",
|
|
expected: Expected{
|
|
sources: map[string][]string{
|
|
"include": {u.HomeDir + "/foo"},
|
|
"exclude": {".git"},
|
|
},
|
|
aliases: map[string]string{},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "FROM ./foo/ AS foo",
|
|
expected: Expected{
|
|
sources: map[string][]string{
|
|
"include": {"foo"},
|
|
"exclude": {},
|
|
},
|
|
aliases: map[string]string{"foo": "foo"},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
{
|
|
input: "FROM",
|
|
expected: Expected{err: io.ErrUnexpectedEOF},
|
|
},
|
|
|
|
{
|
|
input: "FROM WHERE",
|
|
expected: Expected{
|
|
err: &ErrUnexpectedToken{
|
|
Actual: tokenizer.Where,
|
|
Expected: tokenizer.Identifier,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
q := query.NewQuery()
|
|
err := (&parser{tokenizer: tokenizer.NewTokenizer(c.input)}).parseFromClause(q)
|
|
|
|
if c.expected.err == nil {
|
|
if err != nil {
|
|
t.Fatalf("\nExpected no error\n Got %v", err)
|
|
}
|
|
if !reflect.DeepEqual(c.expected.sources, q.Sources) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.sources, q.Sources)
|
|
}
|
|
if !reflect.DeepEqual(c.expected.aliases, q.SourceAliases) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.aliases, q.SourceAliases)
|
|
}
|
|
} else if !reflect.DeepEqual(c.expected.err, err) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.err, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParser_ParseWhere(t *testing.T) {
|
|
type Expected struct {
|
|
tree *query.ConditionNode
|
|
err error
|
|
}
|
|
|
|
type Case struct {
|
|
input string
|
|
expected Expected
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
input: "WHERE name LIKE foo",
|
|
expected: Expected{
|
|
tree: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.Like,
|
|
Value: "foo",
|
|
},
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
|
|
// Our tree is fully-zeroed in this case, so it's easier just to give it
|
|
// an empty Expected struct.
|
|
{input: "", expected: Expected{}},
|
|
|
|
{input: "WHERE", expected: Expected{err: io.ErrUnexpectedEOF}},
|
|
|
|
{
|
|
input: "name LIKE foo",
|
|
expected: Expected{
|
|
err: &ErrUnexpectedToken{
|
|
Expected: tokenizer.Where,
|
|
Actual: tokenizer.Identifier,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
q := query.NewQuery()
|
|
err := (&parser{tokenizer: tokenizer.NewTokenizer(c.input)}).parseWhereClause(q)
|
|
|
|
if c.expected.err == nil {
|
|
if err != nil {
|
|
t.Fatalf("\nExpected no error\n Got %v", err)
|
|
}
|
|
if !reflect.DeepEqual(c.expected.tree, q.ConditionTree) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.tree, q.ConditionTree)
|
|
}
|
|
} else if !reflect.DeepEqual(c.expected.err, err) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.err, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParser_Expect(t *testing.T) {
|
|
type Case struct {
|
|
param tokenizer.TokenType
|
|
expected *tokenizer.Token
|
|
}
|
|
|
|
input := "SELECT all FROM . WHERE name = foo OR size <> 100"
|
|
p := &parser{tokenizer: tokenizer.NewTokenizer(input)}
|
|
|
|
cases := []Case{
|
|
{
|
|
param: tokenizer.Select,
|
|
expected: &tokenizer.Token{Type: tokenizer.Select, Raw: "SELECT"},
|
|
},
|
|
{
|
|
param: tokenizer.From,
|
|
expected: nil,
|
|
},
|
|
{
|
|
param: tokenizer.Identifier,
|
|
expected: &tokenizer.Token{Type: tokenizer.Identifier, Raw: "all"},
|
|
},
|
|
{
|
|
param: tokenizer.Identifier,
|
|
expected: nil,
|
|
},
|
|
{
|
|
param: tokenizer.From,
|
|
expected: &tokenizer.Token{Type: tokenizer.From, Raw: "FROM"},
|
|
},
|
|
{
|
|
param: tokenizer.Identifier,
|
|
expected: &tokenizer.Token{Type: tokenizer.Identifier, Raw: "."},
|
|
},
|
|
{
|
|
param: tokenizer.Identifier,
|
|
expected: nil,
|
|
},
|
|
{
|
|
param: tokenizer.Where,
|
|
expected: &tokenizer.Token{Type: tokenizer.Where, Raw: "WHERE"},
|
|
},
|
|
{
|
|
param: tokenizer.Identifier,
|
|
expected: &tokenizer.Token{Type: tokenizer.Identifier, Raw: "name"},
|
|
},
|
|
{
|
|
param: tokenizer.Equals,
|
|
expected: &tokenizer.Token{Type: tokenizer.Equals, Raw: "="},
|
|
},
|
|
{
|
|
param: tokenizer.Identifier,
|
|
expected: &tokenizer.Token{Type: tokenizer.Identifier, Raw: "foo"},
|
|
},
|
|
{
|
|
param: tokenizer.Or,
|
|
expected: &tokenizer.Token{Type: tokenizer.Or, Raw: "OR"},
|
|
},
|
|
{
|
|
param: tokenizer.Identifier,
|
|
expected: &tokenizer.Token{Type: tokenizer.Identifier, Raw: "size"},
|
|
},
|
|
{
|
|
param: tokenizer.Identifier,
|
|
expected: nil,
|
|
},
|
|
{
|
|
param: tokenizer.NotEquals,
|
|
expected: &tokenizer.Token{Type: tokenizer.NotEquals, Raw: "<>"},
|
|
},
|
|
{
|
|
param: tokenizer.Identifier,
|
|
expected: &tokenizer.Token{Type: tokenizer.Identifier, Raw: "100"},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual := p.expect(c.param)
|
|
if !reflect.DeepEqual(c.expected, actual) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParser_SelectAllVariations(t *testing.T) {
|
|
expected := &query.Query{
|
|
Attributes: allAttributes,
|
|
Sources: map[string][]string{
|
|
"include": {"."},
|
|
"exclude": {},
|
|
},
|
|
ConditionTree: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.Like,
|
|
Value: "foo",
|
|
},
|
|
},
|
|
SourceAliases: map[string]string{},
|
|
Modifiers: map[string][]query.Modifier{},
|
|
}
|
|
|
|
cases := []string{
|
|
"FROM . WHERE name LIKE foo",
|
|
"all FROM . WHERE name LIKE foo",
|
|
"SELECT FROM . WHERE name LIKE foo",
|
|
"SELECT all FROM . WHERE name LIKE foo",
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual, err := Run(c)
|
|
if err != nil {
|
|
t.Fatalf("\nExpected no error\n Got %v", err)
|
|
}
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("\nExpected %v\n Got %v", expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParser_Run(t *testing.T) {
|
|
type Expected struct {
|
|
q *query.Query
|
|
err error
|
|
}
|
|
|
|
type Case struct {
|
|
input string
|
|
expected Expected
|
|
}
|
|
|
|
// TODO: Add more cases.
|
|
cases := []Case{
|
|
{
|
|
input: "SELECT all FROM . WHERE name LIKE foo",
|
|
expected: Expected{
|
|
q: &query.Query{
|
|
Attributes: allAttributes,
|
|
Sources: map[string][]string{
|
|
"include": {"."},
|
|
"exclude": {},
|
|
},
|
|
ConditionTree: &query.ConditionNode{
|
|
Condition: &query.Condition{
|
|
Attribute: "name",
|
|
Operator: tokenizer.Like,
|
|
Value: "foo",
|
|
},
|
|
},
|
|
SourceAliases: map[string]string{},
|
|
Modifiers: map[string][]query.Modifier{},
|
|
},
|
|
err: nil,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual, err := Run(c.input)
|
|
if c.expected.err == nil {
|
|
if err != nil {
|
|
t.Fatalf("\nExpected no error\n Got %v", err)
|
|
}
|
|
if !reflect.DeepEqual(c.expected.q, actual) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.q, actual)
|
|
}
|
|
} else if !reflect.DeepEqual(c.expected.err, err) {
|
|
t.Fatalf("\nExpected %v\n Got %v", c.expected.err, err)
|
|
}
|
|
}
|
|
}
|