github.com/ravendb/ravendb-go-client@v0.0.0-20240229102137-4474ee7aa0fa/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 }