Skip to content

Commit a7c64f4

Browse files
Add new filters type in the query of the state component (dapr#3218)
Signed-off-by: Luigi Rende <[email protected]> Co-authored-by: Bernd Verst <[email protected]>
1 parent 87aea87 commit a7c64f4

File tree

17 files changed

+885
-1
lines changed

17 files changed

+885
-1
lines changed

common/component/postgresql/postgresql_query.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,46 @@ func (q *Query) VisitEQ(f *query.EQ) (string, error) {
4040
return q.whereFieldEqual(f.Key, f.Val), nil
4141
}
4242

43+
func (q *Query) VisitNEQ(f *query.NEQ) (string, error) {
44+
return q.whereFieldNotEqual(f.Key, f.Val), nil
45+
}
46+
47+
func (q *Query) VisitGT(f *query.GT) (string, error) {
48+
switch v := f.Val.(type) {
49+
case string:
50+
return "", fmt.Errorf("unsupported type of value %s; string type not permitted", v)
51+
default:
52+
return q.whereFieldGreaterThan(f.Key, v), nil
53+
}
54+
}
55+
56+
func (q *Query) VisitGTE(f *query.GTE) (string, error) {
57+
switch v := f.Val.(type) {
58+
case string:
59+
return "", fmt.Errorf("unsupported type of value %s; string type not permitted", v)
60+
default:
61+
return q.whereFieldGreaterThanEqual(f.Key, v), nil
62+
}
63+
}
64+
65+
func (q *Query) VisitLT(f *query.LT) (string, error) {
66+
switch v := f.Val.(type) {
67+
case string:
68+
return "", fmt.Errorf("unsupported type of value %s; string type not permitted", v)
69+
default:
70+
return q.whereFieldLessThan(f.Key, v), nil
71+
}
72+
}
73+
74+
func (q *Query) VisitLTE(f *query.LTE) (string, error) {
75+
switch v := f.Val.(type) {
76+
case string:
77+
return "", fmt.Errorf("unsupported type of value %s; string type not permitted", v)
78+
default:
79+
return q.whereFieldLessThanEqual(f.Key, v), nil
80+
}
81+
}
82+
4383
func (q *Query) VisitIN(f *query.IN) (string, error) {
4484
if len(f.Vals) == 0 {
4585
return "", fmt.Errorf("empty IN operator for key %q", f.Key)
@@ -70,6 +110,31 @@ func (q *Query) visitFilters(op string, filters []query.Filter) (string, error)
70110
return "", err
71111
}
72112
arr = append(arr, str)
113+
case *query.NEQ:
114+
if str, err = q.VisitNEQ(f); err != nil {
115+
return "", err
116+
}
117+
arr = append(arr, str)
118+
case *query.GT:
119+
if str, err = q.VisitGT(f); err != nil {
120+
return "", err
121+
}
122+
arr = append(arr, str)
123+
case *query.GTE:
124+
if str, err = q.VisitGTE(f); err != nil {
125+
return "", err
126+
}
127+
arr = append(arr, str)
128+
case *query.LT:
129+
if str, err = q.VisitLT(f); err != nil {
130+
return "", err
131+
}
132+
arr = append(arr, str)
133+
case *query.LTE:
134+
if str, err = q.VisitLTE(f); err != nil {
135+
return "", err
136+
}
137+
arr = append(arr, str)
73138
case *query.IN:
74139
if str, err = q.VisitIN(f); err != nil {
75140
return "", err
@@ -214,3 +279,38 @@ func (q *Query) whereFieldEqual(key string, value interface{}) string {
214279
query := filterField + "=$" + strconv.Itoa(position)
215280
return query
216281
}
282+
283+
func (q *Query) whereFieldNotEqual(key string, value interface{}) string {
284+
position := q.addParamValueAndReturnPosition(value)
285+
filterField := translateFieldToFilter(key)
286+
query := filterField + "!=$" + strconv.Itoa(position)
287+
return query
288+
}
289+
290+
func (q *Query) whereFieldGreaterThan(key string, value interface{}) string {
291+
position := q.addParamValueAndReturnPosition(value)
292+
filterField := translateFieldToFilter(key)
293+
query := filterField + ">$" + strconv.Itoa(position)
294+
return query
295+
}
296+
297+
func (q *Query) whereFieldGreaterThanEqual(key string, value interface{}) string {
298+
position := q.addParamValueAndReturnPosition(value)
299+
filterField := translateFieldToFilter(key)
300+
query := filterField + ">=$" + strconv.Itoa(position)
301+
return query
302+
}
303+
304+
func (q *Query) whereFieldLessThan(key string, value interface{}) string {
305+
position := q.addParamValueAndReturnPosition(value)
306+
filterField := translateFieldToFilter(key)
307+
query := filterField + "<$" + strconv.Itoa(position)
308+
return query
309+
}
310+
311+
func (q *Query) whereFieldLessThanEqual(key string, value interface{}) string {
312+
position := q.addParamValueAndReturnPosition(value)
313+
filterField := translateFieldToFilter(key)
314+
query := filterField + "<=$" + strconv.Itoa(position)
315+
return query
316+
}

common/component/postgresql/postgresql_query_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,18 @@ func TestPostgresqlQueryBuildQuery(t *testing.T) {
4949
input: "../../../tests/state/query/q4.json",
5050
query: "SELECT key, value, xmin as etag FROM state WHERE (value->'person'->>'org'=$1 OR (value->'person'->>'org'=$2 AND (value->>'state'=$3 OR value->>'state'=$4))) ORDER BY value->>'state' DESC, value->'person'->>'name' LIMIT 2",
5151
},
52+
{
53+
input: "../../../tests/state/query/q4-notequal.json",
54+
query: "SELECT key, value, xmin as etag FROM state WHERE (value->'person'->>'org'=$1 OR (value->'person'->>'org'!=$2 AND (value->>'state'=$3 OR value->>'state'=$4))) ORDER BY value->>'state' DESC, value->'person'->>'name' LIMIT 2",
55+
},
5256
{
5357
input: "../../../tests/state/query/q5.json",
5458
query: "SELECT key, value, xmin as etag FROM state WHERE (value->'person'->>'org'=$1 AND (value->'person'->>'name'=$2 OR (value->>'state'=$3 OR value->>'state'=$4))) ORDER BY value->>'state' DESC, value->'person'->>'name' LIMIT 2",
5559
},
60+
{
61+
input: "../../../tests/state/query/q8.json",
62+
query: "SELECT key, value, xmin as etag FROM state WHERE (value->'person'->>'org'>=$1 OR (value->'person'->>'org'<$2 AND (value->>'state'=$3 OR value->>'state'=$4))) ORDER BY value->>'state' DESC, value->'person'->>'name' LIMIT 2",
63+
},
5664
}
5765
for _, test := range tests {
5866
data, err := os.ReadFile(test.input)

state/README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,31 @@ type FilterEQ struct {
8080
Val interface{}
8181
}
8282

83+
type FilterNEQ struct {
84+
Key string
85+
Val interface{}
86+
}
87+
88+
type FilterGT struct {
89+
Key string
90+
Val interface{}
91+
}
92+
93+
type FilterGTE struct {
94+
Key string
95+
Val interface{}
96+
}
97+
98+
type FilterLT struct {
99+
Key string
100+
Val interface{}
101+
}
102+
103+
type FilterLTE struct {
104+
Key string
105+
Val interface{}
106+
}
107+
83108
type FilterIN struct {
84109
Key string
85110
Vals []interface{}
@@ -100,6 +125,16 @@ To simplify the process of query translation, we leveraged [visitor design patte
100125
type Visitor interface {
101126
// returns "equal" expression
102127
VisitEQ(*FilterEQ) (string, error)
128+
// returns "not equal" expression
129+
VisitNEQ(*FilterNEQ) (string, error)
130+
// returns "greater than" expression
131+
VisitGT(*FilterGT) (string, error)
132+
// returns "greater than equal" expression
133+
VisitGTE(*FilterGTE) (string, error)
134+
// returns "less than" expression
135+
VisitLT(*FilterLT) (string, error)
136+
// returns "less than equal" expression
137+
VisitLTE(*FilterLTE) (string, error)
103138
// returns "in" expression
104139
VisitIN(*FilterIN) (string, error)
105140
// returns "and" expression
@@ -152,4 +187,4 @@ func (m *MyComponent) Query(req *state.QueryRequest) (*state.QueryResponse, erro
152187
}
153188
```
154189

155-
Some of the examples of State Query API implementation are [MongoDB](./mongodb/mongodb_query.go) and [CosmosDB](./azure/cosmosdb/cosmosdb_query.go) state store components.
190+
Some of the examples of State Query API implementation are [Redis](./redis/redis_query.go), [MongoDB](./mongodb/mongodb_query.go) and [CosmosDB](./azure/cosmosdb/cosmosdb_query.go) state store components.

state/azure/cosmosdb/cosmosdb_query.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,76 @@ func (q *Query) VisitEQ(f *query.EQ) (string, error) {
5050
return replaceKeywords("c.value."+f.Key) + " = " + name, nil
5151
}
5252

53+
func (q *Query) VisitNEQ(f *query.NEQ) (string, error) {
54+
// <key> != <val>
55+
val, ok := f.Val.(string)
56+
if !ok {
57+
return "", fmt.Errorf("unsupported type of value %#v; expected string", f.Val)
58+
}
59+
name := q.setNextParameter(val)
60+
61+
return replaceKeywords("c.value."+f.Key) + " != " + name, nil
62+
}
63+
64+
func (q *Query) VisitGT(f *query.GT) (string, error) {
65+
// <key> > <val>
66+
var name string
67+
switch value := f.Val.(type) {
68+
case int:
69+
name = q.setNextParameterInt(value)
70+
case float64:
71+
name = q.setNextParameterFloat(value)
72+
default:
73+
return "", fmt.Errorf("unsupported type of value %#v; expected number", f.Val)
74+
}
75+
return replaceKeywords("c.value."+f.Key) + " > " + name, nil
76+
}
77+
78+
func (q *Query) VisitGTE(f *query.GTE) (string, error) {
79+
// <key> >= <val>
80+
var name string
81+
switch value := f.Val.(type) {
82+
case int:
83+
name = q.setNextParameterInt(value)
84+
case float64:
85+
name = q.setNextParameterFloat(value)
86+
default:
87+
return "", fmt.Errorf("unsupported type of value %#v; expected number", f.Val)
88+
}
89+
90+
return replaceKeywords("c.value."+f.Key) + " >= " + name, nil
91+
}
92+
93+
func (q *Query) VisitLT(f *query.LT) (string, error) {
94+
// <key> < <val>
95+
var name string
96+
switch value := f.Val.(type) {
97+
case int:
98+
name = q.setNextParameterInt(value)
99+
case float64:
100+
name = q.setNextParameterFloat(value)
101+
default:
102+
return "", fmt.Errorf("unsupported type of value %#v; expected number", f.Val)
103+
}
104+
105+
return replaceKeywords("c.value."+f.Key) + " < " + name, nil
106+
}
107+
108+
func (q *Query) VisitLTE(f *query.LTE) (string, error) {
109+
// <key> <= <val>
110+
var name string
111+
switch value := f.Val.(type) {
112+
case int:
113+
name = q.setNextParameterInt(value)
114+
case float64:
115+
name = q.setNextParameterFloat(value)
116+
default:
117+
return "", fmt.Errorf("unsupported type of value %#v; expected number", f.Val)
118+
}
119+
120+
return replaceKeywords("c.value."+f.Key) + " <= " + name, nil
121+
}
122+
53123
func (q *Query) VisitIN(f *query.IN) (string, error) {
54124
// <key> IN ( <val1>, <val2>, ... , <valN> )
55125
if len(f.Vals) == 0 {
@@ -80,6 +150,31 @@ func (q *Query) visitFilters(op string, filters []query.Filter) (string, error)
80150
return "", err
81151
}
82152
arr = append(arr, str)
153+
case *query.NEQ:
154+
if str, err = q.VisitNEQ(f); err != nil {
155+
return "", err
156+
}
157+
arr = append(arr, str)
158+
case *query.GT:
159+
if str, err = q.VisitGT(f); err != nil {
160+
return "", err
161+
}
162+
arr = append(arr, str)
163+
case *query.GTE:
164+
if str, err = q.VisitGTE(f); err != nil {
165+
return "", err
166+
}
167+
arr = append(arr, str)
168+
case *query.LT:
169+
if str, err = q.VisitLT(f); err != nil {
170+
return "", err
171+
}
172+
arr = append(arr, str)
173+
case *query.LTE:
174+
if str, err = q.VisitLTE(f); err != nil {
175+
return "", err
176+
}
177+
arr = append(arr, str)
83178
case *query.IN:
84179
if str, err = q.VisitIN(f); err != nil {
85180
return "", err
@@ -144,6 +239,20 @@ func (q *Query) setNextParameter(val string) string {
144239
return pname
145240
}
146241

242+
func (q *Query) setNextParameterInt(val int) string {
243+
pname := fmt.Sprintf("@__param__%d__", len(q.query.parameters))
244+
q.query.parameters = append(q.query.parameters, azcosmos.QueryParameter{Name: pname, Value: val})
245+
246+
return pname
247+
}
248+
249+
func (q *Query) setNextParameterFloat(val float64) string {
250+
pname := fmt.Sprintf("@__param__%d__", len(q.query.parameters))
251+
q.query.parameters = append(q.query.parameters, azcosmos.QueryParameter{Name: pname, Value: val})
252+
253+
return pname
254+
}
255+
147256
func (q *Query) execute(ctx context.Context, client *azcosmos.ContainerClient) ([]state.QueryItem, string, error) {
148257
opts := &azcosmos.QueryOptions{}
149258

state/azure/cosmosdb/cosmosdb_query_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,54 @@ func TestCosmosDbQuery(t *testing.T) {
126126
},
127127
},
128128
},
129+
{
130+
input: "../../../tests/state/query/q4-notequal.json",
131+
query: InternalQuery{
132+
query: "SELECT * FROM c WHERE c['value']['person']['org'] = @__param__0__ OR (c['value']['person']['org'] != @__param__1__ AND c['value']['state'] IN (@__param__2__, @__param__3__)) ORDER BY c['value']['state'] DESC, c['value']['person']['name'] ASC",
133+
parameters: []azcosmos.QueryParameter{
134+
{
135+
Name: "@__param__0__",
136+
Value: "A",
137+
},
138+
{
139+
Name: "@__param__1__",
140+
Value: "B",
141+
},
142+
{
143+
Name: "@__param__2__",
144+
Value: "CA",
145+
},
146+
{
147+
Name: "@__param__3__",
148+
Value: "WA",
149+
},
150+
},
151+
},
152+
},
153+
{
154+
input: "../../../tests/state/query/q8.json",
155+
query: InternalQuery{
156+
query: "SELECT * FROM c WHERE c['value']['person']['org'] >= @__param__0__ OR (c['value']['person']['org'] < @__param__1__ AND c['value']['state'] IN (@__param__2__, @__param__3__)) ORDER BY c['value']['state'] DESC, c['value']['person']['name'] ASC",
157+
parameters: []azcosmos.QueryParameter{
158+
{
159+
Name: "@__param__0__",
160+
Value: 123.0,
161+
},
162+
{
163+
Name: "@__param__1__",
164+
Value: 10.0,
165+
},
166+
{
167+
Name: "@__param__2__",
168+
Value: "CA",
169+
},
170+
{
171+
Name: "@__param__3__",
172+
Value: "WA",
173+
},
174+
},
175+
},
176+
},
129177
}
130178
for _, test := range tests {
131179
data, err := os.ReadFile(test.input)

0 commit comments

Comments
 (0)