package xsql

import (
	"fmt"
	"reflect"
	"strings"
	"testing"
)

// Ensure the parser can parse strings into Statement ASTs.
func TestParser_ParseStatement(t *testing.T) {
	var tests = []struct {
		s    string
		stmt *SelectStatement
		err  string
	}{
		{
			s: `SELECT name FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},
		{
			s: "SELECT `select` FROM tbl",
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "select"},
						Name:  "select",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},
		{
			s: `SELECT name FROM topic/sensor1`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1"}},
			},
		},

		{
			s: `SELECT t1.name FROM topic/sensor1 AS t1`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{StreamName: StreamName("t1"), Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
			},
		},
		{
			s: "SELECT t1.name FROM topic/sensor1 AS `join`",
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{StreamName: StreamName("t1"), Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "join"}},
			},
		},

		{
			s: `SELECT name FROM topic/sensor1 AS t1`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
			},
		},

		{
			s: `SELECT name FROM topic/sensor1/#`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1/#"}},
			},
		},

		{
			s: `SELECT name FROM topic/sensor1/# AS t2 `,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1/#", Alias: "t2"}},
			},
		},

		{
			s: `SELECT name FROM /topic/sensor1/#`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "/topic/sensor1/#"}},
			},
		},

		{
			s: `SELECT name FROM /topic/sensor1/#/`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "/topic/sensor1/#/"}},
			},
		},

		{
			s: `SELECT name FROM /topic/sensor1/+/temp1/`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "/topic/sensor1/+/temp1/"}},
			},
		},

		{
			s: `SELECT name FROM topic/sensor1/+/temp`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1/+/temp"}},
			},
		},

		{
			s: `SELECT * FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &Wildcard{Token: ASTERISK},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},
		{
			s: `SELECT a,b FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "a"}, Name: "a", AName: ""},
					{Expr: &FieldRef{Name: "b"}, Name: "b", AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},
		{
			s: `SELECT a, b,c FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "a"}, Name: "a", AName: ""},
					{Expr: &FieldRef{Name: "b"}, Name: "b", AName: ""},
					{Expr: &FieldRef{Name: "c"}, Name: "c", AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT a AS alias FROM tbl`,
			stmt: &SelectStatement{
				Fields:  []Field{{Expr: &FieldRef{Name: "a"}, Name: "a", AName: "alias"}},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT a AS alias1, b as Alias2 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "a"}, Name: "a", AName: "alias1"},
					{Expr: &FieldRef{Name: "b"}, Name: "b", AName: "Alias2"},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT length("test") FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "length",
						Expr: &Call{
							Name: "length",
							Args: []Expr{&StringLiteral{Val: "test"}},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT length(test) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "length",
						Expr: &Call{
							Name: "length",
							Args: []Expr{&FieldRef{Name: "test"}},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT sin(123) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "sin",
						Expr: &Call{
							Name: "sin",
							Args: []Expr{&IntegerLiteral{Val: 123}},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT lpad("abc", 123) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "lpad",
						Expr: &Call{
							Name: "lpad",
							Args: []Expr{&StringLiteral{Val: "abc"}, &IntegerLiteral{Val: 123}},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT newuuid() FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "newuuid",
						Expr: &Call{
							Name: "newuuid",
							Args: nil,
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT indexof("abc", field1) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "indexof",
						Expr: &Call{
							Name: "indexof",
							Args: []Expr{
								&StringLiteral{Val: "abc"},
								&FieldRef{Name: "field1"},
							},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT lpad(lower(test),1) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "lpad",
						Expr: &Call{
							Name: "lpad",
							Args: []Expr{
								&Call{
									Name: "lower",
									Args: []Expr{
										&FieldRef{Name: "test"},
									},
								},
								&IntegerLiteral{Val: 1},
							},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT lpad(lower(test),1) AS field1 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "field1",
						Name:  "lpad",
						Expr: &Call{
							Name: "lpad",
							Args: []Expr{
								&Call{
									Name: "lower",
									Args: []Expr{
										&FieldRef{Name: "test"},
									},
								},
								&IntegerLiteral{Val: 1},
							},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT length(lower("test")) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "length",
						Expr: &Call{
							Name: "length",
							Args: []Expr{
								&Call{
									Name: "lower",
									Args: []Expr{
										&StringLiteral{Val: "test"},
									},
								},
							},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT count(*) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "count",
						Expr: &Call{
							Name: "count",
							Args: []Expr{&StringLiteral{Val: "*"}},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s:    `SELECT count(*, f1) FROM tbl`,
			stmt: nil,
			err:  `found ",", expected right paren.`,
		},

		{
			s: `SELECT "abc" FROM tbl`,
			stmt: &SelectStatement{
				Fields:  []Field{{AName: "", Name: "", Expr: &StringLiteral{Val: "abc"}}},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT "abc" AS field1 FROM tbl`,
			stmt: &SelectStatement{
				Fields:  []Field{{AName: "field1", Name: "", Expr: &StringLiteral{Val: "abc"}}},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT field0,   "abc" AS field1, field2 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{AName: "", Name: "field0", Expr: &FieldRef{Name: "field0"}},
					{AName: "field1", Name: "", Expr: &StringLiteral{Val: "abc"}},
					{AName: "", Name: "field2", Expr: &FieldRef{Name: "field2"}}},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s:    `SELECT * AS alias FROM tbl`,
			stmt: nil,
			err:  `found "AS", expected FROM.`,
		},

		{
			s:    `SELECT *, FROM tbl`,
			stmt: nil,
			err:  `found ",", expected FROM.`,
		},

		{
			s:    `SELECTname FROM tbl`,
			stmt: nil,
			err:  `Found "SELECTname", Expected SELECT.` + "\n",
		},

		{
			s: `SELECT abc FROM tbl WHERE abc > 12 `,
			stmt: &SelectStatement{
				Fields:  []Field{{AName: "", Name: "abc", Expr: &FieldRef{Name: "abc"}}},
				Sources: []Source{&Table{Name: "tbl"}},
				Condition: &BinaryExpr{
					LHS: &FieldRef{Name: "abc"},
					OP:  GT,
					RHS: &IntegerLiteral{Val: 12},
				},
			},
		},

		{
			s: `SELECT abc FROM tbl WHERE abc = "hello" `,
			stmt: &SelectStatement{
				Fields:  []Field{{AName: "", Name: "abc", Expr: &FieldRef{Name: "abc"}}},
				Sources: []Source{&Table{Name: "tbl"}},
				Condition: &BinaryExpr{
					LHS: &FieldRef{Name: "abc"},
					OP:  EQ,
					RHS: &StringLiteral{Val: "hello"},
				},
			},
		},

		{
			s: `SELECT t1.abc FROM tbl AS t1 WHERE t1.abc = "hello" `,
			stmt: &SelectStatement{
				Fields:  []Field{{AName: "", Name: "abc", Expr: &FieldRef{StreamName: StreamName("t1"), Name: "abc"}}},
				Sources: []Source{&Table{Name: "tbl", Alias: "t1"}},
				Condition: &BinaryExpr{
					LHS: &FieldRef{StreamName: StreamName("t1"), Name: "abc"},
					OP:  EQ,
					RHS: &StringLiteral{Val: "hello"},
				},
			},
		},

		{
			s: `SELECT abc, "fff" AS fa FROM tbl WHERE fa >= 5 `,
			stmt: &SelectStatement{
				Fields:  []Field{{AName: "", Name: "abc", Expr: &FieldRef{Name: "abc"}}, {AName: "fa", Name: "", Expr: &StringLiteral{Val: "fff"}}},
				Sources: []Source{&Table{Name: "tbl"}},
				Condition: &BinaryExpr{
					LHS: &FieldRef{Name: "fa"},
					OP:  GTE,
					RHS: &IntegerLiteral{Val: 5},
				},
			},
		},

		{
			s: `SELECT field2 FROM tbl WHERE field2 != 5 `,
			stmt: &SelectStatement{
				Fields:  []Field{{AName: "", Name: "field2", Expr: &FieldRef{Name: "field2"}}},
				Sources: []Source{&Table{Name: "tbl"}},
				Condition: &BinaryExpr{
					LHS: &FieldRef{Name: "field2"},
					OP:  NEQ,
					RHS: &IntegerLiteral{Val: 5},
				},
			},
		},

		{
			s: `SELECT field2 FROM tbl WHERE field2 !   = 5 `, //Add space char in expression
			stmt: &SelectStatement{
				Fields:  []Field{{AName: "", Name: "field2", Expr: &FieldRef{Name: "field2"}}},
				Sources: []Source{&Table{Name: "tbl"}},
				Condition: &BinaryExpr{
					LHS: &FieldRef{Name: "field2"},
					OP:  NEQ,
					RHS: &IntegerLiteral{Val: 5},
				},
			},
		},

		{
			s:    `SELECT *f FROM tbl`,
			stmt: nil,
			err:  `found "f", expected FROM.`,
		},

		////TODO
		//{
		//	s: `SELECT *from FROM tbl`,
		//	stmt: nil,
		//	err: `found "f", expected FROM.`,
		//},

		{
			s: `SELECT abc+2 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "",
						Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "abc"},
							OP:  ADD,
							RHS: &IntegerLiteral{Val: 2},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT t1.abc+2 FROM tbl AS t1`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "",
						Expr: &BinaryExpr{
							LHS: &FieldRef{StreamName: StreamName("t1"), Name: "abc"},
							OP:  ADD,
							RHS: &IntegerLiteral{Val: 2},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl", Alias: "t1"}},
			},
		},

		{
			s: `SELECT abc + "hello" FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "",
						Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "abc"},
							OP:  ADD,
							RHS: &StringLiteral{Val: "hello"},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT abc*2 + 3 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "",
						Expr: &BinaryExpr{
							LHS: &BinaryExpr{
								LHS: &FieldRef{Name: "abc"},
								OP:  MUL,
								RHS: &IntegerLiteral{Val: 2},
							},
							OP:  ADD,
							RHS: &IntegerLiteral{Val: 3},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT ln(abc*2 + 3) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "ln",
						Expr: &Call{
							Name: "ln",
							Args: []Expr{
								&BinaryExpr{
									LHS: &BinaryExpr{
										LHS: &FieldRef{Name: "abc"},
										OP:  MUL,
										RHS: &IntegerLiteral{Val: 2},
									},
									OP:  ADD,
									RHS: &IntegerLiteral{Val: 3},
								},
							},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT ln(t1.abc*2 + 3) FROM tbl AS t1`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "ln",
						Expr: &Call{
							Name: "ln",
							Args: []Expr{
								&BinaryExpr{
									LHS: &BinaryExpr{
										LHS: &FieldRef{StreamName: StreamName("t1"), Name: "abc"},
										OP:  MUL,
										RHS: &IntegerLiteral{Val: 2},
									},
									OP:  ADD,
									RHS: &IntegerLiteral{Val: 3},
								},
							},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl", Alias: "t1"}},
			},
		},

		{
			s: `SELECT lpad("param2", abc*2 + 3) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "lpad",
						Expr: &Call{
							Name: "lpad",
							Args: []Expr{
								&StringLiteral{Val: "param2"},
								&BinaryExpr{
									LHS: &BinaryExpr{
										LHS: &FieldRef{Name: "abc"},
										OP:  MUL,
										RHS: &IntegerLiteral{Val: 2},
									},
									OP:  ADD,
									RHS: &IntegerLiteral{Val: 3},
								},
							},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT 0.2 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "",
						Expr:  &NumberLiteral{Val: 0.2},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT .2 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "",
						Expr:  &NumberLiteral{Val: 0.2},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT sin(.2) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "sin",
						Expr: &Call{
							Name: "sin",
							Args: []Expr{&NumberLiteral{Val: 0.2}},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT power(.2, 4) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "power",
						Expr: &Call{
							Name: "power",
							Args: []Expr{&NumberLiteral{Val: 0.2}, &IntegerLiteral{Val: 4}},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT power(.2, 4) AS f1 FROM tbl WHERE f1 > 2.2`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "f1",
						Name:  "power",
						Expr: &Call{
							Name: "power",
							Args: []Expr{&NumberLiteral{Val: 0.2}, &IntegerLiteral{Val: 4}},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
				Condition: &BinaryExpr{
					LHS: &FieldRef{Name: "f1"},
					OP:  GT,
					RHS: &NumberLiteral{Val: 2.2},
				},
			},
		},

		{
			s: `SELECT deviceId, name FROM topic/sensor1 WHERE deviceId=1 AND name = "dname"`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "deviceId"}, Name: "deviceId", AName: ""},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1"}},
				Condition: &BinaryExpr{
					LHS: &BinaryExpr{LHS: &FieldRef{Name: "deviceId"}, OP: EQ, RHS: &IntegerLiteral{Val: 1}},
					OP:  AND,
					RHS: &BinaryExpr{LHS: &FieldRef{Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				},
			},
		},

		{
			s: `SELECT deviceId, name FROM topic/sensor1 AS t1 WHERE t1.deviceId=1 AND t1.name = "dname"`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "deviceId"}, Name: "deviceId", AName: ""},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
				Condition: &BinaryExpr{
					LHS: &BinaryExpr{LHS: &FieldRef{StreamName: StreamName("t1"), Name: "deviceId"}, OP: EQ, RHS: &IntegerLiteral{Val: 1}},
					OP:  AND,
					RHS: &BinaryExpr{LHS: &FieldRef{StreamName: StreamName("t1"), Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				},
			},
		},

		{
			s: `SELECT temp AS t, name FROM topic/sensor1 WHERE t> = 20.5 OR name = "dname"`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1"}},
				Condition: &BinaryExpr{
					LHS: &BinaryExpr{LHS: &FieldRef{Name: "t"}, OP: GTE, RHS: &NumberLiteral{Val: 20.5}},
					OP:  OR,
					RHS: &BinaryExpr{LHS: &FieldRef{Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				},
			},
		},

		{
			s: `SELECT temp AS t, name FROM topic/sensor1 WHERE name = "dname" GROUP BY name`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources:    []Source{&Table{Name: "topic/sensor1"}},
				Condition:  &BinaryExpr{LHS: &FieldRef{Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				Dimensions: Dimensions{Dimension{Expr: &FieldRef{Name: "name"}}},
			},
		},

		{
			s: `SELECT temp AS t, name FROM topic/sensor1 WHERE name = "dname" GROUP BY name HAVING count(name) > 3`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources:    []Source{&Table{Name: "topic/sensor1"}},
				Condition:  &BinaryExpr{LHS: &FieldRef{Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				Dimensions: Dimensions{Dimension{Expr: &FieldRef{Name: "name"}}},
				Having:     &BinaryExpr{LHS: &Call{Name: "count", Args: []Expr{&FieldRef{StreamName: "", Name: "name"}}}, OP: GT, RHS: &IntegerLiteral{Val: 3}},
			},
		},

		{
			s: `SELECT temp AS t, name FROM topic/sensor1 WHERE name = "dname" HAVING count(name) > 3`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources:   []Source{&Table{Name: "topic/sensor1"}},
				Condition: &BinaryExpr{LHS: &FieldRef{Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				Having:    &BinaryExpr{LHS: &Call{Name: "count", Args: []Expr{&FieldRef{StreamName: "", Name: "name"}}}, OP: GT, RHS: &IntegerLiteral{Val: 3}},
			},
		},

		{
			s:    `SELECT temp AS t, name FROM topic/sensor1 WHERE name = "dname" HAVING sin(name) > 0.3`,
			stmt: nil,
			err:  "Not allowed to call none-aggregate functions in HAVING clause.",
		},

		{
			s:    `SELECT temp AS t, name FROM topic/sensor1 WHERE count(name) = 3`,
			stmt: nil,
			err:  "Not allowed to call aggregate functions in WHERE clause.",
		},

		{
			s: `SELECT s1.temp AS t, name FROM topic/sensor1 AS s1 WHERE t = "dname" GROUP BY s1.temp`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{StreamName: "s1", Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources:    []Source{&Table{Name: "topic/sensor1", Alias: "s1"}},
				Condition:  &BinaryExpr{LHS: &FieldRef{Name: "t"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				Dimensions: Dimensions{Dimension{Expr: &FieldRef{StreamName: "s1", Name: "temp"}}},
			},
		},

		{
			s: `SELECT temp AS t, name FROM topic/sensor1 WHERE name = "dname" GROUP BY lpad(name,1)`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources:   []Source{&Table{Name: "topic/sensor1"}},
				Condition: &BinaryExpr{LHS: &FieldRef{Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				Dimensions: Dimensions{Dimension{
					Expr: &Call{Name: "lpad", Args: []Expr{&FieldRef{Name: "name"}, &IntegerLiteral{Val: 1}}},
				},
				},
			},
		},

		{
			s: `SELECT temp AS t, name FROM topic/sensor1 AS s1 WHERE name = "dname" GROUP BY lpad(s1.name,1)`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources:   []Source{&Table{Name: "topic/sensor1", Alias: "s1"}},
				Condition: &BinaryExpr{LHS: &FieldRef{Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				Dimensions: Dimensions{Dimension{
					Expr: &Call{Name: "lpad", Args: []Expr{&FieldRef{StreamName: StreamName("s1"), Name: "name"}, &IntegerLiteral{Val: 1}}},
				},
				},
			},
		},

		{
			s: `SELECT temp AS t, name FROM topic/sensor1 WHERE name = "dname" GROUP BY lpad(name,1) ORDER BY name`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources:   []Source{&Table{Name: "topic/sensor1"}},
				Condition: &BinaryExpr{LHS: &FieldRef{Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				Dimensions: Dimensions{
					Dimension{
						Expr: &Call{Name: "lpad", Args: []Expr{
							&FieldRef{Name: "name"},
							&IntegerLiteral{Val: 1}},
						},
					},
				},
				SortFields: []SortField{{Name: "name", Ascending: true}},
			},
		},

		{
			s: `SELECT temp AS t, name FROM topic/sensor1 AS s1 WHERE s1.name = "dname" GROUP BY lpad(s1.name,1) ORDER BY s1.name`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources:   []Source{&Table{Name: "topic/sensor1", Alias: "s1"}},
				Condition: &BinaryExpr{LHS: &FieldRef{StreamName: StreamName("s1"), Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				Dimensions: Dimensions{
					Dimension{
						Expr: &Call{Name: "lpad", Args: []Expr{
							&FieldRef{StreamName: StreamName("s1"), Name: "name"},
							&IntegerLiteral{Val: 1}},
						},
					},
				},
				SortFields: []SortField{{Name: "s1\007name", Ascending: true}},
			},
		},

		{
			s: `SELECT temp AS t, name FROM topic/sensor1 WHERE name = "dname" GROUP BY lpad(name,1) ORDER BY name DESC`,
			stmt: &SelectStatement{
				Fields: []Field{
					{Expr: &FieldRef{Name: "temp"}, Name: "temp", AName: "t"},
					{Expr: &FieldRef{Name: "name"}, Name: "name", AName: ""},
				},
				Sources:   []Source{&Table{Name: "topic/sensor1"}},
				Condition: &BinaryExpr{LHS: &FieldRef{Name: "name"}, OP: EQ, RHS: &StringLiteral{Val: "dname"}},
				Dimensions: Dimensions{
					Dimension{
						Expr: &Call{Name: "lpad", Args: []Expr{
							&FieldRef{Name: "name"},
							&IntegerLiteral{Val: 1}},
						},
					},
				},
				SortFields: []SortField{{Name: "name", Ascending: false}},
			},
		},

		{
			s: `SELECT * FROM topic/sensor1 ORDER BY name DESC`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &Wildcard{Token: ASTERISK},
						Name:  "",
						AName: ""},
				},
				Sources:    []Source{&Table{Name: "topic/sensor1"}},
				SortFields: []SortField{{Name: "name", Ascending: false}},
			},
		},

		{
			s: `SELECT * FROM topic/sensor1 ORDER BY name DESC, name2 ASC`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &Wildcard{Token: ASTERISK},
						Name:  "",
						AName: ""},
				},
				Sources:    []Source{&Table{Name: "topic/sensor1"}},
				SortFields: []SortField{{Name: "name", Ascending: false}, {Name: "name2", Ascending: true}},
			},
		},

		{
			s: `SELECT * FROM topic/sensor1 GROUP BY name, name2,power(name3,1.8) ORDER BY name DESC, name2 ASC`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &Wildcard{Token: ASTERISK},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1"}},
				Dimensions: Dimensions{
					Dimension{Expr: &FieldRef{Name: "name"}},
					Dimension{Expr: &FieldRef{Name: "name2"}},
					Dimension{
						Expr: &Call{Name: "power", Args: []Expr{
							&FieldRef{Name: "name3"},
							&NumberLiteral{Val: 1.8}},
						},
					},
				},
				SortFields: []SortField{{Name: "name", Ascending: false}, {Name: "name2", Ascending: true}},
			},
		},

		//{
		//	s: `SELECT .2sd FROM tbl`,
		//	stmt: &SelectStatement{
		//		Fields:    []Field{
		//			Field{
		//				AName:"",
		//				Expr: &NumberLiteral{Val: 0.2},
		//			},
		//		},
		//		TableName: "tbl",
		//	},
		//},

		{
			s: `SELECT name FROM tbl/*SELECT comment FROM testComments*/`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `/*SELECT comment FROM testComments*/SELECT name FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT name /*SELECT comment FROM testComments*/ FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT true AS f1, FALSE as f2 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{AName: "f1", Name: "", Expr: &BooleanLiteral{Val: true}},
					{AName: "f2", Name: "", Expr: &BooleanLiteral{Val: false}},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT true AS f1 FROM tbl WHERE f2 = true`,
			stmt: &SelectStatement{
				Fields: []Field{
					{AName: "f1", Name: "", Expr: &BooleanLiteral{Val: true}},
				},
				Sources:   []Source{&Table{Name: "tbl"}},
				Condition: &BinaryExpr{LHS: &FieldRef{Name: "f2"}, OP: EQ, RHS: &BooleanLiteral{Val: true}},
			},
		},

		{
			s: `SELECT indexof(field1, "abc") FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						AName: "",
						Name:  "indexof",
						Expr: &Call{
							Name: "indexof",
							Args: []Expr{&FieldRef{Name: "field1"}, &StringLiteral{Val: "abc"}},
						},
					},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		//The negative value expression support.
		{
			s: `SELECT -3 AS t1 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &IntegerLiteral{Val: -3},
						Name:  "",
						AName: "t1"},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT - 3 AS t1 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &IntegerLiteral{Val: -3},
						Name:  "",
						AName: "t1"},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT -. 3 AS t1 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &NumberLiteral{Val: -.3},
						Name:  "",
						AName: "t1"},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT -3.3 AS t1 FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &NumberLiteral{Val: -3.3},
						Name:  "",
						AName: "t1"},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s: `SELECT sample(-.3,) FROM tbl`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &Call{
							Name: "sample",
							Args: []Expr{
								&NumberLiteral{Val: -0.3},
							},
						},
						Name:  "sample",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},

		{
			s:    `select timestamp() as tp from demo`,
			stmt: nil,
			err:  "found \"TIMESTAMP\", expected expression.",
		},

		{
			s: `select tstamp() as tp from demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &Call{
							Name: "tstamp",
							Args: nil,
						},
						Name:  "tstamp",
						AName: "tp"},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
			err: "",
		},

		{
			s:    "SELECT `half FROM tb",
			stmt: nil,
			err:  `found "EOF", expected FROM.`,
		},
		{
			s: "SELECT `space var` FROM tbl",
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "space var"},
						Name:  "space var",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},
		{
			s: "SELECT `中文 Chinese` FROM tbl",
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "中文 Chinese"},
						Name:  "中文 Chinese",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
			},
		},
	}

	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
	for i, tt := range tests {
		//fmt.Printf("Parsing SQL %q.\n", tt.s)
		stmt, err := NewParser(strings.NewReader(tt.s)).Parse()
		if !reflect.DeepEqual(tt.err, errstring(err)) {
			t.Errorf("%d. %q: error mismatch:\n  exp=%s\n  got=%s\n\n", i, tt.s, tt.err, err)
		} else if tt.err == "" && !reflect.DeepEqual(tt.stmt, stmt) {
			t.Errorf("%d. %q\n\nstmt mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.s, tt.stmt, stmt)
		}
	}
}

func TestParser_ParseWindowsExpr(t *testing.T) {
	var tests = []struct {
		s    string
		stmt *SelectStatement
		err  string
	}{
		{
			s: `SELECT f1 FROM tbl GROUP BY TUMBLINGWINDOW(ss, 10)`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "f1"},
						Name:  "f1",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
				Dimensions: Dimensions{
					Dimension{
						Expr: &Window{
							WindowType: TUMBLING_WINDOW,
							Length:     &IntegerLiteral{Val: 10000},
							Interval:   &IntegerLiteral{Val: 0},
						},
					},
				},
			},
		},

		{
			s: `SELECT f1 FROM tbl GROUP BY HOPPINGWINDOW(mi, 5, 1)`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "f1"},
						Name:  "f1",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
				Dimensions: Dimensions{
					Dimension{
						Expr: &Window{
							WindowType: HOPPING_WINDOW,
							Length:     &IntegerLiteral{Val: 3e5},
							Interval:   &IntegerLiteral{Val: 6e4},
						},
					},
				},
			},
		},

		{
			s: `SELECT f1 FROM tbl GROUP BY SESSIONWINDOW(hh, 5, 1)`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "f1"},
						Name:  "f1",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
				Dimensions: Dimensions{
					Dimension{
						Expr: &Window{
							WindowType: SESSION_WINDOW,
							Length:     &IntegerLiteral{Val: 1.8e7},
							Interval:   &IntegerLiteral{Val: 3.6e6},
						},
					},
				},
			},
		},

		{
			s: `SELECT f1 FROM tbl GROUP BY SLIDINGWINDOW(ms, 5)`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "f1"},
						Name:  "f1",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
				Dimensions: Dimensions{
					Dimension{
						Expr: &Window{
							WindowType: SLIDING_WINDOW,
							Length:     &IntegerLiteral{Val: 5},
							Interval:   &IntegerLiteral{Val: 0},
						},
					},
				},
			},
		},

		{
			s:    `SELECT f1 FROM tbl GROUP BY SLIDINGWINDOW(mi, 5, 1)`,
			stmt: nil,
			err:  "The arguments for slidingwindow should be 2.\n",
		},

		{
			s:    `SELECT f1 FROM tbl GROUP BY SLIDINGWINDOW("mi", 5)`,
			stmt: nil,
			err:  "The 1st argument for slidingwindow is expecting timer literal expression. One value of [dd|hh|mi|ss|ms].\n",
		},

		{
			s: `SELECT f1 FROM tbl GROUP BY COUNTWINDOW(10)`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "f1"},
						Name:  "f1",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
				Dimensions: Dimensions{
					Dimension{
						Expr: &Window{
							WindowType: COUNT_WINDOW,
							Length:     &IntegerLiteral{Val: 10},
						},
					},
				},
			},
		},

		{
			s: `SELECT f1 FROM tbl GROUP BY COUNTWINDOW(10, 5)`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{Name: "f1"},
						Name:  "f1",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "tbl"}},
				Dimensions: Dimensions{
					Dimension{
						Expr: &Window{
							WindowType: COUNT_WINDOW,
							Length:     &IntegerLiteral{Val: 10},
							Interval:   &IntegerLiteral{Val: 5},
						},
					},
				},
			},
		},

		{
			s:    `SELECT f1 FROM tbl GROUP BY COUNTWINDOW(3, 5)`,
			stmt: nil,
			err:  "The second parameter value 5 should be less than the first parameter 3.",
		},
	}

	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
	for i, tt := range tests {
		//fmt.Printf("Parsing SQL %q.\n", tt.s)
		stmt, err := NewParser(strings.NewReader(tt.s)).Parse()
		if !reflect.DeepEqual(tt.err, errstring(err)) {
			t.Errorf("%d. %q: error mismatch:\n  exp=%s\n  got=%s\n\n", i, tt.s, tt.err, err)
		} else if tt.err == "" && !reflect.DeepEqual(tt.stmt, stmt) {
			t.Errorf("%d. %q\n\nstmt mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.s, tt.stmt, stmt)
		}
	}
}

