github.com/janelia-flyem/dvid@v1.0.0/datatype/neuronjson/query.go (about) 1 package neuronjson 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/http" 8 reflect "reflect" 9 "regexp" 10 "strconv" 11 "strings" 12 13 "github.com/janelia-flyem/dvid/datastore" 14 "github.com/janelia-flyem/dvid/dvid" 15 "github.com/janelia-flyem/dvid/storage" 16 ) 17 18 type QueryJSON map[string]interface{} 19 type ListQueryJSON []QueryJSON 20 21 type FieldExistence bool // field is present or not 22 23 // UnmarshalJSON parses JSON with numbers preferentially converted to uint64 24 // or int64 if negative, and strings with "re/" as prefix are compiled as 25 // a regular expression. 26 func (qj *QueryJSON) UnmarshalJSON(jsonText []byte) error { 27 var raw map[string]json.RawMessage 28 if err := json.Unmarshal([]byte(jsonText), &raw); err != nil { 29 return err 30 } 31 *qj = make(QueryJSON, len(raw)) 32 33 dvid.Infof("query unmarshal on: %s\n", string(jsonText)) 34 35 for key, val := range raw { 36 s := string(val) 37 u, err := strconv.ParseUint(s, 10, 64) 38 if err == nil { 39 (*qj)[key] = u 40 continue 41 } 42 i, err := strconv.ParseInt(s, 10, 64) 43 if err == nil { 44 (*qj)[key] = i 45 continue 46 } 47 f, err := strconv.ParseFloat(s, 64) 48 if err == nil { 49 (*qj)[key] = f 50 continue 51 } 52 var int64list []int64 53 if err = json.Unmarshal(val, &int64list); err == nil { 54 (*qj)[key] = int64list 55 continue 56 } 57 if len(s) > 4 && strings.HasPrefix(s, `"re/`) { 58 re, err := regexp.Compile(s[4 : len(s)-1]) 59 if err == nil { 60 (*qj)[key] = re 61 continue 62 } 63 } 64 if len(s) == 10 && strings.HasPrefix(s, `"exists/`) { 65 if s[8] == '0' { 66 (*qj)[key] = FieldExistence(false) 67 } else { 68 (*qj)[key] = FieldExistence(true) 69 } 70 continue 71 } 72 var strlist []string 73 if err = json.Unmarshal(val, &strlist); err == nil { 74 hasRegex := false 75 iflist := make([]interface{}, len(strlist)) 76 for i, s := range strlist { 77 if len(s) > 3 && strings.HasPrefix(s, "re/") { 78 hasRegex = true 79 if re, err := regexp.Compile(s[3:]); err == nil { 80 iflist[i] = re 81 } 82 } 83 if iflist[i] == nil { 84 iflist[i] = s 85 } 86 } 87 if hasRegex { 88 (*qj)[key] = iflist 89 } else { 90 (*qj)[key] = strlist 91 } 92 continue 93 } 94 var listVal interface{} 95 if err = json.Unmarshal(val, &listVal); err == nil { 96 (*qj)[key] = listVal 97 continue 98 } 99 return fmt.Errorf("unable to parse JSON value %q: %v", s, err) 100 } 101 return nil 102 } 103 104 func checkIntMatch(query int64, field []int64) bool { 105 if len(field) == 0 { 106 return false 107 } 108 for _, fieldValue := range field { 109 if fieldValue == query { 110 return true 111 } 112 } 113 return false 114 } 115 116 func checkFloatMatch(query float64, field []float64) bool { 117 if len(field) == 0 { 118 return false 119 } 120 for _, fieldValue := range field { 121 if fieldValue == query { 122 return true 123 } 124 } 125 return false 126 } 127 128 func checkStrMatch(query string, field []string) bool { 129 if len(field) == 0 { 130 return false 131 } 132 for _, fieldValue := range field { 133 if fieldValue == query { 134 return true 135 } 136 } 137 return false 138 } 139 140 func checkRegexMatch(query *regexp.Regexp, field []string) bool { 141 if len(field) == 0 { 142 return false 143 } 144 for _, fieldValue := range field { 145 if query.Match([]byte(fieldValue)) { 146 return true 147 } 148 } 149 return false 150 } 151 152 // given a query on this field composed of one or a list of values of unknown type, 153 // see if any of the field's values match a query value regardless of slightly 154 // different integer typing. 155 func checkField(queryValue, fieldValue interface{}) bool { 156 // if field value is integer of some kind, convert to []int64 assuming MSB not 157 // needed for our data. 158 // if field value is string, make it []string. 159 // Field can be (single or list of) number, string, other. 160 var fieldNumList []int64 161 var fieldFloatList []float64 162 var fieldStrList []string 163 switch v := fieldValue.(type) { 164 case int64: 165 fieldNumList = []int64{v} 166 case []int64: 167 fieldNumList = v 168 case uint64: 169 fieldNumList = []int64{int64(v)} 170 case []uint64: 171 fieldNumList = make([]int64, len(v)) 172 for i, val := range v { 173 fieldNumList[i] = int64(val) 174 } 175 case string: 176 fieldStrList = []string{v} 177 case []string: 178 fieldStrList = v 179 case float64: 180 if v == float64(int(v)) { 181 fieldNumList = []int64{int64(v)} 182 } else { 183 fieldFloatList = []float64{v} 184 } 185 186 default: 187 dvid.Errorf("Unknown field value of type %s: %v\n", reflect.TypeOf(v), v) 188 return false 189 } 190 if len(fieldNumList) == 0 && len(fieldStrList) == 0 && len(fieldFloatList) == 0 { 191 return false 192 } 193 194 // convert query value to list of types as above for field value. 195 switch v := queryValue.(type) { 196 case int64: 197 if checkIntMatch(v, fieldNumList) { 198 return true 199 } 200 case []int64: 201 for _, i := range v { 202 if checkIntMatch(i, fieldNumList) { 203 return true 204 } 205 } 206 case uint64: 207 if checkIntMatch(int64(v), fieldNumList) { 208 return true 209 } 210 case []uint64: 211 for _, val := range v { 212 if checkIntMatch(int64(val), fieldNumList) { 213 return true 214 } 215 } 216 case string: 217 if checkStrMatch(v, fieldStrList) { 218 return true 219 } 220 case []string: 221 for _, s := range v { 222 if checkStrMatch(s, fieldStrList) { 223 return true 224 } 225 } 226 case *regexp.Regexp: 227 if checkRegexMatch(v, fieldStrList) { 228 return true 229 } 230 case []interface{}: 231 elem := v[0] 232 switch e := elem.(type) { 233 case int: 234 for _, val := range v { 235 if checkIntMatch(int64(val.(int)), fieldNumList) { 236 return true 237 } 238 } 239 case float64: 240 for _, val := range v { 241 if checkFloatMatch(val.(float64), fieldFloatList) { 242 return true 243 } 244 } 245 case string, *regexp.Regexp: 246 for _, val := range v { 247 switch query := val.(type) { 248 case string: 249 if checkStrMatch(query, fieldStrList) { 250 return true 251 } 252 case *regexp.Regexp: 253 if checkRegexMatch(query, fieldStrList) { 254 return true 255 } 256 } 257 } 258 default: 259 var t = reflect.TypeOf(e) 260 dvid.Errorf("neuronjson query value %v has elements of illegal type %v\n", v, t) 261 } 262 default: 263 var t = reflect.TypeOf(v) 264 dvid.Errorf("neuronjson query value %v has illegal type %v\n", v, t) 265 } 266 return false 267 } 268 269 func fieldMatch(queryValue, fieldValue interface{}) bool { 270 if queryValue == nil { 271 return false 272 } 273 if fieldValue == nil { 274 return false 275 } 276 return checkField(queryValue, fieldValue) 277 } 278 279 // --- Data Query support --- 280 281 // returns true if at least one query on the list matches the value. 282 func queryMatch(queryList ListQueryJSON, value map[string]interface{}) (matches bool, err error) { 283 if len(queryList) == 0 { 284 matches = false 285 return 286 } 287 for _, query := range queryList { 288 and_match := true 289 for queryKey, queryValue := range query { // all query keys must be present and match 290 // field existence check 291 recordValue, found := value[queryKey] 292 switch v := queryValue.(type) { 293 case FieldExistence: 294 found = found && recordValue != nil 295 dvid.Infof("checking existence of field %s: %v where field %t (%v)", queryKey, v, found, recordValue) 296 if (bool(v) && !found) || (!bool(v) && found) { 297 and_match = false 298 } 299 default: 300 // if field exists, check if it matches query 301 if !found || !fieldMatch(queryValue, recordValue) { 302 and_match = false 303 } 304 } 305 if !and_match { 306 break 307 } 308 } 309 if and_match { 310 return true, nil 311 } 312 } 313 return false, nil 314 } 315 316 func (d *Data) queryInMemory(mdb *memdb, w http.ResponseWriter, queryL ListQueryJSON, fieldMap map[string]struct{}, showFields Fields) (err error) { 317 mdb.mu.RLock() 318 defer mdb.mu.RUnlock() 319 320 dvid.Infof("in-memory query using mdb with queryL: %v\n", queryL) 321 showUser, showTime := showFields.Bools() 322 numMatches := 0 323 var jsonBytes []byte 324 for _, bodyid := range mdb.ids { 325 value := mdb.data[bodyid] 326 var matches bool 327 if matches, err = queryMatch(queryL, value); err != nil { 328 return 329 } else if matches { 330 out := selectFields(value, fieldMap, showUser, showTime) 331 if jsonBytes, err = json.Marshal(out); err != nil { 332 break 333 } 334 if numMatches > 0 { 335 fmt.Fprint(w, ",") 336 } 337 fmt.Fprint(w, string(jsonBytes)) 338 numMatches++ 339 } 340 } 341 return 342 } 343 344 func (d *Data) queryBackingStore(ctx storage.VersionedCtx, w http.ResponseWriter, 345 queryL ListQueryJSON, fieldMap map[string]struct{}, showFields Fields) (err error) { 346 347 dvid.Infof("store query using mdb with queryL: %v\n", queryL) 348 numMatches := 0 349 process_func := func(key string, value NeuronJSON) { 350 value = NeuronJSON(value) 351 if matches, err := queryMatch(queryL, value); err != nil { 352 dvid.Errorf("error in matching process: %v\n", err) // TODO: alter d.processRange to allow return of err 353 return 354 } else if !matches { 355 return 356 } 357 out := removeReservedFields(value, showFields) 358 jsonBytes, err := json.Marshal(out) 359 if err != nil { 360 dvid.Errorf("error in JSON encoding: %v\n", err) 361 return 362 } 363 if numMatches > 0 { 364 fmt.Fprint(w, ",") 365 } 366 fmt.Fprint(w, string(jsonBytes)) 367 numMatches++ 368 } 369 return d.processStoreRange(ctx, process_func) 370 } 371 372 // Query reads POSTed data and returns JSON. 373 func (d *Data) Query(ctx *datastore.VersionedCtx, w http.ResponseWriter, uuid dvid.UUID, onlyid bool, fieldMap map[string]struct{}, showFields Fields, in io.ReadCloser) (err error) { 374 var queryBytes []byte 375 if queryBytes, err = io.ReadAll(in); err != nil { 376 return 377 } 378 // Try to parse as list of queries and if fails, try as object and make it a one-item list. 379 var queryL ListQueryJSON 380 if err = json.Unmarshal(queryBytes, &queryL); err != nil { 381 var queryObj QueryJSON 382 if err = queryObj.UnmarshalJSON(queryBytes); err != nil { 383 err = fmt.Errorf("unable to parse JSON query: %s", string(queryBytes)) 384 return 385 } 386 queryL = ListQueryJSON{queryObj} 387 } 388 389 // Perform the query 390 w.Header().Set("Content-Type", "application/json") 391 fmt.Fprint(w, "[") 392 mdb, found := d.getMemDBbyVersion(ctx.VersionID()) 393 if found { 394 if err = d.queryInMemory(mdb, w, queryL, fieldMap, showFields); err != nil { 395 return 396 } 397 } else { 398 if err = d.queryBackingStore(ctx, w, queryL, fieldMap, showFields); err != nil { 399 return 400 } 401 } 402 fmt.Fprint(w, "]") 403 return 404 }