search-engine-for-typesense 30.1.8.10 → 30.1.8.12

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: 384ef1b163431fc5ed44e94b039ed6c63475d03eac7beb021895f3707c2ed68b
4
- data.tar.gz: 8a138c55d4de91d43d4d99ccff25c3b0be6c514660b5afd8d94d04384e7a815a
3
+ metadata.gz: e12c8943b56f8054dbb1cfff9c52f17f0a926664eeb77436ee711cfba06e9d0c
4
+ data.tar.gz: 833df3fbf7c7ff98576c29e218a15dea996230e1a1a624f63fdc896a3728d2ed
5
5
  SHA512:
6
- metadata.gz: af0f76bc2e13c1a14685a5cd9f50efa6a1493c57a697c14534a053668549393906f2ad22b87e2efe27ccf7c1b45cf7b8d0bf2d7298280bddad53048239618ce6
7
- data.tar.gz: abafcd224584bb4ac206de5e95959c3a22102560f97bd6d3ea6122045377e2b0e3c397a699a813ec83d428df1d80003b8eb490581af98e01b8090067f78e08cb
6
+ metadata.gz: 65dcbf97c8b91ae1f85836dcb69a955fa6cf63a52bf71063b8ebd53a8ec7c82eac0a8df211fc9e2e4fc8da0f488b5c8f3ee442dbb870d9002987c959e990f689
7
+ data.tar.gz: 78fbe84b31b3fa457144ce7c81466a1b074a25c4218705f19d4ce3875f0e7cd6fedfbd32f72658396eca255db0aee5554693c2df078590d65bd04765489a274f
@@ -294,6 +294,8 @@ module SearchEngine
294
294
  end
295
295
 
296
296
  def exists?(relation)
297
+ return count(relation).positive? if relation.send(:curation_filter_curated_hits?)
298
+
297
299
  loaded = relation.instance_variable_get(:@__loaded)
298
300
  memo = relation.instance_variable_get(:@__result_memo)
299
301
  return memo.found.to_i.positive? if loaded && memo
@@ -303,8 +305,8 @@ module SearchEngine
303
305
 
304
306
  def count(relation)
305
307
  if relation.send(:curation_filter_curated_hits?)
306
- to_a(relation)
307
- return relation.send(:curated_indices_for_current_result).size
308
+ result = execute(relation)
309
+ return curated_total_count(result)
308
310
  end
309
311
 
310
312
  loaded = relation.instance_variable_get(:@__loaded)
@@ -364,6 +366,21 @@ module SearchEngine
364
366
  end
365
367
  module_function :fetch_found_only
366
368
 
369
+ def curated_total_count(result)
370
+ raw = result.raw || {}
371
+ base_count = raw['found_docs'] || raw[:found_docs] || result.found
372
+
373
+ base_count.to_i + curated_hits_count(result)
374
+ end
375
+ module_function :curated_total_count
376
+
377
+ def curated_hits_count(result)
378
+ result.to_a.count do |obj|
379
+ obj.respond_to?(:curated_hit?) && obj.curated_hit?
380
+ end
381
+ end
382
+ module_function :curated_hits_count
383
+
367
384
  # Detect Typesense 400 errors caused by missing infix/prefix configuration
368
385
  # e.g., "Could not find `name` in the infix index. Make sure to enable infix search by specifying `infix: true` in the schema."
369
386
  def infix_missing_error?(error)
@@ -27,7 +27,7 @@ module SearchEngine
27
27
  targets = delivery_targets
28
28
  return enqueue_legacy(limit: limit) if targets.empty?
29
29
 
30
- repository.materialize_deliveries!
30
+ materialize_deliveries(limit: limit)
31
31
  targets.each { |target| enqueue_target(target, limit: limit) }
32
32
  nil
33
33
  end
@@ -36,6 +36,12 @@ module SearchEngine
36
36
 
37
37
  attr_reader :repository, :targets_resolver
38
38
 
39
+ def materialize_deliveries(limit:)
40
+ return repository.materialize_deliveries! if limit.nil?
41
+
42
+ repository.materialize_deliveries!(limit: limit)
43
+ end
44
+
39
45
  def enqueue_legacy(limit:)
