class ElasticGraph::GraphQL::DatastoreQuery::DocumentPaginator
pagination. (Paginator also supports aggregation bucket pagination.)
contains most of the logic. This merely adapts the ‘Paginator` to the needs of document
Contains query logic related to pagination. Mostly delegates to `Paginator`, which
def effective_size
def effective_size individual_docs_needed ? paginator.requested_page_size : 0 end
def effective_sort
def effective_sort return [] unless effective_size > 0 paginator.search_in_reverse? ? reverse_sort : sort end
def reverse_sort
def reverse_sort @reverse_sort ||= sort.map do |sort_clause| sort_clause.transform_values do |options| { "order" => DIRECTION_OPPOSITES.fetch(options.fetch("order")), "missing" => MISSING_OPPOSITES.fetch(options.fetch("missing")) } end end end
def search_after
def search_after paginator.search_after&.then do |cursor| decoded_cursor_factory.sort_fields.map do |field| cursor.sort_values.fetch(field) do raise ::GraphQL::ExecutionError, "`#{cursor.encode}` is not a valid cursor for the current `#{schema_element_names.order_by}` argument." end end end end
def sort
def sort @sort ||= sort_clauses.map do |clause| clause.transform_values do |options| # As per the Elasticsearch docs[^1] missing/null values get sorted last by default, but we can control # it here. We want to control it here to make our sorting behavior more consistent in a couple ways: # # 1. We want _document_ sorting and _aggregation_ sorting to behave the same. Aggregation sorting puts # missing value buckets first when sorting ascending and last when sorting descending[^2]. Note that in # Elasticsearch 7.16[^3] and above, you can control if missing buckets go first or last, but below that # version you have no control. Here we match that behavior. # 2. Clients are likely to expect that descending sorting will produce a list in reverse order from what # ascending sorting produces, but with the default behavior (missing/null values get sorted last), this # is not the case. We have to use the opposite `missing` option when the `order` is the opposite. # # [^1]: https://www.elastic.co/guide/en/elasticsearch/reference/7.10/sort-search-results.html#_missing_values # [^2]: https://www.elastic.co/guide/en/elasticsearch/reference/7.10/search-aggregations-bucket-composite-aggregation.html#_missing_bucket # [^3]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/search-aggregations-bucket-composite-aggregation.html#_missing_bucket missing = (options.fetch("order") == "asc") ? "_first" : "_last" options.merge({"missing" => missing}) end end end
def to_datastore_body
def to_datastore_body { size: effective_size, sort: effective_sort, search_after: search_after, track_total_hits: total_document_count_needed }.reject { |key, value| Array(value).empty? } end