private async Task <SearchResult> SearchImpl(SearchOptions searchOptions, bool historySearch, CancellationToken cancellationToken) { Expression searchExpression = searchOptions.Expression; // AND in the continuation token if (!string.IsNullOrWhiteSpace(searchOptions.ContinuationToken) && !searchOptions.CountOnly) { if (long.TryParse(searchOptions.ContinuationToken, NumberStyles.None, CultureInfo.InvariantCulture, out var token)) { var sortOrder = searchOptions.GetFirstSortOrderForSupportedParam(); Expression lastUpdatedExpression = sortOrder == SortOrder.Ascending ? Expression.GreaterThan(SqlFieldName.ResourceSurrogateId, null, token) : Expression.LessThan(SqlFieldName.ResourceSurrogateId, null, token); var tokenExpression = Expression.SearchParameter(SqlSearchParameters.ResourceSurrogateIdParameter, lastUpdatedExpression); searchExpression = searchExpression == null ? tokenExpression : (Expression)Expression.And(tokenExpression, searchExpression); } else { throw new BadRequestException(Resources.InvalidContinuationToken); } } SqlRootExpression expression = (SqlRootExpression)searchExpression ?.AcceptVisitor(LastUpdatedToResourceSurrogateIdRewriter.Instance) .AcceptVisitor(DateTimeEqualityRewriter.Instance) .AcceptVisitor(FlatteningRewriter.Instance) .AcceptVisitor(_sqlRootExpressionRewriter) .AcceptVisitor(DenormalizedPredicateRewriter.Instance) .AcceptVisitor(NormalizedPredicateReorderer.Instance) .AcceptVisitor(_chainFlatteningRewriter) .AcceptVisitor(DateTimeBoundedRangeRewriter.Instance) .AcceptVisitor(_stringOverflowRewriter) .AcceptVisitor(NumericRangeRewriter.Instance) .AcceptVisitor(MissingSearchParamVisitor.Instance) .AcceptVisitor(IncludeDenormalizedRewriter.Instance) .AcceptVisitor(TopRewriter.Instance, searchOptions) .AcceptVisitor(IncludeRewriter.Instance) ?? SqlRootExpression.WithDenormalizedExpressions(); using (SqlConnectionWrapper sqlConnectionWrapper = _sqlConnectionWrapperFactory.ObtainSqlConnectionWrapper(true)) using (SqlCommandWrapper sqlCommandWrapper = sqlConnectionWrapper.CreateSqlCommand()) { var stringBuilder = new IndentedStringBuilder(new StringBuilder()); EnableTimeAndIoMessageLogging(stringBuilder, sqlConnectionWrapper); var queryGenerator = new SqlQueryGenerator(stringBuilder, new SqlQueryParameterManager(sqlCommandWrapper.Parameters), _model, historySearch); expression.AcceptVisitor(queryGenerator, searchOptions); sqlCommandWrapper.CommandText = stringBuilder.ToString(); LogSqlCommand(sqlCommandWrapper); using (var reader = await sqlCommandWrapper.ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken)) { if (searchOptions.CountOnly) { await reader.ReadAsync(cancellationToken); return(new SearchResult(reader.GetInt32(0), searchOptions.UnsupportedSearchParams)); } var resources = new List <SearchResultEntry>(searchOptions.MaxItemCount); long?newContinuationId = null; bool moreResults = false; int matchCount = 0; while (await reader.ReadAsync(cancellationToken)) { (short resourceTypeId, string resourceId, int version, bool isDeleted, long resourceSurrogateId, string requestMethod, bool isMatch, Stream rawResourceStream) = reader.ReadRow( VLatest.Resource.ResourceTypeId, VLatest.Resource.ResourceId, VLatest.Resource.Version, VLatest.Resource.IsDeleted, VLatest.Resource.ResourceSurrogateId, VLatest.Resource.RequestMethod, _isMatch, VLatest.Resource.RawResource); // If we get to this point, we know there are more results so we need a continuation token // Additionally, this resource shouldn't be included in the results if (matchCount >= searchOptions.MaxItemCount && isMatch) { moreResults = true; continue; } // See if this resource is a continuation token candidate and increase the count if (isMatch) { newContinuationId = resourceSurrogateId; matchCount++; } string rawResource; using (rawResourceStream) using (var gzipStream = new GZipStream(rawResourceStream, CompressionMode.Decompress)) using (var streamReader = new StreamReader(gzipStream, SqlServerFhirDataStore.ResourceEncoding)) { rawResource = await streamReader.ReadToEndAsync(); } resources.Add(new SearchResultEntry( new ResourceWrapper( resourceId, version.ToString(CultureInfo.InvariantCulture), _model.GetResourceTypeName(resourceTypeId), new RawResource(rawResource, FhirResourceFormat.Json), new ResourceRequest(requestMethod), new DateTimeOffset(ResourceSurrogateIdHelper.ResourceSurrogateIdToLastUpdated(resourceSurrogateId), TimeSpan.Zero), isDeleted, null, null, null), isMatch ? SearchEntryMode.Match : SearchEntryMode.Include)); } // call NextResultAsync to get the info messages await reader.NextResultAsync(cancellationToken); IReadOnlyList <(string parameterName, string reason)> unsupportedSortingParameters; if (searchOptions.Sort?.Count > 0) { unsupportedSortingParameters = searchOptions .UnsupportedSortingParams .Concat(searchOptions.Sort .Where(x => !x.searchParameterInfo.IsSortSupported()) .Select(s => (s.searchParameterInfo.Name, Core.Resources.SortNotSupported))).ToList(); } else { unsupportedSortingParameters = searchOptions.UnsupportedSortingParams; } return(new SearchResult(resources, searchOptions.UnsupportedSearchParams, unsupportedSortingParameters, moreResults ? newContinuationId.Value.ToString(CultureInfo.InvariantCulture) : null)); } } }