40
46
  return drain_job.perform_later if limit.nil?
41
47
 
@@ -111,11 +111,78 @@ module SearchEngine
111
111
 
112
112
  # Create missing delivery rows for all configured delivery targets.
113
113
  # @return [void]
114
- def materialize_deliveries!
114
+ def materialize_deliveries!(limit: SearchEngine.config.postgres_outbox.batch_size)
115
115
  targets = delivery_targets
116
116
  return if targets.empty?
117
117
 
118
- execute(<<~SQL)
118
+ rows = []
119
+ connection.transaction do
120
+ rows = select_rows(delivery_materialization_select_sql(limit.to_i, targets))
121
+ next if rows.empty?
122
+
123
+ execute(materialization_supersede_older_deliveries_sql(rows, targets))
124
+ execute(supersede_older_pending_sql(rows))
125
+ execute(delivery_materialization_insert_sql(rows, targets))
126
+ end
127
+
128
+ rows
129
+ end
130
+
131
+ private
132
+
133
+ attr_reader :target_key
134
+
135
+ def connection
136
+ @connection ||= begin
137
+ require 'active_record'
138
+ ActiveRecord::Base.connection
139
+ end
140
+ end
141
+
142
+ def claim_pending_deliveries(limit:, worker_id:)
143
+ reset_stale_delivery_processing!
144
+ rows = claim_pending_delivery_rows(limit: limit, worker_id: worker_id)
145
+
146
+ if rows.empty?
147
+ materialize_deliveries!(limit: limit)
148
+ rows = claim_pending_delivery_rows(limit: limit, worker_id: worker_id)
149
+ end
150
+
151
+ rows.map { |row| Event.new(row) }
152
+ end
153
+
154
+ def delivery_materialization_select_sql(limit, targets)
155
+ <<~SQL
156
+ WITH target(target_key, queue_name) AS (
157
+ VALUES #{delivery_target_values_sql(targets)}
158
+ ),
159
+ candidate_events AS (
160
+ SELECT outbox.*
161
+ FROM #{quoted_table} outbox
162
+ WHERE outbox.status IN ('pending', 'processing', 'failed')
163
+ AND (outbox.next_attempt_at IS NULL OR outbox.next_attempt_at <= CURRENT_TIMESTAMP)
164
+ AND EXISTS (
165
+ SELECT 1
166
+ FROM target
167
+ WHERE NOT EXISTS (
168
+ SELECT 1
169
+ FROM #{quoted_delivery_table} deliveries
170
+ WHERE deliveries.event_id = outbox.id
171
+ AND deliveries.target_key = target.target_key
172
+ )
173
+ )
174
+ ORDER BY outbox.id ASC
175
+ LIMIT #{limit}
176
+ FOR UPDATE SKIP LOCKED
177
+ )
178
+ SELECT DISTINCT ON (collection, document_id) *
179
+ FROM candidate_events
180
+ ORDER BY collection, document_id, id DESC
181
+ SQL
182
+ end
183
+
184
+ def delivery_materialization_insert_sql(rows, targets)
185
+ <<~SQL
119
186
  INSERT INTO #{quoted_delivery_table} (
120
187
  event_id,
121
188
  target_key,
@@ -132,29 +199,19 @@ module SearchEngine
132
199
  0,
133
200
  CURRENT_TIMESTAMP,
134
201
  CURRENT_TIMESTAMP
135
- FROM #{quoted_table} outbox
202
+ FROM (
203
+ VALUES #{materialization_event_values_sql(rows)}
204
+ ) AS selected_events(event_id)
205
+ INNER JOIN #{quoted_table} outbox
206
+ ON outbox.id = selected_events.event_id
136
207
  CROSS JOIN (
137
208
  VALUES #{delivery_target_values_sql(targets)}
138
209
  ) AS target(target_key, queue_name)
139
- WHERE outbox.status IN ('pending', 'processing', 'failed')
140
210
  ON CONFLICT (event_id, target_key) DO NOTHING
141
211
  SQL
142
212
  end
143
213
 
