Class: ElasticGraph::GraphQL::DatastoreResponse::SearchResponse
- Inherits:
-
Object
- Object
- ElasticGraph::GraphQL::DatastoreResponse::SearchResponse
- Extended by:
- Forwardable
- Includes:
- Enumerable
- Defined in:
- lib/elastic_graph/graphql/datastore_response/search_response.rb
Overview
Represents a search response from the datastore. Exposes both the raw metadata provided by the datastore and the collection of documents. Can be treated as a collection of documents when you don’t care about the metadata.
Constant Summary collapse
- EXCLUDED_METADATA_KEYS =
%w[hits aggregations].freeze
- RAW_EMPTY =
Benign empty response that can be used in place of datastore response errors as needed.
{"hits" => {"hits" => [], "total" => {"value" => 0}}}.freeze
- EMPTY =
build(RAW_EMPTY)
Class Method Summary collapse
- .build(raw_data, decoded_cursor_factory: DecodedCursor::Factory::Null, aggregations_unavailable_reason: nil) ⇒ Object
- .synthesize_from_ids(index, ids, decoded_cursor_factory: DecodedCursor::Factory::Null) ⇒ Object
Instance Method Summary collapse
- #aggregations ⇒ Object
- #docs_description ⇒ Object
-
#filter_results(field_path, values, size) ⇒ Object
Returns a response filtered to results that have matching ‘values` at the given `field_path`, limiting the results to the first `size` results.
- #to_s ⇒ Object (also: #inspect)
- #total_document_count(default: nil) ⇒ Object
Class Method Details
.build(raw_data, decoded_cursor_factory: DecodedCursor::Factory::Null, aggregations_unavailable_reason: nil) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/elastic_graph/graphql/datastore_response/search_response.rb', line 31 def self.build(raw_data, decoded_cursor_factory: DecodedCursor::Factory::Null, aggregations_unavailable_reason: nil) documents = raw_data.fetch("hits").fetch("hits").map do |doc| Document.build(doc, decoded_cursor_factory: decoded_cursor_factory) end = raw_data.except(*EXCLUDED_METADATA_KEYS) ["hits"] = raw_data.fetch("hits").except("hits") # `hits.total` is exposed as an object like: # # { # "value" => 200, # "relation" => "eq", # or "gte" # } # # This allows it to provide a lower bound on the number of hits, rather than having # to give an exact count. We may want to handle the `gte` case differently at some # point but for now we just use the value as-is. # # In the case where `track_total_hits` flag is set to `false`, `hits.total` field will be completely absent. # This means the client intentionally chose not to query the total doc count, and `total_document_count` will be nil. # In this case, we will throw an exception if the client later tries to access `total_document_count`. total_document_count = .dig("hits", "total", "value") new( raw_data:, metadata:, documents:, total_document_count:, aggregations_unavailable_reason:, decoded_cursor_factory: ) end |
.synthesize_from_ids(index, ids, decoded_cursor_factory: DecodedCursor::Factory::Null) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/elastic_graph/graphql/datastore_response/search_response.rb', line 65 def self.synthesize_from_ids(index, ids, decoded_cursor_factory: DecodedCursor::Factory::Null) hits = ids.map do |id| { "_index" => index, "_type" => "_doc", "_id" => id, "_score" => nil, "_source" => {"id" => id}, "sort" => [id] } end raw_data = { "took" => 0, "timed_out" => false, "_shards" => { "total" => 0, "successful" => 0, "skipped" => 0, "failed" => 0 }, "hits" => { "total" => { "value" => ids.size, "relation" => "eq" }, "max_score" => nil, "hits" => hits } } build(raw_data, decoded_cursor_factory: decoded_cursor_factory) end |
Instance Method Details
#aggregations ⇒ Object
138 139 140 141 142 143 144 |
# File 'lib/elastic_graph/graphql/datastore_response/search_response.rb', line 138 def aggregations if (reason = aggregations_unavailable_reason) raise Errors::AggregationsUnavailableError, "Aggregations are unavailable on this search response: #{reason}." end raw_data["aggregations"] || {} end |
#docs_description ⇒ Object
130 131 132 |
# File 'lib/elastic_graph/graphql/datastore_response/search_response.rb', line 130 def docs_description (documents.size < 3) ? documents.inspect : "[#{documents.first}, ..., #{documents.last}]" end |
#filter_results(field_path, values, size) ⇒ Object
Returns a response filtered to results that have matching ‘values` at the given `field_path`, limiting the results to the first `size` results.
This is designed for use in situations where we have N different datastore queries which are identical except for differing filter values. For efficiency, we combine those queries into a single query that filters on the set union of values. We can then use this method to “split” the single response into what the separate responses would have been if we hadn’t combined into a single query.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/elastic_graph/graphql/datastore_response/search_response.rb', line 110 def filter_results(field_path, values, size) filter = if field_path == ["id"] # `id` filtering is a very common case, and we want to avoid having to request # `id` within `_source`, given it's available as `_id`. ->(hit) { values.include?(hit.fetch("_id")) } else ->(hit) { values.intersect?(Support::HashUtil.fetch_leaf_values_at_path(hit.fetch("_source"), field_path).to_set) } end hits = raw_data.fetch("hits").fetch("hits").select(&filter).first(size) updated_raw_data = Support::HashUtil.deep_merge(raw_data, {"hits" => {"hits" => hits, "total" => nil}}) SearchResponse.build( updated_raw_data, decoded_cursor_factory: decoded_cursor_factory, aggregations_unavailable_reason: "aggregations cannot be provided accurately on a search response filtered in memory" ) end |
#to_s ⇒ Object Also known as: inspect
146 147 148 |
# File 'lib/elastic_graph/graphql/datastore_response/search_response.rb', line 146 def to_s "#<#{self.class.name} size=#{documents.size} #{docs_description}>" end |
#total_document_count(default: nil) ⇒ Object
134 135 136 |
# File 'lib/elastic_graph/graphql/datastore_response/search_response.rb', line 134 def total_document_count(default: nil) super() || default || raise(Errors::CountUnavailableError, "#{__method__} is unavailable; set `query.total_document_count_needed = true` to make it available") end |