@@ -3,137 +3,165 @@ package statecouchdb
3
3
import (
4
4
"encoding/json"
5
5
"fmt"
6
- "strings "
6
+ "reflect "
7
7
)
8
8
9
- //ApplyQueryWrapper parses the query string passed to CouchDB
10
- //the wrapper prepends the wrapper "data." to all fields specified in the query
11
- //All fields in the selector must have "data." prepended to the field names
12
- //Fields listed in fields key will have "data." prepended
13
- //Fields in the sort key will have "data." prepended
14
- func ApplyQueryWrapper (queryString string ) []byte {
9
+ var dataWrapper = "data"
10
+ var jsonQueryFields = "fields"
15
11
16
- //create a generic map for the query json
17
- jsonQuery := make (map [string ]interface {})
12
+ var validOperators = []string {"$and" , "$or" , "$not" , "$nor" , "$all" , "$elemMatch" ,
13
+ "$lt" , "$lte" , "$eq" , "$ne" , "$gte" , "$gt" , "$exits" , "$type" , "$in" , "$nin" ,
14
+ "$size" , "$mod" , "$regex" }
18
15
19
- //unmarshal the selected json into the generic map
20
- json .Unmarshal ([]byte (queryString ), & jsonQuery )
16
+ /*
17
+ ApplyQueryWrapper parses the query string passed to CouchDB
18
+ the wrapper prepends the wrapper "data." to all fields specified in the query
19
+ All fields in the selector must have "data." prepended to the field names
20
+ Fields listed in fields key will have "data." prepended
21
+ Fields in the sort key will have "data." prepended
21
22
22
- //iterate through the JSON query
23
- for jsonKey , jsonValue := range jsonQuery {
23
+ Example:
24
24
25
- //create a case for the data types found in the JSON query
26
- switch jsonQueryPart := jsonValue .(type ) {
25
+ Source Query:
26
+ {"selector":{"owner": {"$eq": "tom"}},
27
+ "fields": ["owner", "asset_name", "color", "size"],
28
+ "sort": ["size", "color"], "limit": 10, "skip": 0}
27
29
28
- //if the type is an array, then this is either the "fields" or "sort" part of the query
29
- case []interface {}:
30
+ Result Wrapped Query:
31
+ {"selector":{"data.owner":{"$eq":"tom"}},
32
+ "fields": ["data.owner","data.asset_name","data.color","data.size","_id","version"],
33
+ "sort":["data.size","data.color"],"limit":10,"skip":0}
30
34
31
- //check to see if this is a "fields" or "sort" array
32
- //if jsonKey == jsonQueryFields || jsonKey == jsonQuerySort {
33
- if jsonKey == jsonQueryFields {
34
35
35
- //iterate through the names and add the data wrapper for each field
36
- for itemKey , fieldName := range jsonQueryPart {
36
+ */
37
+ func ApplyQueryWrapper ( queryString string ) string {
37
38
38
- //add "data" wrapper to each field definition
39
- jsonQueryPart [itemKey ] = fmt .Sprintf ("%v.%v" , dataWrapper , fieldName )
40
- }
39
+ //create a generic map for the query json
40
+ jsonQueryMap := make (map [string ]interface {})
41
41
42
- //Add the "_id" and "version" fields, these are needed by default
43
- if jsonKey == jsonQueryFields {
42
+ //unmarshal the selected json into the generic map
43
+ json . Unmarshal ([] byte ( queryString ), & jsonQueryMap )
44
44
45
- jsonQueryPart = append ( jsonQueryPart , "_id" )
46
- jsonQueryPart = append ( jsonQueryPart , "version" )
45
+ //traverse through the json query and wrap any field names
46
+ processAndWrapQuery ( jsonQueryMap )
47
47
48
- //Overwrite the query fields if the "_id" field has been added
49
- jsonQuery [jsonQueryFields ] = jsonQueryPart
50
- }
48
+ //process the query and add the version and fields if fields are specified
49
+ for jsonKey , jsonValue := range jsonQueryMap {
50
+
51
+ //Add the "_id" and "version" fields, these are needed by default
52
+ if jsonKey == jsonQueryFields {
51
53
54
+ //check to see if this is an interface map
55
+ if reflect .TypeOf (jsonValue ).String () == "[]interface {}" {
56
+
57
+ //Add the "_id" and "version" fields, these are needed by default
58
+ //Overwrite the query fields if the "_id" field has been added
59
+ jsonQueryMap [jsonQueryFields ] = append (jsonValue .([]interface {}), "_id" , "version" )
52
60
}
53
61
54
- if jsonKey == jsonQuerySort {
62
+ }
63
+ }
64
+
65
+ //Marshal the updated json query
66
+ editedQuery , _ := json .Marshal (jsonQueryMap )
67
+
68
+ logger .Debugf ("Rewritten query with data wrapper: %s" , editedQuery )
69
+
70
+ return string (editedQuery )
71
+
72
+ }
55
73
56
- //iterate through the names and add the data wrapper for each field
57
- for sortItemKey , sortField := range jsonQueryPart {
74
+ func processAndWrapQuery (jsonQueryMap map [string ]interface {}) {
58
75
59
- //create a case for the data types found in the JSON query
60
- switch sortFieldType := sortField .( type ) {
76
+ //iterate through the JSON query
77
+ for _ , jsonValue := range jsonQueryMap {
61
78
62
- //if the type is string, then this is a simple array of field names.
63
- //Add the datawrapper to the field name
64
- case string :
79
+ //create a case for the data types found in the JSON query
80
+ switch jsonValueType := jsonValue .(type ) {
65
81
66
- //simple case, update the existing array item with the updated name
67
- jsonQueryPart [sortItemKey ] = fmt .Sprintf ("%v.%v" , dataWrapper , sortField )
82
+ case string :
83
+ //intercept the string case and prevent the []interface{} case from
84
+ //incorrectly processing the string
68
85
69
- case interface {}:
86
+ case float64 :
87
+ //intercept the float64 case and prevent the []interface{} case from
88
+ //incorrectly processing the float64
70
89
71
- //this case is a little more complicated. Here we need to
72
- //iterate over the mapped field names since this is an array of objects
73
- //example: {"fieldname":"desc"}
74
- for key , itemValue := range sortField .(map [string ]interface {}) {
75
- //delete the mapping for the field definition, since we have to change the
76
- //value of the key
77
- delete (sortField .(map [string ]interface {}), key )
90
+ //if the type is an array, then iterate through the items
91
+ case []interface {}:
78
92
79
- //add the key back into the map with the field name wrapped with then "data" wrapper
80
- sortField .(map [string ]interface {})[fmt .Sprintf ("%v.%v" , dataWrapper , key )] = itemValue
81
- }
93
+ //iterate the the items in the array
94
+ for itemKey , itemValue := range jsonValueType {
82
95
83
- default :
96
+ switch itemValue .( type ) {
84
97
85
- logger . Debugf ( "The type %v was not recognized as a valid sort field type." , sortFieldType )
98
+ case string :
86
99
87
- }
100
+ //This is a simple string, so wrap the field and replace in the array
101
+ jsonValueType [itemKey ] = fmt .Sprintf ("%v.%v" , dataWrapper , itemValue )
88
102
89
- }
90
- }
103
+ case []interface {}:
91
104
92
- case interface {}:
105
+ //This is a array, so traverse to the next level
106
+ processAndWrapQuery (itemValue .(map [string ]interface {}))
93
107
94
- //if this is the "selector", the field names need to be mapped with the
95
- //data wrapper
96
- if jsonKey == jsonQuerySelector {
108
+ case interface {}:
97
109
98
- processSelector (jsonQueryPart .(map [string ]interface {}))
110
+ //process this part as a map
111
+ processInterfaceMap (itemValue .(map [string ]interface {}))
99
112
113
+ }
100
114
}
101
115
102
- default :
116
+ case interface {} :
103
117
104
- logger .Debugf ("The value %v was not recognized as a valid selector field." , jsonKey )
118
+ //process this part as a map
119
+ processInterfaceMap (jsonValue .(map [string ]interface {}))
105
120
106
121
}
107
122
}
123
+ }
108
124
109
- //Marshal the updated json query
110
- editedQuery , _ := json . Marshal ( jsonQuery )
125
+ //processInterfaceMap processes an interface map and wraps field names or traverses the next level of the json query
126
+ func processInterfaceMap ( jsonFragment map [ string ] interface {}) {
111
127
112
- logger .Debugf ("Rewritten query with data wrapper: %s" , editedQuery )
128
+ //iterate the the item in the map
129
+ for keyVal , itemVal := range jsonFragment {
113
130
114
- return editedQuery
131
+ //check to see if the key is an operator
132
+ if arrayContains (validOperators , keyVal ) {
115
133
116
- }
134
+ //if this is an operator, traverse the next level of the json query
135
+ processAndWrapQuery (jsonFragment )
117
136
118
- //processSelector is a recursion function for traversing the selector part of the query
119
- func processSelector (selectorFragment map [string ]interface {}) {
137
+ } else {
120
138
121
- //iterate through the top level definitions
122
- for itemKey , itemValue := range selectorFragment {
139
+ //if this is not an operator, this is a field name and needs to be wrapped
140
+ wrapFieldName (jsonFragment , keyVal , itemVal )
141
+
142
+ }
143
+ }
144
+ }
123
145
124
- //check to see if the itemKey starts with a $. If so, this indicates an operator
125
- if strings . HasPrefix ( fmt . Sprintf ( "%s" , itemKey ), "$" ) {
146
+ //wrapFieldName "wraps" the field name with the data wrapper, and replaces the key in the json fragment
147
+ func wrapFieldName ( jsonFragment map [ string ] interface {}, key string , value interface {} ) {
126
148
127
- processSelector (itemValue .(map [string ]interface {}))
149
+ //delete the mapping for the field definition, since we have to change the
150
+ //value of the key
151
+ delete (jsonFragment , key )
128
152
129
- } else {
153
+ //add the key back into the map with the field name wrapped with then "data" wrapper
154
+ jsonFragment [fmt .Sprintf ("%v.%v" , dataWrapper , key )] = value
130
155
131
- //delete the mapping for the field definition, since we have to change the
132
- //value of the key
133
- delete (selectorFragment , itemKey )
134
- //add the key back into the map with the field name wrapped with then "data" wrapper
135
- selectorFragment [fmt .Sprintf ("%v.%v" , dataWrapper , itemKey )] = itemValue
156
+ }
136
157
137
- }
158
+ //arrayContains is a function to detect if a soure array of strings contains the selected string
159
+ //for this application, it is used to determine if a string is a valid CouchDB operator
160
+ func arrayContains (sourceArray []string , selectItem string ) bool {
161
+ set := make (map [string ]struct {}, len (sourceArray ))
162
+ for _ , s := range sourceArray {
163
+ set [s ] = struct {}{}
138
164
}
165
+ _ , ok := set [selectItem ]
166
+ return ok
139
167
}
0 commit comments