144
- private
145
-
146
- attr_reader :target_key
147
-
148
- def connection
149
- @connection ||= begin
150
- require 'active_record'
151
- ActiveRecord::Base.connection
152
- end
153
- end
154
-
155
- def claim_pending_deliveries(limit:, worker_id:)
156
- materialize_deliveries!
157
- reset_stale_delivery_processing!
214
+ def claim_pending_delivery_rows(limit:, worker_id:)
158
215
  rows = []
159
216
 
160
217
  connection.transaction do
@@ -164,7 +221,7 @@ module SearchEngine
164
221
  execute(delivery_claim_update_sql(delivery_ids, worker_id)) unless delivery_ids.empty?
165
222
  end
166
223
 
167
- rows.map { |row| Event.new(row) }
224
+ rows
168
225
  end
169
226
 
170
227
  def reset_stale_delivery_processing!
@@ -271,6 +328,46 @@ module SearchEngine
271
328
  SQL
272
329
  end
273
330
 
331
+ def materialization_supersede_older_deliveries_sql(rows, targets)
332
+ <<~SQL
333
+ WITH updated_deliveries AS (
334
+ UPDATE #{quoted_delivery_table} older_deliveries
335
+ SET status = 'superseded',
336
+ processed_at = CURRENT_TIMESTAMP,
337
+ locked_at = NULL,
338
+ locked_by = NULL,
339
+ updated_at = CURRENT_TIMESTAMP
340
+ FROM #{quoted_table} older_events,
341
+ (
342
+ VALUES #{coalesce_values_sql(rows)}
343
+ ) AS latest(collection, document_id, id),
344
+ (
345
+ VALUES #{delivery_target_values_sql(targets)}
346
+ ) AS target(target_key, queue_name)
347
+ WHERE older_deliveries.event_id = older_events.id
348
+ AND older_deliveries.status = 'pending'
349
+ AND older_deliveries.target_key = target.target_key
350
+ AND older_events.collection = latest.collection
351
+ AND older_events.document_id = latest.document_id
352
+ AND older_events.id < latest.id
353
+ RETURNING older_deliveries.event_id
354
+ ),
355
+ aggregate AS (
356
+ #{event_status_aggregate_sql('SELECT event_id FROM updated_deliveries')}
357
+ )
358
+ UPDATE #{quoted_table} events
359
+ SET status = aggregate.status,
360
+ processed_at = CASE
361
+ WHEN aggregate.status IN ('processed', 'superseded') THEN CURRENT_TIMESTAMP
362
+ ELSE NULL
363
+ END,
364
+ last_error = aggregate.last_error,
365
+ updated_at = CURRENT_TIMESTAMP
366
+ FROM aggregate
367
+ WHERE events.id = aggregate.event_id
368
+ SQL
369
+ end
370
+
274
371
  def supersede_older_pending_sql(rows)
275
372
  <<~SQL
276
373
  UPDATE #{quoted_table} older
@@ -468,6 +565,12 @@ module SearchEngine
468
565
  end.join(', ')
469
566
  end
470
567
 
568
+ def materialization_event_values_sql(rows)
569
+ rows.map do |row|
570
+ "(#{quote(row_value(row, :id))})"
571
+ end.join(', ')
572
+ end
573
+
471
574
  def quoted_table
472
575
  connection.quote_table_name(SearchEngine.config.postgres_outbox.table_name)
473
576
  end
@@ -563,12 +563,6 @@ module SearchEngine
563
563
 
564
564
  # pluck helpers reside in Materializers
565
565
 
566
- def curated_indices_for_current_result
567
- @__result_memo.to_a.each_with_index.select do |obj, _idx|
568
- obj.respond_to?(:curated_hit?) && obj.curated_hit?
569
- end.map(&:last)
570
- end
571
-
572
566
  def curation_filter_curated_hits?
573
567
  @state[:curation] && @state[:curation][:filter_curated_hits]
574
568
  end
@@ -3,5 +3,5 @@
3
3
  module SearchEngine
4
4
  # Current gem version.
5
5
  # @return [String]
6
- VERSION = '30.1.8.10'
6
+ VERSION = '30.1.8.12'
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search-engine-for-typesense
3
3
  version: !ruby/object:Gem::Version
4
- version: 30.1.8.10
4
+ version: 30.1.8.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikita Shkoda