full_search 0.4.5 → 0.4.6

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: fccc79c0dbfc8eeb24424e156c9be822470e2bbc3ecfdb75539f8a70010b3d38
4
- data.tar.gz: 3e50db78f978cb095834dd9f0d58802dfb1de69f9c80e04fc6c18cbdf1d9ccef
3
+ metadata.gz: ffdca4041a68a79686da2b8d636b3feadb88d39fd6ca2ff8f3c6ec955d52355c
4
+ data.tar.gz: 8438b904a538c0d8c8e0250bf751209b44f8a095aacfc1089cb180a5cbb21d8f
5
5
  SHA512:
6
- metadata.gz: 239eb848fe5631b1a8212840246ccd6be8387642b8f269687ae5052dc765afd25ccb49bd55c395b760aa772eb2da696bf786a2155f451326dbd8908da4f95532
7
- data.tar.gz: bd01ae3b672adc80f97c5ee70699444d773cb2a66c8ab2b98133f3528cba5099b3750fea9682cd6cdb4315e0fac17b7781b720a60f6c79519bf1857b78324aa4
6
+ metadata.gz: 447a5e7b51909e3098a38439935facf534ad7a97c84eb399af7fbd5e844c966787ac74651a58993000d12b0c18d3a6851fe3ea1e32b854629ff97b5c9e1906a0
7
+ data.tar.gz: d43d646f9e95ad6bc8d4425c8601b9c5f2695f106965cba1b53881cf8e155581a3421055f1b6babc0468aaf0a02c589e1e615f95dfecb42cd776adebae320bb5
@@ -19,6 +19,19 @@ module FullSearch
19
19
  models_in_bulk_import.delete(model)
20
20
  FullSearch::Index.create_triggers!(model)
21
21
  FullSearch::BackfillJob.perform_later(model.name)
22
+ rescue => e
23
+ # Triggers are still dropped at this point; the FTS index will rot on
24
+ # every subsequent write. Retry once, then raise loudly so the caller
25
+ # knows the index is compromised instead of failing silent.
26
+ retry_count = (Thread.current[:_fs_bulk_import_retry] ||= 0)
27
+ if retry_count < 1
28
+ Thread.current[:_fs_bulk_import_retry] = retry_count + 1
29
+ retry
30
+ end
31
+ raise FullSearch::TriggerRestoreError,
32
+ "Failed to restore FTS triggers for #{model.table_name} after bulk import: #{e.message}"
33
+ ensure
34
+ Thread.current[:_fs_bulk_import_retry] = nil
22
35
  end
23
36
 
24
37
  def bulk_importing?(model)
@@ -10,4 +10,5 @@ module FullSearch
10
10
  class UnsupportedDatabaseError < Error; end
11
11
  class MissingTableError < Error; end
12
12
  class InvalidQueryError < Error; end
13
+ class TriggerRestoreError < Error; end
13
14
  end
@@ -267,6 +267,22 @@ module FullSearch
267
267
  ).any?
268
268
  end
269
269
 
270
+ def missing_triggers?(model)
271
+ return false if FullSearch.bulk_importing?(model)
272
+ return false unless model_table_exists?(model)
273
+ return false unless table_exists?(model)
274
+
275
+ IndexCache.fetch("missing_triggers:#{model.table_name}") do
276
+ existing = connection.execute(
277
+ "SELECT name FROM sqlite_master WHERE type='trigger' AND tbl_name=#{q(model.table_name)}"
278
+ ).map { |r| r["name"] }
279
+
280
+ expected = trigger_names(model)
281
+ expected += trigram_trigger_names(model) if trigram_table_needed?(model)
282
+ (expected - existing).any?
283
+ end
284
+ end
285
+
270
286
  private
271
287
 
272
288
  def connection
@@ -290,9 +306,11 @@ module FullSearch
290
306
  end
291
307
 
292
308
  def model_table_exists?(model)
293
- connection.execute(
294
- "SELECT name FROM sqlite_master WHERE type='table' AND name=#{q(model.table_name)} LIMIT 1"
295
- ).any?
309
+ IndexCache.fetch("model_table_exists:#{model.table_name}") do
310
+ connection.execute(
311
+ "SELECT name FROM sqlite_master WHERE type='table' AND name=#{q(model.table_name)} LIMIT 1"
312
+ ).any?
313
+ end
296
314
  end
297
315
 
298
316
  def table_exists?(model)
@@ -354,16 +372,7 @@ module FullSearch
354
372
 
355
373
  def ensure_triggers!(model)
356
374
  return if FullSearch.bulk_importing?(model)
357
-
358
- existing = connection.execute(
359
- "SELECT name FROM sqlite_master WHERE type='trigger' AND tbl_name=#{q(model.table_name)}"
360
- ).map { |r| r["name"] }
361
-
362
- expected = trigger_names(model)
363
- expected += trigram_trigger_names(model) if trigram_table_needed?(model)
364
- return if (expected - existing).empty? && (existing - expected).empty?
365
-
366
- rebuild!(model)
375
+ rebuild!(model) if missing_triggers?(model)
367
376
  end
368
377
 
369
378
  def trigger_names(model)