func TestParser_ParseJsonExpr(t *testing.T) {
	var tests = []struct {
		s    string
		stmt *SelectStatement
		err  string
	}{
		{
			s: `SELECT children[0] FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "children"},
							OP:  SUBSET,
							RHS: &IndexExpr{Index: 0},
						},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT children[0]->first FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &BinaryExpr{
								LHS: &FieldRef{Name: "children"},
								OP:  SUBSET,
								RHS: &IndexExpr{Index: 0},
							},
							OP:  ARROW,
							RHS: &FieldRef{Name: "first"},
						},

						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT children->first[2] FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &BinaryExpr{
								LHS: &FieldRef{Name: "children"},
								OP:  ARROW,
								RHS: &FieldRef{Name: "first"},
							},
							OP:  SUBSET,
							RHS: &IndexExpr{Index: 2},
						},

						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT children->first[2]->test FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &BinaryExpr{
								LHS: &BinaryExpr{
									LHS: &FieldRef{Name: "children"},
									OP:  ARROW,
									RHS: &FieldRef{Name: "first"},
								},
								OP:  SUBSET,
								RHS: &IndexExpr{Index: 2},
							},
							OP:  ARROW,
							RHS: &FieldRef{Name: "test"},
						},

						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT children[0:1] FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "children"},
							OP:  SUBSET,
							RHS: &ColonExpr{Start: 0, End: 1},
						},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT children[:1] FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "children"},
							OP:  SUBSET,
							RHS: &ColonExpr{Start: 0, End: 1},
						},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT children[:] FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "children"},
							OP:  SUBSET,
							RHS: &ColonExpr{Start: 0, End: -1},
						},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT children[2:] AS c FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "children"},
							OP:  SUBSET,
							RHS: &ColonExpr{Start: 2, End: -1},
						},
						Name:  "",
						AName: "c"},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT children[2:]->first AS c FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &BinaryExpr{LHS: &FieldRef{Name: "children"}, OP: SUBSET, RHS: &ColonExpr{Start: 2, End: -1}},
							OP:  ARROW,
							RHS: &FieldRef{Name: "first"},
						},
						Name:  "",
						AName: "c"},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT demo.* FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{StreamName: StreamName("demo"), Name: "*"},
						Name:  "*",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT demo.children[2:]->first AS c FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &BinaryExpr{LHS: &FieldRef{StreamName: StreamName("demo"), Name: "children"}, OP: SUBSET, RHS: &ColonExpr{Start: 2, End: -1}},
							OP:  ARROW,
							RHS: &FieldRef{Name: "first"},
						},
						Name:  "",
						AName: "c"},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT lower(demo.children[2:]->first) AS c FROM demo`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &Call{
							Name: "lower",
							Args: []Expr{
								&BinaryExpr{
									LHS: &BinaryExpr{LHS: &FieldRef{StreamName: StreamName("demo"), Name: "children"}, OP: SUBSET, RHS: &ColonExpr{Start: 2, End: -1}},
									OP:  ARROW,
									RHS: &FieldRef{Name: "first"},
								},
							},
						},
						Name:  "lower",
						AName: "c"},
				},
				Sources: []Source{&Table{Name: "demo"}},
			},
		},

		{
			s: `SELECT children[:1] FROM demo WHERE abc[0] > 12`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "children"},
							OP:  SUBSET,
							RHS: &ColonExpr{Start: 0, End: 1},
						},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
				Condition: &BinaryExpr{
					LHS: &BinaryExpr{
						LHS: &FieldRef{Name: "abc"},
						OP:  SUBSET,
						RHS: &IndexExpr{Index: 0},
					},
					OP:  GT,
					RHS: &IntegerLiteral{Val: 12},
				},
			},
		},

		{
			s:    `SELECT demo.children.first AS c FROM demo`,
			stmt: nil,
			err:  "Too many field names. Please use -> to reference keys in struct.\n",
		},
	}

	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
	for i, tt := range tests {
		stmt, err := NewParser(strings.NewReader(tt.s)).Parse()
		if !reflect.DeepEqual(tt.err, errstring(err)) {
			t.Errorf("%d. %q: error mismatch:\n  exp=%s\n  got=%s\n\n", i, tt.s, tt.err, err)
		} else if tt.err == "" && !reflect.DeepEqual(tt.stmt, stmt) {
			t.Errorf("%d. %q\n\nstmt mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.s, tt.stmt, stmt)
		}
	}
}

