diff --git a/lexical/lexemes.go b/lexical/lexemes.go index 9533a4f..600cfd7 100644 --- a/lexical/lexemes.go +++ b/lexical/lexemes.go @@ -11,3 +11,4 @@ const L_LIMIT = "limit" const L_IN = "in" const L_ASC = "asc" const L_DESC = "desc" +const L_LIKE = "like" diff --git a/lexical/lexical.go b/lexical/lexical.go index f5c0d65..1435254 100644 --- a/lexical/lexical.go +++ b/lexical/lexical.go @@ -218,7 +218,8 @@ func lexemeToToken(lexeme string) uint8 { return T_ASC case L_DESC: return T_DESC - + case L_LIKE: + return T_LIKE } return T_ID } diff --git a/lexical/tokens.go b/lexical/tokens.go index 57fca2f..3658347 100644 --- a/lexical/tokens.go +++ b/lexical/tokens.go @@ -25,6 +25,7 @@ const T_PARENTH_L = 22 const T_PARENTH_R = 23 const T_IN = 24 const T_ASC = 25 +const T_LIKE = 26 const T_EOF = 0 const T_FUCK = 66 diff --git a/parser/ast.go b/parser/ast.go index f46f90c..43c1fc3 100644 --- a/parser/ast.go +++ b/parser/ast.go @@ -1,6 +1,7 @@ package parser import ( + "regexp" "strconv" "strings" "time" @@ -68,6 +69,12 @@ type NodeNotEqual struct { rightValue NodeExpr } +type NodeLike struct { + leftValue NodeExpr + rightValue NodeExpr + Pattern *regexp.Regexp +} + type NodeGreater struct { leftValue NodeExpr rightValue NodeExpr @@ -201,6 +208,31 @@ func (n *NodeNotEqual) LeftValue() NodeExpr { return n.leftValue } +// LIKE +func (n *NodeLike) Assertion(lvalue string, rvalue string) bool { + return n.Pattern.MatchString(lvalue) +} + +func (n *NodeLike) Operator() uint8 { + return lexical.T_LIKE +} + +func (n *NodeLike) SetLeftValue(e NodeExpr) { + n.leftValue = e +} + +func (n *NodeLike) SetRightValue(e NodeExpr) { + n.rightValue = e +} + +func (n *NodeLike) RightValue() NodeExpr { + return n.rightValue +} + +func (n *NodeLike) LeftValue() NodeExpr { + return n.leftValue +} + // GREATER func (n *NodeGreater) Assertion(lvalue string, rvalue string) bool { time := ExtractDate(rvalue) diff --git a/parser/parser.go b/parser/parser.go index 6a59510..7ca9e2f 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -2,7 +2,9 @@ package parser import ( "fmt" + "regexp" "strconv" + "strings" _ "unicode" "github.com/cloudson/gitql/lexical" @@ -339,7 +341,7 @@ func gWC3(eating bool) (NodeExpr, error) { return expr, nil } -// where cond 'equal', 'in' and 'not equal' +// where cond 'equal', 'in', 'like' and 'not equal' func gWC4(eating bool) (NodeExpr, error) { if eating { token, err := lexical.Token() @@ -373,6 +375,19 @@ func gWC4(eating bool) (NodeExpr, error) { } op.SetRightValue(expr2) return op, nil + case lexical.T_LIKE: + op := new(NodeLike) + op.SetLeftValue(expr) + expr2, err2 := gWC4(true) + if err2 != nil { + return nil, err2 + } + op.SetRightValue(expr2) + // Compile the regex while parsing, so that + // we don't need to compile for every row + rx := strings.Replace(expr2.(*NodeLiteral).Value(), "%", "(.*)", -1) + op.Pattern, err2 = regexp.Compile(rx) + return op, err2 case lexical.T_IN: op := new(NodeIn) op.SetLeftValue(expr) diff --git a/runtime/commits_test.go b/runtime/commits_test.go index 98c7ed8..2a09143 100644 --- a/runtime/commits_test.go +++ b/runtime/commits_test.go @@ -84,6 +84,14 @@ func TestSelectedFieldsCount(t *testing.T) { } } +func TestWhereLike(t *testing.T) { + query := "select hash, author from commits where hash like '%8813f1c5e6f5d10ef%'" + table := getTableForQuery(query, "../", t) + if len(table.rows) != 1 { + t.Errorf("Expecting 1 row. Got %d rows", len(table.rows)) + } +} + func TestNotEqualsInWhereLTGT(t *testing.T) { queryData := "select committer, hash from commits limit 1" table := getTableForQuery(queryData, "../", t) @@ -94,6 +102,7 @@ func TestNotEqualsInWhereLTGT(t *testing.T) { t.Errorf("Still got the same committer as the first one. - %s", firstCommitter) } } + func TestNotEqualsInWhere(t *testing.T) { queryData := "select committer, hash from commits limit 1" table := getTableForQuery(queryData, "../", t) diff --git a/runtime/visitor.go b/runtime/visitor.go index 9d7a0ea..70074db 100644 --- a/runtime/visitor.go +++ b/runtime/visitor.go @@ -117,11 +117,13 @@ func (v *RuntimeVisitor) VisitExpr(n parser.NodeExpr) error { case reflect.TypeOf(new(parser.NodeIn)): g := n.(*parser.NodeIn) return v.VisitIn(g) + case reflect.TypeOf(new(parser.NodeLike)): + g := n.(*parser.NodeLike) + return v.VisitLike(g) case reflect.TypeOf(new(parser.NodeNotEqual)): g := n.(*parser.NodeNotEqual) return v.VisitNotEqual(g) } - return nil } @@ -132,6 +134,13 @@ func (v *RuntimeVisitor) VisitEqual(n *parser.NodeEqual) error { return nil } +func (v *RuntimeVisitor) VisitLike(n *parser.NodeLike) error { + lvalue := n.LeftValue().(*parser.NodeId).Value() + rvalue := n.RightValue().(*parser.NodeLiteral).Value() + boolRegister = n.Assertion(metadata(lvalue), rvalue) + return nil +} + func (v *RuntimeVisitor) VisitNotEqual(n *parser.NodeNotEqual) error { lvalue := n.LeftValue().(*parser.NodeId).Value() rvalue := n.RightValue().(*parser.NodeLiteral).Value() @@ -143,9 +152,7 @@ func (v *RuntimeVisitor) VisitGreater(n *parser.NodeGreater) error { lvalue := n.LeftValue().(*parser.NodeId).Value() lvalue = metadata(lvalue) rvalue := n.RightValue().(*parser.NodeLiteral).Value() - boolRegister = n.Assertion(lvalue, rvalue) - return nil } @@ -153,9 +160,7 @@ func (v *RuntimeVisitor) VisitSmaller(n *parser.NodeSmaller) error { lvalue := n.LeftValue().(*parser.NodeId).Value() lvalue = metadata(lvalue) rvalue := n.RightValue().(*parser.NodeLiteral).Value() - boolRegister = n.Assertion(lvalue, rvalue) - return nil } @@ -164,7 +169,6 @@ func (v *RuntimeVisitor) VisitOr(n *parser.NodeOr) error { boolLeft := boolRegister v.VisitExpr(n.RightValue()) boolRight := boolRegister - boolRegister = boolLeft || boolRight return nil }