xapian_db 1.3.5.4 → 1.3.7.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +252 -201
- data/README.rdoc +34 -4
- data/lib/type_codec.rb +3 -3
- data/lib/xapian_db.rb +4 -5
- data/lib/xapian_db/adapters/active_record_adapter.rb +7 -6
- data/lib/xapian_db/config.rb +17 -1
- data/lib/xapian_db/database.rb +17 -2
- data/lib/xapian_db/document_blueprint.rb +62 -10
- data/lib/xapian_db/index_writers/beanstalk_worker.rb +2 -3
- data/lib/xapian_db/index_writers/beanstalk_writer.rb +2 -2
- data/lib/xapian_db/index_writers/direct_writer.rb +6 -6
- data/lib/xapian_db/index_writers/no_op_writer.rb +2 -2
- data/lib/xapian_db/index_writers/resque_worker.rb +1 -2
- data/lib/xapian_db/index_writers/resque_writer.rb +2 -3
- data/lib/xapian_db/index_writers/sidekiq_worker.rb +1 -1
- data/lib/xapian_db/index_writers/sidekiq_writer.rb +2 -3
- data/lib/xapian_db/index_writers/transactional_writer.rb +2 -4
- data/lib/xapian_db/indexer.rb +1 -0
- data/lib/xapian_db/query_parser.rb +13 -3
- data/lib/xapian_db/resultset.rb +14 -6
- metadata +28 -14
data/README.rdoc
CHANGED
@@ -32,9 +32,9 @@ I tried hard but I couldn't find such a thing so I decided to write it, based on
|
|
32
32
|
|
33
33
|
== Requirements
|
34
34
|
|
35
|
-
* ruby
|
35
|
+
* ruby 2.0.0 or newer
|
36
36
|
* rails 3.0 or newer (if you want to use it with rails)
|
37
|
-
* xapian-core and xapian-ruby binaries installed
|
37
|
+
* xapian-core and xapian-ruby binaries 1.2.x installed
|
38
38
|
|
39
39
|
== Installing xapian binaries
|
40
40
|
|
@@ -47,7 +47,7 @@ Make sure version 1.2.6 or newer is installed.
|
|
47
47
|
If you want to use xapian_db in a Rails app, you need Rails 3 or newer.
|
48
48
|
|
49
49
|
For a first look, look at the examples in the examples folder. There's the simple ruby script basic.rb that shows the basic
|
50
|
-
usage of XapianDB without rails. In the basic_rails folder you'll find a very simple Rails app
|
50
|
+
usage of XapianDB without rails. In the basic_rails folder you'll find a very simple Rails app using XapianDb.
|
51
51
|
|
52
52
|
The following steps assume that you are using xapian_db within a Rails app.
|
53
53
|
|
@@ -64,7 +64,7 @@ You can override these defaults by placing a config file named 'xapian_db.yml' i
|
|
64
64
|
adapter: datamapper # Avaliable adapters: :active_record, :datamapper
|
65
65
|
language: de # Global language; can be overridden for specific blueprints
|
66
66
|
term_min_length: 2 # Ignore single character terms
|
67
|
-
|
67
|
+
enabled_query_flags: FLAG_PHRASE, FLAG_SPELLING_CORRECTION
|
68
68
|
|
69
69
|
development:
|
70
70
|
database: db/xapian_db/development
|
@@ -194,6 +194,32 @@ are ordered by relevance and - within the same relevance - by the natural sort o
|
|
194
194
|
end
|
195
195
|
end
|
196
196
|
|
197
|
+
You can specify a Ruby method for preprocessing the indexed terms. The method needs to be a class method that takes one argument (the terms to be indexed) and
|
198
|
+
returns a string. You can configure it globaly in the config or on a blueprint. For example, this setup will make words with accented e characters searchable
|
199
|
+
by their accentless form:
|
200
|
+
|
201
|
+
class Util
|
202
|
+
def self.strip_accents(terms)
|
203
|
+
terms.gsub(/[éèêëÉÈÊË]/, "e")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
XapianDb::Config.setup do |config|
|
208
|
+
config.indexer_preprocess_callback Util.method(:strip_accents)
|
209
|
+
end
|
210
|
+
|
211
|
+
You may use attributes from associated objects in a blueprint; if you do that and an associated object is updated, your objects should be reindexed, too. You can tell XapnaDB about those dependencies like so:
|
212
|
+
|
213
|
+
XapianDb::DocumentBlueprint.setup(:Person) do |blueprint|
|
214
|
+
blueprint.attribue :address, :as => :json
|
215
|
+
|
216
|
+
blueprint.dependency :Address, when_changed: %i(street zip city) do |address|
|
217
|
+
Person.joins{ address }.where{ adresses.id == my{ address.id } }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
The block you supply to the dependency declaration must return a collection of objects that should get reindexed, too.
|
222
|
+
|
197
223
|
If you want to manage the (re)indexing of your objects on your own, turn off autoindexing in your blueprint:
|
198
224
|
|
199
225
|
XapianDb::DocumentBlueprint.setup(:Person) do |blueprint|
|
@@ -246,6 +272,10 @@ You can query attributes:
|
|
246
272
|
|
247
273
|
results = XapianDb.search "name:Foo"
|
248
274
|
|
275
|
+
You can force the sort order:
|
276
|
+
|
277
|
+
results = XapianDb.search "name:Foo", order: [:name]
|
278
|
+
|
249
279
|
You can query objects of a specific class:
|
250
280
|
|
251
281
|
results = Person.search "name:Foo"
|
data/lib/type_codec.rb
CHANGED
@@ -146,7 +146,7 @@ module XapianDb
|
|
146
146
|
# @return [String] the encoded number
|
147
147
|
def self.encode(number)
|
148
148
|
case number.class.name
|
149
|
-
when "Fixnum", "Float", "Bignum"
|
149
|
+
when "Fixnum", "Float", "Bignum", "Integer"
|
150
150
|
Xapian::sortable_serialise number
|
151
151
|
when "BigDecimal"
|
152
152
|
Xapian::sortable_serialise number.to_f
|
@@ -160,7 +160,7 @@ module XapianDb
|
|
160
160
|
# @return [BigDecimal] the decoded number
|
161
161
|
def self.decode(encoded_number)
|
162
162
|
begin
|
163
|
-
BigDecimal
|
163
|
+
BigDecimal(Xapian::sortable_unserialise(encoded_number).to_s)
|
164
164
|
rescue TypeError
|
165
165
|
raise ArgumentError.new "#{encoded_number} cannot be unserialized"
|
166
166
|
end
|
@@ -175,7 +175,7 @@ module XapianDb
|
|
175
175
|
def self.encode(number)
|
176
176
|
return nil if number.nil?
|
177
177
|
case number.class.name
|
178
|
-
when "Fixnum"
|
178
|
+
when "Fixnum", "Integer"
|
179
179
|
Xapian::sortable_serialise number
|
180
180
|
else
|
181
181
|
raise ArgumentError.new "#{number} was expected to be an integer"
|
data/lib/xapian_db.rb
CHANGED
@@ -105,9 +105,9 @@ module XapianDb
|
|
105
105
|
|
106
106
|
# Update an object in the index
|
107
107
|
# @param [Object] obj An instance of a class with a blueprint configuration
|
108
|
-
def self.index(obj, commit=true)
|
108
|
+
def self.index(obj, commit=true, changed_attrs: [])
|
109
109
|
writer = @block_writer || XapianDb::Config.writer
|
110
|
-
writer.index obj, commit
|
110
|
+
writer.index obj, commit, changed_attrs: changed_attrs
|
111
111
|
end
|
112
112
|
|
113
113
|
# Remove a document from the index
|
@@ -119,11 +119,11 @@ module XapianDb
|
|
119
119
|
|
120
120
|
# Update or delete a xapian document belonging to an object depending on the ignore_if logic(if present)
|
121
121
|
# @param [Object] object An instance of a class with a blueprint configuration
|
122
|
-
def self.reindex(object, commit=true)
|
122
|
+
def self.reindex(object, commit=true, changed_attrs: [])
|
123
123
|
writer = @block_writer || XapianDb::Config.writer
|
124
124
|
blueprint = XapianDb::DocumentBlueprint.blueprint_for object.class.name
|
125
125
|
if blueprint.should_index?(object)
|
126
|
-
writer.index object, commit
|
126
|
+
writer.index object, commit, changed_attrs: changed_attrs
|
127
127
|
else
|
128
128
|
writer.delete_doc_with object.xapian_id, commit
|
129
129
|
end
|
@@ -191,5 +191,4 @@ module XapianDb
|
|
191
191
|
@block_writer = nil
|
192
192
|
end
|
193
193
|
end
|
194
|
-
|
195
194
|
end
|
@@ -49,7 +49,12 @@ module XapianDb
|
|
49
49
|
# add the after commit logic, unless the blueprint has autoindexing turned off
|
50
50
|
if XapianDb::DocumentBlueprint.blueprint_for(klass.name).autoindex?
|
51
51
|
after_commit do
|
52
|
-
|
52
|
+
unless self.destroyed?
|
53
|
+
XapianDb.reindex(self, true, changed_attrs: self.previous_changes.keys)
|
54
|
+
XapianDb::DocumentBlueprint.dependencies_for(klass.name, self.previous_changes.keys).each do |dependency|
|
55
|
+
dependency.block.call(self).each{ |model| XapianDb.reindex model, true, changed_attrs: self.previous_changes.keys }
|
56
|
+
end
|
57
|
+
end
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
@@ -64,7 +69,6 @@ module XapianDb
|
|
64
69
|
XapianDb.reindex_class(klass, options)
|
65
70
|
end
|
66
71
|
end
|
67
|
-
|
68
72
|
end
|
69
73
|
|
70
74
|
# Implement the document helper methods on a module
|
@@ -76,7 +80,7 @@ module XapianDb
|
|
76
80
|
|
77
81
|
# Implement access to the model id
|
78
82
|
define_method :id do
|
79
|
-
return @id unless @
|
83
|
+
return @id unless @id.nil?
|
80
84
|
# retrieve the class and id from data
|
81
85
|
klass_name, id = data.split("-")
|
82
86
|
@id = id.to_i
|
@@ -90,11 +94,8 @@ module XapianDb
|
|
90
94
|
klass = constantize klass_name
|
91
95
|
@indexed_object = klass.find(id.to_i)
|
92
96
|
end
|
93
|
-
|
94
97
|
end
|
95
|
-
|
96
98
|
end
|
97
|
-
|
98
99
|
end
|
99
100
|
end
|
100
101
|
end
|
data/lib/xapian_db/config.rb
CHANGED
@@ -32,7 +32,7 @@ module XapianDb
|
|
32
32
|
end
|
33
33
|
|
34
34
|
# Install delegates for the config instance variables
|
35
|
-
[:database, :adapter, :writer, :stemmer, :stopper].each do |attr|
|
35
|
+
[:database, :adapter, :writer, :stemmer, :stopper, :preprocess_terms].each do |attr|
|
36
36
|
define_method attr do
|
37
37
|
@config.nil? ? nil : @config.instance_variable_get("@_#{attr}")
|
38
38
|
end
|
@@ -183,5 +183,21 @@ module XapianDb
|
|
183
183
|
@_enabled_query_flags.delete flag
|
184
184
|
end
|
185
185
|
|
186
|
+
# Set the indexer preprocess callback.
|
187
|
+
# @param [Method] method a class method; needs to take one parameter and return a string.
|
188
|
+
# @example
|
189
|
+
# class Util
|
190
|
+
# def self.strip_accents(terms)
|
191
|
+
# terms.gsub(/[éèêëÉÈÊË]/, "e")
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# XapianDb::Config.setup do |config|
|
196
|
+
# config.indexer_preprocess_callback Util.method(:strip_accents)
|
197
|
+
# end
|
198
|
+
def indexer_preprocess_callback(method)
|
199
|
+
@_preprocess_terms = method
|
200
|
+
end
|
201
|
+
|
186
202
|
end
|
187
203
|
end
|
data/lib/xapian_db/database.rb
CHANGED
@@ -35,10 +35,19 @@ module XapianDb
|
|
35
35
|
true
|
36
36
|
end
|
37
37
|
|
38
|
-
# Delete all docs of a specific class
|
38
|
+
# Delete all docs of a specific class.
|
39
|
+
#
|
40
|
+
# If `klass` tracks its descendants, then docs of any subclasses will be deleted, too.
|
41
|
+
# (ActiveRecord does this by default; the gem 'descendants_tracker' offers an alternative.)
|
42
|
+
#
|
39
43
|
# @param [Class] klass A class that has a {XapianDb::DocumentBlueprint} configuration
|
40
44
|
def delete_docs_of_class(klass)
|
41
45
|
writer.delete_document("C#{klass}")
|
46
|
+
if klass.respond_to? :descendants
|
47
|
+
klass.descendants.each do |subclass|
|
48
|
+
writer.delete_document("C#{subclass}")
|
49
|
+
end
|
50
|
+
end
|
42
51
|
true
|
43
52
|
end
|
44
53
|
|
@@ -73,6 +82,12 @@ module XapianDb
|
|
73
82
|
enquiry.query = query
|
74
83
|
sort_indices = opts.delete :sort_indices
|
75
84
|
sort_decending = opts.delete :sort_decending
|
85
|
+
order = opts.delete :order
|
86
|
+
raise ArgumentError.new "you can't use sort_indices and order, only one of them" if sort_indices && order
|
87
|
+
|
88
|
+
if order
|
89
|
+
sort_indices = order.map{ |attr_name| XapianDb::DocumentBlueprint.value_number_for attr_name.to_sym }
|
90
|
+
end
|
76
91
|
|
77
92
|
sorter = Xapian::MultiValueKeyMaker.new
|
78
93
|
if sort_indices
|
@@ -80,7 +95,7 @@ module XapianDb
|
|
80
95
|
enquiry.set_sort_by_key_then_relevance(sorter, sort_decending)
|
81
96
|
else
|
82
97
|
sorter.add_value DocumentBlueprint.value_number_for(:natural_sort_order)
|
83
|
-
enquiry.set_sort_by_relevance_then_key sorter,
|
98
|
+
enquiry.set_sort_by_relevance_then_key sorter, false
|
84
99
|
end
|
85
100
|
|
86
101
|
opts[:spelling_suggestion] = @query_parser.spelling_suggestion
|
@@ -26,6 +26,8 @@ module XapianDb
|
|
26
26
|
# ---------------------------------------------------------------------------------
|
27
27
|
class << self
|
28
28
|
|
29
|
+
attr_reader :blueprints
|
30
|
+
|
29
31
|
# Configure the blueprint for a class.
|
30
32
|
# Available options:
|
31
33
|
# - adapter (see {#adapter} for details)
|
@@ -50,6 +52,7 @@ module XapianDb
|
|
50
52
|
# We can always do a field search on the name of the indexed class
|
51
53
|
@searchable_prefixes << "indexed_class"
|
52
54
|
@attributes = @blueprints.values.map { |blueprint| blueprint.attribute_names}.flatten.compact.uniq.sort || []
|
55
|
+
blueprint
|
53
56
|
end
|
54
57
|
|
55
58
|
# reset the blueprint setup
|
@@ -66,11 +69,17 @@ module XapianDb
|
|
66
69
|
# Get all configured classes
|
67
70
|
# @return [Array<Class>]
|
68
71
|
def configured_classes
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
if @blueprints
|
73
|
+
@blueprints.keys.map {|class_name| XapianDb::Utilities.constantize(class_name) }
|
74
|
+
else
|
75
|
+
[]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def dependencies_for(klass_name, changed_attrs)
|
80
|
+
@blueprints.values.map(&:dependencies)
|
81
|
+
.flatten
|
82
|
+
.select{ |dependency| dependency.dependent_on == klass_name && dependency.interested_in?(changed_attrs) }
|
74
83
|
end
|
75
84
|
|
76
85
|
# Get the blueprint for a class
|
@@ -167,7 +176,6 @@ module XapianDb
|
|
167
176
|
end
|
168
177
|
end
|
169
178
|
end
|
170
|
-
|
171
179
|
end
|
172
180
|
|
173
181
|
# ---------------------------------------------------------------------------------
|
@@ -258,15 +266,17 @@ module XapianDb
|
|
258
266
|
# Blueprint DSL methods
|
259
267
|
# ---------------------------------------------------------------------------------
|
260
268
|
|
261
|
-
attr_reader :lazy_base_query, :_natural_sort_order
|
269
|
+
attr_reader :lazy_base_query, :_natural_sort_order, :dependencies
|
262
270
|
|
263
271
|
# Construct the blueprint
|
264
272
|
def initialize
|
265
273
|
@attributes_hash = {}
|
266
274
|
@indexed_methods_hash = {}
|
267
275
|
@type_map = {}
|
276
|
+
@dependencies = []
|
268
277
|
@_natural_sort_order = :id
|
269
278
|
@autoindex = true
|
279
|
+
@indexer_preprocess_callback = nil
|
270
280
|
end
|
271
281
|
|
272
282
|
# Set the adapter
|
@@ -377,7 +387,7 @@ module XapianDb
|
|
377
387
|
@ignore_expression = block
|
378
388
|
end
|
379
389
|
|
380
|
-
# Define a base query to select one or all objects of the indexed class. The reason for a
|
390
|
+
# Define a base query to select one or all objects of the indexed class. The reason for a
|
381
391
|
# base query is to optimize the query avoiding th 1+n problematic. The base query should only
|
382
392
|
# include joins(...) and includes(...) calls.
|
383
393
|
# @param [expression] a base query expression
|
@@ -400,6 +410,33 @@ module XapianDb
|
|
400
410
|
@_natural_sort_order = name || block
|
401
411
|
end
|
402
412
|
|
413
|
+
def dependency(klass_name, when_changed: [], &block)
|
414
|
+
@dependencies << Dependency.new(klass_name.to_s, when_changed, block)
|
415
|
+
end
|
416
|
+
|
417
|
+
# Set the indexer preprocess callback.
|
418
|
+
# @param [Method] method a class method; needs to take one parameter and return a string.
|
419
|
+
# @example
|
420
|
+
# class Util
|
421
|
+
# def self.strip_accents(terms)
|
422
|
+
# terms.gsub(/[éèêëÉÈÊË]/, "e")
|
423
|
+
# end
|
424
|
+
# end
|
425
|
+
#
|
426
|
+
# XapianDb::DocumentBlueprint.setup(:IndexedObject) do |blueprint|
|
427
|
+
# blueprint.attribute :name
|
428
|
+
# blueprint.indexer_preprocess_callback Util.method(:strip_accents)
|
429
|
+
# end
|
430
|
+
def indexer_preprocess_callback(method)
|
431
|
+
@indexer_preprocess_callback = method
|
432
|
+
end
|
433
|
+
|
434
|
+
# Reader for indexer_preprocess_callback.
|
435
|
+
# Returns the terms preprocessing method for this blueprint, the global method from config or nil.
|
436
|
+
def preprocess_terms
|
437
|
+
@indexer_preprocess_callback || XapianDb::Config.preprocess_terms
|
438
|
+
end
|
439
|
+
|
403
440
|
# Options for an indexed method
|
404
441
|
class IndexOptions
|
405
442
|
|
@@ -414,7 +451,24 @@ module XapianDb
|
|
414
451
|
@no_split = options[:no_split]
|
415
452
|
@block = options[:block]
|
416
453
|
end
|
454
|
+
end
|
455
|
+
|
456
|
+
class Dependency
|
457
|
+
|
458
|
+
attr_reader :dependent_on, :trigger_attributes, :block
|
417
459
|
|
460
|
+
# Constructor
|
461
|
+
# @param [String] klass_name Name of the dependent class
|
462
|
+
# @param [Array] trigger_attributes List of attributes to watch for changes (if empty, triggers on any change)
|
463
|
+
# @option [Block] block Block that is called when changes are detected; the block must return an array of indexeable objects
|
464
|
+
def initialize(klass_name, trigger_attributes, block)
|
465
|
+
@dependent_on, @trigger_attributes, @block = klass_name, trigger_attributes.map(&:to_s), block
|
466
|
+
end
|
467
|
+
|
468
|
+
def interested_in?(changed_attrs)
|
469
|
+
return true if @trigger_attributes.empty?
|
470
|
+
(@trigger_attributes & changed_attrs).any?
|
471
|
+
end
|
418
472
|
end
|
419
473
|
|
420
474
|
private
|
@@ -431,7 +485,5 @@ module XapianDb
|
|
431
485
|
@reserved_method_names ||= Xapian::Document.instance_methods
|
432
486
|
@reserved_method_names.include?(attr_name.to_sym)
|
433
487
|
end
|
434
|
-
|
435
488
|
end
|
436
|
-
|
437
489
|
end
|
@@ -13,7 +13,7 @@ module XapianDb
|
|
13
13
|
def index_task(options)
|
14
14
|
klass = constantize options[:class]
|
15
15
|
obj = klass.respond_to?(:get) ? klass.get(options[:id]) : klass.find(options[:id])
|
16
|
-
DirectWriter.index obj
|
16
|
+
DirectWriter.index obj, true, changed_attrs: options[:changed_attrs]
|
17
17
|
end
|
18
18
|
|
19
19
|
def delete_doc_task(options)
|
@@ -24,7 +24,6 @@ module XapianDb
|
|
24
24
|
klass = constantize options[:class]
|
25
25
|
DirectWriter.reindex_class klass, :verbose => false
|
26
26
|
end
|
27
|
-
|
28
27
|
end
|
29
28
|
end
|
30
|
-
end
|
29
|
+
end
|
@@ -18,8 +18,8 @@ module XapianDb
|
|
18
18
|
|
19
19
|
# Update an object in the index
|
20
20
|
# @param [Object] obj An instance of a class with a blueprint configuration
|
21
|
-
def index(obj, commit=true)
|
22
|
-
beanstalk.put( {:task => "index_task", :class => obj.class.name, :id => obj.id }.to_json )
|
21
|
+
def index(obj, commit=true, changed_attrs: [])
|
22
|
+
beanstalk.put( { :task => "index_task", :class => obj.class.name, :id => obj.id, :changed_attrs => changed_attrs }.to_json )
|
23
23
|
end
|
24
24
|
|
25
25
|
# Remove an object from the index
|
@@ -17,11 +17,11 @@ module XapianDb
|
|
17
17
|
class << self
|
18
18
|
|
19
19
|
# Update an object in the index
|
20
|
-
# @param [Object]
|
21
|
-
def index(
|
22
|
-
blueprint = XapianDb::DocumentBlueprint.blueprint_for(
|
20
|
+
# @param [Object] object An instance of a class with a blueprint configuration
|
21
|
+
def index(object, commit=true, changed_attrs: [])
|
22
|
+
blueprint = XapianDb::DocumentBlueprint.blueprint_for(object.class.name)
|
23
23
|
indexer = XapianDb::Indexer.new(XapianDb.database, blueprint)
|
24
|
-
doc = indexer.build_document_for(
|
24
|
+
doc = indexer.build_document_for(object)
|
25
25
|
XapianDb.database.store_doc(doc)
|
26
26
|
XapianDb.database.commit if commit
|
27
27
|
end
|
@@ -35,10 +35,10 @@ module XapianDb
|
|
35
35
|
|
36
36
|
# Update or delete a xapian document belonging to an object depending on the ignore_if logic(if present)
|
37
37
|
# @param [Object] object An instance of a class with a blueprint configuration
|
38
|
-
def reindex(object, commit=true)
|
38
|
+
def reindex(object, commit=true, changed_attrs: [])
|
39
39
|
blueprint = XapianDb::DocumentBlueprint.blueprint_for object.class.name
|
40
40
|
if blueprint.should_index?(object)
|
41
|
-
index object, commit
|
41
|
+
index object, commit, changed_attrs: changed_attrs
|
42
42
|
else
|
43
43
|
delete_doc_with object.xapian_id, commit
|
44
44
|
end
|
@@ -12,7 +12,7 @@ module XapianDb
|
|
12
12
|
|
13
13
|
# Update an object in the index
|
14
14
|
# @param [Object] obj An instance of a class with a blueprint configuration
|
15
|
-
def index(obj, commit=true); end
|
15
|
+
def index(obj, commit=true, changed_attrs: []); end
|
16
16
|
|
17
17
|
# Remove an object from the index
|
18
18
|
# @param [String] xapian_id The document id
|
@@ -28,4 +28,4 @@ module XapianDb
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
|
-
end
|
31
|
+
end
|