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  }