full_search 0.4.8 → 0.4.9
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 +4 -4
- data/README.md +3 -1
- data/lib/full_search/index.rb +40 -12
- data/lib/full_search/model.rb +20 -0
- data/lib/full_search/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 834fb641e471174fd832f6c0e825d2486dbb94985e35f5ab95f5fc068c08ece1
|
|
4
|
+
data.tar.gz: c5d19d2c11fdf7eaa95c2af1fdc2a39d2db9d170e248591e29bba107249f691e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7bd69680899b8b405d3de387d1c3eafbb3e98ce0b08f937f7a9cac5282e5cb32cd24b96e7c20019b470937c817f419d2094ca48e9e9dd2a1fc49b9a0502295d7
|
|
7
|
+
data.tar.gz: 50f29a99547ce19b2a3c5098a992476dbd5b0423e6d251768a4ec355db482f81bd94b0e23e13e0008c671c8d35911e0394cd716cf278f1808de52f94f5daaa71
|
data/README.md
CHANGED
|
@@ -150,7 +150,9 @@ FTS indexes are SQLite virtual tables (`customers_fts`, `vehicles_fts`, etc.) th
|
|
|
150
150
|
These two operations are often confused:
|
|
151
151
|
|
|
152
152
|
- **Rebuild** (`full_search:rebuild` / `Customer.rebuild!`) — drops and recreates the FTS virtual table. Needed when the DSL changes (fields added/removed, tokenizer changed, etc.). The table is re-created from scratch, backfilled, triggers re-installed, and the index is optimized.
|
|
153
|
-
- **Reindex** (`Customer.reindex!` / `FullSearch::Index.reindex_source_fields!`) — updates existing FTS rows with fresh values from computed `source:` fields only. The table structure is untouched. Database triggers cover regular column changes automatically
|
|
153
|
+
- **Reindex** (`Customer.reindex!` / `Customer.reindex_ids([ids])` / `FullSearch::Index.reindex_source_fields!`) — updates existing FTS rows with fresh values from computed `source:` fields only. The table structure is untouched. Database triggers cover regular column changes automatically, and since v0.4.9 they **preserve** existing `source:` field values rather than wiping them to `''` on UPDATE. Only Ruby-evaluated `source:` blocks need an explicit reindex.
|
|
154
|
+
|
|
155
|
+
**Important:** raw SQL operations that bypass Rails callbacks (`update_all`, `update_column`, `insert_all`, `upsert_all`, `counter_cache`, `touch`) will not run the `after_save_commit` reindex hook. Use `Model.reindex_ids(ids)` after a callback-free `update_all` that changes data backing a `source:` field, or `Model.reindex!` after a bulk insert, to keep the FTS source fields in sync.
|
|
154
156
|
|
|
155
157
|
### Setup lifecycle
|
|
156
158
|
|
data/lib/full_search/index.rb
CHANGED
|
@@ -452,24 +452,27 @@ module FullSearch
|
|
|
452
452
|
|
|
453
453
|
when_clause = SoftDelete.active_update_clause(model)
|
|
454
454
|
|
|
455
|
+
update_assignments = update_assignments_sql(cols)
|
|
456
|
+
|
|
455
457
|
if dsl.conditional_index?
|
|
456
458
|
select_parts = cols.map { |c| column_ref(c, prefix: "new") }.join(", ")
|
|
457
459
|
<<~SQL
|
|
458
460
|
CREATE TRIGGER #{qt(trigger_names(model)[2])} AFTER UPDATE ON #{qt(model.table_name)} #{when_clause}
|
|
459
461
|
BEGIN
|
|
460
|
-
DELETE FROM #{fts_table} WHERE rowid = old.id
|
|
462
|
+
DELETE FROM #{fts_table} WHERE rowid = old.id
|
|
463
|
+
AND NOT EXISTS (SELECT 1 FROM #{tbl} WHERE #{tbl}.id = old.id AND (#{dsl.index_if_sql}));
|
|
464
|
+
UPDATE #{fts_table} SET #{update_assignments} WHERE rowid = new.id
|
|
465
|
+
AND EXISTS (SELECT 1 FROM #{tbl} WHERE #{tbl}.id = new.id AND (#{dsl.index_if_sql}));
|
|
461
466
|
INSERT INTO #{fts_table}(rowid, #{cols_str})
|
|
462
|
-
|
|
467
|
+
SELECT new.id, #{select_parts} FROM #{tbl} WHERE #{tbl}.id = new.id AND (#{dsl.index_if_sql})
|
|
468
|
+
AND NOT EXISTS (SELECT 1 FROM #{fts_table} WHERE #{fts_table}.rowid = new.id);
|
|
463
469
|
END;
|
|
464
470
|
SQL
|
|
465
471
|
else
|
|
466
|
-
values = cols.map { |c| column_ref(c, prefix: "new") }.join(", ")
|
|
467
472
|
<<~SQL
|
|
468
473
|
CREATE TRIGGER #{qt(trigger_names(model)[2])} AFTER UPDATE ON #{qt(model.table_name)} #{when_clause}
|
|
469
474
|
BEGIN
|
|
470
|
-
|
|
471
|
-
INSERT INTO #{fts_table}(rowid, #{cols_str})
|
|
472
|
-
VALUES (new.id, #{values});
|
|
475
|
+
UPDATE #{fts_table} SET #{update_assignments} WHERE rowid = new.id;
|
|
473
476
|
END;
|
|
474
477
|
SQL
|
|
475
478
|
end
|
|
@@ -562,24 +565,27 @@ module FullSearch
|
|
|
562
565
|
|
|
563
566
|
when_clause = SoftDelete.active_update_clause(model)
|
|
564
567
|
|
|
568
|
+
update_assignments = update_assignments_sql(cols)
|
|
569
|
+
|
|
565
570
|
if dsl.conditional_index?
|
|
566
571
|
select_parts = cols.map { |c| column_ref(c, prefix: "new") }.join(", ")
|
|
567
572
|
<<~SQL
|
|
568
573
|
CREATE TRIGGER #{qt(trigram_trigger_names(model)[2])} AFTER UPDATE ON #{qt(model.table_name)} #{when_clause}
|
|
569
574
|
BEGIN
|
|
570
|
-
DELETE FROM #{trigram_table} WHERE rowid = old.id
|
|
575
|
+
DELETE FROM #{trigram_table} WHERE rowid = old.id
|
|
576
|
+
AND NOT EXISTS (SELECT 1 FROM #{tbl} WHERE #{tbl}.id = old.id AND (#{dsl.index_if_sql}));
|
|
577
|
+
UPDATE #{trigram_table} SET #{update_assignments} WHERE rowid = new.id
|
|
578
|
+
AND EXISTS (SELECT 1 FROM #{tbl} WHERE #{tbl}.id = new.id AND (#{dsl.index_if_sql}));
|
|
571
579
|
INSERT INTO #{trigram_table}(rowid, #{cols_str})
|
|
572
|
-
|
|
580
|
+
SELECT new.id, #{select_parts} FROM #{tbl} WHERE #{tbl}.id = new.id AND (#{dsl.index_if_sql})
|
|
581
|
+
AND NOT EXISTS (SELECT 1 FROM #{trigram_table} WHERE #{trigram_table}.rowid = new.id);
|
|
573
582
|
END;
|
|
574
583
|
SQL
|
|
575
584
|
else
|
|
576
|
-
values = cols.map { |c| column_ref(c, prefix: "new") }.join(", ")
|
|
577
585
|
<<~SQL
|
|
578
586
|
CREATE TRIGGER #{qt(trigram_trigger_names(model)[2])} AFTER UPDATE ON #{qt(model.table_name)} #{when_clause}
|
|
579
587
|
BEGIN
|
|
580
|
-
|
|
581
|
-
INSERT INTO #{trigram_table}(rowid, #{cols_str})
|
|
582
|
-
VALUES (new.id, #{values});
|
|
588
|
+
UPDATE #{trigram_table} SET #{update_assignments} WHERE rowid = new.id;
|
|
583
589
|
END;
|
|
584
590
|
SQL
|
|
585
591
|
end
|
|
@@ -604,6 +610,28 @@ module FullSearch
|
|
|
604
610
|
end
|
|
605
611
|
end
|
|
606
612
|
|
|
613
|
+
# Builds `col = value` assignments for the AFTER UPDATE trigger. Source
|
|
614
|
+
# fields are deliberately excluded so a raw SQL UPDATE (e.g. a
|
|
615
|
+
# counter_cache or touch) does not wipe the previously computed source
|
|
616
|
+
# value back to an empty string. Source fields are refreshed later by the
|
|
617
|
+
# after_save_commit reindex callback on normal Rails saves.
|
|
618
|
+
#
|
|
619
|
+
# When every column is a source field (nothing to update), emit a valid
|
|
620
|
+
# no-op assignment so the generated trigger SQL stays syntactically valid.
|
|
621
|
+
def update_assignments_sql(cols)
|
|
622
|
+
assignments = cols.filter_map do |col|
|
|
623
|
+
if col.respond_to?(:unindexed?) && col.name == "indexed"
|
|
624
|
+
"#{qc(col.name)} = '1'"
|
|
625
|
+
elsif col.respond_to?(:source) && col.source
|
|
626
|
+
nil
|
|
627
|
+
else
|
|
628
|
+
"#{qc(column_name(col))} = new.#{qc(column_name(col))}"
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
assignments << "#{qc(column_name(cols.first))} = #{qc(column_name(cols.first))}" if assignments.empty?
|
|
632
|
+
assignments.join(", ")
|
|
633
|
+
end
|
|
634
|
+
|
|
607
635
|
# NOTE: This lock prevents concurrent rebuilds within the same process/connection only.
|
|
608
636
|
# For multi-process or multi-host deployments, run full_search:rebuild from a single
|
|
609
637
|
# deployment step. See README.
|
data/lib/full_search/model.rb
CHANGED
|
@@ -40,6 +40,19 @@ module FullSearch
|
|
|
40
40
|
FullSearch::Index.reindex_source_fields!(self)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# Re-evaluates source: fields for a specific set of records and updates
|
|
44
|
+
# the FTS table in place. Use after bulk operations that bypass model
|
|
45
|
+
# callbacks (e.g. `update_all`, `insert_all`, `upsert_all`, `update_column`)
|
|
46
|
+
# so the source fields don't drift out of sync with the underlying rows.
|
|
47
|
+
def reindex_ids(ids)
|
|
48
|
+
source_ids = ids.to_a.map(&:to_i).reject(&:zero?)
|
|
49
|
+
return if source_ids.empty?
|
|
50
|
+
|
|
51
|
+
where(id: source_ids).find_each(batch_size: 500) do |record|
|
|
52
|
+
FullSearch::Callbacks.reindex_record!(record)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
43
56
|
def bulk_import(&block)
|
|
44
57
|
FullSearch.bulk_import(self, &block)
|
|
45
58
|
end
|
|
@@ -75,6 +88,13 @@ module FullSearch
|
|
|
75
88
|
|
|
76
89
|
FullSearch::Model.evaluate_source(self, field)
|
|
77
90
|
end
|
|
91
|
+
|
|
92
|
+
# Re-evaluates this record's source: fields and writes them to the FTS
|
|
93
|
+
# table immediately (synchronously). Use after a callback-free mutation
|
|
94
|
+
# (update_column, update_all, counter_cache/touch) to refresh the index.
|
|
95
|
+
def reindex_source_fields!
|
|
96
|
+
FullSearch::Callbacks.reindex_record!(self)
|
|
97
|
+
end
|
|
78
98
|
end
|
|
79
99
|
end
|
|
80
100
|
end
|
data/lib/full_search/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.4.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben D'Angelo
|
|
@@ -189,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
189
189
|
- !ruby/object:Gem::Version
|
|
190
190
|
version: '2.0'
|
|
191
191
|
requirements: []
|
|
192
|
-
rubygems_version: 4.0.
|
|
192
|
+
rubygems_version: 4.0.18
|
|
193
193
|
specification_version: 4
|
|
194
194
|
summary: SQLite FTS5 full-text search for Rails/ActiveRecord
|
|
195
195
|
test_files: []
|