github.com/altipla-consulting/ravendb-go-client@v0.1.3/query_operation.go (about)

     1  package ravendb
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"reflect"
     7  	"time"
     8  )
     9  
    10  // queryOperation describes query operation
    11  type queryOperation struct {
    12  	session                 *InMemoryDocumentSessionOperations
    13  	indexName               string
    14  	indexQuery              *IndexQuery
    15  	metadataOnly            bool
    16  	indexEntriesOnly        bool
    17  	currentQueryResults     *QueryResult
    18  	fieldsToFetch           *fieldsToFetchToken
    19  	startTime               time.Time
    20  	disableEntitiesTracking bool
    21  
    22  	// static  Log logger = LogFactory.getLog(queryOperation.class);
    23  }
    24  
    25  func newQueryOperation(session *InMemoryDocumentSessionOperations, indexName string, indexQuery *IndexQuery, fieldsToFetch *fieldsToFetchToken, disableEntitiesTracking bool, metadataOnly bool, indexEntriesOnly bool) (*queryOperation, error) {
    26  	res := &queryOperation{
    27  		session:                 session,
    28  		indexName:               indexName,
    29  		indexQuery:              indexQuery,
    30  		fieldsToFetch:           fieldsToFetch,
    31  		disableEntitiesTracking: disableEntitiesTracking,
    32  		metadataOnly:            metadataOnly,
    33  		indexEntriesOnly:        indexEntriesOnly,
    34  	}
    35  	if err := res.assertPageSizeSet(); err != nil {
    36  		return nil, err
    37  	}
    38  	return res, nil
    39  }
    40  
    41  func (o *queryOperation) createRequest() (*QueryCommand, error) {
    42  	if err := o.session.incrementRequestCount(); err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	//o.logQuery();
    47  
    48  	return NewQueryCommand(o.session.GetConventions(), o.indexQuery, o.metadataOnly, o.indexEntriesOnly)
    49  }
    50  
    51  func (o *queryOperation) setResult(queryResult *QueryResult) error {
    52  	return o.ensureIsAcceptableAndSaveResult(queryResult)
    53  }
    54  
    55  func (o *queryOperation) assertPageSizeSet() error {
    56  	if !o.session.GetConventions().ErrorIfQueryPageSizeIsNotSet {
    57  		return nil
    58  	}
    59  
    60  	if o.indexQuery.pageSize > 0 {
    61  		return nil
    62  	}
    63  
    64  	return newIllegalStateError("Attempt to query without explicitly specifying a page size. " +
    65  		"You can use .take() methods to set maximum number of results. By default the page //size is set to Integer.MAX_VALUE and can cause severe performance degradation.")
    66  }
    67  
    68  func (o *queryOperation) startTiming() {
    69  	o.startTime = time.Now()
    70  }
    71  
    72  func (o *queryOperation) logQuery() {
    73  	/*
    74  		if (logger.isInfoEnabled()) {
    75  			logger.info("Executing query " + _indexQuery.getQuery() + " on index " + _indexName + " in " + _session.storeIdentifier());
    76  		}
    77  	*/
    78  }
    79  
    80  func (o *queryOperation) enterQueryContext() io.Closer {
    81  	o.startTiming()
    82  
    83  	if !o.indexQuery.waitForNonStaleResults {
    84  		var res *nilCloser
    85  		return res
    86  	}
    87  
    88  	return o.session.GetDocumentStore().DisableAggressiveCaching(o.session.DatabaseName)
    89  }
    90  
    91  // results must be *[]<type>. If results is a nil pointer to slice,
    92  // we create a slice and set pointer.
    93  // we return reflect.Value that represents the slice
    94  func makeSliceForResults(results interface{}) (reflect.Value, error) {
    95  	slicePtr := reflect.ValueOf(results)
    96  	rt := slicePtr.Type()
    97  
    98  	if rt.Kind() != reflect.Ptr || rt.Elem().Kind() != reflect.Slice {
    99  		return reflect.Value{}, fmt.Errorf("results should *[]<type>, is %T. rt: %s", results, rt)
   100  	}
   101  	slice := slicePtr.Elem()
   102  	// if this is a pointer to nil slice, create a new slice
   103  	// otherwise we use the slice that was provided by the caller
   104  	// TODO: should this always be a new slice? (in which case we should error
   105  	// if provided non-nil slice, since that implies user error
   106  	// r at least we should reset the slice to empty. Appending to existing
   107  	// slice might be confusing/unexpected to callers
   108  	if slice.IsNil() {
   109  		slice.Set(reflect.MakeSlice(slice.Type(), 0, 0))
   110  	}
   111  	return slice, nil
   112  }
   113  
   114  // results is *[]<type> and we'll create the slice and fill it with values
   115  // of <type> and do the equivalent of: *results = our_slice
   116  func (o *queryOperation) complete(results interface{}) error {
   117  	queryResult := o.currentQueryResults.createSnapshot()
   118  
   119  	if !o.disableEntitiesTracking {
   120  		o.session.registerIncludes(queryResult.Includes)
   121  	}
   122  
   123  	slice, err := makeSliceForResults(results)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	tmpSlice := slice
   129  
   130  	clazz := slice.Type().Elem()
   131  	for _, document := range queryResult.Results {
   132  		metadataI, ok := document[MetadataKey]
   133  		if !ok {
   134  			return newIllegalStateError("missing metadata")
   135  		}
   136  		metadata := metadataI.(map[string]interface{})
   137  		id, _ := jsonGetAsText(metadata, MetadataID)
   138  		result := reflect.New(clazz) // this is a pointer to desired value
   139  		err := queryOperationDeserialize(result.Interface(), id, document, metadata, o.fieldsToFetch, o.disableEntitiesTracking, o.session)
   140  		if err != nil {
   141  			return newRuntimeError("Unable to read json: %s", err)
   142  		}
   143  		// de-reference pointer value
   144  		tmpSlice = reflect.Append(tmpSlice, result.Elem())
   145  	}
   146  
   147  	if !o.disableEntitiesTracking {
   148  		o.session.registerMissingIncludes(queryResult.Results, queryResult.Includes, queryResult.IncludedPaths)
   149  	}
   150  	// appending to slice might re-allocate slice value
   151  	if tmpSlice != slice {
   152  		slice.Set(tmpSlice)
   153  	}
   154  	return nil
   155  }
   156  
   157  func jsonIsValueNode(v interface{}) bool {
   158  	switch v.(type) {
   159  	case string, float64, bool:
   160  		return true
   161  	case []interface{}, map[string]interface{}:
   162  		return false
   163  	}
   164  	panicIf(true, "unhandled type %T", v)
   165  	return false
   166  }
   167  
   168  // result is pointer to value that will be set with value decoded from JSON
   169  func queryOperationDeserialize(result interface{}, id string, document map[string]interface{}, metadata map[string]interface{}, fieldsToFetch *fieldsToFetchToken, disableEntitiesTracking bool, session *InMemoryDocumentSessionOperations) error {
   170  	_, ok := jsonGetAsBool(metadata, MetadataProjection)
   171  	if !ok {
   172  		return session.TrackEntity(result, id, document, metadata, disableEntitiesTracking)
   173  	}
   174  	tp := reflect.TypeOf(result)
   175  	panicIf(tp.Kind() != reflect.Ptr, "result should be a *<type>, is %T", result)
   176  	clazz := tp.Elem()
   177  	if fieldsToFetch != nil && len(fieldsToFetch.projections) == 1 {
   178  		// we only select a single field
   179  		isString := clazz.Kind() == reflect.String
   180  		if isString || isPrimitiveOrWrapper(clazz) || typeIsEnum(clazz) {
   181  			projectionField := fieldsToFetch.projections[0]
   182  
   183  			if fieldsToFetch.sourceAlias != "" {
   184  				// remove source-alias from projection name
   185  				projectionField = projectionField[len(fieldsToFetch.sourceAlias)+1:]
   186  
   187  			}
   188  
   189  			jsonNode, ok := document[projectionField]
   190  			if ok && jsonIsValueNode(jsonNode) {
   191  				res, err := treeToValue(clazz, jsonNode)
   192  				if err != nil {
   193  					return err
   194  				}
   195  				if res != nil {
   196  					return setInterfaceToValue(result, res)
   197  				}
   198  				return nil
   199  			}
   200  		}
   201  
   202  		inner, ok := document[fieldsToFetch.projections[0]]
   203  		if !ok {
   204  			return nil
   205  		}
   206  
   207  		if fieldsToFetch.fieldsToFetch != nil && fieldsToFetch.fieldsToFetch[0] == fieldsToFetch.projections[0] {
   208  			doc, ok := inner.(map[string]interface{})
   209  			if ok {
   210  				// extraction from original type
   211  				document = doc
   212  			}
   213  		}
   214  	}
   215  
   216  	res, err := treeToValue(clazz, document)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	if stringIsNotEmpty(id) {
   222  		// we need to make an additional check, since it is possible that a value was explicitly stated
   223  		// for the identity property, in which case we don't want to override it.
   224  
   225  		identityProperty := session.GetConventions().GetIdentityProperty(clazz)
   226  		if identityProperty != "" {
   227  			if _, ok := document[identityProperty]; !ok {
   228  				session.generateEntityIDOnTheClient.trySetIdentity(res, id)
   229  			}
   230  		}
   231  	}
   232  
   233  	return setInterfaceToValue(result, res)
   234  }
   235  
   236  func (o *queryOperation) ensureIsAcceptableAndSaveResult(result *QueryResult) error {
   237  	if result == nil {
   238  		return newIndexDoesNotExistError("Could not find index " + o.indexName)
   239  	}
   240  
   241  	err := queryOperationEnsureIsAcceptable(result, o.indexQuery.waitForNonStaleResults, o.startTime, o.session)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	o.currentQueryResults = result
   246  
   247  	// TODO: port me when we have logger
   248  	/*
   249  	   if (logger.isInfoEnabled()) {
   250  	       string isStale = result.isStale() ? " stale " : " ";
   251  
   252  	       stringBuilder parameters = new stringBuilder();
   253  	       if (_indexQuery.getQueryParameters() != null && !_indexQuery.getQueryParameters().isEmpty()) {
   254  	           parameters.append("(parameters: ");
   255  
   256  	           bool first = true;
   257  
   258  	           for (Map.Entry<string, Object> parameter : _indexQuery.getQueryParameters().entrySet()) {
   259  	               if (!first) {
   260  	                   parameters.append(", ");
   261  	               }
   262  
   263  	               parameters.append(parameter.getKey())
   264  	                       .append(" = ")
   265  	                       .append(parameter.getValue());
   266  
   267  	               first = false;
   268  	           }
   269  
   270  	           parameters.append(") ");
   271  	       }
   272  
   273  	       logger.info("Query " + _indexQuery.getQuery() + " " + parameters.tostring() + "returned " + result.getResults().size() + isStale + "results (total index results: " + result.getTotalResults() + ")");
   274  	   }
   275  	*/
   276  	return nil
   277  }
   278  
   279  func queryOperationEnsureIsAcceptable(result *QueryResult, waitForNonStaleResults bool, startTime time.Time, session *InMemoryDocumentSessionOperations) error {
   280  	if waitForNonStaleResults && result.IsStale {
   281  		duration := time.Since(startTime)
   282  		msg := "Waited for " + duration.String() + " for the query to return non stale result."
   283  		return NewTimeoutError(msg)
   284  	}
   285  	return nil
   286  }