github.com/tenywen/fabric@v1.0.0-beta.0.20170620030522-a5b1ed380643/core/ledger/kvledger/txmgmt/statedb/statecouchdb/query_wrapper.go (about) 1 /* 2 Copyright IBM Corp. 2016 All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package statecouchdb 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 ) 25 26 const dataWrapper = "data" 27 const jsonQueryFields = "fields" 28 const jsonQuerySelector = "selector" 29 const jsonQueryUseIndex = "use_index" 30 const jsonQueryLimit = "limit" 31 const jsonQuerySkip = "skip" 32 33 var validOperators = []string{"$and", "$or", "$not", "$nor", "$all", "$elemMatch", 34 "$lt", "$lte", "$eq", "$ne", "$gte", "$gt", "$exits", "$type", "$in", "$nin", 35 "$size", "$mod", "$regex"} 36 37 /* 38 ApplyQueryWrapper parses the query string passed to CouchDB 39 the wrapper prepends the wrapper "data." to all fields specified in the query 40 All fields in the selector must have "data." prepended to the field names 41 Fields listed in fields key will have "data." prepended 42 Fields in the sort key will have "data." prepended 43 44 - The query will be scoped to the chaincodeid 45 46 - limit be added to the query and is based on config 47 - skip is defaulted to 0 and is currently not used, this is for future paging implementation 48 49 In the example a contextID of "marble" is assumed. 50 51 Example: 52 53 Source Query: 54 {"selector":{"owner": {"$eq": "tom"}}, 55 "fields": ["owner", "asset_name", "color", "size"], 56 "sort": ["size", "color"]} 57 58 Result Wrapped Query: 59 {"selector":{"$and":[{"chaincodeid":"marble"},{"data.owner":{"$eq":"tom"}}]}, 60 "fields": ["data.owner","data.asset_name","data.color","data.size","_id","version"], 61 "sort":["data.size","data.color"],"limit":10,"skip":0} 62 63 */ 64 func ApplyQueryWrapper(namespace, queryString string, queryLimit, querySkip int) (string, error) { 65 66 //create a generic map for the query json 67 jsonQueryMap := make(map[string]interface{}) 68 69 //unmarshal the selected json into the generic map 70 decoder := json.NewDecoder(bytes.NewBuffer([]byte(queryString))) 71 decoder.UseNumber() 72 err := decoder.Decode(&jsonQueryMap) 73 if err != nil { 74 return "", err 75 } 76 77 //traverse through the json query and wrap any field names 78 processAndWrapQuery(jsonQueryMap) 79 80 //if "fields" are specified in the query, then add the "_id", "version" and "chaincodeid" fields 81 if jsonValue, ok := jsonQueryMap[jsonQueryFields]; ok { 82 //check to see if this is an interface map 83 if reflect.TypeOf(jsonValue).String() == "[]interface {}" { 84 85 //Add the "_id", "version" and "chaincodeid" fields, these are needed by default 86 jsonQueryMap[jsonQueryFields] = append(jsonValue.([]interface{}), 87 "_id", "version", "chaincodeid") 88 } 89 } 90 91 //Check to see if the "selector" is specified in the query 92 if jsonValue, ok := jsonQueryMap[jsonQuerySelector]; ok { 93 //if the "selector" is found, then add the "$and" clause and the namespace filter 94 setNamespaceInSelector(namespace, jsonValue, jsonQueryMap) 95 } else { 96 //if the "selector" is not found, then add a default namespace filter 97 setDefaultNamespaceInSelector(namespace, jsonQueryMap) 98 } 99 100 //Add limit 101 jsonQueryMap[jsonQueryLimit] = queryLimit 102 103 //Add skip 104 jsonQueryMap[jsonQuerySkip] = querySkip 105 106 //Marshal the updated json query 107 editedQuery, _ := json.Marshal(jsonQueryMap) 108 109 logger.Debugf("Rewritten query with data wrapper: %s", editedQuery) 110 111 return string(editedQuery), nil 112 113 } 114 115 //setNamespaceInSelector adds an additional hierarchy in the "selector" 116 //{"owner": {"$eq": "tom"}} 117 //would be mapped as (assuming a namespace of "marble"): 118 //{"$and":[{"chaincodeid":"marble"},{"data.owner":{"$eq":"tom"}}]} 119 func setNamespaceInSelector(namespace, jsonValue interface{}, 120 jsonQueryMap map[string]interface{}) { 121 122 //create a array to store the parts of the query 123 var queryParts = make([]interface{}, 0) 124 125 //Add the namespace filter to filter on the chaincodeid 126 namespaceFilter := make(map[string]interface{}) 127 namespaceFilter["chaincodeid"] = namespace 128 129 //Add the context filter and the existing selector value 130 queryParts = append(queryParts, namespaceFilter, jsonValue) 131 132 //Create a new mapping for the new query structure 133 mappedSelector := make(map[string]interface{}) 134 135 //Specify the "$and" operator for the parts of the query 136 mappedSelector["$and"] = queryParts 137 138 //Set the new mapped selector to the query selector 139 jsonQueryMap[jsonQuerySelector] = mappedSelector 140 141 } 142 143 //setDefaultNamespaceInSelector adds an default namespace filter in "selector" 144 //If no selector is specified, the following is mapped to the "selector" 145 //assuming a namespace of "marble" 146 //{"chaincodeid":"marble"} 147 func setDefaultNamespaceInSelector(namespace string, jsonQueryMap map[string]interface{}) { 148 149 //Add the context filter to filter on the chaincodeid 150 namespaceFilter := make(map[string]interface{}) 151 namespaceFilter["chaincodeid"] = namespace 152 153 //Set the new mapped selector to the query selector 154 jsonQueryMap[jsonQuerySelector] = namespaceFilter 155 } 156 157 func processAndWrapQuery(jsonQueryMap map[string]interface{}) { 158 159 //iterate through the JSON query 160 for jsonKey, jsonValue := range jsonQueryMap { 161 162 //create a case for the data types found in the JSON query 163 switch jsonValueType := jsonValue.(type) { 164 165 case string: 166 //intercept the string case and prevent the []interface{} case from 167 //incorrectly processing the string 168 169 case float64: 170 //intercept the float64 case and prevent the []interface{} case from 171 //incorrectly processing the float64 172 173 case json.Number: 174 //intercept the Number case and prevent the []interface{} case from 175 //incorrectly processing the float64 176 177 //if the type is an array, then iterate through the items 178 case []interface{}: 179 180 //iterate the items in the array 181 for itemKey, itemValue := range jsonValueType { 182 183 switch itemValue.(type) { 184 185 case string: 186 187 //if this is not "use_index", the wrap the string 188 if jsonKey != jsonQueryUseIndex { 189 //This is a simple string, so wrap the field and replace in the array 190 jsonValueType[itemKey] = fmt.Sprintf("%v.%v", dataWrapper, itemValue) 191 } 192 193 case []interface{}: 194 195 //This is a array, so traverse to the next level 196 processAndWrapQuery(itemValue.(map[string]interface{})) 197 198 case interface{}: 199 200 //process this part as a map 201 processInterfaceMap(itemValue.(map[string]interface{})) 202 203 } 204 } 205 206 case interface{}: 207 208 //process this part as a map 209 processInterfaceMap(jsonValue.(map[string]interface{})) 210 211 } 212 } 213 } 214 215 //processInterfaceMap processes an interface map and wraps field names or traverses 216 //the next level of the json query 217 func processInterfaceMap(jsonFragment map[string]interface{}) { 218 219 //create a copy of the jsonFragment for iterating 220 var bufferFragment = make(map[string]interface{}) 221 for keyVal, itemVal := range jsonFragment { 222 bufferFragment[keyVal] = itemVal 223 } 224 225 //iterate the item in the map 226 for keyVal, itemVal := range bufferFragment { 227 228 //check to see if the key is an operator 229 if arrayContains(validOperators, keyVal) { 230 231 //if this is an operator, traverse the next level of the json query 232 processAndWrapQuery(jsonFragment) 233 234 } else { 235 236 //if this is not an operator, this is a field name and needs to be wrapped 237 wrapFieldName(jsonFragment, keyVal, itemVal) 238 239 } 240 } 241 } 242 243 //wrapFieldName "wraps" the field name with the data wrapper, and replaces the key in the json fragment 244 func wrapFieldName(jsonFragment map[string]interface{}, key string, value interface{}) { 245 246 //delete the mapping for the field definition, since we have to change the 247 //value of the key 248 delete(jsonFragment, key) 249 250 //add the key back into the map with the field name wrapped with then "data" wrapper 251 jsonFragment[fmt.Sprintf("%v.%v", dataWrapper, key)] = value 252 253 } 254 255 //arrayContains is a function to detect if a soure array of strings contains the selected string 256 //for this application, it is used to determine if a string is a valid CouchDB operator 257 func arrayContains(sourceArray []string, selectItem string) bool { 258 set := make(map[string]struct{}, len(sourceArray)) 259 for _, s := range sourceArray { 260 set[s] = struct{}{} 261 } 262 _, ok := set[selectItem] 263 return ok 264 }