//---------------------------------------------------------------------------
        // Helper for FindText() test cases
        //---------------------------------------------------------------------------
        internal void FindTextHelper(SampleText sampleText, SearchText searchTextType, FindResults result1, FindResults result2, FindResults result3, FindResults result4, Type expectedException)
        {
            string searchText;

            // Pre-Condition Verify text is expected value <<sampleText>> 
            TS_SetText(sampleText, CheckType.IncorrectElementConfiguration);

            // Pre-Condition Text to search for is <<searchText>>
            TS_SetSearchText(out searchText, searchTextType, CheckType.Verification);

            // CallFindText(forwards, case-insensitive) <<result1>>
            if((searchTextType != SearchText.MatchesLastBlock) && (searchTextType != SearchText.MismatchedCaseLastBlock))
                TS_FindText(searchText, false, false, result1, expectedException, CheckType.Verification);
            else
                m_TestStep++;

            // CallFindText(forwards, case-sensitive)   <<result2>>
            if ((searchTextType != SearchText.MatchesLastBlock) && (searchTextType != SearchText.MismatchedCaseLastBlock))
                TS_FindText(searchText, false, true, result2, expectedException, CheckType.Verification);
            else
                m_TestStep++;

            // CallFindText(backwards,case-insensitive) <<result3>>
            if ((searchTextType != SearchText.MatchesFirstBlock) && (searchTextType != SearchText.MismatchedCaseFirstBlock))
                TS_FindText(searchText, true, false, result3, expectedException, CheckType.Verification);
            else
                m_TestStep++;


            // CallFindText(backwards,case-sensitive)  <<result4>>
            if ((searchTextType != SearchText.MatchesFirstBlock) && (searchTextType != SearchText.MismatchedCaseFirstBlock))
                TS_FindText(searchText, true, true, result4, expectedException, CheckType.Verification);
            else
                m_TestStep++;

        }
        internal void TS_FindText(string searchText, bool backward, bool ignoreCase, FindResults results, Type expectedException, CheckType checkType)
        {
            int actualResult = 0;  // contains flags for each "find result" that occurs
            string msg = "FindText(<<searchText>>," +
                        (backward == true ? "backward" : "forward") + "," +
                        (ignoreCase == true ? "ignore case" : "case-sensitive") + ")";
            TextPatternRange findRange = null;
            TextPatternRange documentRange = Pattern_DocumentRange(CheckType.Verification);

            if ( expectedException == null )
                Library.ValidateArgumentNonNull(searchText, "searchText cannot be null");

            Range_FindText(documentRange, ref findRange, searchText,
                    backward, ignoreCase, expectedException, checkType);

            // Identify actual range
            if (expectedException != null)
                actualResult += (int)FindResults.Exception;

            if (findRange == null)
                actualResult += (int)FindResults.Null;
            else
            {
                int compareResult = 0;   // End point comparison
                object val = true;
                string findText = " ";   // Required for FXCop Rule CA1820
                string allText = " "; // text for entire range.
                TextPatternRange hiddenRange = null;

                // Get text from FindRange, get text for entire document
                Range_GetText(findRange, ref findText, -1, null, checkType);
                Range_GetText(documentRange, ref allText, -1, null, checkType);
                TrimTrailingCRLF(m_le, ref allText);
                TrimTrailingCRLF(m_le, ref findText);
                TrimTrailingCRLF(m_le, ref searchText);

                if (string.Compare(searchText, findText, false, CultureInfo.InvariantCulture) == 0) // case sensitive
                    actualResult += (int)FindResults.MatchingRange;
                else if (string.Compare(searchText, findText, true, CultureInfo.InvariantCulture) == 0) // case insensitive
                {
                    actualResult += (int)FindResults.MatchingRangeCaseLess;
                }

                // Do start points match?
                Range_CompareEndpoints(documentRange, TextPatternRangeEndpoint.Start, findRange, TextPatternRangeEndpoint.Start, ref compareResult, null, checkType);
                if (compareResult == 0)
                    actualResult += (int)FindResults.MatchesFirst;

                // Do end points (mostly) match?
                Range_CompareEndpoints(documentRange, TextPatternRangeEndpoint.End, findRange, TextPatternRangeEndpoint.End, ref compareResult, null, checkType);
                if (TextLibrary.IsRichEdit(m_le))
                {
                    if ((compareResult >= 0) && (compareResult <= 2))
                        actualResult += (int)FindResults.MatchesLast;
                }
                else
                {
                    if (compareResult == 0)
                        actualResult += (int)FindResults.MatchesLast;
                }

                if (findText.Length == 0)
                    actualResult += (int)FindResults.EmptyRange;

                Range_FindAttribute(findRange, ref hiddenRange, TextPattern.IsHiddenAttribute, val, true, null, checkType);

                if (hiddenRange == null)
                    actualResult += (int)FindResults.NoHiddenText;
            }

            if ((actualResult & ((int)results)) > 0)
                Comment(msg + " returned " + Parse(results, expectedException));
            else
                ThrowMe(checkType, msg + " failed to return " + Parse(results, expectedException));
            m_TestStep++;
        }
        //---------------------------------------------------------------------------
        // Helper for FindAttribute() test cases
        //---------------------------------------------------------------------------
        internal void FindAttributeHelper(SampleText sampleText, TypeValue typeValue, AttributeType attribType, bool backward, FindResults FindResults, Type expectedException)
        {
            bool isConsistentAttributes;
            IDictionary attributes = null;
            TextPatternRange documentRange = null;

            // Pre-Condition Verify text is expected value <<sampleText>> 
            TS_SetText(sampleText, CheckType.IncorrectElementConfiguration);

            // Pre-Condition Identify & use <<attribType>> attributes
            documentRange = Pattern_DocumentRange(CheckType.IncorrectElementConfiguration);
            TS_GetAttributeValues(documentRange, out attributes, attribType, CheckType.Verification);

            // Pre-Condition Identify if document has consistent attribute values
            TS_VerifyAttributeRanges(attributes, out isConsistentAttributes);

            // Pre-Condition For each attribute, Val argument has <<typeValue>> type and <<typeValue>> value
            TS_SetAttributeValues(ref attributes, typeValue);

            // Call FindAttribute(<<backward>>) without errors (<<FindResults>>)/with <<expectedException, if not null>>
            TS_FindAttributes(attributes, typeValue, backward, FindResults, expectedException, CheckType.Verification);
        }
 /// ---------------------------------------------------------------------------
 /// <summary>Parses values for enum</summary>
 /// ---------------------------------------------------------------------------
 static public string ParseType(FindResults value)
 {
     return ParseType(value.GetType().ToString(), value.ToString());
 }
        internal void TS_FindAttributes(IDictionary attributes, TypeValue typeValue, bool backward, FindResults FindResults, Type expectedException, CheckType checkType)
        {
            int errorCount = 0;
            bool isEqual = false;
            string text = "";
            AutomationTextAttribute key = null;    // key / name of attribute
            object valueToFind = null;    // Value to perform FindAttribute on
            TextPatternRange callingRange = null;   // Range to do FindAttribute on, default to entire document
            TextPatternRange findRange = null;    // Range FindAttribute returns
            IDictionaryEnumerator enum1 = attributes.GetEnumerator(); // Enumerate through the dictionary

            callingRange = Pattern_DocumentRange(checkType);

            // Bug1134056: RichEdit oddness again. We can't just use the entire document range.
            TextLibrary.TrimRangeCRLF(m_le, callingRange);

            // Enumerate through attributes
            while (enum1.MoveNext())
            {
                string keyString = "";

                // get key and value for this attribute
                key = (AutomationTextAttribute)enum1.Key;
                keyString = Helpers.GetProgrammaticName(key);

                if (enum1.Value == TextPattern.MixedAttributeValue) // Try to get a smaller range for a non-mixed attribute value
                {
                    Comment("Range has mixed attribute value for attribute " + keyString + ". Tryig to create single-character range to determine non-mixed attribute");

                    // Limit range to first character so we should get CONSISTENT attribute value(!)
                    CreateEmptyRange(out callingRange, RangeLocation.Start, checkType);
                    Range_ExpandToEnclosingUnit(callingRange, TextUnit.Character, null, checkType);

                    Range_GetAttributeValue(callingRange, (AutomationTextAttribute)enum1.Key, ref valueToFind, null, checkType);

                    if (valueToFind == TextPattern.MixedAttributeValue)
                    {
                        ThrowMe(checkType, "Single character range should(!) not have a mixed attribute value for attribute " + keyString + ". (International Locale perhaps?)");
                    }
                }
                else if (enum1.Value == AutomationElement.NotSupported)
                {
                    Comment("Attribute " + keyString + " is not supported, skipping...");
                    continue;
                }
                else
                {
                    valueToFind = enum1.Value;
                }

                SetValue(typeValue, key, ref valueToFind); // cast to correct type

                string msg = "FindAttribute(" + keyString + "," +
                        (valueToFind != null ? valueToFind.ToString() : "NULL") + "," +
                        (backward == true ? "backwards" : "forwards") +
                        ")";

                // Finally, find the attribute value(!)
                Range_FindAttribute(callingRange, ref findRange, (AutomationTextAttribute)enum1.Key, valueToFind, backward,
                                    expectedException, checkType);

                // Did we get correct results?
                switch (FindResults)
                {
                    case FindResults.Exception:
                        if (expectedException == null)
                            ThrowMe(checkType, "Test code error, null expected exception is incorrect when getresult = " + Parse(FindResults, expectedException));
                        break;                  // actually takes place in Range_FindAttribute
                    case FindResults.MatchingRange:
                        if (findRange == null)
                        {
                            Comment(KNOWNISSUE + msg + " returned null range. Expected non-null range");
                            errorCount++;
                        }
                        else
                        {


                            Range_Compare(callingRange, findRange, ref isEqual, null, checkType);
                            if (isEqual == false)
                            {
                                string callingText = "", findText = "";
                                Comment(KNOWNISSUE + "Comparison failed. Expected calling and actual range to have matching text");
                                Range_GetText(callingRange, ref callingText, -1, null, checkType);
                                Comment("Calling range text = '" + TrimText(callingText, 512) + "'");
                                Range_GetText(findRange, ref findText, -1, null, checkType);
                                Comment("Actual  range text = '" + TrimText(findText, 512) + "'");
                                errorCount++;
                            }
                        }
                        break;
                    case FindResults.EmptyRange:
                        if (findRange == null)
                        {
                            Comment(KNOWNISSUE + msg + " returned null range. Expected non-null range");
                            errorCount++;
                        }
                        else
                        {
                            Range_GetText(findRange, ref text, -1, null, checkType);
                            if (text.Length != 0)
                            {
                                Comment(KNOWNISSUE + msg + " returned non-zero length text for range. Expected zero-length" +
                                        ", text = '" + TrimText(Range_GetText(findRange), 512) + "'");
                                errorCount++;
                            }
                        }
                        break;
                    case FindResults.Null:
                        if (findRange != null)
                        {
                            if (key == TextPattern.TabsAttribute)
                            {
                                // This test condition failure is only really valid for this attribute
                                // if we have a control with a non-zero length array of tab marks
                                if (((double[])valueToFind).Length == 0)
                                    continue; // skip to next attribute
                            }
                            Comment(KNOWNISSUE + msg + " returned non-null range, expected null for attribute " +
                                    keyString +
                                    ", text = '" + TrimText(Range_GetText(findRange), 512) + "'");
                            errorCount++;
                        }
                        break;
                    default:
                        throw new ArgumentException("TS_FindAttributes() has no support for " + ParseType(FindResults));
                }
            }
            if (errorCount == 0)
                Comment("FindAttribute(...) returned the correct range (" + ParseType(FindResults) + ")");
            else
                ThrowMe(checkType, "FindAttribute(...) returned " + errorCount + " incorrect results");

            m_TestStep++;
        }
 static public string Parse(FindResults value, Type type)
 {
     switch (value)
     {
         case FindResults.MatchingRange: return "verify result MATCHING range (case sensitive)";
         case FindResults.MatchingRangeCaseLess: return "matching range (case insensitive)";
         case FindResults.MatchingFirstCaseLess: return "Matches first range and is caseless";
         case FindResults.MatchingLastCaseLess: return "Matches last range and is caseless";
         case FindResults.EmptyRange: return "verify result EMPTY range";
         case FindResults.Null: return "verify result NULL";
         case FindResults.Exception:
             if (type == null)
                 throw new ArgumentException("Can't haveFindResults.Exception with null value for Type");
             return ParseEx1(type);
         case FindResults.NoHiddenText: return "verify result has NO hidden text";
         case FindResults.MatchesFirst: return "verify result MATCHES 1st instance of dup'd text";
         case FindResults.MatchesLast: return "verify result MATCHES LAST instance of dup'd text";
         default:
             throw new ArgumentException("Parse() has no support for " + ParseType(value));
     }
 }
        protected async Task <FindResults <TResult> > FindAsAsync <TResult>(object query) where TResult : class, new()
        {
            if (query == null)
            {
                throw new ArgumentNullException(nameof(query));
            }

            var pagableQuery = query as IPagableQuery;
            // don't use caching with snapshot paging.
            bool allowCaching = IsCacheEnabled && (pagableQuery == null || pagableQuery.UseSnapshotPaging == false);

            Func <FindResults <TResult>, Task <FindResults <TResult> > > getNextPageFunc = async r => {
                if (!String.IsNullOrEmpty(r.ScrollId))
                {
                    var scrollResponse = await Context.ElasticClient.ScrollAsync <TResult>("2m", r.ScrollId).AnyContext();

                    return(new FindResults <TResult> {
                        Documents = scrollResponse.Documents.ToList(),
                        Total = r.Total,
                        ScrollId = r.ScrollId
                    });
                }

                if (pagableQuery == null)
                {
                    return(new FindResults <TResult>());
                }

                pagableQuery.Page = pagableQuery.Page == null ? 2 : pagableQuery.Page + 1;
                return(await FindAsAsync <TResult>(query).AnyContext());
            };

            string cacheSuffix = pagableQuery?.ShouldUseLimit() == true?pagableQuery.Page?.ToString() ?? "1" : String.Empty;

            FindResults <TResult> result;

            if (allowCaching)
            {
                result = await GetCachedQueryResultAsync <FindResults <TResult> >(query, cacheSuffix : cacheSuffix).AnyContext();

                if (result != null)
                {
                    result.GetNextPageFunc = getNextPageFunc;
                    return(result);
                }
            }

            var searchDescriptor = ConfigureSearchDescriptor(null, query);

            if (pagableQuery?.UseSnapshotPaging == true)
            {
                searchDescriptor.SearchType(SearchType.Scan).Scroll("2m");
            }

            var response = await Context.ElasticClient.SearchAsync <TResult>(searchDescriptor).AnyContext();

            if (!response.IsValid)
            {
                throw new ApplicationException($"Elasticsearch error code \"{response.ConnectionStatus.HttpStatusCode}\".", response.ConnectionStatus.OriginalException);
            }

            if (pagableQuery?.UseSnapshotPaging == true)
            {
                var scanResponse = response;
                response = await Context.ElasticClient.ScrollAsync <TResult>("2m", response.ScrollId).AnyContext();

                if (!response.IsValid)
                {
                    throw new ApplicationException($"Elasticsearch error code \"{response.ConnectionStatus.HttpStatusCode}\".", response.ConnectionStatus.OriginalException);
                }

                result = new FindResults <TResult> {
                    Documents       = response.Documents.ToList(),
                    Total           = scanResponse.Total,
                    ScrollId        = scanResponse.ScrollId,
                    GetNextPageFunc = getNextPageFunc
                };
            }
            else if (pagableQuery?.ShouldUseLimit() == true)
            {
                result = new FindResults <TResult> {
                    Documents       = response.Documents.Take(pagableQuery.GetLimit()).ToList(),
                    Total           = response.Total,
                    HasMore         = pagableQuery.ShouldUseLimit() && response.Documents.Count() > pagableQuery.GetLimit(),
                    GetNextPageFunc = getNextPageFunc
                };
            }
            else
            {
                result = new FindResults <TResult> {
                    Documents = response.Documents.ToList(),
                    Total     = response.Total
                };
            }

            result.Facets = response.ToFacetResults();

            if (allowCaching)
            {
                var nextPageFunc = result.GetNextPageFunc;
                result.GetNextPageFunc = null;
                await SetCachedQueryResultAsync(query, result, cacheSuffix : cacheSuffix).AnyContext();

                result.GetNextPageFunc = nextPageFunc;
            }

            return(result);
        }
        public async Task <FindResults <T> > GetByIdsAsync(ICollection <string> ids, bool useCache = false, TimeSpan?expiresIn = null)
        {
            var results = new FindResults <T>();

            if (ids == null || ids.Count == 0)
            {
                return(results);
            }

            var options = Options as IQueryOptions;

            if (options == null || !options.HasIdentity)
            {
                throw new NotSupportedException("Model type must implement IIdentity.");
            }

            if (IsCacheEnabled && useCache)
            {
                var cacheHits = await Cache.GetAllAsync <T>(ids.Distinct()).AnyContext();

                results.Documents.AddRange(cacheHits.Where(kvp => kvp.Value.HasValue).Select(kvp => kvp.Value.Value));
                results.Total = results.Documents.Count;

                var notCachedIds = ids.Except(results.Documents.Select(i => ((IIdentity)i).Id)).ToArray();
                if (notCachedIds.Length == 0)
                {
                    return(results);
                }
            }

            var itemsToFind = new List <string>(ids.Distinct().Except(results.Documents.Select(i => ((IIdentity)i).Id)));
            var multiGet    = new MultiGetDescriptor();

            if (GetParentIdFunc == null)
            {
                itemsToFind.ForEach(id => multiGet.Get <T>(f => f.Id(id).Index(GetIndexById(id))));

                var multiGetResults = await Context.ElasticClient.MultiGetAsync(multiGet).AnyContext();

                foreach (var doc in multiGetResults.Documents)
                {
                    if (!doc.Found)
                    {
                        continue;
                    }

                    results.Documents.Add(doc.Source as T);
                    itemsToFind.Remove(doc.Id);
                }
            }

            // fallback to doing a find
            if (itemsToFind.Count > 0 && (GetParentIdFunc != null || GetDocumentIndexFunc != null))
            {
                results.Documents.AddRange((await FindAsync(new ElasticQuery().WithIds(itemsToFind)).AnyContext()).Documents);
            }

            if (IsCacheEnabled && useCache)
            {
                foreach (var item in results.Documents)
                {
                    await Cache.SetAsync(((IIdentity)item).Id, item, expiresIn.HasValue?DateTime.UtcNow.Add(expiresIn.Value) : DateTime.UtcNow.AddSeconds(RepositoryConstants.DEFAULT_CACHE_EXPIRATION_SECONDS)).AnyContext();
                }
            }

            results.Total = results.Documents.Count;
            return(results);
        }
        public virtual async Task <FindResults <TResult> > FindAsAsync <TResult>(IRepositoryQuery query, ICommandOptions options = null) where TResult : class, new()
        {
            options = ConfigureOptions(options.As <T>());
            bool useSnapshotPaging = options.ShouldUseSnapshotPaging();
            // don't use caching with snapshot paging.
            bool allowCaching = IsCacheEnabled && useSnapshotPaging == false;

            await OnBeforeQueryAsync(query, options, typeof(TResult)).AnyContext();

            await RefreshForConsistency(query, options).AnyContext();

            async Task <FindResults <TResult> > GetNextPageFunc(FindResults <TResult> r)
            {
                var previousResults = r;

                if (previousResults == null)
                {
                    throw new ArgumentException(nameof(r));
                }

                string scrollId = previousResults.GetScrollId();

                if (!String.IsNullOrEmpty(scrollId))
                {
                    var scrollResponse = await _client.ScrollAsync <TResult>(options.GetSnapshotLifetime(), scrollId).AnyContext();

                    _logger.LogRequest(scrollResponse, options.GetQueryLogLevel());

                    var results = scrollResponse.ToFindResults();
                    results.Page    = previousResults.Page + 1;
                    results.HasMore = scrollResponse.Hits.Count >= options.GetLimit() || scrollResponse.Hits.Count >= options.GetMaxLimit();

                    // clear the scroll
                    if (!results.HasMore)
                    {
                        await _client.ClearScrollAsync(s => s.ScrollId(scrollId));
                    }

                    return(results);
                }

                if (options.ShouldUseSearchAfterPaging())
                {
                    options.SearchAfterToken(previousResults.GetSearchAfterToken());
                }

                if (options == null)
                {
                    return(new FindResults <TResult>());
                }

                options?.PageNumber(!options.HasPageNumber() ? 2 : options.GetPage() + 1);
                return(await FindAsAsync <TResult>(query, options).AnyContext());
            }

            string cacheSuffix = options?.HasPageLimit() == true?String.Concat(options.GetPage().ToString(), ":", options.GetLimit().ToString()) : null;

            FindResults <TResult> result;

            if (allowCaching)
            {
                result = await GetCachedQueryResultAsync <FindResults <TResult> >(options, cacheSuffix : cacheSuffix).AnyContext();

                if (result != null)
                {
                    ((IGetNextPage <TResult>)result).GetNextPageFunc = async r => await GetNextPageFunc(r).AnyContext();

                    return(result);
                }
            }

            ISearchResponse <TResult> response;

            if (useSnapshotPaging == false || !options.HasSnapshotScrollId())
            {
                var searchDescriptor = await CreateSearchDescriptorAsync(query, options).AnyContext();

                if (useSnapshotPaging)
                {
                    searchDescriptor.Scroll(options.GetSnapshotLifetime());
                }

                if (query.ShouldOnlyHaveIds())
                {
                    searchDescriptor.Source(false);
                }

                response = await _client.SearchAsync <TResult>(searchDescriptor).AnyContext();
            }
            else
            {
                response = await _client.ScrollAsync <TResult>(options.GetSnapshotLifetime(), options.GetSnapshotScrollId()).AnyContext();
            }

            if (response.IsValid)
            {
                _logger.LogRequest(response, options.GetQueryLogLevel());
            }
            else
            {
                if (response.ApiCall.HttpStatusCode.GetValueOrDefault() == 404)
                {
                    return(new FindResults <TResult>());
                }

                _logger.LogErrorRequest(response, "Error while searching");
                throw new ApplicationException(response.GetErrorMessage(), response.OriginalException);
            }

            if (useSnapshotPaging)
            {
                result         = response.ToFindResults();
                result.HasMore = response.Hits.Count >= options.GetLimit();

                // clear the scroll
                if (!result.HasMore)
                {
                    var scrollId = result.GetScrollId();
                    if (!String.IsNullOrEmpty(scrollId))
                    {
                        await _client.ClearScrollAsync(s => s.ScrollId(result.GetScrollId()));
                    }
                }

                ((IGetNextPage <TResult>)result).GetNextPageFunc = GetNextPageFunc;
            }
            else
            {
                int limit = options.GetLimit();
                result         = response.ToFindResults(limit);
                result.HasMore = response.Hits.Count > limit || response.Hits.Count >= options.GetMaxLimit();
                ((IGetNextPage <TResult>)result).GetNextPageFunc = GetNextPageFunc;
            }

            if (options.HasSearchAfter())
            {
                result.SetSearchBeforeToken();
                if (result.HasMore)
                {
                    result.SetSearchAfterToken();
                }
            }
            else if (options.HasSearchBefore())
            {
                // reverse results
                bool hasMore = result.HasMore;
                result         = new FindResults <TResult>(result.Hits.Reverse(), result.Total, result.Aggregations.ToDictionary(k => k.Key, v => v.Value), GetNextPageFunc, result.Data.ToDictionary(k => k.Key, v => v.Value));
                result.HasMore = hasMore;

                result.SetSearchAfterToken();
                if (result.HasMore)
                {
                    result.SetSearchBeforeToken();
                }
            }
            else if (result.HasMore)
            {
                result.SetSearchAfterToken();
            }

            result.Page = options.GetPage();

            if (!allowCaching)
            {
                return(result);
            }

            var nextPageFunc = ((IGetNextPage <TResult>)result).GetNextPageFunc;

            ((IGetNextPage <TResult>)result).GetNextPageFunc = null;
            await SetCachedQueryResultAsync(options, result, cacheSuffix : cacheSuffix).AnyContext();

            ((IGetNextPage <TResult>)result).GetNextPageFunc = nextPageFunc;

            return(result);
        }