func TestParser_ParseJoins(t *testing.T) {
	var tests = []struct {
		s    string
		stmt *SelectStatement
		err  string
	}{
		{
			s: `SELECT * FROM topic/sensor1 LEFT JOIN topic1 ON f=k`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &Wildcard{Token: ASTERISK},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1"}},
				Joins: []Join{
					{
						Name: "topic1", Alias: "", JoinType: LEFT_JOIN, Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "f"},
							OP:  EQ,
							RHS: &FieldRef{Name: "k"},
						},
					},
				},
			},
		},

		{
			s: `SELECT * FROM topic/sensor1 AS t1 INNER JOIN topic1 AS t2 ON f=k`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &Wildcard{Token: ASTERISK},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
				Joins: []Join{
					{
						Name: "topic1", Alias: "t2", JoinType: INNER_JOIN, Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "f"},
							OP:  EQ,
							RHS: &FieldRef{Name: "k"},
						},
					},
				},
			},
		},

		{
			s: `SELECT * FROM topic/sensor1 AS t1 LEFT JOIN topic1/sensor2 AS t2 ON f=k`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &Wildcard{Token: ASTERISK},
						Name:  "",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
				Joins: []Join{
					{
						Name: "topic1/sensor2", Alias: "t2", JoinType: LEFT_JOIN, Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "f"},
							OP:  EQ,
							RHS: &FieldRef{Name: "k"},
						},
					},
				},
			},
		},

		{
			s: `SELECT t1.name FROM topic/sensor1 AS t1 LEFT JOIN topic1/sensor2 AS t2 ON f=k`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{StreamName: StreamName("t1"), Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
				Joins: []Join{
					{
						Name: "topic1/sensor2", Alias: "t2", JoinType: LEFT_JOIN, Expr: &BinaryExpr{
							LHS: &FieldRef{Name: "f"},
							OP:  EQ,
							RHS: &FieldRef{Name: "k"},
						},
					},
				},
			},
		},

		{
			s: `SELECT t1.name FROM topic/sensor1 AS t1 LEFT JOIN topic1/sensor2 AS t2 ON t1.f=t2.k`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{StreamName: StreamName("t1"), Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
				Joins: []Join{
					{
						Name: "topic1/sensor2", Alias: "t2", JoinType: LEFT_JOIN, Expr: &BinaryExpr{
							LHS: &FieldRef{StreamName: StreamName("t1"), Name: "f"},
							OP:  EQ,
							RHS: &FieldRef{StreamName: StreamName("t2"), Name: "k"},
						},
					},
				},
			},
		},

		{
			s: `SELECT t1.name FROM topic/sensor1 AS t1 RIGHT JOIN topic1/sensor2 AS t2 ON t1.f=t2.k`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{StreamName: StreamName("t1"), Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
				Joins: []Join{
					{
						Name: "topic1/sensor2", Alias: "t2", JoinType: RIGHT_JOIN, Expr: &BinaryExpr{
							LHS: &FieldRef{StreamName: StreamName("t1"), Name: "f"},
							OP:  EQ,
							RHS: &FieldRef{StreamName: StreamName("t2"), Name: "k"},
						},
					},
				},
			},
		},

		{
			s: `SELECT t1.name FROM topic/sensor1 AS t1 FULL JOIN topic1/sensor2 AS t2 ON t1.f=t2.k`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{StreamName: StreamName("t1"), Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
				Joins: []Join{
					{
						Name: "topic1/sensor2", Alias: "t2", JoinType: FULL_JOIN, Expr: &BinaryExpr{
							LHS: &FieldRef{StreamName: StreamName("t1"), Name: "f"},
							OP:  EQ,
							RHS: &FieldRef{StreamName: StreamName("t2"), Name: "k"},
						},
					},
				},
			},
		},

		{
			s:    `SELECT t1.name FROM topic/sensor1 AS t1 CROSS JOIN topic1/sensor2 AS t2 ON t1.f=t2.k`,
			stmt: nil,
			err:  "On expression is not required for cross join type.\n",
		},

		{
			s: `SELECT t1.name FROM topic/sensor1 AS t1 CROSS JOIN topic1/sensor2 AS t2`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{StreamName: StreamName("t1"), Name: "name"},
						Name:  "name",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "topic/sensor1", Alias: "t1"}},
				Joins: []Join{
					{
						Name: "topic1/sensor2", Alias: "t2", JoinType: CROSS_JOIN, Expr: nil,
					},
				},
			},
		},

		{
			s: `SELECT demo.*, demo2.* FROM demo LEFT JOIN demo2 on demo.f1 = demo2.f2`,
			stmt: &SelectStatement{
				Fields: []Field{
					{
						Expr:  &FieldRef{StreamName: StreamName("demo"), Name: "*"},
						Name:  "*",
						AName: ""},
					{
						Expr:  &FieldRef{StreamName: StreamName("demo2"), Name: "*"},
						Name:  "*",
						AName: ""},
				},
				Sources: []Source{&Table{Name: "demo"}},
				Joins: []Join{
					{
						Name: "demo2", Alias: "", JoinType: LEFT_JOIN, Expr: &BinaryExpr{
							LHS: &FieldRef{StreamName: StreamName("demo"), Name: "f1"},
							OP:  EQ,
							RHS: &FieldRef{StreamName: StreamName("demo2"), Name: "f2"},
						},
					},
				},
			},
		},
	}

	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
	for i, tt := range tests {
		stmt, err := NewParser(strings.NewReader(tt.s)).Parse()
		if !reflect.DeepEqual(tt.err, errstring(err)) {
			t.Errorf("%d. %q: error mismatch:\n  exp=%s\n  got=%s\n\n", i, tt.s, tt.err, err)
		} else if tt.err == "" && !reflect.DeepEqual(tt.stmt, stmt) {
			t.Errorf("%d. %q\n\nstmt mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.s, tt.stmt, stmt)
		}
	}
}

