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));
                    }
                }
        }
Esempio n. 2
0
        private async Task <SearchResult> SearchImpl(SearchOptions searchOptions, bool historySearch, CancellationToken cancellationToken)
        {
            await _model.EnsureInitialized();

            Expression searchExpression = searchOptions.Expression;

            // AND in the continuation token
            if (!string.IsNullOrWhiteSpace(searchOptions.ContinuationToken))
            {
                if (long.TryParse(searchOptions.ContinuationToken, NumberStyles.None, CultureInfo.InvariantCulture, out var token))
                {
                    var tokenExpression = Expression.SearchParameter(SqlSearchParameters.ResourceSurrogateIdParameter, Expression.GreaterThan(SqlFieldName.ResourceSurrogateId, null, token));
                    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(TableExpressionCombiner.Instance)
                                           .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 (var connection = new SqlConnection(_configuration.ConnectionString))
            {
                connection.Open();

                using (SqlCommand sqlCommand = connection.CreateCommand())
                {
                    var stringBuilder = new IndentedStringBuilder(new StringBuilder());

                    EnableTimeAndIoMessageLogging(stringBuilder, connection);

                    var queryGenerator = new SqlQueryGenerator(stringBuilder, new SqlQueryParameterManager(sqlCommand.Parameters), _model, historySearch);

                    expression.AcceptVisitor(queryGenerator, searchOptions);

                    sqlCommand.CommandText = stringBuilder.ToString();

                    LogSqlComand(sqlCommand);

                    using (var reader = await sqlCommand.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(
                                V1.Resource.ResourceTypeId,
                                V1.Resource.ResourceId,
                                V1.Resource.Version,
                                V1.Resource.IsDeleted,
                                V1.Resource.ResourceSurrogateId,
                                V1.Resource.RequestMethod,
                                _isMatch,
                                V1.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)
                        {
                            // we don't currently support sort
                            unsupportedSortingParameters = searchOptions.UnsupportedSortingParams.Concat(searchOptions.Sort.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));
                    }
                }
            }
        }
Esempio n. 3
0
        private async Task <SearchResult> SearchImpl(SearchOptions searchOptions, SqlSearchType searchType, string currentSearchParameterHash, 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 _, SortOrder sortOrder) = searchOptions.Sort.Count == 0 ? default : searchOptions.Sort[0];

                                                                       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.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(UntypedReferenceRewriter.Instance)
                                           .AcceptVisitor(_sqlRootExpressionRewriter)
                                           .AcceptVisitor(_sortRewriter, searchOptions)
                                           .AcceptVisitor(SearchParamTableExpressionReorderer.Instance)
                                           .AcceptVisitor(MissingSearchParamVisitor.Instance)
                                           .AcceptVisitor(NotExpressionRewriter.Instance)
                                           .AcceptVisitor(_chainFlatteningRewriter)
                                           .AcceptVisitor(ResourceColumnPredicatePushdownRewriter.Instance)
                                           .AcceptVisitor(DateTimeBoundedRangeRewriter.Instance)
                                           .AcceptVisitor(StringOverflowRewriter.Instance)
                                           .AcceptVisitor(NumericRangeRewriter.Instance)
                                           .AcceptVisitor(IncludeMatchSeedRewriter.Instance)
                                           .AcceptVisitor(TopRewriter.Instance, searchOptions)
                                           .AcceptVisitor(IncludeRewriter.Instance)
                                           ?? SqlRootExpression.WithResourceTableExpressions();

            using (SqlConnectionWrapper sqlConnectionWrapper = await _sqlConnectionWrapperFactory.ObtainSqlConnectionWrapperAsync(cancellationToken, 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,
                        searchType,
                        _schemaInformation,
                        currentSearchParameterHash);

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

                            var searchResult = new SearchResult(reader.GetInt32(0), searchOptions.UnsupportedSearchParams);

                            // call NextResultAsync to get the info messages
                            await reader.NextResultAsync(cancellationToken);

                            return(searchResult);
                        }

                        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;

                        var isResultPartial = false;

                        while (await reader.ReadAsync(cancellationToken))
                        {
                            PopulateResourceTableColumnsToRead(
                                reader,
                                out short resourceTypeId,
                                out string resourceId,
                                out int version,
                                out bool isDeleted,
                                out long resourceSurrogateId,
                                out string requestMethod,
                                out bool isMatch,
                                out bool isPartialEntry,
                                out bool isRawResourceMetaSet,
                                out string searchParameterHash,
                                out Stream rawResourceStream);

                            // 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;
                            }

                            string rawResource;
                            using (rawResourceStream)
                            {
                                rawResource = await CompressedRawResourceConverter.ReadCompressedRawResource(rawResourceStream);
                            }

                            // See if this resource is a continuation token candidate and increase the count
                            if (isMatch)
                            {
                                newContinuationId = resourceSurrogateId;

                                // Keep track of sort value if this is the last row.
                                // if we have more than 10 columns, it means sort expressions were added.
                                if (matchCount == searchOptions.MaxItemCount - 1 && reader.FieldCount > _resourceTableColumnCount + 1)
                                {
                                    sortValue = reader.GetValue(SortValueColumnName) as DateTime?;
                                }

                                matchCount++;
                            }

                            // 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,
                                                  searchParameterHash),
                                              isMatch ? SearchEntryMode.Match : SearchEntryMode.Include));
                        }

                        // call NextResultAsync to get the info messages
                        await reader.NextResultAsync(cancellationToken);

                        // 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,
                                });
                            }
                        }

                        if (isResultPartial)
                        {
                            _requestContextAccessor.FhirRequestContext.BundleIssues.Add(
                                new OperationOutcomeIssue(
                                    OperationOutcomeConstants.IssueSeverity.Warning,
                                    OperationOutcomeConstants.IssueType.Incomplete,
                                    Core.Resources.TruncatedIncludeMessage));
                        }

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