esse 0.4.0.rc5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb2a09b3e73966e0ae1f5e330f9f48604220ea9f8be3e764fd78637ec8e375f1
4
- data.tar.gz: 7dd0dc43a3e2f0823ae4af63f64318f181fee7fd48ba477ca04feb3e48e2ba66
3
+ metadata.gz: dc6a554b787bfb51e5568bd590790171cc88ad8c5ce432e1de2c3ef68a5d1def
4
+ data.tar.gz: cac82a25d6e8dd6dad3667a734d58be098f4b7020db76472359f7bdb4fe2067c
5
5
  SHA512:
6
- metadata.gz: 16022150a582f772821c47760aae2edc07b8b3f9cc050ca616b0a2a2da3ca0d0e394769b041b6f17730d00fa01c38f8ae7cf3afbb7ab8f9370d94c3ef45ab57e
7
- data.tar.gz: 440417523c35fc153e6b0e48cafee2fc8d332e0e37058852b8bc0abe74728eae3b76acd29274f950b14a35137fcb47cff3a1b233cad916639f2412f8b9dfba9f
6
+ metadata.gz: f26afad34715ed37cec3f1d1cad11ca6e05d2921a6f31884d9ea9f1136ef239ba7661e29796c37abf69ea19aa5f5407884cf88b108bbfa211e5639e371d492ed
7
+ data.tar.gz: 13b1921d85723d3c33bacc54c7bcf95885a196c3a62c095a5ea3be5a5d5061b8cd4eb8fbb1bdbf1bb91c141ed4457f9a1baab5ec121dd7e4f4dc1c1c423277d2
@@ -92,9 +92,14 @@ module Esse
92
92
 
93
93
  # @api private
94
94
  def process(event_id, payload)
95
+ # Build the event once and reuse for all listeners. Previously, a new
96
+ # Event was created per listener via Event#payload which does
97
+ # @payload.merge(data) — a full hash copy each time. With 24+
98
+ # esse-rails subscribers, each search was creating 24+ Event objects
99
+ # with 24+ hash merges of the full payload (including Response with
100
+ # the entire OpenSearch JSON). Now it's 1 Event + 1 merge total.
101
+ event = events[event_id].payload(payload)
95
102
  listeners[event_id].each do |listener|
96
- event = events[event_id].payload(payload)
97
-
98
103
  yield(event, listener)
99
104
  end
100
105
  end
@@ -60,6 +60,11 @@ module Esse
60
60
  if publish_event
61
61
  payload[:runtime] ||= Time.now - payload.delete(:__started_at__) if payload[:__started_at__]
62
62
  __bus__.publish(event_id, payload)
63
+ # Release references to large objects (Query, Response with full
64
+ # OpenSearch JSON) so they become GC-eligible immediately after
65
+ # event dispatch, rather than being held until the caller's stack
66
+ # frame unwinds.
67
+ payload.clear
63
68
  end
64
69
  end
65
70
 
data/lib/esse/events.rb CHANGED
@@ -56,6 +56,7 @@ module Esse
56
56
  register_event 'elasticsearch.exist'
57
57
  register_event 'elasticsearch.count'
58
58
  register_event 'elasticsearch.get'
59
+ register_event 'elasticsearch.mget'
59
60
  register_event 'elasticsearch.reindex'
60
61
  register_event 'elasticsearch.update_by_query'
61
62
  register_event 'elasticsearch.delete_by_query'
@@ -29,6 +29,37 @@ module Esse
29
29
  cluster.api.get(**options)
30
30
  end
31
31
 
32
+ # Retrieves multiple JSON documents by ID from an index.
33
+ #
34
+ # UsersIndex.mget(ids: [1, 2, 3])
35
+ # UsersIndex.mget(ids: [Esse::HashDocument.new(id: 1), Esse::HashDocument.new(id: 2)])
36
+ #
37
+ # @param ids [Array<Esse::Document, Hash, String, Integer>] documents, hashes, or IDs to retrieve
38
+ # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
39
+ # @option [String, nil] :suffix The index suffix. Defaults to the nil.
40
+ # @return [Hash] The elasticsearch response with 'docs' array
41
+ #
42
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-multi-get.html
43
+ def mget(ids:, suffix: nil, **options)
44
+ options[:body] = {
45
+ docs: ids.map do |doc|
46
+ if document?(doc)
47
+ datum = { _id: doc.id }
48
+ datum[:_type] = doc.type if doc.type?
49
+ datum[:routing] = doc.routing if doc.routing?
50
+ datum
51
+ elsif doc.is_a?(Hash)
52
+ doc
53
+ else
54
+ { _id: doc }
55
+ end
56
+ end,
57
+ }
58
+ options[:index] = index_name(suffix: suffix)
59
+ cluster.may_update_type!(options)
60
+ cluster.api.mget(**options)
61
+ end
62
+
32
63
  # Check if a JSON document exists
33
64
  #
34
65
  # UsersIndex.exist?(id: 1) # true
@@ -40,7 +40,7 @@ module Esse
40
40
 
41
41
  def scroll_hits(batch_size: 1_000, scroll: '1m')
42
42
  response = execute_search_query!(size: batch_size, scroll: scroll)
43
- scroll_id = nil
43
+ scroll_id = response.raw_response['scroll_id'] || response.raw_response['_scroll_id']
44
44
  fetched = 0
45
45
  total = response.total
46
46
 
@@ -48,9 +48,9 @@ module Esse
48
48
  fetched += response.hits.size
49
49
  yield(response.hits) if response.hits.any?
50
50
  break if fetched >= total
51
- scroll_id = response.raw_response['scroll_id'] || response.raw_response['_scroll_id']
52
51
  break unless scroll_id