func TestParser_ParseStatements(t *testing.T) {
	var tests = []struct {
		s     string
		stmts SelectStatements
		err   string
	}{
		{s: "SELECT name FROM tbl;\nSELECT name FROM topic/sensor1\n",
			stmts: []SelectStatement{
				{
					Fields: []Field{
						{
							Expr:  &FieldRef{Name: "name"},
							Name:  "name",
							AName: ""},
					},
					Sources: []Source{&Table{Name: "tbl"}},
				},
				{
					Fields: []Field{
						{
							Expr:  &FieldRef{Name: "name"},
							Name:  "name",
							AName: ""},
					},
					Sources: []Source{&Table{Name: "topic/sensor1"}},
				},
			},
		},
		{s: "SELECT name FROM tbl;\nSELECT name FROM topic/sensor1;\n--SELECT comment FROM topic/comment",
			stmts: []SelectStatement{
				{
					Fields: []Field{
						{
							Expr:  &FieldRef{Name: "name"},
							Name:  "name",
							AName: ""},
					},
					Sources: []Source{&Table{Name: "tbl"}},
				},
				{
					Fields: []Field{
						{
							Expr:  &FieldRef{Name: "name"},
							Name:  "name",
							AName: ""},
					},
					Sources: []Source{&Table{Name: "topic/sensor1"}},
				},
			},
		},
	}

	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
	for i, tt := range tests {
		stmts, err := NewParser(strings.NewReader(tt.s)).ParseQueries()
		if !reflect.DeepEqual(tt.err, errstring(err)) {
			t.Errorf("%d. %q: error mismatch:\n  exp=%s\n  got=%s\n\n", i, tt.s, tt.err, err)
		} else if tt.err == "" && !reflect.DeepEqual(tt.stmts, stmts) {
			t.Errorf("%d. %q\n\nstmt mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.s, tt.stmts, stmts)
		}
	}
}

// errstring returns the string representation of an error.
func errstring(err error) string {
	if err != nil {
		return err.Error()
	}
	return ""
}
