full_search 0.4.2 → 0.4.4

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: 2d5cf6b2007aa968f7243df89c2e957288336c03f64500278b88583e3a75035e
4
- data.tar.gz: b575ebcb9d41e2a16fe3a86f5201705551c41f2066b4db80e31b2b15ecd2be09
3
+ metadata.gz: 99b631f72162065698bc187ad0b88cb7602cbead185b5c82209db713774e763d
4
+ data.tar.gz: 5e1dd5a0782e69cba81b0b2c8f32170046f4c0e3ad55debb063837117285a559
5
5
  SHA512:
6
- metadata.gz: '0740089cb32424858a53942ab6695e7b4c65963a6cfa1ed75ee42aa500f702203c02f460d735d82f1fc9712e8df82f27f1e8b203700cb7e6be793641d4050821'
7
- data.tar.gz: 02d341539689e75d8f2064fd457ae241f4008434804ba62931493c2bef64033197a968387b3ded9db17bbbfaa42687ae28249419a3ade9969ebee42aaced5e1c
6
+ metadata.gz: 146763f433fba78ed55b6a638b1d8a80bed0ea886daf294ff8e9389d09efef104b7ae6729d8897ee3ea267a4d38cf5d9956e691f4524da4a0861df6827c909ba
7
+ data.tar.gz: 65c5e59005e1730e12fadda9126b397a5fffdef462bcda088e7f14259b3635d1df7356d91dd23ebb312e6403489bf461b6088fc29a68b86b38fdbb80c4786ac2
@@ -29,6 +29,10 @@ module FullSearch
29
29
 
30
30
  create_metadata_table!
31
31
 
32
+ if table_exists?(model) && !healthy?(model)
33
+ rebuild!(model)
34
+ end
35
+
32
36
  fts_was_created = false
33
37
  unless table_exists?(model)
34
38
  conn.execute(create_virtual_table_sql(model))
@@ -67,8 +71,8 @@ module FullSearch
67
71
  with_rebuild_lock(model) do
68
72
  FullSearch::Instrumentation.instrument("rebuild", model: model.name) do
69
73
  drop_triggers!(model)
70
- conn.execute("DROP TABLE IF EXISTS #{qt(fts_table_name(model))};")
71
- conn.execute("DROP TABLE IF EXISTS #{qt(trigram_table_name(model))};")
74
+ drop_fts_table_safely!(fts_table_name(model))
75
+ drop_fts_table_safely!(trigram_table_name(model))
72
76
  conn.execute(create_virtual_table_sql(model))
73
77
  if trigram_table_needed?(model)
74
78
  FullSearch::Typo.warn_unsupported!(model) unless FullSearch::Typo.supported?(model)
@@ -162,8 +166,14 @@ module FullSearch
162
166
  return unless table_exists?(model)
163
167
 
164
168
  conn = connection
165
- conn.execute("DELETE FROM #{qt(fts_table_name(model))};")
166
- conn.execute("DELETE FROM #{qt(trigram_table_name(model))};") if trigram_table_needed?(model) && trigram_table_exists?(model)
169
+ begin
170
+ conn.execute("DELETE FROM #{qt(fts_table_name(model))};")
171
+ conn.execute("DELETE FROM #{qt(trigram_table_name(model))};") if trigram_table_needed?(model) && trigram_table_exists?(model)
172
+ rescue ActiveRecord::StatementInvalid => e
173
+ raise unless e.message.match?(/fts5: corruption|invalid fts5 file format|malformed database schema/i)
174
+
175
+ rebuild!(model)
176
+ end
167
177
  end
168
178
 
169
179
  def drop!(model)
@@ -171,8 +181,32 @@ module FullSearch
171
181
  verified_tables.delete(model.table_name)
172
182
  sqlite!(model)
173
183
  drop_triggers!(model)
174
- connection.execute("DROP TABLE IF EXISTS #{qt(fts_table_name(model))};")
175
- connection.execute("DROP TABLE IF EXISTS #{qt(trigram_table_name(model))};")
184
+ drop_fts_table_safely!(fts_table_name(model))
185
+ drop_fts_table_safely!(trigram_table_name(model))
186
+ end
187
+ # Drops an FTS5 virtual table, recovering from corruption where a bare
188
+ # DROP TABLE IF EXISTS raises "invalid fts5 file format" because the
189
+ # shadow tables were tampered with out-of-band. When that happens we
190
+ # drop the shadow tables directly, then drop the (now-empty) virtual
191
+ # table wrapper, so the caller can recreate a clean index.
192
+ def drop_fts_table_safely!(table_name)
193
+ connection.execute("DROP TABLE IF EXISTS #{qt(table_name)};")
194
+ rescue ActiveRecord::StatementInvalid => e
195
+ raise unless e.message.match?(/fts5: corruption|invalid fts5 file format|malformed database schema/i)
196
+
197
+ drop_shadow_tables!(table_name)
198
+ connection.execute("DROP TABLE IF EXISTS #{qt(table_name)};")
199
+ end
200
+
201
+ # FTS5 stores its index data in a fixed set of shadow tables named
202
+ # <table>_content, <table>_data, <table>_idx, <table>_docsize and
203
+ # <table>_config. Dropping these lets SQLite recover a virtual table
204
+ # that is otherwise undroppable due to internal corruption.
205
+ def drop_shadow_tables!(table_name)
206
+ %w[content data idx docsize config].each do |suffix|
207
+ shadow = "#{table_name}_#{suffix}"
208
+ connection.execute("DROP TABLE IF EXISTS #{qt(shadow)};")
209
+ end
176
210
  end
177
211
 
178
212
  def fts_table_name(model)
@@ -211,6 +245,17 @@ module FullSearch
211
245
  !table_exists?(model)
212
246
  end
213
247
 
248
+ def healthy?(model)
249
+ return false unless table_exists?(model)
250
+
251
+ connection.execute("SELECT count(*) FROM #{qt(fts_table_name(model))}")
252
+ true
253
+ rescue ActiveRecord::StatementInvalid => e
254
+ raise unless e.message.match?(/fts5: corruption|invalid fts5 file format|malformed database schema/i)
255
+
256
+ false
257
+ end
258
+
214
259
  def trigram_table_needed?(model)
215
260
  model.full_search_dsl&.typo_tolerance? && model.full_search_dsl.tokenize != "trigram"
216
261
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FullSearch
4
- VERSION = "0.4.2"
4
+ VERSION = "0.4.4"
5
5
  end
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.2
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben D'Angelo
@@ -188,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
188
  - !ruby/object:Gem::Version
189
189
  version: '2.0'
190
190
  requirements: []
191
- rubygems_version: 4.0.15
191
+ rubygems_version: 4.0.10
192
192
  specification_version: 4
193
193
  summary: SQLite FTS5 full-text search for Rails/ActiveRecord
194
194
  test_files: []