From a16dd5184fa586778c25e64a46bd15a61e526d46 Mon Sep 17 00:00:00 2001 From: Ryan Fan Date: Tue, 28 Nov 2017 07:24:40 +0000 Subject: [PATCH 1/3] tpl: Add contains operator in where tpl function Add "contains" operator in "where" tpl function to compare whether the given field value contains match value or not, in fact which does the similar logic as "in" operator but exchanged the comparsion objects. This is useful when filter pages collection under specific subdir. See #4131 --- docs/content/functions/where.md | 3 +++ tpl/collections/where.go | 20 ++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/content/functions/where.md b/docs/content/functions/where.md index 262fd6ebc52..3aa41f3bd9b 100644 --- a/docs/content/functions/where.md +++ b/docs/content/functions/where.md @@ -76,6 +76,9 @@ The following logical operators are vailable with `where`: `not in` : `true` if a given field value isn't included in a matching value; a matching value must be an array or a slice +`contains` +: `true` if a given field value contains a matching value; a matching value must be an array or a slice + `intersect` : `true` if a given field value that is a slice/array of strings or integers contains elements in common with the matching value; it follows the same rules as the [`intersect` function][intersect]. diff --git a/tpl/collections/where.go b/tpl/collections/where.go index be5c8205b11..f3ce4e221f5 100644 --- a/tpl/collections/where.go +++ b/tpl/collections/where.go @@ -188,15 +188,27 @@ func (ns *Namespace) checkCondition(v, mv reflect.Value, op string) (bool, error } else if svp != nil && smvp != nil { return *svp < *smvp, nil } - case "in", "not in": + case "in", "not in", "contains": var r bool if ivp != nil && len(ima) > 0 { - r = ns.In(ima, *ivp) + if op == "contains" { + r = ns.In(*ivp, ima) + } else { + r = ns.In(ima, *ivp) + } } else if svp != nil { if len(sma) > 0 { - r = ns.In(sma, *svp) + if op == "contains" { + r = ns.In(*svp, sma) + } else { + r = ns.In(sma, *svp) + } } else if smvp != nil { - r = ns.In(*smvp, *svp) + if op == "contains" { + r = ns.In(*svp, *smvp) + } else { + r = ns.In(*smvp, *svp) + } } } else { return false, nil From b28952d85f78226681a9ea5c44e6f16791273706 Mon Sep 17 00:00:00 2001 From: Ryan Fan Date: Thu, 30 Nov 2017 06:39:22 +0000 Subject: [PATCH 2/3] rebuld the contains logic to support more types --- docs/content/functions/where.md | 2 +- tpl/collections/where.go | 123 +++++++++++++++++++++++++++----- tpl/collections/where_test.go | 80 +++++++++++++++++++++ 3 files changed, 186 insertions(+), 19 deletions(-) diff --git a/docs/content/functions/where.md b/docs/content/functions/where.md index 3aa41f3bd9b..5257c52968b 100644 --- a/docs/content/functions/where.md +++ b/docs/content/functions/where.md @@ -77,7 +77,7 @@ The following logical operators are vailable with `where`: : `true` if a given field value isn't included in a matching value; a matching value must be an array or a slice `contains` -: `true` if a given field value contains a matching value; a matching value must be an array or a slice +: `true` if a given field value contains a matching value; the given field value must be a string or an array or a slice `intersect` : `true` if a given field value that is a slice/array of strings or integers contains elements in common with the matching value; it follows the same rules as the [`intersect` function][intersect]. diff --git a/tpl/collections/where.go b/tpl/collections/where.go index f3ce4e221f5..cd19b085685 100644 --- a/tpl/collections/where.go +++ b/tpl/collections/where.go @@ -81,8 +81,107 @@ func (ns *Namespace) checkCondition(v, mv reflect.Value, op string) (bool, error var ivp, imvp *int64 var svp, smvp *string var slv, slmv interface{} - var ima []int64 - var sma []string + var iva, ima []int64 + var sva, sma []string + + if op == "contains" { + switch v.Kind() { + case reflect.String: + sv := v.String() + svp = &sv + case reflect.Array, reflect.Slice: + slv = v.Interface() + default: + return false, errors.New("non supported given field value") + } + + if v.Len() == 0 { + return false, nil + } + + if v.Type() != mv.Type() { + if mv.Kind() != reflect.Interface && v.Type().Elem().Kind() != reflect.Interface && v.Type().Elem() != mv.Type() && mv.Kind() != reflect.Array && mv.Kind() != reflect.Slice { + return false, nil + } + } + + switch mv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + imv := mv.Int() + imvp = &imv + for i := 0; i < v.Len(); i++ { + if anInt, err := toInt(v.Index(i)); err == nil { + iva = append(iva, anInt) + } + } + case reflect.String: + smv := mv.String() + smvp = &smv + for i := 0; i < v.Len(); i++ { + if aString, err := toString(v.Index(i)); err == nil { + sva = append(sva, aString) + } + } + case reflect.Struct: + switch mv.Type() { + case timeType: + imv := toTimeUnix(mv) + imvp = &imv + for i := 0; i < v.Len(); i++ { + iva = append(iva, toTimeUnix(v.Index(i))) + } + } + case reflect.Array, reflect.Slice: + slmv = mv.Interface() + } + + // to support timeType, if mv is slice or array, + // need convert child item to int + if slv != nil && slmv != nil { + if v.Type().Elem() == timeType && mv.Type().Elem() == timeType { + for i := 0; i < mv.Len(); i++ { + ima = append(ima, toTimeUnix(mv.Index(i))) + } + + for i := 0; i < v.Len(); i++ { + iva = append(iva, toTimeUnix(v.Index(i))) + } + } + } + + var r bool + if imvp != nil && len(iva) > 0 { + r = ns.In(iva, *imvp) + } else if smvp != nil { + if len(sva) > 0 { + r = ns.In(sva, *smvp) + } else if svp != nil { + fmt.Println("check string", *svp, *smvp) + r = ns.In(*svp, *smvp) + } + } else if slmv != nil && slv != nil { + // timeType support in slice or array + if len(iva) > 0 { + for i := 0; i < len(ima); i++ { + if r = ns.In(iva, ima[i]); !r { + return false, nil + } + } + return true, nil + } + + for i := 0; i < mv.Len(); i++ { + if r = ns.In(slv, mv.Index(i).Interface()); !r { + return false, nil + } + } + } else { + return false, nil + } + + return r, nil + } + if mv.Type() == v.Type() { switch v.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -188,27 +287,15 @@ func (ns *Namespace) checkCondition(v, mv reflect.Value, op string) (bool, error } else if svp != nil && smvp != nil { return *svp < *smvp, nil } - case "in", "not in", "contains": + case "in", "not in": var r bool if ivp != nil && len(ima) > 0 { - if op == "contains" { - r = ns.In(*ivp, ima) - } else { - r = ns.In(ima, *ivp) - } + r = ns.In(ima, *ivp) } else if svp != nil { if len(sma) > 0 { - if op == "contains" { - r = ns.In(*svp, sma) - } else { - r = ns.In(sma, *svp) - } + r = ns.In(sma, *svp) } else if smvp != nil { - if op == "contains" { - r = ns.In(*svp, *smvp) - } else { - r = ns.In(*smvp, *svp) - } + r = ns.In(*smvp, *svp) } } else { return false, nil diff --git a/tpl/collections/where_test.go b/tpl/collections/where_test.go index e702837782f..ef447b5de39 100644 --- a/tpl/collections/where_test.go +++ b/tpl/collections/where_test.go @@ -207,6 +207,15 @@ func TestWhere(t *testing.T) { {"a": []string{"A", "B", "C"}, "b": []string{"D", "E", "F"}}, {"a": []string{"M", "N", "O"}, "b": []string{"P", "Q", "R"}}, }, }, + { + seq: []map[string][]string{ + {"a": []string{"A", "B", "C"}, "b": []string{"A1", "B1", "C1"}}, {"a": []string{"D", "E", "F"}, "b": []string{"A2", "B2", "C2"}}, {"a": []string{"G", "H", "I"}, "b": []string{"A2", "B3", "C2"}}, + }, + key: "b", op: "contains", match: []string{"A2", "C2"}, + expect: []map[string][]string{ + {"a": []string{"D", "E", "F"}, "b": []string{"A2", "B2", "C2"}}, {"a": []string{"G", "H", "I"}, "b": []string{"A2", "B3", "C2"}}, + }, + }, { seq: []map[string][]int{ {"a": []int{1, 2, 3}, "b": []int{4, 5, 6}}, {"a": []int{7, 8, 9}, "b": []int{10, 11, 12}}, {"a": []int{13, 14, 15}, "b": []int{16, 17, 18}}, @@ -216,6 +225,15 @@ func TestWhere(t *testing.T) { {"a": []int{1, 2, 3}, "b": []int{4, 5, 6}}, {"a": []int{7, 8, 9}, "b": []int{10, 11, 12}}, }, }, + { + seq: []map[string][]int{ + {"a": []int{1, 2, 3}, "b": []int{10, 20, 30}}, {"a": []int{4, 5, 6}, "b": []int{40, 50, 60}}, {"a": []int{7, 8, 9}, "b": []int{10, 80, 30}}, + }, + key: "b", op: "contains", match: []int{10, 30}, + expect: []map[string][]int{ + {"a": []int{1, 2, 3}, "b": []int{10, 20, 30}}, {"a": []int{7, 8, 9}, "b": []int{10, 80, 30}}, + }, + }, { seq: []map[string][]int8{ {"a": []int8{1, 2, 3}, "b": []int8{4, 5, 6}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 11, 12}}, {"a": []int8{13, 14, 15}, "b": []int8{16, 17, 18}}, @@ -225,6 +243,15 @@ func TestWhere(t *testing.T) { {"a": []int8{1, 2, 3}, "b": []int8{4, 5, 6}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 11, 12}}, }, }, + { + seq: []map[string][]int8{ + {"a": []int8{1, 2, 3}, "b": []int8{10, 20, 30}}, {"a": []int8{4, 5, 6}, "b": []int8{40, 50, 60}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 80, 30}}, + }, + key: "b", op: "contains", match: []int8{10, 30}, + expect: []map[string][]int8{ + {"a": []int8{1, 2, 3}, "b": []int8{10, 20, 30}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 80, 30}}, + }, + }, { seq: []map[string][]int16{ {"a": []int16{1, 2, 3}, "b": []int16{4, 5, 6}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 11, 12}}, {"a": []int16{13, 14, 15}, "b": []int16{16, 17, 18}}, @@ -234,6 +261,15 @@ func TestWhere(t *testing.T) { {"a": []int16{1, 2, 3}, "b": []int16{4, 5, 6}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 11, 12}}, }, }, + { + seq: []map[string][]int16{ + {"a": []int16{1, 2, 3}, "b": []int16{10, 20, 30}}, {"a": []int16{4, 5, 6}, "b": []int16{40, 50, 60}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 80, 30}}, + }, + key: "b", op: "contains", match: []int16{10, 30}, + expect: []map[string][]int16{ + {"a": []int16{1, 2, 3}, "b": []int16{10, 20, 30}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 80, 30}}, + }, + }, { seq: []map[string][]int32{ {"a": []int32{1, 2, 3}, "b": []int32{4, 5, 6}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 11, 12}}, {"a": []int32{13, 14, 15}, "b": []int32{16, 17, 18}}, @@ -243,6 +279,15 @@ func TestWhere(t *testing.T) { {"a": []int32{1, 2, 3}, "b": []int32{4, 5, 6}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 11, 12}}, }, }, + { + seq: []map[string][]int32{ + {"a": []int32{1, 2, 3}, "b": []int32{10, 20, 30}}, {"a": []int32{4, 5, 6}, "b": []int32{40, 50, 60}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 80, 30}}, + }, + key: "b", op: "contains", match: []int32{10, 30}, + expect: []map[string][]int32{ + {"a": []int32{1, 2, 3}, "b": []int32{10, 20, 30}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 80, 30}}, + }, + }, { seq: []map[string][]int64{ {"a": []int64{1, 2, 3}, "b": []int64{4, 5, 6}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 11, 12}}, {"a": []int64{13, 14, 15}, "b": []int64{16, 17, 18}}, @@ -252,6 +297,24 @@ func TestWhere(t *testing.T) { {"a": []int64{1, 2, 3}, "b": []int64{4, 5, 6}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 11, 12}}, }, }, + { + seq: []map[string][]int64{ + {"a": []int64{1, 2, 3}, "b": []int64{10, 20, 30}}, {"a": []int64{4, 5, 6}, "b": []int64{40, 50, 60}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 80, 30}}, + }, + key: "b", op: "contains", match: []int64{10, 30}, + expect: []map[string][]int64{ + {"a": []int64{1, 2, 3}, "b": []int64{10, 20, 30}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 80, 30}}, + }, + }, + { + seq: []map[string][]time.Time{ + {"a": []time.Time{d1, d2}, "b": []time.Time{d1, d2, d3}}, {"a": []time.Time{d3, d4}, "b": []time.Time{d2, d3, d4}}, {"a": []time.Time{d5, d6}, "b": []time.Time{d3, d4, d5}}, + }, + key: "b", op: "contains", match: []time.Time{d3, d4}, + expect: []map[string][]time.Time{ + {"a": []time.Time{d3, d4}, "b": []time.Time{d2, d3, d4}}, {"a": []time.Time{d5, d6}, "b": []time.Time{d3, d4, d5}}, + }, + }, { seq: []map[string][]float32{ {"a": []float32{1.0, 2.0, 3.0}, "b": []float32{4.0, 5.0, 6.0}}, {"a": []float32{7.0, 8.0, 9.0}, "b": []float32{10.0, 11.0, 12.0}}, {"a": []float32{13.0, 14.0, 15.0}, "b": []float32{16.0, 17.0, 18.0}}, @@ -499,7 +562,9 @@ func TestCheckCondition(t *testing.T) { expect{true, false}, }, {reflect.ValueOf(123), reflect.ValueOf([]int{123, 45, 678}), "in", expect{true, false}}, + {reflect.ValueOf([]int{123, 45, 678}), reflect.ValueOf(123), "contains", expect{true, false}}, {reflect.ValueOf("foo"), reflect.ValueOf([]string{"foo", "bar", "baz"}), "in", expect{true, false}}, + {reflect.ValueOf([]string{"foo", "bar", "baz"}), reflect.ValueOf("foo"), "contains", expect{true, false}}, { reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)), reflect.ValueOf([]time.Time{ @@ -510,6 +575,16 @@ func TestCheckCondition(t *testing.T) { "in", expect{true, false}, }, + { + reflect.ValueOf([]time.Time{ + time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC), + time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC), + time.Date(2015, time.June, 26, 19, 18, 56, 12345, time.UTC), + }), + reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)), + "contains", + expect{true, false}, + }, {reflect.ValueOf(123), reflect.ValueOf([]int{45, 678}), "not in", expect{true, false}}, {reflect.ValueOf("foo"), reflect.ValueOf([]string{"bar", "baz"}), "not in", expect{true, false}}, { @@ -523,6 +598,7 @@ func TestCheckCondition(t *testing.T) { expect{true, false}, }, {reflect.ValueOf("foo"), reflect.ValueOf("bar-foo-baz"), "in", expect{true, false}}, + {reflect.ValueOf("bar-foo-baz"), reflect.ValueOf("foo"), "contains", expect{true, false}}, {reflect.ValueOf("foo"), reflect.ValueOf("bar--baz"), "not in", expect{true, false}}, {reflect.Value{}, reflect.ValueOf("foo"), "", expect{false, false}}, {reflect.ValueOf("foo"), reflect.Value{}, "", expect{false, false}}, @@ -535,12 +611,16 @@ func TestCheckCondition(t *testing.T) { {reflect.ValueOf((*TstX)(nil)), reflect.ValueOf((*TstX)(nil)), ">", expect{false, false}}, {reflect.ValueOf(true), reflect.ValueOf(false), ">", expect{false, false}}, {reflect.ValueOf(123), reflect.ValueOf([]int{}), "in", expect{false, false}}, + {reflect.ValueOf([]int{}), reflect.ValueOf(123), "contains", expect{false, false}}, {reflect.ValueOf(123), reflect.ValueOf(123), "op", expect{false, true}}, // Issue #3718 {reflect.ValueOf([]interface{}{"a"}), reflect.ValueOf([]string{"a", "b"}), "intersect", expect{true, false}}, + {reflect.ValueOf([]string{"a", "b"}), reflect.ValueOf([]interface{}{"a"}), "contains", expect{true, false}}, {reflect.ValueOf([]string{"a"}), reflect.ValueOf([]interface{}{"a", "b"}), "intersect", expect{true, false}}, + {reflect.ValueOf([]interface{}{"a", "b"}), reflect.ValueOf([]string{"a"}), "contains", expect{true, false}}, {reflect.ValueOf([]interface{}{1, 2}), reflect.ValueOf([]int{1}), "intersect", expect{true, false}}, + {reflect.ValueOf([]interface{}{1, 2}), reflect.ValueOf([]int{1}), "contains", expect{true, false}}, {reflect.ValueOf([]int{1}), reflect.ValueOf([]interface{}{1, 2}), "intersect", expect{true, false}}, } { result, err := ns.checkCondition(test.value, test.match, test.op) From 5f5b455f383987dba246d3b7b1c9a7d4a31cc312 Mon Sep 17 00:00:00 2001 From: Ryan Fan Date: Thu, 30 Nov 2017 08:07:55 +0000 Subject: [PATCH 3/3] remove debug message --- tpl/collections/where.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tpl/collections/where.go b/tpl/collections/where.go index cd19b085685..87a30bf2bd1 100644 --- a/tpl/collections/where.go +++ b/tpl/collections/where.go @@ -156,7 +156,6 @@ func (ns *Namespace) checkCondition(v, mv reflect.Value, op string) (bool, error if len(sva) > 0 { r = ns.In(sva, *smvp) } else if svp != nil { - fmt.Println("check string", *svp, *smvp) r = ns.In(*svp, *smvp) } } else if slmv != nil && slv != nil {