private static Tuple <string, string, string> GetFormattedFilters(
            string[] expressions,
            CosmosElement[] orderByItems,
            SortOrder[] sortOrders)
        {
            // When we run cross partition queries,
            // we only serialize the continuation token for the partition that we left off on.
            // The only problem is that when we resume the order by query,
            // we don't have continuation tokens for all other partition.
            // The saving grace is that the data has a composite sort order(query sort order, partition key range id)
            // so we can generate range filters which in turn the backend will turn into rid based continuation tokens,
            // which is enough to get the streams of data flowing from all partitions.
            // The details of how this is done is described below:
            int           numOrderByItems = expressions.Length;
            bool          isSingleOrderBy = numOrderByItems == 1;
            StringBuilder left            = new StringBuilder();
            StringBuilder target          = new StringBuilder();
            StringBuilder right           = new StringBuilder();

            Tuple <StringBuilder, StringBuilder, StringBuilder> builders = new Tuple <StringBuilder, StringBuilder, StringBuilder>(left, right, target);

            if (isSingleOrderBy)
            {
                //For a single order by query we resume the continuations in this manner
                //    Suppose the query is SELECT* FROM c ORDER BY c.string ASC
                //        And we left off on partition N with the value "B"
                //        Then
                //            All the partitions to the left will have finished reading "B"
                //            Partition N is still reading "B"
                //            All the partitions to the right have let to read a "B
                //        Therefore the filters should be
                //            > "B" , >= "B", and >= "B" respectively
                //    Repeat the same logic for DESC and you will get
                //            < "B", <= "B", and <= "B" respectively
                //    The general rule becomes
                //        For ASC
                //            > for partitions to the left
                //            >= for the partition we left off on
                //            >= for the partitions to the right
                //        For DESC
                //            < for partitions to the left
                //            <= for the partition we left off on
                //            <= for the partitions to the right
                string        expression          = expressions.First();
                SortOrder     sortOrder           = sortOrders.First();
                CosmosElement orderByItem         = orderByItems.First();
                string        orderByItemToString = JsonConvert.SerializeObject(orderByItem, DefaultJsonSerializationSettings.Value);
                left.Append($"{expression} {(sortOrder == SortOrder.Descending ? "<" : ">")} {orderByItemToString}");
                target.Append($"{expression} {(sortOrder == SortOrder.Descending ? "<=" : ">=")} {orderByItemToString}");
                right.Append($"{expression} {(sortOrder == SortOrder.Descending ? "<=" : ">=")} {orderByItemToString}");
            }
            else
            {
                //For a multi order by query
                //    Suppose the query is SELECT* FROM c ORDER BY c.string ASC, c.number ASC
                //        And we left off on partition N with the value("A", 1)
                //        Then
                //            All the partitions to the left will have finished reading("A", 1)
                //            Partition N is still reading("A", 1)
                //            All the partitions to the right have let to read a "(A", 1)
                //        The filters are harder to derive since their are multiple columns
                //        But the problem reduces to "How do you know one document comes after another in a multi order by query"
                //        The answer is to just look at it one column at a time.
                //        For this particular scenario:
                //        If a first column is greater ex. ("B", blah), then the document comes later in the sort order
                //            Therefore we want all documents where the first column is greater than "A" which means > "A"
                //        Or if the first column is a tie, then you look at the second column ex. ("A", blah).
                //            Therefore we also want all documents where the first column was a tie but the second column is greater which means = "A" AND > 1
                //        Therefore the filters should be
                //            (> "A") OR (= "A" AND > 1), (> "A") OR (= "A" AND >= 1), (> "A") OR (= "A" AND >= 1)
                //            Notice that if we repeated the same logic we for single order by we would have gotten
                //            > "A" AND > 1, >= "A" AND >= 1, >= "A" AND >= 1
                //            which is wrong since we missed some documents
                //    Repeat the same logic for ASC, DESC
                //            (> "A") OR (= "A" AND < 1), (> "A") OR (= "A" AND <= 1), (> "A") OR (= "A" AND <= 1)
                //        Again for DESC, ASC
                //            (< "A") OR (= "A" AND > 1), (< "A") OR (= "A" AND >= 1), (< "A") OR (= "A" AND >= 1)
                //        And again for DESC DESC
                //            (< "A") OR (= "A" AND < 1), (< "A") OR (= "A" AND <= 1), (< "A") OR (= "A" AND <= 1)
                //    The general we look at all prefixes of the order by columns to look for tie breakers.
                //        Except for the full prefix whose last column follows the rules for single item order by
                //        And then you just OR all the possibilities together
                for (int prefixLength = 1; prefixLength <= numOrderByItems; prefixLength++)
                {
                    ArraySegment <string>        expressionPrefix   = new ArraySegment <string>(expressions, 0, prefixLength);
                    ArraySegment <SortOrder>     sortOrderPrefix    = new ArraySegment <SortOrder>(sortOrders, 0, prefixLength);
                    ArraySegment <CosmosElement> orderByItemsPrefix = new ArraySegment <CosmosElement>(orderByItems, 0, prefixLength);

                    bool lastPrefix  = prefixLength == numOrderByItems;
                    bool firstPrefix = prefixLength == 1;

                    CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, "(");

                    for (int index = 0; index < prefixLength; index++)
                    {
                        string        expression  = expressionPrefix.ElementAt(index);
                        SortOrder     sortOrder   = sortOrderPrefix.ElementAt(index);
                        CosmosElement orderByItem = orderByItemsPrefix.ElementAt(index);
                        bool          lastItem    = index == prefixLength - 1;

                        // Append Expression
                        CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, expression);
                        CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, " ");

                        // Append binary operator
                        if (lastItem)
                        {
                            string inequality = sortOrder == SortOrder.Descending ? "<" : ">";
                            CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, inequality);
                            if (lastPrefix)
                            {
                                CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, string.Empty, "=", "=");
                            }
                        }
                        else
                        {
                            CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, "=");
                        }

                        // Append SortOrder
                        string orderByItemToString = JsonConvert.SerializeObject(orderByItem, DefaultJsonSerializationSettings.Value);
                        CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, " ");
                        CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, orderByItemToString);
                        CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, " ");

                        if (!lastItem)
                        {
                            CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, "AND ");
                        }
                    }

                    CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, ")");
                    if (!lastPrefix)
                    {
                        CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, " OR ");
                    }
                }
            }

            return(new Tuple <string, string, string>(left.ToString(), target.ToString(), right.ToString()));
        }
 private static void AppendToBuilders(Tuple <StringBuilder, StringBuilder, StringBuilder> builders, object str)
 {
     CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, str, str, str);
 }