diff --git a/src/query/expr/parser.go b/src/query/expr/parser.go index d092f8d..4fa70c0 100644 --- a/src/query/expr/parser.go +++ b/src/query/expr/parser.go @@ -40,6 +40,8 @@ var precedences = map[tokenType]int{ tokenNeq: EQUALS, tokenIn: EQUALS, tokenNot: EQUALS, + tokenLike: EQUALS, + tokenIlike: EQUALS, tokenLt: LESSGREATER, tokenGt: LESSGREATER, tokenLte: LESSGREATER, @@ -107,6 +109,8 @@ func NewParser(s string) (*Parser, error) { tokenEq: p.parseInfixExpression, tokenIs: p.parseInfixExpression, tokenNeq: p.parseInfixExpression, + tokenLike: p.parseInfixExpression, + tokenIlike: p.parseInfixExpression, tokenIn: p.parseInfixExpression, tokenNot: p.parseInfixExpression, tokenLt: p.parseInfixExpression, @@ -265,8 +269,8 @@ func (p *Parser) parseInfixExpression(left Expression) Expression { p.position++ // IS NOT => NOT - // TODO(like): && (p.cur == tokenNot || p.cur == tokenLike || p.cur == tokenIlike) - // expr.operator = p.cur + // ARCH/COMPAT: maybe this whole IS IN, IS NOT IN, IS LIKE etc. are not supported (at least I can't get them to work in pg) + // it would certainly simplify a lot of code over here if expr.operator == tokenIs && p.curToken().ttype == tokenNot { expr.operator = tokenNot p.position++ @@ -276,8 +280,7 @@ func (p *Parser) parseInfixExpression(left Expression) Expression { // and a weird one, because it turns an infix operation to a prefix one (`foo NOT IN bar` -> `NOT(foo IN bar)`) // but we also have to support a range of expressions: foo not true, foo is not true, foo is in bar, foo is not in bar, ... if expr.operator == tokenNot { - // TODO(like): p.curToken().ttype == tokenIn || p.curToken().ttype == tokenLike || p.curToken().ttype == tokenIlike - if p.curToken().ttype == tokenIn { + if p.curToken().ttype == tokenIn || p.curToken().ttype == tokenLike || p.curToken().ttype == tokenIlike { infix := p.parseInfixExpression(expr.left) return &Prefix{operator: tokenNot, right: infix} diff --git a/src/query/expr/parser_test.go b/src/query/expr/parser_test.go index 9cb5d22..f8f1717 100644 --- a/src/query/expr/parser_test.go +++ b/src/query/expr/parser_test.go @@ -120,6 +120,26 @@ func TestParsingContents(t *testing.T) { right: &Integer{value: 4}, }, }}, + {"foo like '%ahoy%'", &Infix{operator: tokenLike, + left: &Identifier{Name: "foo"}, + right: &String{value: "%ahoy%"}, + }}, + {"foo not like '%ahoy%'", &Prefix{operator: tokenNot, + right: &Infix{operator: tokenLike, + left: &Identifier{Name: "foo"}, + right: &String{value: "%ahoy%"}, + }, + }}, + {"foo ilike '%ahoy%'", &Infix{operator: tokenIlike, + left: &Identifier{Name: "foo"}, + right: &String{value: "%ahoy%"}, + }}, + {"foo not ilike '%ahoy%'", &Prefix{operator: tokenNot, + right: &Infix{operator: tokenIlike, + left: &Identifier{Name: "foo"}, + right: &String{value: "%ahoy%"}, + }, + }}, // prefix and infix {"-4 / foo", &Infix{operator: tokenQuo, diff --git a/src/query/expr/types.go b/src/query/expr/types.go index 2181252..8c25e59 100644 --- a/src/query/expr/types.go +++ b/src/query/expr/types.go @@ -498,6 +498,12 @@ func (ex *Infix) ReturnType(ts column.TableSchema) (column.Schema, error) { } schema.Dtype = column.DtypeBool schema.Nullable = t1.Nullable || t2.Nullable + case tokenLike, tokenIlike: + if _, ok := ex.right.(*String); !ok { + return schema, errTypeMismatch // ARCH: specify more? wrap? + } + schema.Dtype = column.DtypeBool + schema.Nullable = t1.Nullable case tokenAdd, tokenSub, tokenMul, tokenQuo: if !comparableTypes(t1.Dtype, t2.Dtype) { return schema, errTypeMismatch