Exemple #10
0
        public async Task BuildAsync <T>(QueryBuilderContext <T> ctx) where T : class, new()
        {
            if (!ctx.Source.ShouldEnforceEventStackFilter())
            {
                return;
            }

            // TODO: Handle search expressions as well
            string filter             = ctx.Source.GetFilterExpression() ?? String.Empty;
            bool   altInvertRequested = false;

            if (filter.StartsWith("@!"))
            {
                altInvertRequested = true;
                filter             = filter.Substring(2);
                ctx.Source.FilterExpression(filter);
            }

            // when inverting to get excluded stack ids, add is_deleted as an alternate inverted criteria
            if (ctx.Options.GetSoftDeleteMode() == SoftDeleteQueryMode.ActiveOnly)
            {
                ctx.SetAlternateInvertedCriteria(new TermNode {
                    Field = "is_deleted", Term = "true"
                });
            }

            var stackFilter = await _eventStackFilter.GetStackFilterAsync(filter, ctx);

            const int stackIdLimit = 10000;

            string[] stackIds   = Array.Empty <string>();
            long     stackTotal = 0;

            string stackFilterValue  = stackFilter.Filter;
            bool   isStackIdsNegated = false; //= stackFilter.HasStatusOpen && !altInvertRequested;

            if (isStackIdsNegated)
            {
                stackFilterValue = stackFilter.InvertedFilter;
            }

            if (String.IsNullOrEmpty(stackFilterValue) && (!ctx.Source.ShouldEnforceEventStackFilter() || ctx.Options.GetSoftDeleteMode() != SoftDeleteQueryMode.ActiveOnly))
            {
                return;
            }

            _logger.LogTrace("Source: {Filter} Stack Filter: {StackFilter} Inverted Stack Filter: {InvertedStackFilter}", filter, stackFilter.Filter, stackFilter.InvertedFilter);

            var systemFilterQuery = GetSystemFilterQuery(ctx, isStackIdsNegated);

            systemFilterQuery.FilterExpression(stackFilterValue);
            var softDeleteMode = isStackIdsNegated ? SoftDeleteQueryMode.All : SoftDeleteQueryMode.ActiveOnly;

            systemFilterQuery.EventStackFilterInverted(isStackIdsNegated);

            FindResults <Stack> results = null;
            var tooManyStacksCheck      = await _cacheClient.GetAsync <long>(GetQueryHash(systemFilterQuery));

            if (tooManyStacksCheck.HasValue)
            {
                stackTotal = tooManyStacksCheck.Value;
            }
            else
            {
                results = await _stackRepository.GetIdsByQueryAsync(q => systemFilterQuery.As <Stack>(), o => o.PageLimit(stackIdLimit).SoftDeleteMode(softDeleteMode)).AnyContext();

                stackTotal = results.Total;
            }

            if (stackTotal > stackIdLimit)
            {
                if (!tooManyStacksCheck.HasValue)
                {
                    await _cacheClient.SetAsync(GetQueryHash(systemFilterQuery), stackTotal, TimeSpan.FromMinutes(15));
                }

                _logger.LogTrace("Query: {query} will be inverted due to id limit: {ResultCount}", stackFilterValue, stackTotal);
                isStackIdsNegated = !isStackIdsNegated;
                stackFilterValue  = isStackIdsNegated ? stackFilter.InvertedFilter : stackFilter.Filter;
                systemFilterQuery.FilterExpression(stackFilterValue);
                softDeleteMode = isStackIdsNegated ? SoftDeleteQueryMode.All : SoftDeleteQueryMode.ActiveOnly;
                systemFilterQuery.EventStackFilterInverted(isStackIdsNegated);

                tooManyStacksCheck = await _cacheClient.GetAsync <long>(GetQueryHash(systemFilterQuery));

                if (tooManyStacksCheck.HasValue)
                {
                    stackTotal = tooManyStacksCheck.Value;
                }
                else
                {
                    results = await _stackRepository.GetIdsByQueryAsync(q => systemFilterQuery.As <Stack>(), o => o.PageLimit(stackIdLimit).SoftDeleteMode(softDeleteMode)).AnyContext();

                    stackTotal = results.Total;
                }
            }

            if (stackTotal > stackIdLimit)
            {
                if (!tooManyStacksCheck.HasValue)
                {
                    await _cacheClient.SetAsync(GetQueryHash(systemFilterQuery), stackTotal, TimeSpan.FromMinutes(15));
                }
                throw new DocumentLimitExceededException("Please limit your search criteria.");
            }

            if (results?.Hits != null)
            {
                stackIds = results.Hits.Select(h => h.Id).ToArray();
            }

            _logger.LogTrace("Setting stack filter with {IdCount} ids", stackIds?.Length ?? 0);

            if (!isStackIdsNegated)
            {
                if (stackIds.Length > 0)
                {
                    ctx.Source.Stack(stackIds);
                }
                else
                {
                    ctx.Source.Stack("none");
                }
            }
            else
            {
                if (stackIds.Length > 0)
                {
                    ctx.Source.ExcludeStack(stackIds);
                }
            }

            // Strips stack only fields and stack only special fields
            string eventFilter = await _eventStackFilter.GetEventFilterAsync(filter, ctx);

            ctx.Source.FilterExpression(eventFilter);
        }