53
52
  response = execute_scroll_query(scroll: scroll, scroll_id: scroll_id)
53
+ scroll_id = response.raw_response['scroll_id'] || response.raw_response['_scroll_id']
54
54
  end
55
55
  ensure
56
56
  begin
@@ -59,12 +59,40 @@ module Esse
59
59
  end
60
60
  end
61
61
 
62
+ def search_after_hits(batch_size: 1_000)
63
+ body = HashUtils.deep_dup(definition.fetch(:body, {}))
64
+ body[:size] = batch_size
65
+ body.delete(:from)
66
+ body.delete('from')
67
+
68
+ unless body.key?(:sort) || body.key?('sort')
69
+ raise ArgumentError, 'The query body must include a :sort to use search_after'
70
+ end
71
+
72
+ loop do
73
+ response = execute_search_query!(body: body)
74
+ break if response.hits.empty?
75
+
76
+ yield(response.hits)
77
+
78
+ last_sort = response.hits.last['sort']
79
+ break unless last_sort
80
+ break if response.hits.size < batch_size
81
+
82
+ body[:search_after] = last_sort
83
+ end
84
+ end
85
+
86
+ def reset!
87
+ @response = nil
88
+ end
89
+
62
90
  private
63
91
 
64
92
  def execute_search_query!(**execution_options)
65
93
  resp, err = nil
66
94
  Esse::Events.instrument('elasticsearch.execute_search_query') do |payload|
67
- payload[:query] = self
95
+ payload[:query_definition] = definition
68
96
  begin
69
97
  resp = Response.new(self, transport.search(**definition, **execution_options))
70
98
  rescue => e
@@ -81,7 +109,7 @@ module Esse
81
109
  def execute_scroll_query(scroll:, scroll_id:)
82
110
  resp, err = nil
83
111
  Esse::Events.instrument('elasticsearch.execute_search_query') do |payload|
84
- payload[:query] = self
112
+ payload[:query_definition] = definition
85
113
  begin
86
114
  resp = Response.new(self, transport.scroll(scroll: scroll, body: { scroll_id: scroll_id }))
87
115
  rescue => e
@@ -94,10 +122,6 @@ module Esse
94
122
 
95
123
  resp
96
124
  end
97
-
98
- def reset!
99
- @response = nil
100
- end
101
125
  end
102
126
  end
103
127
  end
@@ -7,13 +7,13 @@ module Esse
7
7
  extend Forwardable
8
8
 
9
9
  def_delegators :hits, :each, :size, :empty?
10
- attr_reader :query, :raw_response, :options
10
+ attr_reader :query_definition, :raw_response, :options
11
11
 
12
- # @param [Esse::Search::Query] query The search query
12
+ # @param [Esse::Search::Query, Hash] query The search query or its definition hash
13
13
  # @param [Hash] raw_response The raw response from Elasticsearch
14
14
  # @param [Hash] options The options passed to the search
15
15
  def initialize(query, raw_response, **options)
16
- @query = query
16
+ @query_definition = query.is_a?(Hash) ? query : query.definition
17
17
  @raw_response = raw_response
18
18
  @options = options
19
19
  end
@@ -29,6 +29,30 @@ module Esse
29
29
  end
30
30
  end
31
31
 
32
+ # Allows to get multiple documents in a single request.
33
+ #
34
+ # @option [String] :index The name of the index
35
+ # @option [Hash] :body Document identifiers; can be either `docs` (specifying full document information)
36
+ # or `ids` (when index is provided). (*Required*)
37
+ # @option [String] :preference Specify the node or shard the operation should be performed on (default: random)
38
+ # @option [Boolean] :realtime Specify whether to perform the operation in realtime or search mode
39
+ # @option [Boolean] :refresh Refresh the shard containing the document before performing the operation
40
+ # @option [String] :routing Specific routing value
41
+ # @option [List] :stored_fields A comma-separated list of stored fields to return in the response
42
+ # @option [List] :_source True or false to return the _source field or not, or a list of fields to return
43
+ # @option [List] :_source_excludes A list of fields to exclude from the returned _source field
44
+ # @option [List] :_source_includes A list of fields to extract and return from the _source field
45
+ # @option [Hash] :headers Custom HTTP headers
46
+ #
47
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html
48
+ #
49
+ def mget(index:, body:, **options)
50
+ Esse::Events.instrument('elasticsearch.mget') do |payload|
51
+ payload[:request] = opts = options.merge(index: index, body: body)
52
+ payload[:response] = coerce_exception { client.mget(**opts) }
53
+ end
54
+ end
55
+
32
56
  # Returns information about whether a document exists in an index.
33
57
  #
34
58
  # @option [String] :id The document ID
data/lib/esse/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Esse
4
- VERSION = '0.4.0.rc5'
4
+ VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: esse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0.rc5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcos G. Zimmermann
8
8
  autorequire:
9
9
  bindir: exec
10
10
  cert_chain: []
11
- date: 2025-08-04 00:00:00.000000000 Z
11
+ date: 2026-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -319,14 +319,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
319
319
  requirements:
320
320
  - - ">="
321
321
  - !ruby/object:Gem::Version
322
- version: 2.3.0
322
+ version: '2.7'
323
323
  required_rubygems_version: !ruby/object:Gem::Requirement
324
324
  requirements:
325
- - - ">"
325
+ - - ">="
326
326
  - !ruby/object:Gem::Version
327
- version: 1.3.1
327
+ version: '0'
328
328
  requirements: []
329
- rubygems_version: 3.1.6
329
+ rubygems_version: 3.4.10
330
330
  signing_key:
331
331
  specification_version: 4
332
332
  summary: Pure Ruby and framework-agnostic ElasticSearch/OpenSearch toolkit for building