@@ -378,6 +387,8 @@ module FullSearch
378
387
  %W[#{base}_ai #{base}_ad #{base}_au]
379
388
  end
380
389
 
390
+ private
391
+
381
392
  def insert_trigger_sql(model)
382
393
  dsl = model.full_search_dsl
383
394
  cols = dsl.fields + dsl.filters + extra_columns(model)
@@ -12,40 +12,42 @@ module FullSearch
12
12
  end
13
13
 
14
14
  def call
15
- searched = groups.map do |group|
16
- model = fetch(group, :model)
17
- raise FullSearch::NotConfiguredError, "#{model} is not full_search configured" unless model.full_search_dsl
15
+ IndexCache.with_cache do
16
+ searched = groups.map do |group|
17
+ model = fetch(group, :model)
18
+ raise FullSearch::NotConfiguredError, "#{model} is not full_search configured" unless model.full_search_dsl
18
19
 
19
- filters = group[:filters] || {}
20
- limit = positive_integer(group[:limit], 8)
21
- offset = positive_integer(group[:offset], 0)
22
- raw_limit = limit + 1
20
+ filters = group[:filters] || {}
21
+ limit = positive_integer(group[:limit], 8)
22
+ offset = positive_integer(group[:offset], 0)
23
+ raw_limit = limit + 1
23
24
 
24
- relation = model.full_search(
25
- query,
26
- filters: filters,
27
- limit: raw_limit,
28
- offset: offset,
29
- highlight: group[:highlight],
30
- highlight_fields: group[:highlight_fields],
31
- matching_strategy: group[:matching_strategy],
32
- per_strategy_limit: group[:per_strategy_limit],
33
- scope: group[:scope],
34
- includes: group[:includes]
35
- )
25
+ relation = model.full_search(
26
+ query,
27
+ filters: filters,
28
+ limit: raw_limit,
29
+ offset: offset,
30
+ highlight: group[:highlight],
31
+ highlight_fields: group[:highlight_fields],
32
+ matching_strategy: group[:matching_strategy],
33
+ per_strategy_limit: group[:per_strategy_limit],
34
+ scope: group[:scope],
35
+ includes: group[:includes]
36
+ )
36
37
 
37
- records = relation.to_a
38
- has_more = records.size > limit
39
- records = records.first(limit) if has_more
38
+ records = relation.to_a
39
+ has_more = records.size > limit
40
+ records = records.first(limit) if has_more
40
41
 
41
- group.slice(:key, :label, :icon, :model).merge(
42
- results: records,
43
- has_more: has_more,
44
- total_count: records.size
45
- )
46
- end
42
+ group.slice(:key, :label, :icon, :model).merge(
43
+ results: records,
44
+ has_more: has_more,
45
+ total_count: records.size
46
+ )
47
+ end
47
48
 
48
- {groups: searched, total_count: searched.sum { |g| g[:total_count] }}
49
+ {groups: searched, total_count: searched.sum { |g| g[:total_count] }}
50
+ end
49
51
  end
50
52
 
51
53
  private
@@ -92,6 +92,21 @@ module FullSearch
92
92
  raise MissingTableError, "FTS table `#{FullSearch::Index.fts_table_name(model)}` does not exist. Run `bin/rails full_search:prepare` to create it."
93
93
  end
94
94
 
95
+ if FullSearch::Index.missing_triggers?(model)
96
+ if FullSearch.config.auto_rebuild_on_stale_query
97
+ FullSearch::Index.rebuild!(model)
98
+ return
99
+ end
100
+
101
+ case FullSearch.config.stale_query_behavior
102
+ when :raise
103
+ raise ConfigChangedError, "FTS triggers for #{model.table_name} are missing; run full_search:rebuild"
104
+ when :log_and_fallback
105
+ Rails.logger.warn("[full_search] FTS triggers for #{model.table_name} are missing; results may be incomplete")
106
+ end
107
+ return
108
+ end
109
+
95
110
  stored = begin
96
111
  FullSearch::Index.stored_config_hash(model)
97
112
  rescue ActiveRecord::StatementInvalid
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FullSearch
4
- VERSION = "0.4.5"
4
+ VERSION = "0.4.6"
5
5
  end
@@ -105,7 +105,9 @@ namespace :full_search do
105
105
  ""
106
106
  end
107
107
 
108
- puts "#{model.table_name}: #{status}#{drift_info}"
108
+ trigger_info = FullSearch::Index.missing_triggers?(model) ? " | MISSING TRIGGERS" : ""
109
+
110
+ puts "#{model.table_name}: #{status}#{drift_info}#{trigger_info}"
109
111
  end
110
112
  end
111
113
 
@@ -120,6 +122,10 @@ namespace :full_search do
120
122
  next
121
123
  end
122
124
 
125
+ if FullSearch::Index.missing_triggers?(model)
126
+ unhealthy << "#{model.table_name}: missing FTS triggers"
127
+ end
128
+
123
129
  stored = FullSearch::Index.stored_config_hash(model)
124
130
  current = model.full_search_dsl.config_hash
125
131
  if stored != current
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: full_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben D'Angelo