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)
            {
                var continuationToken = ContinuationToken.FromString(searchOptions.ContinuationToken);
                if (continuationToken != null)
                {
                    // in case it's a _lastUpdated sort optimization
                    if (string.IsNullOrEmpty(continuationToken.SortValue))
                    {
                        (SearchParameterInfo searchParamInfo, SortOrder sortOrder) = searchOptions.GetFirstSupportedSortParam();

                        Expression lastUpdatedExpression = sortOrder == SortOrder.Ascending ? Expression.GreaterThan(SqlFieldName.ResourceSurrogateId, null, continuationToken.ResourceSurrogateId)
                                                                                                    : Expression.LessThan(SqlFieldName.ResourceSurrogateId, null, continuationToken.ResourceSurrogateId);

                        var tokenExpression = Expression.SearchParameter(SqlSearchParameters.ResourceSurrogateIdParameter, lastUpdatedExpression);
                        searchExpression = searchExpression == null ? tokenExpression : (Expression)Expression.And(tokenExpression, searchExpression);
                    }
                }
                else
                {
                    throw new BadRequestException(Resources.InvalidContinuationToken);
                }
            }

            if (searchOptions.CountOnly)
            {
                // if we're only returning a count, discard any _include parameters since included resources are not counted.
                searchExpression = searchExpression?.AcceptVisitor(RemoveIncludesRewriter.Instance);
            }

            SqlRootExpression expression = (SqlRootExpression)searchExpression
                                           ?.AcceptVisitor(LastUpdatedToResourceSurrogateIdRewriter.Instance)
                                           .AcceptVisitor(DateTimeEqualityRewriter.Instance)
                                           .AcceptVisitor(FlatteningRewriter.Instance)
                                           .AcceptVisitor(_sqlRootExpressionRewriter)
                                           .AcceptVisitor(_sortRewriter, searchOptions)
                                           .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, _schemaInformation);

                    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;

                        // Currently we support only date time sort type.
                        DateTime?sortValue = null;

                        while (await reader.ReadAsync(cancellationToken))
                        {
                            (short resourceTypeId, string resourceId, int version, bool isDeleted, long resourceSurrogateId, string requestMethod, bool isMatch, bool isPartialEntry, bool isRawResourceMetaSet, Stream rawResourceStream) = reader.ReadRow(
                                VLatest.Resource.ResourceTypeId,
                                VLatest.Resource.ResourceId,
                                VLatest.Resource.Version,
                                VLatest.Resource.IsDeleted,
                                VLatest.Resource.ResourceSurrogateId,
                                VLatest.Resource.RequestMethod,
                                _isMatch,
                                _isPartial,
                                VLatest.Resource.IsRawResourceMetaSet,
                                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;

                                // At this point we are at the last row.
                                // if we have more columns, it means sort expressions were added.
                                if (reader.FieldCount > 10)
                                {
                                    sortValue = reader.GetValue(SortValueColumnName) as DateTime?;
                                }

                                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();
                                    }

                            // as long as at least one entry was marked as partial, this resultset
                            // should be marked as partial
                            _isResultPartial = _isResultPartial || isPartialEntry;

                            resources.Add(new SearchResultEntry(
                                              new ResourceWrapper(
                                                  resourceId,
                                                  version.ToString(CultureInfo.InvariantCulture),
                                                  _model.GetResourceTypeName(resourceTypeId),
                                                  new RawResource(rawResource, FhirResourceFormat.Json, isMetaSet: isRawResourceMetaSet),
                                                  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;
                        }

                        // Continuation token prep
                        ContinuationToken continuationToken = null;
                        if (moreResults)
                        {
                            if (sortValue.HasValue)
                            {
                                continuationToken = new ContinuationToken(new object[]
                                {
                                    sortValue.Value.ToString("o"),
                                    newContinuationId ?? 0,
                                });
                            }
                            else
                            {
                                continuationToken = new ContinuationToken(new object[]
                                {
                                    newContinuationId ?? 0,
                                });
                            }
                        }

                        return(new SearchResult(resources, searchOptions.UnsupportedSearchParams, unsupportedSortingParameters, continuationToken?.ToJson(), _isResultPartial));
                    }
                }
        }