From 1efb359b82e8e73fcbe93dc127d9dc986c3e38e0 Mon Sep 17 00:00:00 2001 From: doug-martin Date: Wed, 2 Oct 2019 23:46:25 -0500 Subject: [PATCH] Fix for #172 * [ADDED] Ability to use regexp like, ilike, notlike, and notilike without a regexp #172 --- HISTORY.md | 4 + exp/bool.go | 20 ++ exp/cast.go | 58 +++-- exp/cast_test.go | 79 ++++++ exp/exp.go | 21 +- exp/exp_map.go | 13 +- exp/exp_map_test.go | 319 ++++++++++++++++++++++++ exp/func.go | 12 + exp/func_test.go | 78 ++++++ exp/ident.go | 40 +-- exp/ident_test.go | 46 ++++ exp/literal.go | 56 +++-- exp/literal_test.go | 79 ++++++ exp/window_func.go | 12 + exp/window_func_test.go | 151 +++-------- sqlgen/expression_sql_generator_test.go | 78 +++++- 16 files changed, 867 insertions(+), 199 deletions(-) create mode 100644 exp/cast_test.go create mode 100644 exp/exp_map_test.go create mode 100644 exp/func_test.go create mode 100644 exp/literal_test.go diff --git a/HISTORY.md b/HISTORY.md index 9d1f3060..64947751 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,7 @@ +# v9.5.0 + +* [ADDED] Ability to use regexp like, ilike, notlike, and notilike without a regexp [#172](https://github.com/doug-martin/goqu/issues/172) + # v9.4.0 * [ADDED] Ability to scan into struct fields from multiple tables [#160](https://github.com/doug-martin/goqu/issues/160) diff --git a/exp/bool.go b/exp/bool.go index 15707100..25ba8012 100644 --- a/exp/bool.go +++ b/exp/bool.go @@ -111,6 +111,26 @@ func notILike(lhs Expression, val interface{}) BooleanExpression { return checkLikeExp(ILikeOp, lhs, val, true) } +// used internally to create a LIKE BooleanExpression +func regexpLike(lhs Expression, val interface{}) BooleanExpression { + return checkLikeExp(RegexpLikeOp, lhs, val, false) +} + +// used internally to create an ILIKE BooleanExpression +func regexpILike(lhs Expression, val interface{}) BooleanExpression { + return checkLikeExp(RegexpILikeOp, lhs, val, false) +} + +// used internally to create a NOT LIKE BooleanExpression +func regexpNotLike(lhs Expression, val interface{}) BooleanExpression { + return checkLikeExp(RegexpLikeOp, lhs, val, true) +} + +// used internally to create a NOT ILIKE BooleanExpression +func regexpNotILike(lhs Expression, val interface{}) BooleanExpression { + return checkLikeExp(RegexpILikeOp, lhs, val, true) +} + // checks an like rhs to create the proper like expression for strings or regexps func checkLikeExp(op BooleanOperation, lhs Expression, val interface{}, invert bool) BooleanExpression { rhs := val diff --git a/exp/cast.go b/exp/cast.go index 0df80fa8..ef38032a 100644 --- a/exp/cast.go +++ b/exp/cast.go @@ -23,30 +23,34 @@ func (c cast) Clone() Expression { return cast{casted: c.casted.Clone(), t: c.t} } -func (c cast) Expression() Expression { return c } -func (c cast) As(val interface{}) AliasedExpression { return aliased(c, val) } -func (c cast) Eq(val interface{}) BooleanExpression { return eq(c, val) } -func (c cast) Neq(val interface{}) BooleanExpression { return neq(c, val) } -func (c cast) Gt(val interface{}) BooleanExpression { return gt(c, val) } -func (c cast) Gte(val interface{}) BooleanExpression { return gte(c, val) } -func (c cast) Lt(val interface{}) BooleanExpression { return lt(c, val) } -func (c cast) Lte(val interface{}) BooleanExpression { return lte(c, val) } -func (c cast) Asc() OrderedExpression { return asc(c) } -func (c cast) Desc() OrderedExpression { return desc(c) } -func (c cast) Like(i interface{}) BooleanExpression { return like(c, i) } -func (c cast) NotLike(i interface{}) BooleanExpression { return notLike(c, i) } -func (c cast) ILike(i interface{}) BooleanExpression { return iLike(c, i) } -func (c cast) NotILike(i interface{}) BooleanExpression { return notILike(c, i) } -func (c cast) In(i ...interface{}) BooleanExpression { return in(c, i...) } -func (c cast) NotIn(i ...interface{}) BooleanExpression { return notIn(c, i...) } -func (c cast) Is(i interface{}) BooleanExpression { return is(c, i) } -func (c cast) IsNot(i interface{}) BooleanExpression { return isNot(c, i) } -func (c cast) IsNull() BooleanExpression { return is(c, nil) } -func (c cast) IsNotNull() BooleanExpression { return isNot(c, nil) } -func (c cast) IsTrue() BooleanExpression { return is(c, true) } -func (c cast) IsNotTrue() BooleanExpression { return isNot(c, true) } -func (c cast) IsFalse() BooleanExpression { return is(c, false) } -func (c cast) IsNotFalse() BooleanExpression { return isNot(c, nil) } -func (c cast) Distinct() SQLFunctionExpression { return NewSQLFunctionExpression("DISTINCT", c) } -func (c cast) Between(val RangeVal) RangeExpression { return between(c, val) } -func (c cast) NotBetween(val RangeVal) RangeExpression { return notBetween(c, val) } +func (c cast) Expression() Expression { return c } +func (c cast) As(val interface{}) AliasedExpression { return aliased(c, val) } +func (c cast) Eq(val interface{}) BooleanExpression { return eq(c, val) } +func (c cast) Neq(val interface{}) BooleanExpression { return neq(c, val) } +func (c cast) Gt(val interface{}) BooleanExpression { return gt(c, val) } +func (c cast) Gte(val interface{}) BooleanExpression { return gte(c, val) } +func (c cast) Lt(val interface{}) BooleanExpression { return lt(c, val) } +func (c cast) Lte(val interface{}) BooleanExpression { return lte(c, val) } +func (c cast) Asc() OrderedExpression { return asc(c) } +func (c cast) Desc() OrderedExpression { return desc(c) } +func (c cast) Like(i interface{}) BooleanExpression { return like(c, i) } +func (c cast) NotLike(i interface{}) BooleanExpression { return notLike(c, i) } +func (c cast) ILike(i interface{}) BooleanExpression { return iLike(c, i) } +func (c cast) NotILike(i interface{}) BooleanExpression { return notILike(c, i) } +func (c cast) RegexpLike(val interface{}) BooleanExpression { return regexpLike(c, val) } +func (c cast) RegexpNotLike(val interface{}) BooleanExpression { return regexpNotLike(c, val) } +func (c cast) RegexpILike(val interface{}) BooleanExpression { return regexpILike(c, val) } +func (c cast) RegexpNotILike(val interface{}) BooleanExpression { return regexpNotILike(c, val) } +func (c cast) In(i ...interface{}) BooleanExpression { return in(c, i...) } +func (c cast) NotIn(i ...interface{}) BooleanExpression { return notIn(c, i...) } +func (c cast) Is(i interface{}) BooleanExpression { return is(c, i) } +func (c cast) IsNot(i interface{}) BooleanExpression { return isNot(c, i) } +func (c cast) IsNull() BooleanExpression { return is(c, nil) } +func (c cast) IsNotNull() BooleanExpression { return isNot(c, nil) } +func (c cast) IsTrue() BooleanExpression { return is(c, true) } +func (c cast) IsNotTrue() BooleanExpression { return isNot(c, true) } +func (c cast) IsFalse() BooleanExpression { return is(c, false) } +func (c cast) IsNotFalse() BooleanExpression { return isNot(c, false) } +func (c cast) Distinct() SQLFunctionExpression { return NewSQLFunctionExpression("DISTINCT", c) } +func (c cast) Between(val RangeVal) RangeExpression { return between(c, val) } +func (c cast) NotBetween(val RangeVal) RangeExpression { return notBetween(c, val) } diff --git a/exp/cast_test.go b/exp/cast_test.go new file mode 100644 index 00000000..b04fc5b0 --- /dev/null +++ b/exp/cast_test.go @@ -0,0 +1,79 @@ +package exp + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type castExpressionSuite struct { + suite.Suite + ce CastExpression +} + +func TestCastExpressionSuite(t *testing.T) { + suite.Run(t, &castExpressionSuite{ + ce: NewCastExpression(NewIdentifierExpression("", "", "a"), "TEXT"), + }) +} + +func (ces *castExpressionSuite) TestClone() { + ces.Equal(ces.ce, ces.ce.Clone()) +} + +func (ces *castExpressionSuite) TestExpression() { + ces.Equal(ces.ce, ces.ce.Expression()) +} + +func (ces *castExpressionSuite) TestCasted() { + ces.Equal(NewIdentifierExpression("", "", "a"), ces.ce.Casted()) +} +func (ces *castExpressionSuite) TestType() { + ces.Equal(NewLiteralExpression("TEXT"), ces.ce.Type()) +} + +func (ces *castExpressionSuite) TestAllOthers() { + ce := ces.ce + rv := NewRangeVal(1, 2) + pattern := "cast like%" + inVals := []interface{}{1, 2} + testCases := []struct { + Ex Expression + Expected Expression + }{ + {Ex: ce.As("a"), Expected: aliased(ce, "a")}, + {Ex: ce.Eq(1), Expected: NewBooleanExpression(EqOp, ce, 1)}, + {Ex: ce.Neq(1), Expected: NewBooleanExpression(NeqOp, ce, 1)}, + {Ex: ce.Gt(1), Expected: NewBooleanExpression(GtOp, ce, 1)}, + {Ex: ce.Gte(1), Expected: NewBooleanExpression(GteOp, ce, 1)}, + {Ex: ce.Lt(1), Expected: NewBooleanExpression(LtOp, ce, 1)}, + {Ex: ce.Lte(1), Expected: NewBooleanExpression(LteOp, ce, 1)}, + {Ex: ce.Asc(), Expected: asc(ce)}, + {Ex: ce.Desc(), Expected: desc(ce)}, + {Ex: ce.Between(rv), Expected: between(ce, rv)}, + {Ex: ce.NotBetween(rv), Expected: notBetween(ce, rv)}, + {Ex: ce.Like(pattern), Expected: NewBooleanExpression(LikeOp, ce, pattern)}, + {Ex: ce.NotLike(pattern), Expected: NewBooleanExpression(NotLikeOp, ce, pattern)}, + {Ex: ce.ILike(pattern), Expected: NewBooleanExpression(ILikeOp, ce, pattern)}, + {Ex: ce.NotILike(pattern), Expected: NewBooleanExpression(NotILikeOp, ce, pattern)}, + {Ex: ce.RegexpLike(pattern), Expected: NewBooleanExpression(RegexpLikeOp, ce, pattern)}, + {Ex: ce.RegexpNotLike(pattern), Expected: NewBooleanExpression(RegexpNotLikeOp, ce, pattern)}, + {Ex: ce.RegexpILike(pattern), Expected: NewBooleanExpression(RegexpILikeOp, ce, pattern)}, + {Ex: ce.RegexpNotILike(pattern), Expected: NewBooleanExpression(RegexpNotILikeOp, ce, pattern)}, + {Ex: ce.In(inVals), Expected: NewBooleanExpression(InOp, ce, inVals)}, + {Ex: ce.NotIn(inVals), Expected: NewBooleanExpression(NotInOp, ce, inVals)}, + {Ex: ce.Is(true), Expected: NewBooleanExpression(IsOp, ce, true)}, + {Ex: ce.IsNot(true), Expected: NewBooleanExpression(IsNotOp, ce, true)}, + {Ex: ce.IsNull(), Expected: NewBooleanExpression(IsOp, ce, nil)}, + {Ex: ce.IsNotNull(), Expected: NewBooleanExpression(IsNotOp, ce, nil)}, + {Ex: ce.IsTrue(), Expected: NewBooleanExpression(IsOp, ce, true)}, + {Ex: ce.IsNotTrue(), Expected: NewBooleanExpression(IsNotOp, ce, true)}, + {Ex: ce.IsFalse(), Expected: NewBooleanExpression(IsOp, ce, false)}, + {Ex: ce.IsNotFalse(), Expected: NewBooleanExpression(IsNotOp, ce, false)}, + {Ex: ce.Distinct(), Expected: NewSQLFunctionExpression("DISTINCT", ce)}, + } + + for _, tc := range testCases { + ces.Equal(tc.Expected, tc.Ex) + } +} diff --git a/exp/exp.go b/exp/exp.go index 2246aacd..2b9e78c2 100644 --- a/exp/exp.go +++ b/exp/exp.go @@ -71,6 +71,19 @@ type ( // Creates an Boolean expression for case insensitive NOT LIKE clauses // ds.Where(I("a").NotILike("a%")) //("a" NOT ILIKE 'a%') NotILike(interface{}) BooleanExpression + + // Creates an Boolean expression for REGEXP LIKE clauses + // ds.Where(I("a").RegexpLike("a%")) //("a" ~ 'a%') + RegexpLike(interface{}) BooleanExpression + // Creates an Boolean expression for REGEXP NOT LIKE clauses + // ds.Where(I("a").RegexpNotLike("a%")) //("a" !~ 'a%') + RegexpNotLike(interface{}) BooleanExpression + // Creates an Boolean expression for case insensitive REGEXP ILIKE clauses + // ds.Where(I("a").RegexpILike("a%")) //("a" ~* 'a%') + RegexpILike(interface{}) BooleanExpression + // Creates an Boolean expression for case insensitive REGEXP NOT ILIKE clauses + // ds.Where(I("a").RegexpNotILike("a%")) //("a" !~* 'a%') + RegexpNotILike(interface{}) BooleanExpression } // Interface that an expression should implement if it can be compared with other values. @@ -564,13 +577,13 @@ func (bo BooleanOperation) String() string { case NotILikeOp: return "notilike" case RegexpLikeOp: - return "regexp like" + return "regexplike" case RegexpNotLikeOp: - return "regexp notlike" + return "regexpnotlike" case RegexpILikeOp: - return "regexp ilike" + return "regexpilike" case RegexpNotILikeOp: - return "regexp notilike" + return "regexpnotilike" } return fmt.Sprintf("%d", bo) } diff --git a/exp/exp_map.go b/exp/exp_map.go index a46cf6bf..c470a953 100644 --- a/exp/exp_map.go +++ b/exp/exp_map.go @@ -47,7 +47,7 @@ func (eo ExOr) Expression() Expression { } func (eo ExOr) Clone() Expression { - ret := Ex{} + ret := ExOr{} for key, val := range eo { ret[key] = val } @@ -108,6 +108,7 @@ func createOredExpressionFromMap(lhs IdentifierExpression, op Op) ([]Expression, return ors, nil } +// nolint:gocyclo func createExpressionFromOp(lhs IdentifierExpression, opKey string, op Op) (exp Expression, err error) { switch strings.ToLower(opKey) { case EqOp.String(): @@ -138,6 +139,14 @@ func createExpressionFromOp(lhs IdentifierExpression, opKey string, op Op) (exp exp = lhs.ILike(op[opKey]) case NotILikeOp.String(): exp = lhs.NotILike(op[opKey]) + case RegexpLikeOp.String(): + exp = lhs.RegexpLike(op[opKey]) + case RegexpNotLikeOp.String(): + exp = lhs.RegexpNotLike(op[opKey]) + case RegexpILikeOp.String(): + exp = lhs.RegexpILike(op[opKey]) + case RegexpNotILikeOp.String(): + exp = lhs.RegexpNotILike(op[opKey]) case betweenStr: rangeVal, ok := op[opKey].(RangeVal) if ok { @@ -149,7 +158,7 @@ func createExpressionFromOp(lhs IdentifierExpression, opKey string, op Op) (exp exp = lhs.NotBetween(rangeVal) } default: - err = errors.New("unsupported expression type %s", op) + err = errors.New("unsupported expression type %s", opKey) } return exp, err } diff --git a/exp/exp_map_test.go b/exp/exp_map_test.go new file mode 100644 index 00000000..ef499b3f --- /dev/null +++ b/exp/exp_map_test.go @@ -0,0 +1,319 @@ +package exp + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type exTestSuite struct { + suite.Suite +} + +func TestExSuite(t *testing.T) { + suite.Run(t, new(exTestSuite)) +} + +func (ets *exTestSuite) TestExpression() { + ex := Ex{"a": "b"} + ets.Equal(ex, ex.Expression()) +} + +func (ets *exTestSuite) TestClone() { + ex := Ex{"a": "b"} + ets.Equal(ex, ex.Clone()) +} + +func (ets *exTestSuite) TestIsEmpty() { + ets.False(Ex{"a": "b"}.IsEmpty()) + ets.True(Ex{}.IsEmpty()) +} + +func (ets *exTestSuite) TestToExpression() { + ident := NewIdentifierExpression("", "", "a") + testCases := []struct { + ExMap Ex + El ExpressionList + Err string + }{ + { + ExMap: Ex{"a": "b"}, + El: NewExpressionList(AndType, ident.Eq("b")), + }, + { + ExMap: Ex{"a": "b", "b": "c"}, + El: NewExpressionList( + AndType, + ident.Eq("b"), + NewIdentifierExpression("", "", "b").Eq("c"), + ), + }, + { + ExMap: Ex{"a": Op{"eq": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Eq("b"))), + }, + { + ExMap: Ex{"a": Op{"neq": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Neq("b"))), + }, + { + ExMap: Ex{"a": Op{"is": nil}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Is(nil))), + }, + { + ExMap: Ex{"a": Op{"isNot": nil}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.IsNot(nil))), + }, + { + ExMap: Ex{"a": Op{"gt": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Gt("b"))), + }, + { + ExMap: Ex{"a": Op{"gte": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Gte("b"))), + }, + { + ExMap: Ex{"a": Op{"lt": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Lt("b"))), + }, + { + ExMap: Ex{"a": Op{"lte": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Lte("b"))), + }, + { + ExMap: Ex{"a": Op{"in": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.In("b"))), + }, + { + ExMap: Ex{"a": Op{"notIn": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.NotIn("b"))), + }, + { + ExMap: Ex{"a": Op{"like": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Like("b"))), + }, + { + ExMap: Ex{"a": Op{"notLike": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.NotLike("b"))), + }, + { + ExMap: Ex{"a": Op{"iLike": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.ILike("b"))), + }, + { + ExMap: Ex{"a": Op{"notILike": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.NotILike("b"))), + }, + { + ExMap: Ex{"a": Op{"regexpLike": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.RegexpLike("b"))), + }, + { + ExMap: Ex{"a": Op{"regexpNotLike": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.RegexpNotLike("b"))), + }, + { + ExMap: Ex{"a": Op{"regexpILike": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.RegexpILike("b"))), + }, + { + ExMap: Ex{"a": Op{"regexpNotILike": "b"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.RegexpNotILike("b"))), + }, + { + ExMap: Ex{"a": Op{"between": NewRangeVal("a", "z")}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Between(NewRangeVal("a", "z")))), + }, + { + ExMap: Ex{"a": Op{"notBetween": NewRangeVal("a", "z")}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.NotBetween(NewRangeVal("a", "z")))), + }, + { + ExMap: Ex{"a": Op{"foo": "z"}}, + Err: "goqu: unsupported expression type foo", + }, + { + ExMap: Ex{"a": Op{"eq": "b", "neq": "c", "gt": "m"}}, + El: NewExpressionList(AndType, NewExpressionList(OrType, ident.Eq("b"), ident.Gt("m"), ident.Neq("c"))), + }, + + { + ExMap: Ex{ + "a": "b", + "c": "d", + }, + El: NewExpressionList( + AndType, + ident.Eq("b"), + NewIdentifierExpression("", "", "c").Eq("d"), + ), + }, + } + + for _, tc := range testCases { + el, err := tc.ExMap.ToExpressions() + + if tc.Err == "" { + ets.NoError(err) + ets.Equal(tc.El, el, "For Ex %v", tc.ExMap) + } else { + ets.EqualError(err, tc.Err) + } + } +} + +type exOrTestSuite struct { + suite.Suite +} + +func TestExOrSuite(t *testing.T) { + suite.Run(t, new(exOrTestSuite)) +} + +func (ets *exOrTestSuite) TestExpression() { + ex := ExOr{"a": "b"} + ets.Equal(ex, ex.Expression()) +} + +func (ets *exOrTestSuite) TestClone() { + ex := ExOr{"a": "b"} + ets.Equal(ex, ex.Clone()) +} + +func (ets *exOrTestSuite) TestIsEmpty() { + ets.False(ExOr{"a": "b"}.IsEmpty()) + ets.True(ExOr{}.IsEmpty()) +} + +func (ets *exOrTestSuite) TestToExpression() { + ident := NewIdentifierExpression("", "", "a") + testCases := []struct { + ExMap ExOr + El ExpressionList + Err string + }{ + { + ExMap: ExOr{"a": "b"}, + El: NewExpressionList(OrType, ident.Eq("b")), + }, + { + ExMap: ExOr{"a": "b", "b": "c"}, + El: NewExpressionList( + OrType, + ident.Eq("b"), + NewIdentifierExpression("", "", "b").Eq("c"), + ), + }, + { + ExMap: ExOr{"a": Op{"eq": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Eq("b"))), + }, + { + ExMap: ExOr{"a": Op{"neq": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Neq("b"))), + }, + { + ExMap: ExOr{"a": Op{"is": nil}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Is(nil))), + }, + { + ExMap: ExOr{"a": Op{"isNot": nil}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.IsNot(nil))), + }, + { + ExMap: ExOr{"a": Op{"gt": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Gt("b"))), + }, + { + ExMap: ExOr{"a": Op{"gte": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Gte("b"))), + }, + { + ExMap: ExOr{"a": Op{"lt": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Lt("b"))), + }, + { + ExMap: ExOr{"a": Op{"lte": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Lte("b"))), + }, + { + ExMap: ExOr{"a": Op{"in": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.In("b"))), + }, + { + ExMap: ExOr{"a": Op{"notIn": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.NotIn("b"))), + }, + { + ExMap: ExOr{"a": Op{"like": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Like("b"))), + }, + { + ExMap: ExOr{"a": Op{"notLike": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.NotLike("b"))), + }, + { + ExMap: ExOr{"a": Op{"iLike": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.ILike("b"))), + }, + { + ExMap: ExOr{"a": Op{"notILike": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.NotILike("b"))), + }, + { + ExMap: ExOr{"a": Op{"regexpLike": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.RegexpLike("b"))), + }, + { + ExMap: ExOr{"a": Op{"regexpNotLike": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.RegexpNotLike("b"))), + }, + { + ExMap: ExOr{"a": Op{"regexpILike": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.RegexpILike("b"))), + }, + { + ExMap: ExOr{"a": Op{"regexpNotILike": "b"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.RegexpNotILike("b"))), + }, + { + ExMap: ExOr{"a": Op{"between": NewRangeVal("a", "z")}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Between(NewRangeVal("a", "z")))), + }, + { + ExMap: ExOr{"a": Op{"notBetween": NewRangeVal("a", "z")}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.NotBetween(NewRangeVal("a", "z")))), + }, + { + ExMap: ExOr{"a": Op{"foo": "z"}}, + Err: "goqu: unsupported expression type foo", + }, + { + ExMap: ExOr{"a": Op{"eq": "b", "neq": "c", "gt": "m"}}, + El: NewExpressionList(OrType, NewExpressionList(OrType, ident.Eq("b"), ident.Gt("m"), ident.Neq("c"))), + }, + + { + ExMap: ExOr{ + "a": "b", + "c": "d", + }, + El: NewExpressionList( + OrType, + ident.Eq("b"), + NewIdentifierExpression("", "", "c").Eq("d"), + ), + }, + } + + for _, tc := range testCases { + el, err := tc.ExMap.ToExpressions() + + if tc.Err == "" { + ets.NoError(err) + ets.Equal(tc.El, el, "For Ex %v", tc.ExMap) + } else { + ets.EqualError(err, tc.Err) + } + } +} diff --git a/exp/func.go b/exp/func.go index 67b62a39..55cf72d6 100644 --- a/exp/func.go +++ b/exp/func.go @@ -34,6 +34,18 @@ func (sfe sqlFunctionExpression) ILike(val interface{}) BooleanExpression { re func (sfe sqlFunctionExpression) NotILike(val interface{}) BooleanExpression { return notILike(sfe, val) } +func (sfe sqlFunctionExpression) RegexpLike(val interface{}) BooleanExpression { + return regexpLike(sfe, val) +} +func (sfe sqlFunctionExpression) RegexpNotLike(val interface{}) BooleanExpression { + return regexpNotLike(sfe, val) +} +func (sfe sqlFunctionExpression) RegexpILike(val interface{}) BooleanExpression { + return regexpILike(sfe, val) +} +func (sfe sqlFunctionExpression) RegexpNotILike(val interface{}) BooleanExpression { + return regexpNotILike(sfe, val) +} func (sfe sqlFunctionExpression) In(vals ...interface{}) BooleanExpression { return in(sfe, vals...) } func (sfe sqlFunctionExpression) NotIn(vals ...interface{}) BooleanExpression { return notIn(sfe, vals...) diff --git a/exp/func_test.go b/exp/func_test.go new file mode 100644 index 00000000..6e2bcdd4 --- /dev/null +++ b/exp/func_test.go @@ -0,0 +1,78 @@ +package exp + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type sqlFunctionExpressionSuite struct { + suite.Suite + fn SQLFunctionExpression +} + +func TestSQLFunctionExpressionSuite(t *testing.T) { + suite.Run(t, &sqlFunctionExpressionSuite{ + fn: NewSQLFunctionExpression("COUNT", Star()), + }) +} + +func (sfes *sqlFunctionExpressionSuite) TestClone() { + sfes.Equal(sfes.fn, sfes.fn.Clone()) +} + +func (sfes *sqlFunctionExpressionSuite) TestExpression() { + sfes.Equal(sfes.fn, sfes.fn.Expression()) +} + +func (sfes *sqlFunctionExpressionSuite) TestArgs() { + sfes.Equal([]interface{}{Star()}, sfes.fn.Args()) +} + +func (sfes *sqlFunctionExpressionSuite) TestName() { + sfes.Equal("COUNT", sfes.fn.Name()) +} + +func (sfes *sqlFunctionExpressionSuite) TestAllOthers() { + fn := sfes.fn + + rv := NewRangeVal(1, 2) + pattern := "func like%" + inVals := []interface{}{1, 2} + testCases := []struct { + Ex Expression + Expected Expression + }{ + {Ex: fn.As("a"), Expected: aliased(fn, "a")}, + {Ex: fn.Eq(1), Expected: NewBooleanExpression(EqOp, fn, 1)}, + {Ex: fn.Neq(1), Expected: NewBooleanExpression(NeqOp, fn, 1)}, + {Ex: fn.Gt(1), Expected: NewBooleanExpression(GtOp, fn, 1)}, + {Ex: fn.Gte(1), Expected: NewBooleanExpression(GteOp, fn, 1)}, + {Ex: fn.Lt(1), Expected: NewBooleanExpression(LtOp, fn, 1)}, + {Ex: fn.Lte(1), Expected: NewBooleanExpression(LteOp, fn, 1)}, + {Ex: fn.Between(rv), Expected: between(fn, rv)}, + {Ex: fn.NotBetween(rv), Expected: notBetween(fn, rv)}, + {Ex: fn.Like(pattern), Expected: NewBooleanExpression(LikeOp, fn, pattern)}, + {Ex: fn.NotLike(pattern), Expected: NewBooleanExpression(NotLikeOp, fn, pattern)}, + {Ex: fn.ILike(pattern), Expected: NewBooleanExpression(ILikeOp, fn, pattern)}, + {Ex: fn.NotILike(pattern), Expected: NewBooleanExpression(NotILikeOp, fn, pattern)}, + {Ex: fn.RegexpLike(pattern), Expected: NewBooleanExpression(RegexpLikeOp, fn, pattern)}, + {Ex: fn.RegexpNotLike(pattern), Expected: NewBooleanExpression(RegexpNotLikeOp, fn, pattern)}, + {Ex: fn.RegexpILike(pattern), Expected: NewBooleanExpression(RegexpILikeOp, fn, pattern)}, + {Ex: fn.RegexpNotILike(pattern), Expected: NewBooleanExpression(RegexpNotILikeOp, fn, pattern)}, + {Ex: fn.In(inVals), Expected: NewBooleanExpression(InOp, fn, inVals)}, + {Ex: fn.NotIn(inVals), Expected: NewBooleanExpression(NotInOp, fn, inVals)}, + {Ex: fn.Is(true), Expected: NewBooleanExpression(IsOp, fn, true)}, + {Ex: fn.IsNot(true), Expected: NewBooleanExpression(IsNotOp, fn, true)}, + {Ex: fn.IsNull(), Expected: NewBooleanExpression(IsOp, fn, nil)}, + {Ex: fn.IsNotNull(), Expected: NewBooleanExpression(IsNotOp, fn, nil)}, + {Ex: fn.IsTrue(), Expected: NewBooleanExpression(IsOp, fn, true)}, + {Ex: fn.IsNotTrue(), Expected: NewBooleanExpression(IsNotOp, fn, true)}, + {Ex: fn.IsFalse(), Expected: NewBooleanExpression(IsOp, fn, false)}, + {Ex: fn.IsNotFalse(), Expected: NewBooleanExpression(IsNotOp, fn, false)}, + } + + for _, tc := range testCases { + sfes.Equal(tc.Expected, tc.Ex) + } +} diff --git a/exp/ident.go b/exp/ident.go index a2882c51..29f5a19b 100644 --- a/exp/ident.go +++ b/exp/ident.go @@ -140,24 +140,28 @@ func (i identifier) Lt(val interface{}) BooleanExpression { return lt(i, val) } func (i identifier) Lte(val interface{}) BooleanExpression { return lte(i, val) } // Returns a BooleanExpression for checking that a identifier is in a list of values or (e.g "my_col" > 1) -func (i identifier) In(vals ...interface{}) BooleanExpression { return in(i, vals...) } -func (i identifier) NotIn(vals ...interface{}) BooleanExpression { return notIn(i, vals...) } -func (i identifier) Like(val interface{}) BooleanExpression { return like(i, val) } -func (i identifier) NotLike(val interface{}) BooleanExpression { return notLike(i, val) } -func (i identifier) ILike(val interface{}) BooleanExpression { return iLike(i, val) } -func (i identifier) NotILike(val interface{}) BooleanExpression { return notILike(i, val) } -func (i identifier) Is(val interface{}) BooleanExpression { return is(i, val) } -func (i identifier) IsNot(val interface{}) BooleanExpression { return isNot(i, val) } -func (i identifier) IsNull() BooleanExpression { return is(i, nil) } -func (i identifier) IsNotNull() BooleanExpression { return isNot(i, nil) } -func (i identifier) IsTrue() BooleanExpression { return is(i, true) } -func (i identifier) IsNotTrue() BooleanExpression { return isNot(i, true) } -func (i identifier) IsFalse() BooleanExpression { return is(i, false) } -func (i identifier) IsNotFalse() BooleanExpression { return isNot(i, false) } -func (i identifier) Asc() OrderedExpression { return asc(i) } -func (i identifier) Desc() OrderedExpression { return desc(i) } -func (i identifier) Distinct() SQLFunctionExpression { return NewSQLFunctionExpression("DISTINCT", i) } -func (i identifier) Cast(t string) CastExpression { return NewCastExpression(i, t) } +func (i identifier) In(vals ...interface{}) BooleanExpression { return in(i, vals...) } +func (i identifier) NotIn(vals ...interface{}) BooleanExpression { return notIn(i, vals...) } +func (i identifier) Like(val interface{}) BooleanExpression { return like(i, val) } +func (i identifier) NotLike(val interface{}) BooleanExpression { return notLike(i, val) } +func (i identifier) ILike(val interface{}) BooleanExpression { return iLike(i, val) } +func (i identifier) NotILike(val interface{}) BooleanExpression { return notILike(i, val) } +func (i identifier) RegexpLike(val interface{}) BooleanExpression { return regexpLike(i, val) } +func (i identifier) RegexpNotLike(val interface{}) BooleanExpression { return regexpNotLike(i, val) } +func (i identifier) RegexpILike(val interface{}) BooleanExpression { return regexpILike(i, val) } +func (i identifier) RegexpNotILike(val interface{}) BooleanExpression { return regexpNotILike(i, val) } +func (i identifier) Is(val interface{}) BooleanExpression { return is(i, val) } +func (i identifier) IsNot(val interface{}) BooleanExpression { return isNot(i, val) } +func (i identifier) IsNull() BooleanExpression { return is(i, nil) } +func (i identifier) IsNotNull() BooleanExpression { return isNot(i, nil) } +func (i identifier) IsTrue() BooleanExpression { return is(i, true) } +func (i identifier) IsNotTrue() BooleanExpression { return isNot(i, true) } +func (i identifier) IsFalse() BooleanExpression { return is(i, false) } +func (i identifier) IsNotFalse() BooleanExpression { return isNot(i, false) } +func (i identifier) Asc() OrderedExpression { return asc(i) } +func (i identifier) Desc() OrderedExpression { return desc(i) } +func (i identifier) Distinct() SQLFunctionExpression { return NewSQLFunctionExpression("DISTINCT", i) } +func (i identifier) Cast(t string) CastExpression { return NewCastExpression(i, t) } // Returns a RangeExpression for checking that a identifier is between two values (e.g "my_col" BETWEEN 1 AND 10) func (i identifier) Between(val RangeVal) RangeExpression { return between(i, val) } diff --git a/exp/ident_test.go b/exp/ident_test.go index 52966a1d..556a0c6c 100644 --- a/exp/ident_test.go +++ b/exp/ident_test.go @@ -164,3 +164,49 @@ func (ies *identifierExpressionSuite) TestIsEmpty() { ies.Equal(tc.IsEmpty, tc.Ident.IsEmpty(), "expected %s IsEmpty to be %b", tc.Ident, tc.IsEmpty) } } + +func (ies *identifierExpressionSuite) TestAllOthers() { + ident := NewIdentifierExpression("", "", "a") + rv := NewRangeVal(1, 2) + pattern := "ident like%" + inVals := []interface{}{1, 2} + testCases := []struct { + Ex Expression + Expected Expression + }{ + {Ex: ident.As("a"), Expected: aliased(ident, "a")}, + {Ex: ident.Eq(1), Expected: NewBooleanExpression(EqOp, ident, 1)}, + {Ex: ident.Neq(1), Expected: NewBooleanExpression(NeqOp, ident, 1)}, + {Ex: ident.Gt(1), Expected: NewBooleanExpression(GtOp, ident, 1)}, + {Ex: ident.Gte(1), Expected: NewBooleanExpression(GteOp, ident, 1)}, + {Ex: ident.Lt(1), Expected: NewBooleanExpression(LtOp, ident, 1)}, + {Ex: ident.Lte(1), Expected: NewBooleanExpression(LteOp, ident, 1)}, + {Ex: ident.Asc(), Expected: asc(ident)}, + {Ex: ident.Desc(), Expected: desc(ident)}, + {Ex: ident.Between(rv), Expected: between(ident, rv)}, + {Ex: ident.NotBetween(rv), Expected: notBetween(ident, rv)}, + {Ex: ident.Like(pattern), Expected: NewBooleanExpression(LikeOp, ident, pattern)}, + {Ex: ident.NotLike(pattern), Expected: NewBooleanExpression(NotLikeOp, ident, pattern)}, + {Ex: ident.ILike(pattern), Expected: NewBooleanExpression(ILikeOp, ident, pattern)}, + {Ex: ident.NotILike(pattern), Expected: NewBooleanExpression(NotILikeOp, ident, pattern)}, + {Ex: ident.RegexpLike(pattern), Expected: NewBooleanExpression(RegexpLikeOp, ident, pattern)}, + {Ex: ident.RegexpNotLike(pattern), Expected: NewBooleanExpression(RegexpNotLikeOp, ident, pattern)}, + {Ex: ident.RegexpILike(pattern), Expected: NewBooleanExpression(RegexpILikeOp, ident, pattern)}, + {Ex: ident.RegexpNotILike(pattern), Expected: NewBooleanExpression(RegexpNotILikeOp, ident, pattern)}, + {Ex: ident.In(inVals), Expected: NewBooleanExpression(InOp, ident, inVals)}, + {Ex: ident.NotIn(inVals), Expected: NewBooleanExpression(NotInOp, ident, inVals)}, + {Ex: ident.Is(true), Expected: NewBooleanExpression(IsOp, ident, true)}, + {Ex: ident.IsNot(true), Expected: NewBooleanExpression(IsNotOp, ident, true)}, + {Ex: ident.IsNull(), Expected: NewBooleanExpression(IsOp, ident, nil)}, + {Ex: ident.IsNotNull(), Expected: NewBooleanExpression(IsNotOp, ident, nil)}, + {Ex: ident.IsTrue(), Expected: NewBooleanExpression(IsOp, ident, true)}, + {Ex: ident.IsNotTrue(), Expected: NewBooleanExpression(IsNotOp, ident, true)}, + {Ex: ident.IsFalse(), Expected: NewBooleanExpression(IsOp, ident, false)}, + {Ex: ident.IsNotFalse(), Expected: NewBooleanExpression(IsNotOp, ident, false)}, + {Ex: ident.Distinct(), Expected: NewSQLFunctionExpression("DISTINCT", ident)}, + } + + for _, tc := range testCases { + ies.Equal(tc.Expected, tc.Ex) + } +} diff --git a/exp/literal.go b/exp/literal.go index 68bb9063..ba9e5436 100644 --- a/exp/literal.go +++ b/exp/literal.go @@ -39,29 +39,33 @@ func (l literal) Args() []interface{} { return l.args } -func (l literal) Expression() Expression { return l } -func (l literal) As(val interface{}) AliasedExpression { return aliased(l, val) } -func (l literal) Eq(val interface{}) BooleanExpression { return eq(l, val) } -func (l literal) Neq(val interface{}) BooleanExpression { return neq(l, val) } -func (l literal) Gt(val interface{}) BooleanExpression { return gt(l, val) } -func (l literal) Gte(val interface{}) BooleanExpression { return gte(l, val) } -func (l literal) Lt(val interface{}) BooleanExpression { return lt(l, val) } -func (l literal) Lte(val interface{}) BooleanExpression { return lte(l, val) } -func (l literal) Asc() OrderedExpression { return asc(l) } -func (l literal) Desc() OrderedExpression { return desc(l) } -func (l literal) Between(val RangeVal) RangeExpression { return between(l, val) } -func (l literal) NotBetween(val RangeVal) RangeExpression { return notBetween(l, val) } -func (l literal) Like(val interface{}) BooleanExpression { return like(l, val) } -func (l literal) NotLike(val interface{}) BooleanExpression { return notLike(l, val) } -func (l literal) ILike(val interface{}) BooleanExpression { return iLike(l, val) } -func (l literal) NotILike(val interface{}) BooleanExpression { return notILike(l, val) } -func (l literal) In(vals ...interface{}) BooleanExpression { return in(l, vals...) } -func (l literal) NotIn(vals ...interface{}) BooleanExpression { return notIn(l, vals...) } -func (l literal) Is(val interface{}) BooleanExpression { return is(l, val) } -func (l literal) IsNot(val interface{}) BooleanExpression { return isNot(l, val) } -func (l literal) IsNull() BooleanExpression { return is(l, nil) } -func (l literal) IsNotNull() BooleanExpression { return isNot(l, nil) } -func (l literal) IsTrue() BooleanExpression { return is(l, true) } -func (l literal) IsNotTrue() BooleanExpression { return isNot(l, true) } -func (l literal) IsFalse() BooleanExpression { return is(l, false) } -func (l literal) IsNotFalse() BooleanExpression { return isNot(l, false) } +func (l literal) Expression() Expression { return l } +func (l literal) As(val interface{}) AliasedExpression { return aliased(l, val) } +func (l literal) Eq(val interface{}) BooleanExpression { return eq(l, val) } +func (l literal) Neq(val interface{}) BooleanExpression { return neq(l, val) } +func (l literal) Gt(val interface{}) BooleanExpression { return gt(l, val) } +func (l literal) Gte(val interface{}) BooleanExpression { return gte(l, val) } +func (l literal) Lt(val interface{}) BooleanExpression { return lt(l, val) } +func (l literal) Lte(val interface{}) BooleanExpression { return lte(l, val) } +func (l literal) Asc() OrderedExpression { return asc(l) } +func (l literal) Desc() OrderedExpression { return desc(l) } +func (l literal) Between(val RangeVal) RangeExpression { return between(l, val) } +func (l literal) NotBetween(val RangeVal) RangeExpression { return notBetween(l, val) } +func (l literal) Like(val interface{}) BooleanExpression { return like(l, val) } +func (l literal) NotLike(val interface{}) BooleanExpression { return notLike(l, val) } +func (l literal) ILike(val interface{}) BooleanExpression { return iLike(l, val) } +func (l literal) NotILike(val interface{}) BooleanExpression { return notILike(l, val) } +func (l literal) RegexpLike(val interface{}) BooleanExpression { return regexpLike(l, val) } +func (l literal) RegexpNotLike(val interface{}) BooleanExpression { return regexpNotLike(l, val) } +func (l literal) RegexpILike(val interface{}) BooleanExpression { return regexpILike(l, val) } +func (l literal) RegexpNotILike(val interface{}) BooleanExpression { return regexpNotILike(l, val) } +func (l literal) In(vals ...interface{}) BooleanExpression { return in(l, vals...) } +func (l literal) NotIn(vals ...interface{}) BooleanExpression { return notIn(l, vals...) } +func (l literal) Is(val interface{}) BooleanExpression { return is(l, val) } +func (l literal) IsNot(val interface{}) BooleanExpression { return isNot(l, val) } +func (l literal) IsNull() BooleanExpression { return is(l, nil) } +func (l literal) IsNotNull() BooleanExpression { return isNot(l, nil) } +func (l literal) IsTrue() BooleanExpression { return is(l, true) } +func (l literal) IsNotTrue() BooleanExpression { return isNot(l, true) } +func (l literal) IsFalse() BooleanExpression { return is(l, false) } +func (l literal) IsNotFalse() BooleanExpression { return isNot(l, false) } diff --git a/exp/literal_test.go b/exp/literal_test.go new file mode 100644 index 00000000..f76733fb --- /dev/null +++ b/exp/literal_test.go @@ -0,0 +1,79 @@ +package exp + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type literalExpressionSuite struct { + suite.Suite + le LiteralExpression +} + +func TestLiteralExpressionSuite(t *testing.T) { + suite.Run(t, &literalExpressionSuite{ + le: NewLiteralExpression("? + ?", 1, 2), + }) +} + +func (les *literalExpressionSuite) TestClone() { + les.Equal(les.le, les.le.Clone()) +} + +func (les *literalExpressionSuite) TestExpression() { + les.Equal(les.le, les.le.Expression()) +} + +func (les *literalExpressionSuite) TestLiteral() { + les.Equal("? + ?", les.le.Literal()) +} + +func (les *literalExpressionSuite) TestArgs() { + les.Equal([]interface{}{1, 2}, les.le.Args()) +} + +func (les *literalExpressionSuite) TestAllOthers() { + le := les.le + rv := NewRangeVal(1, 2) + pattern := "literal like%" + inVals := []interface{}{1, 2} + testCases := []struct { + Ex Expression + Expected Expression + }{ + {Ex: le.As("a"), Expected: aliased(le, "a")}, + {Ex: le.Eq(1), Expected: NewBooleanExpression(EqOp, le, 1)}, + {Ex: le.Neq(1), Expected: NewBooleanExpression(NeqOp, le, 1)}, + {Ex: le.Gt(1), Expected: NewBooleanExpression(GtOp, le, 1)}, + {Ex: le.Gte(1), Expected: NewBooleanExpression(GteOp, le, 1)}, + {Ex: le.Lt(1), Expected: NewBooleanExpression(LtOp, le, 1)}, + {Ex: le.Lte(1), Expected: NewBooleanExpression(LteOp, le, 1)}, + {Ex: le.Asc(), Expected: asc(le)}, + {Ex: le.Desc(), Expected: desc(le)}, + {Ex: le.Between(rv), Expected: between(le, rv)}, + {Ex: le.NotBetween(rv), Expected: notBetween(le, rv)}, + {Ex: le.Like(pattern), Expected: NewBooleanExpression(LikeOp, le, pattern)}, + {Ex: le.NotLike(pattern), Expected: NewBooleanExpression(NotLikeOp, le, pattern)}, + {Ex: le.ILike(pattern), Expected: NewBooleanExpression(ILikeOp, le, pattern)}, + {Ex: le.NotILike(pattern), Expected: NewBooleanExpression(NotILikeOp, le, pattern)}, + {Ex: le.RegexpLike(pattern), Expected: NewBooleanExpression(RegexpLikeOp, le, pattern)}, + {Ex: le.RegexpNotLike(pattern), Expected: NewBooleanExpression(RegexpNotLikeOp, le, pattern)}, + {Ex: le.RegexpILike(pattern), Expected: NewBooleanExpression(RegexpILikeOp, le, pattern)}, + {Ex: le.RegexpNotILike(pattern), Expected: NewBooleanExpression(RegexpNotILikeOp, le, pattern)}, + {Ex: le.In(inVals), Expected: NewBooleanExpression(InOp, le, inVals)}, + {Ex: le.NotIn(inVals), Expected: NewBooleanExpression(NotInOp, le, inVals)}, + {Ex: le.Is(true), Expected: NewBooleanExpression(IsOp, le, true)}, + {Ex: le.IsNot(true), Expected: NewBooleanExpression(IsNotOp, le, true)}, + {Ex: le.IsNull(), Expected: NewBooleanExpression(IsOp, le, nil)}, + {Ex: le.IsNotNull(), Expected: NewBooleanExpression(IsNotOp, le, nil)}, + {Ex: le.IsTrue(), Expected: NewBooleanExpression(IsOp, le, true)}, + {Ex: le.IsNotTrue(), Expected: NewBooleanExpression(IsNotOp, le, true)}, + {Ex: le.IsFalse(), Expected: NewBooleanExpression(IsOp, le, false)}, + {Ex: le.IsNotFalse(), Expected: NewBooleanExpression(IsNotOp, le, false)}, + } + + for _, tc := range testCases { + les.Equal(tc.Expected, tc.Ex) + } +} diff --git a/exp/window_func.go b/exp/window_func.go index 8580f957..b9b856b7 100644 --- a/exp/window_func.go +++ b/exp/window_func.go @@ -58,6 +58,18 @@ func (swfe sqlWindowFunctionExpression) ILike(val interface{}) BooleanExpression func (swfe sqlWindowFunctionExpression) NotILike(val interface{}) BooleanExpression { return notILike(swfe, val) } +func (swfe sqlWindowFunctionExpression) RegexpLike(val interface{}) BooleanExpression { + return regexpLike(swfe, val) +} +func (swfe sqlWindowFunctionExpression) RegexpNotLike(val interface{}) BooleanExpression { + return regexpNotLike(swfe, val) +} +func (swfe sqlWindowFunctionExpression) RegexpILike(val interface{}) BooleanExpression { + return regexpILike(swfe, val) +} +func (swfe sqlWindowFunctionExpression) RegexpNotILike(val interface{}) BooleanExpression { + return regexpNotILike(swfe, val) +} func (swfe sqlWindowFunctionExpression) In(vals ...interface{}) BooleanExpression { return in(swfe, vals...) } diff --git a/exp/window_func_test.go b/exp/window_func_test.go index 3ee6f772..ac2c97fc 100644 --- a/exp/window_func_test.go +++ b/exp/window_func_test.go @@ -62,120 +62,43 @@ func (swfet *sqlWindowFunctionExpressionTest) TestWindowName() { func (swfet *sqlWindowFunctionExpressionTest) TestAllOthers() { wf := NewSQLWindowFunctionExpression(swfet.fn, nil, nil) - expAs := wf.As("a") - swfet.Equal(expAs.Aliased(), wf) - - expEq := wf.Eq(1) - swfet.Equal(expEq.LHS(), wf) - swfet.Equal(expEq.Op(), EqOp) - swfet.Equal(expEq.RHS(), 1) - - expNeq := wf.Neq(1) - swfet.Equal(expNeq.LHS(), wf) - swfet.Equal(expNeq.Op(), NeqOp) - swfet.Equal(expNeq.RHS(), 1) - - expGt := wf.Gt(1) - swfet.Equal(expGt.LHS(), wf) - swfet.Equal(expGt.Op(), GtOp) - swfet.Equal(expGt.RHS(), 1) - - expGte := wf.Gte(1) - swfet.Equal(expGte.LHS(), wf) - swfet.Equal(expGte.Op(), GteOp) - swfet.Equal(expGte.RHS(), 1) - - expLt := wf.Lt(1) - swfet.Equal(expLt.LHS(), wf) - swfet.Equal(expLt.Op(), LtOp) - swfet.Equal(expLt.RHS(), 1) - - expLte := wf.Lte(1) - swfet.Equal(expLte.LHS(), wf) - swfet.Equal(expLte.Op(), LteOp) - swfet.Equal(expLte.RHS(), 1) - rv := NewRangeVal(1, 2) - expBetween := wf.Between(rv) - swfet.Equal(expBetween.LHS(), wf) - swfet.Equal(expBetween.Op(), BetweenOp) - swfet.Equal(expBetween.RHS(), rv) - - expNotBetween := wf.NotBetween(rv) - swfet.Equal(expNotBetween.LHS(), wf) - swfet.Equal(expNotBetween.Op(), NotBetweenOp) - swfet.Equal(expNotBetween.RHS(), rv) - pattern := "a%" - expLike := wf.Like(pattern) - swfet.Equal(expLike.LHS(), wf) - swfet.Equal(expLike.Op(), LikeOp) - swfet.Equal(expLike.RHS(), pattern) - - expNotLike := wf.NotLike(pattern) - swfet.Equal(expNotLike.LHS(), wf) - swfet.Equal(expNotLike.Op(), NotLikeOp) - swfet.Equal(expNotLike.RHS(), pattern) - - expILike := wf.ILike(pattern) - swfet.Equal(expILike.LHS(), wf) - swfet.Equal(expILike.Op(), ILikeOp) - swfet.Equal(expILike.RHS(), pattern) - - expNotILike := wf.NotILike(pattern) - swfet.Equal(expNotILike.LHS(), wf) - swfet.Equal(expNotILike.Op(), NotILikeOp) - swfet.Equal(expNotILike.RHS(), pattern) - - vals := []interface{}{1, 2} - expIn := wf.In(vals) - swfet.Equal(expIn.LHS(), wf) - swfet.Equal(expIn.Op(), InOp) - swfet.Equal(expIn.RHS(), vals) - - expNotIn := wf.NotIn(vals) - swfet.Equal(expNotIn.LHS(), wf) - swfet.Equal(expNotIn.Op(), NotInOp) - swfet.Equal(expNotIn.RHS(), vals) - - obj := 1 - expIs := wf.Is(obj) - swfet.Equal(expIs.LHS(), wf) - swfet.Equal(expIs.Op(), IsOp) - swfet.Equal(expIs.RHS(), obj) - - expIsNot := wf.IsNot(obj) - swfet.Equal(expIsNot.LHS(), wf) - swfet.Equal(expIsNot.Op(), IsNotOp) - swfet.Equal(expIsNot.RHS(), obj) - - expIsNull := wf.IsNull() - swfet.Equal(expIsNull.LHS(), wf) - swfet.Equal(expIsNull.Op(), IsOp) - swfet.Nil(expIsNull.RHS()) - - expIsNotNull := wf.IsNotNull() - swfet.Equal(expIsNotNull.LHS(), wf) - swfet.Equal(expIsNotNull.Op(), IsNotOp) - swfet.Nil(expIsNotNull.RHS()) - - expIsTrue := wf.IsTrue() - swfet.Equal(expIsTrue.LHS(), wf) - swfet.Equal(expIsTrue.Op(), IsOp) - swfet.Equal(expIsTrue.RHS(), true) - - expIsNotTrue := wf.IsNotTrue() - swfet.Equal(expIsNotTrue.LHS(), wf) - swfet.Equal(expIsNotTrue.Op(), IsNotOp) - swfet.Equal(expIsNotTrue.RHS(), true) - - expIsFalse := wf.IsFalse() - swfet.Equal(expIsFalse.LHS(), wf) - swfet.Equal(expIsFalse.Op(), IsOp) - swfet.Equal(expIsFalse.RHS(), false) - - expIsNotFalse := wf.IsNotFalse() - swfet.Equal(expIsNotFalse.LHS(), wf) - swfet.Equal(expIsNotFalse.Op(), IsNotOp) - swfet.Equal(expIsNotFalse.RHS(), false) + inVals := []interface{}{1, 2} + testCases := []struct { + Ex Expression + Expected Expression + }{ + {Ex: wf.As("a"), Expected: aliased(wf, "a")}, + {Ex: wf.Eq(1), Expected: NewBooleanExpression(EqOp, wf, 1)}, + {Ex: wf.Neq(1), Expected: NewBooleanExpression(NeqOp, wf, 1)}, + {Ex: wf.Gt(1), Expected: NewBooleanExpression(GtOp, wf, 1)}, + {Ex: wf.Gte(1), Expected: NewBooleanExpression(GteOp, wf, 1)}, + {Ex: wf.Lt(1), Expected: NewBooleanExpression(LtOp, wf, 1)}, + {Ex: wf.Lte(1), Expected: NewBooleanExpression(LteOp, wf, 1)}, + {Ex: wf.Between(rv), Expected: between(wf, rv)}, + {Ex: wf.NotBetween(rv), Expected: notBetween(wf, rv)}, + {Ex: wf.Like(pattern), Expected: NewBooleanExpression(LikeOp, wf, pattern)}, + {Ex: wf.NotLike(pattern), Expected: NewBooleanExpression(NotLikeOp, wf, pattern)}, + {Ex: wf.ILike(pattern), Expected: NewBooleanExpression(ILikeOp, wf, pattern)}, + {Ex: wf.NotILike(pattern), Expected: NewBooleanExpression(NotILikeOp, wf, pattern)}, + {Ex: wf.RegexpLike(pattern), Expected: NewBooleanExpression(RegexpLikeOp, wf, pattern)}, + {Ex: wf.RegexpNotLike(pattern), Expected: NewBooleanExpression(RegexpNotLikeOp, wf, pattern)}, + {Ex: wf.RegexpILike(pattern), Expected: NewBooleanExpression(RegexpILikeOp, wf, pattern)}, + {Ex: wf.RegexpNotILike(pattern), Expected: NewBooleanExpression(RegexpNotILikeOp, wf, pattern)}, + {Ex: wf.In(inVals), Expected: NewBooleanExpression(InOp, wf, inVals)}, + {Ex: wf.NotIn(inVals), Expected: NewBooleanExpression(NotInOp, wf, inVals)}, + {Ex: wf.Is(true), Expected: NewBooleanExpression(IsOp, wf, true)}, + {Ex: wf.IsNot(true), Expected: NewBooleanExpression(IsNotOp, wf, true)}, + {Ex: wf.IsNull(), Expected: NewBooleanExpression(IsOp, wf, nil)}, + {Ex: wf.IsNotNull(), Expected: NewBooleanExpression(IsNotOp, wf, nil)}, + {Ex: wf.IsTrue(), Expected: NewBooleanExpression(IsOp, wf, true)}, + {Ex: wf.IsNotTrue(), Expected: NewBooleanExpression(IsNotOp, wf, true)}, + {Ex: wf.IsFalse(), Expected: NewBooleanExpression(IsOp, wf, false)}, + {Ex: wf.IsNotFalse(), Expected: NewBooleanExpression(IsNotOp, wf, false)}, + } + + for _, tc := range testCases { + swfet.Equal(tc.Expected, tc.Ex) + } } diff --git a/sqlgen/expression_sql_generator_test.go b/sqlgen/expression_sql_generator_test.go index f6cc9bea..8a507b25 100644 --- a/sqlgen/expression_sql_generator_test.go +++ b/sqlgen/expression_sql_generator_test.go @@ -607,13 +607,13 @@ func (esgs *expressionSQLGeneratorSuite) TestGenerate_BooleanExpression() { expressionTestCase{val: ident.In([]int64{1, 2, 3}), err: "goqu: boolean operator 'in' not supported"}, expressionTestCase{val: ident.NotIn([]int64{1, 2, 3}), err: "goqu: boolean operator 'notin' not supported"}, expressionTestCase{val: ident.Like("a%"), err: "goqu: boolean operator 'like' not supported"}, - expressionTestCase{val: ident.Like(re), err: "goqu: boolean operator 'regexp like' not supported"}, + expressionTestCase{val: ident.Like(re), err: "goqu: boolean operator 'regexplike' not supported"}, expressionTestCase{val: ident.ILike("a%"), err: "goqu: boolean operator 'ilike' not supported"}, - expressionTestCase{val: ident.ILike(re), err: "goqu: boolean operator 'regexp ilike' not supported"}, + expressionTestCase{val: ident.ILike(re), err: "goqu: boolean operator 'regexpilike' not supported"}, expressionTestCase{val: ident.NotLike("a%"), err: "goqu: boolean operator 'notlike' not supported"}, - expressionTestCase{val: ident.NotLike(re), err: "goqu: boolean operator 'regexp notlike' not supported"}, + expressionTestCase{val: ident.NotLike(re), err: "goqu: boolean operator 'regexpnotlike' not supported"}, expressionTestCase{val: ident.NotILike("a%"), err: "goqu: boolean operator 'notilike' not supported"}, - expressionTestCase{val: ident.NotILike(re), err: "goqu: boolean operator 'regexp notilike' not supported"}, + expressionTestCase{val: ident.NotILike(re), err: "goqu: boolean operator 'regexpnotilike' not supported"}, ) } @@ -1128,12 +1128,12 @@ func (esgs *expressionSQLGeneratorSuite) TestGenerate_ExpressionMap() { expressionTestCase{ val: exp.Ex{"a": exp.Op{"badOp": true}}, - err: "goqu: unsupported expression type map[badOp:%!s(bool=true)]", + err: "goqu: unsupported expression type badOp", }, expressionTestCase{ val: exp.Ex{"a": exp.Op{"badOp": true}}, isPrepared: true, - err: "goqu: unsupported expression type map[badOp:%!s(bool=true)]", + err: "goqu: unsupported expression type badOp", }, expressionTestCase{val: exp.Ex{"a": 1}, sql: `("a" = 1)`}, @@ -1248,6 +1248,37 @@ func (esgs *expressionSQLGeneratorSuite) TestGenerate_ExpressionMap() { args: []interface{}{"(a|b)"}, }, + expressionTestCase{val: exp.Ex{"a": exp.Op{"regexpLike": "(a|b)"}}, sql: `("a" ~ '(a|b)')`}, + expressionTestCase{ + val: exp.Ex{"a": exp.Op{"regexpLike": "(a|b)"}}, + sql: `("a" ~ ?)`, + isPrepared: true, + args: []interface{}{"(a|b)"}, + }, + + expressionTestCase{val: exp.Ex{"a": exp.Op{"regexpNotLike": "(a|b)"}}, sql: `("a" !~ '(a|b)')`}, + expressionTestCase{ + val: exp.Ex{"a": exp.Op{"regexpNotLike": "(a|b)"}}, + sql: `("a" !~ ?)`, + isPrepared: true, + args: []interface{}{"(a|b)"}, + }, + + expressionTestCase{val: exp.Ex{"a": exp.Op{"regexpILike": "(a|b)"}}, sql: `("a" ~* '(a|b)')`}, + expressionTestCase{ + val: exp.Ex{"a": exp.Op{"regexpILike": "(a|b)"}}, + sql: `("a" ~* ?)`, + isPrepared: true, + args: []interface{}{"(a|b)"}, + }, + expressionTestCase{val: exp.Ex{"a": exp.Op{"regexpNotILike": "(a|b)"}}, sql: `("a" !~* '(a|b)')`}, + expressionTestCase{ + val: exp.Ex{"a": exp.Op{"regexpNotILike": "(a|b)"}}, + sql: `("a" !~* ?)`, + isPrepared: true, + args: []interface{}{"(a|b)"}, + }, + expressionTestCase{val: exp.Ex{"a": exp.Op{"in": []string{"a", "b", "c"}}}, sql: `("a" IN ('a', 'b', 'c'))`}, expressionTestCase{ val: exp.Ex{"a": exp.Op{"in": []string{"a", "b", "c"}}}, @@ -1308,14 +1339,45 @@ func (esgs *expressionSQLGeneratorSuite) TestGenerate_ExpressionOrMap() { expressionTestCase{val: exp.ExOr{}}, expressionTestCase{val: exp.ExOr{}, isPrepared: true}, + expressionTestCase{val: exp.ExOr{"a": exp.Op{"regexpLike": "(a|b)"}}, sql: `("a" ~ '(a|b)')`}, + expressionTestCase{ + val: exp.ExOr{"a": exp.Op{"regexpLike": "(a|b)"}}, + sql: `("a" ~ ?)`, + isPrepared: true, + args: []interface{}{"(a|b)"}, + }, + + expressionTestCase{val: exp.ExOr{"a": exp.Op{"regexpNotLike": "(a|b)"}}, sql: `("a" !~ '(a|b)')`}, + expressionTestCase{ + val: exp.ExOr{"a": exp.Op{"regexpNotLike": "(a|b)"}}, + sql: `("a" !~ ?)`, + isPrepared: true, + args: []interface{}{"(a|b)"}, + }, + + expressionTestCase{val: exp.ExOr{"a": exp.Op{"regexpILike": "(a|b)"}}, sql: `("a" ~* '(a|b)')`}, + expressionTestCase{ + val: exp.ExOr{"a": exp.Op{"regexpILike": "(a|b)"}}, + sql: `("a" ~* ?)`, + isPrepared: true, + args: []interface{}{"(a|b)"}, + }, + expressionTestCase{val: exp.ExOr{"a": exp.Op{"regexpNotILike": "(a|b)"}}, sql: `("a" !~* '(a|b)')`}, + expressionTestCase{ + val: exp.ExOr{"a": exp.Op{"regexpNotILike": "(a|b)"}}, + sql: `("a" !~* ?)`, + isPrepared: true, + args: []interface{}{"(a|b)"}, + }, + expressionTestCase{ val: exp.ExOr{"a": exp.Op{"badOp": true}}, - err: "goqu: unsupported expression type map[badOp:%!s(bool=true)]", + err: "goqu: unsupported expression type badOp", }, expressionTestCase{ val: exp.ExOr{"a": exp.Op{"badOp": true}}, isPrepared: true, - err: "goqu: unsupported expression type map[badOp:%!s(bool=true)]", + err: "goqu: unsupported expression type badOp", }, expressionTestCase{val: exp.ExOr{"a": 1, "b": true}, sql: `(("a" = 1) OR ("b" IS TRUE))`},