public
Fork of DrMark/ultrasphinx
Description: a maintained fork of Evan Weaver's Ultrasphinx code -- see the escape_sql branch
Homepage: http://blog.evanweaver.com/files/doc/fauna/ultrasphinx/files/README.html
Clone URL: git://github.com/github/ultrasphinx.git
Import 1.9 from private branch. Adds delta index support.


git-svn-id: svn://rubyforge.org/var/svn/fauna/ultrasphinx/trunk@1716 
c1ad287d-65d5-445d-b84c-e64f8492f1e6
evanweaver (author)
Sat Mar 08 12:24:20 -0800 2008
commit  148b5fe033893b318e655cf247669cc36dd20c6b
tree    1c6dd6ff7b42394d75856fb5e09552a025dc648d
parent  2c28488a7d6af1cdb63a46f76057196a4fff4385
...
1
 
 
2
3
4
...
1
2
3
4
5
6
0
@@ -1,4 +1,6 @@
0
 
0
+v1.9. Delta indexing. ERb now supported in .base files. Allow setting the searched indexes at runtime.
0
+
0
 v1.8.1. Use multifind/multiget for record loading; avoid using HashWithIndifferentAccess internally for speed; other minor performance improvements.
0
 
0
 v1.8. Update client for compatibility with Sphinx 0.9.8 r1112. This is a breaking release! You need to update Sphinx along with Ultrasphinx. Float range bugfix; text faceting on association_include bugfix. Postgres users, please note that the return type of CRC32() has changed to bigint.
...
15
16
17
18
19
 
 
 
 
20
21
22
23
 
 
 
24
25
26
...
15
16
17
 
 
18
19
20
21
22
23
24
 
25
26
27
28
29
30
0
@@ -15,12 +15,16 @@ You will <b>not</b> want to keep the generated <tt>development.conf</tt> in the
0
 
0
 It's easy to keep the search daemon and the indexer running in a production environment. Cronjobs are the best way:
0
 
0
- 0,30 * * * * bash -c 'cd /path/to/production/current/; RAILS_ENV=production \
0
- rake ultrasphinx:index >> /log/ultrasphinx-index.log 2>&1'
0
+ */6 * * * * bash -c 'cd /path/to/production/current/; RAILS_ENV=production \
0
+ rake ultrasphinx:index:delta >> /log/ultrasphinx-index.log 2>&1'
0
+ 1 4 * * * * bash -c 'cd /path/to/production/current/; RAILS_ENV=production \
0
+ rake ultrasphinx:index:main >> /log/ultrasphinx-index.log 2>&1'
0
   */3 * * * * bash -c 'cd /path/to/production/current/; RAILS_ENV=production \
0
     rake ultrasphinx:daemon:start >> /log/ultrasphinx-daemon.log 2>&1'
0
 
0
-The first line runs the indexer every thirty minutes. The second line will try to restart the search daemon every three minutes. If it's already running, nothing happens.
0
+The first line reindexes the delta index every 10 minutes. The second line reindexes the main index once a day at 4am. The third line will try to restart the search daemon every three minutes. If it's already running, nothing happens.
0
+
0
+Of course if you don't have any models with deltas, don't include the <tt>ultrasphinx:index:delta</tt> task.
0
 
0
 If you are under severe memory limitations you might want to manage the daemon with Monit instead, so you can keep a closer eye on it. The search daemon is extremely reliable, so don't bother with fancy monitoring infrastructure unless you're sure you need it.
0
 
...
4
5
6
7
 
 
 
8
9
10
...
12
13
14
15
16
 
17
...
4
5
6
 
7
8
9
10
11
12
...
14
15
16
 
17
18
19
0
@@ -4,7 +4,9 @@
0
 These Rake tasks are made available to your Rails app:
0
 
0
 <tt>ultrasphinx:configure</tt>:: Rebuild the configuration file for this particular environment.
0
-<tt>ultrasphinx:index</tt>:: Reindex the database and send an update signal to the search daemon.
0
+<tt>ultrasphinx:index</tt>:: Reindex and rotate all indexes.
0
+<tt>ultrasphinx:index:delta</tt>:: Reindex and rotate the delta index.
0
+<tt>ultrasphinx:index:main</tt>:: Reindex and rotate the main index.
0
 <tt>ultrasphinx:daemon:restart</tt>:: Restart the search daemon.
0
 <tt>ultrasphinx:daemon:start</tt>:: Start the search daemon.
0
 <tt>ultrasphinx:daemon:stop</tt>:: Stop the search daemon.
0
@@ -12,4 +14,4 @@ These Rake tasks are made available to your Rails app:
0
 <tt>ultrasphinx:spelling:build</tt>:: Rebuild the custom spelling dictionary. You may need to use <tt>sudo</tt> if your Aspell folder is not writable by the app user.
0
 <tt>ultrasphinx:bootstrap</tt>:: Bootstrap a full Sphinx environment by running configure, index, then daemon:start.
0
 
0
-All tasks have shortcuts. Use <tt>us:conf</tt>, <tt>us:in</tt>, <tt>us:restart</tt>, <tt>us:start</tt>, <tt>us:stop</tt>, <tt>us:stat</tt>, <tt>us:spell</tt>, and <tt>us:boot</tt>.
0
\ No newline at end of file
0
+All tasks have shortcuts. Use <tt>us:conf</tt>, <tt>us:index</tt>, <tt>us:main</tt>, <tt>us:delta</tt>, <tt>us:restart</tt>, <tt>us:start</tt>, <tt>us:stop</tt>, <tt>us:stat</tt>, <tt>us:spell</tt>, and <tt>us:boot</tt>.
0
\ No newline at end of file
0
...
14
15
16
17
 
18
19
20
...
26
27
28
 
29
30
31
32
33
34
35
36
 
37
38
39
...
53
54
55
56
 
57
58
59
...
93
94
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
97
98
...
14
15
16
 
17
18
19
20
...
26
27
28
29
30
31
32
33
34
35
 
36
37
38
39
40
...
54
55
56
 
57
58
59
60
...
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
0
@@ -14,7 +14,7 @@ If you use this software, please {make a donation}[http://blog.evanweaver.com/do
0
 == Requirements
0
 
0
 * MySQL 5.0, or PostgreSQL 8.2
0
-* Sphinx 0.9.8-dev r1112
0
+* Sphinx 0.9.8-rc1
0
 * Rails 2.0.2
0
 
0
 More recent versions than listed are usually ok.
0
@@ -26,14 +26,15 @@ Sphinx/Ultrasphinx is the fastest and most stable Rails fulltext search solution
0
 Features include:
0
 
0
 * searching and ranking across orthogonal models
0
+* delta index support
0
 * excerpt highlighting
0
 * Google-style query parser
0
 * spellcheck
0
 * faceting on text, date, and numeric fields
0
 * field weighting, merging, and aliases
0
 * <tt>belongs_to</tt> and <tt>has_many</tt> includes
0
-* drop-in compatibility with Interlock[http://blog.evanweaver.com/files/doc/fauna/interlock/]
0
 * drop-in compatibility with will_paginate[http://err.lighthouseapp.com/projects/466/home]
0
+* drop-in compatibility with Interlock[http://blog.evanweaver.com/files/doc/fauna/interlock/]
0
 * multiple deployment environments
0
 * comprehensive Rake tasks
0
 
0
@@ -53,7 +54,7 @@ Then, install the plugin:
0
  
0
 Next, copy the <tt>examples/default.base</tt> file to <tt>RAILS_ROOT/config/ultrasphinx/default.base</tt>. This file sets up the Sphinx daemon options such as port, host, and index location.
0
   
0
-If you need per-environment configuration, you can use <tt>RAILS_ROOT/config/ultrasphinx/development.base</tt>, etc.
0
+If you need per-environment configuration, you can use <tt>RAILS_ROOT/config/ultrasphinx/development.base</tt>, etc. Note that ERb is also allowed within the <tt>.base</tt> files, and can be an alternative way to DRY up multiple configurations.
0
 
0
 Now, in your models, use the <tt>is_indexed</tt> method to configure a model as searchable. For example:
0
   
0
@@ -93,6 +94,23 @@ Once the <tt>@search</tt> object has been <tt>run</tt>, it is directly compatibl
0
 == Spell checking
0
 
0
 See Ultrasphinx::Spell.
0
+
0
+== Delta indexing
0
+
0
+Delta indexing speeds up your updates by not reindexing the entire dataset every time.
0
+
0
+First, in your <tt>.base</tt> file, set the indexer option <tt>delta</tt> to your maximum interval between full reindexes. A day or a week is good, depending. Add a little bit to account for the time it takes the actual index to run:
0
+
0
+ delta = <%= 1.day + 30.minutes %>
0
+
0
+Now, configure your models for delta indexing in the <tt>is_indexed</tt> call:
0
+
0
+ is_indexed :fields => ['created_at', 'title', 'body'],
0
+ :delta => true
0
+
0
+Now you can run <tt>rake ultrasphinx:index:delta</tt> frequently, and only records that were changed within 1 day will be reindexed. You will need to run <tt>rake ultrasphinx:index:main</tt> once a day to move the delta contents into the main index.
0
+
0
+See ActiveRecord::Base .is_indexed and DEPLOYMENT_NOTES[link:files/DEPLOYMENT_NOTES.html] for more.
0
   
0
 == Available Rake tasks
0
 
0
...
8
9
10
11
12
13
...
8
9
10
 
11
12
0
@@ -8,6 +8,5 @@ Planned:
0
 
0
 Not sure:
0
 
0
-* Delta indexing
0
 * Use Pat Allan's association configurator, possibly with an API change to avoid the message-passing DSL
0
 * Use Treetop for the query parser instead of regexes
...
3
4
5
6
7
 
 
8
9
10
...
15
16
17
18
19
 
20
21
 
 
 
22
23
24
 
25
 
 
 
26
27
 
28
29
30
 
31
32
33
34
35
36
 
 
37
38
39
 
40
41
42
 
43
44
45
46
47
 
48
49
50
51
52
 
53
54
55
 
56
57
58
59
60
 
61
62
63
64
65
 
 
 
66
67
 
68
69
70
...
3
4
5
 
 
6
7
8
9
10
...
15
16
17
 
 
18
19
20
21
22
23
24
 
 
25
26
27
28
29
30
31
32
33
 
 
34
35
36
37
38
 
 
39
40
41
42
 
43
44
45
46
47
48
49
 
50
51
52
53
54
55
56
57
58
59
 
 
60
61
62
63
64
65
66
67
 
 
 
 
68
69
70
71
 
72
73
74
75
0
@@ -3,8 +3,8 @@
0
 # Sphinx/Ultrasphinx user-configurable options.
0
 #
0
 # Copy this file to RAILS_ROOT/config/ultrasphinx. You can use individual
0
-# namespaces if you want (e.g. development.base, production.base,
0
-# test.base).
0
+# namespaces if you want (e.g. development.base, production.base,
0
+# test.base). Note that ERb is also allowed.
0
 #
0
 # This file should not be handed directly to Sphinx. Use the rake task
0
 #
0
@@ -15,56 +15,61 @@
0
 # to Sphinx.
0
 #
0
 # It is safe to edit .base files by hand. It is not safe to edit the generated
0
-# .conf files. Do not symlink the .conf file to the .base file! I don't know why
0
-# people think they need to do that. It's wrong.
0
+# .conf files. Do not symlink the .conf file to the .base file; it's wrong.
0
 #
0
 
0
+<% path = "/opt/local/var/db/sphinx/" %>
0
+
0
+# Indexing options
0
 indexer
0
-{
0
- # Indexer running options
0
+{
0
   mem_limit = 256M
0
+
0
+ # Ultrasphinx-specific key
0
+ delta = <%= 1.day + 30.minutes %>
0
 }
0
 
0
+# Daemon options
0
 searchd
0
-{
0
- # Daemon options
0
+{
0
   # What interface the search daemon should listen on and where to store its logs
0
   address = 0.0.0.0
0
   port = 3312
0
   seamless_rotate = 1
0
- log = /opt/local/var/db/sphinx/log/searchd.log
0
- query_log = /opt/local/var/db/sphinx/log/query.log
0
+ log = <%= path %>log/searchd.log
0
+ query_log = <%= path %>log/query.log
0
   read_timeout = 5
0
   max_children = 300
0
- pid_file = /opt/local/var/db/sphinx/log/searchd.pid
0
+ pid_file = <%= path %>log/searchd.pid
0
   max_matches = 100000
0
 }
0
 
0
+# Client options
0
 client
0
 {
0
- # Client options
0
   # Name of the Aspell dictionary (two letters max)
0
   dictionary_name = ap
0
+
0
   # How your application connects to the search daemon (not necessarily the same as above)
0
   server_host = localhost
0
   server_port = 3312
0
 }
0
 
0
+# Individual SQL source options
0
 source
0
-{
0
- # Individual SQL source options
0
+{
0
   sql_ranged_throttle = 0
0
   sql_range_step = 5000
0
   sql_query_post =
0
 }
0
 
0
+# Index building options
0
 index
0
-{
0
- # Index building options
0
- path = /opt/local/var/db/sphinx/
0
- docinfo = extern # just leave this alone
0
+{
0
+ path = <%= path %>
0
+ docinfo = extern # Just leave this alone
0
   morphology = stem_en
0
- stopwords = # /path/to/stopwords.txt
0
+ stopwords = # <%= path %>/ap-stopwords.txt
0
   min_word_len = 1
0
 
0
   # HTML-specific options
...
17
18
19
20
 
21
22
23
...
17
18
19
 
20
21
22
23
0
@@ -17,7 +17,7 @@ require 'ultrasphinx/associations'
0
 require 'ultrasphinx/core_extensions'
0
 require 'ultrasphinx/is_indexed'
0
 
0
-if (ActiveRecord::Base.connection rescue nil) # XXX Forget why this needed to be wrapped
0
+if (ActiveRecord::Base.connection rescue nil) # XXX Not sure why this needed to be wrapped.
0
   require 'ultrasphinx/configure'
0
   require 'ultrasphinx/autoload'
0
   require 'ultrasphinx/fields'
...
41
42
43
44
45
 
46
47
48
49
50
51
52
53
54
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
57
58
59
60
61
...
68
69
70
71
 
 
72
73
74
...
86
87
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
90
91
 
92
93
94
...
101
102
103
104
 
105
106
107
 
108
109
 
 
110
111
112
...
116
117
118
119
 
120
121
122
123
 
 
 
 
 
 
124
125
126
...
140
141
142
143
 
144
145
146
...
271
272
273
274
 
275
276
 
277
278
279
280
 
281
282
283
...
41
42
43
 
 
44
45
 
 
 
 
 
 
 
 
 
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
 
 
71
72
73
...
80
81
82
 
83
84
85
86
87
...
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
 
126
127
128
129
...
136
137
138
 
139
140
141
 
142
143
 
144
145
146
147
148
...
152
153
154
 
155
156
157
158
 
159
160
161
162
163
164
165
166
167
...
181
182
183
 
184
185
186
187
...
312
313
314
 
315
316
 
317
318
319
320
 
321
322
323
324
0
@@ -41,21 +41,33 @@ module Ultrasphinx
0
               
0
         say "rebuilding configurations for #{RAILS_ENV} environment"
0
         say "available models are #{MODEL_CONFIGURATION.keys.to_sentence}"
0
- File.open(CONF_PATH, "w") do |conf|
0
-
0
+ File.open(CONF_PATH, "w") do |conf|
0
           conf.puts global_header
0
- sources = []
0
-
0
- say "generating SQL"
0
- cached_groups = Fields.instance.groups.join("\n")
0
- MODEL_CONFIGURATION.each_with_index do |model_options, class_id|
0
- model, options = model_options
0
- klass, source = model.constantize, model.tableize.gsub('/', '__')
0
- sources << source
0
- conf.puts build_source(Fields.instance, model, options, class_id, klass, source, cached_groups)
0
+ say "generating SQL"
0
+
0
+ INDEXES.each do |index|
0
+ sources = []
0
+ cached_groups = Fields.instance.groups.join("\n")
0
+
0
+ MODEL_CONFIGURATION.each_with_index do |model_and_options, class_id|
0
+ # This relies on hash sort order being deterministic per-machine
0
+ model, options = model_and_options
0
+ klass = model.constantize
0
+ source = "#{model.tableize.gsub('/', '__')}_#{index}"
0
+
0
+ if index != DELTA_INDEX or options['delta']
0
+ # If we are building the delta, we only want to include the models that requested it
0
+ conf.puts build_source(index, Fields.instance, model, options, class_id, klass, source, cached_groups)
0
+ sources << source
0
+ end
0
+ end
0
+
0
+ if sources.any?
0
+ # Don't generate a delta index if there are no delta tables
0
+ conf.puts build_index(index, sources)
0
+ end
0
+
0
           end
0
-
0
- conf.puts build_index(sources)
0
         end
0
       end
0
       
0
@@ -68,7 +80,8 @@ module Ultrasphinx
0
         ["\n# Auto-generated at #{Time.now}.",
0
          "# Hand modifications will be overwritten.",
0
          "# #{BASE_PATH}\n",
0
- INDEXER_SETTINGS._to_conf_string('indexer'),
0
+ INDEXER_SETTINGS.except('delta')._to_conf_string('indexer'),
0
+ "",
0
          DAEMON_SETTINGS._to_conf_string("searchd")]
0
       end
0
       
0
@@ -86,9 +99,31 @@ module Ultrasphinx
0
         end
0
         conf.sort.join("\n")
0
       end
0
+
0
+
0
+ def build_delta_condition(index, klass, options)
0
+ if index == DELTA_INDEX and options['delta']
0
+ # Add delta condition if necessary
0
+ table, field = klass.table_name, options['delta']['field']
0
+ source_string = "#{table}.#{field}"
0
+ delta_column = klass.columns_hash[field]
0
+
0
+ if delta_column
0
+ raise ConfigurationError, "#{source_string} is not a :datetime" unless delta_column.type == :datetime
0
+ if (options['fields'] + options['concatenate'] + options['include']).detect { |entry| entry['sortable'] }
0
+ # Warning about the sortable problem
0
+ # XXX Kind of in an odd place, but I want to happen at index time
0
+ Ultrasphinx.say "warning; text sortable columns on #{klass.name} will return wrong results with partial delta indexing"
0
+ end
0
+ string = "#{source_string} > #{SQL_FUNCTIONS[ADAPTER]['delta']._interpolate(INDEXER_SETTINGS['delta'])}";
0
+ else
0
+ Ultrasphinx.say "warning; #{klass.name} will reindex the entire table during delta indexing"
0
+ end
0
+ end
0
+ end
0
       
0
       
0
- def setup_source_arrays(klass, fields, class_id, conditions)
0
+ def setup_source_arrays(index, klass, fields, class_id, conditions)
0
         condition_strings = Array(conditions).map do |condition|
0
           "(#{condition})"
0
         end
0
@@ -101,12 +136,13 @@ module Ultrasphinx
0
       end
0
       
0
       
0
- def range_select_string(klass)
0
+ def range_select_string(klass, delta_condition)
0
         ["sql_query_range = SELECT",
0
           SQL_FUNCTIONS[ADAPTER]['range_cast']._interpolate("MIN(#{klass.primary_key})"),
0
- ", ",
0
+ ",",
0
           SQL_FUNCTIONS[ADAPTER]['range_cast']._interpolate("MAX(#{klass.primary_key})"),
0
- "FROM #{klass.table_name}"
0
+ "FROM #{klass.table_name}",
0
+ ("WHERE #{delta_condition}" if delta_condition),
0
         ].join(" ")
0
       end
0
       
0
@@ -116,11 +152,16 @@ module Ultrasphinx
0
       end
0
       
0
             
0
- def build_source(fields, model, options, class_id, klass, source, groups)
0
+ def build_source(index, fields, model, options, class_id, klass, source, groups)
0
                 
0
         column_strings, join_strings, condition_strings, group_bys, use_distinct, remaining_columns =
0
           setup_source_arrays(
0
- klass, fields, class_id, options['conditions'])
0
+ index, klass, fields, class_id, options['conditions'])
0
+
0
+ delta_condition =
0
+ build_delta_condition(
0
+ index, klass, options)
0
+ condition_strings << delta_condition if delta_condition
0
 
0
         column_strings, join_strings, group_bys, remaining_columns =
0
           build_regular_fields(
0
@@ -140,7 +181,7 @@ module Ultrasphinx
0
          "source #{source}\n{",
0
           SOURCE_SETTINGS._to_conf_string,
0
           setup_source_database(klass),
0
- range_select_string(klass),
0
+ range_select_string(klass, delta_condition),
0
           build_query(klass, column_strings, join_strings, condition_strings, use_distinct, group_bys),
0
           "\n" + groups,
0
           query_info_string(klass, class_id),
0
@@ -271,13 +312,13 @@ module Ultrasphinx
0
       end
0
       
0
     
0
- def build_index(sources)
0
+ def build_index(index, sources)
0
         ["\n# Index configuration\n\n",
0
- "index #{UNIFIED_INDEX_NAME}\n{",
0
+ "index #{index}\n{",
0
           sources.sort.map do |source|
0
             " source = #{source}"
0
           end.join("\n"),
0
- INDEX_SETTINGS.merge('path' => INDEX_SETTINGS['path'] + "/sphinx_index_#{UNIFIED_INDEX_NAME}")._to_conf_string,
0
+ INDEX_SETTINGS.merge('path' => INDEX_SETTINGS['path'] + "/sphinx_index_#{index}")._to_conf_string,
0
          "}\n\n"]
0
       end
0
       
...
58
59
60
61
 
62
63
64
...
88
89
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
92
93
...
110
111
112
 
113
114
115
...
137
138
139
140
 
 
 
141
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
144
145
146
147
148
149
 
150
151
152
...
155
156
157
158
 
159
160
161
162
 
163
164
165
...
58
59
60
 
61
62
63
64
...
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
...
130
131
132
133
134
135
136
...
158
159
160
 
161
162
163
164
 
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
 
192
193
194
195
...
198
199
200
 
201
202
203
204
 
205
206
207
208
0
@@ -58,7 +58,7 @@ The keys <tt>:facet</tt>, <tt>:sortable</tt>, <tt>:class_name</tt>, <tt>:associa
0
 
0
 == Concatenating several fields within one record
0
 
0
-Use the <tt>:concatenate</tt> key (MySQL only).
0
+Use the <tt>:concatenate</tt> key.
0
 
0
 Accepts an array of option hashes.
0
 
0
@@ -88,6 +88,26 @@ Also, If you want to include a model that you don't have an actual ActiveRecord
0
 
0
 Ultrasphinx is not an object-relational mapper, and the association generation is intended to stay minimal--don't be afraid of <tt>:association_sql</tt>.
0
 
0
+== Enabling delta indexing
0
+
0
+Use the <tt>:delta</tt> key.
0
+
0
+Accepts either <tt>true</tt>, or a hash with a <tt>:field</tt> key.
0
+
0
+If you pass <tt>true</tt>, the <tt>updated_at</tt> column will be used for choosing the delta records, if it exists. If it doesn't exist, the entire table will be reindexed at every delta. Example:
0
+
0
+ :delta => true
0
+
0
+If you need to use a non-default column name, use a hash:
0
+
0
+ :delta => {:field => 'created_at'}
0
+
0
+Note that the column type must be time-comparable in the DB. Also note that faceting may return higher counts than actually exist on delta-indexed tables, and that sorting by string columns will not work well. These are both limitations of Sphinx's index merge scheme. You can perhaps mitigate the issues by only searching the main index for facets or sorts:
0
+
0
+ Ultrasphinx::Search.new(:query => "query", :indexes => Ultrasphinx::MAIN_INDEX)
0
+
0
+The date range of the delta include is set in the <tt>.base</tt> file.
0
+
0
 = Examples
0
 
0
 == Complex configuration
0
@@ -110,6 +130,7 @@ Here's an example configuration using most of the options, taken from production
0
         {:association_name => 'comments', :field => 'body', :as => 'comments',
0
           :conditions => "comments.item_type = '#{base_class}'"}
0
       ],
0
+ :delta => {:field => 'published_at'},
0
       :conditions => self.live_condition_string
0
   end
0
 
0
@@ -137,16 +158,38 @@ If the associations weren't just <tt>has_many</tt> and <tt>belongs_to</tt>, you
0
     def self.is_indexed opts = {}
0
       opts = HashWithIndifferentAccess.new(opts)
0
           
0
- opts.assert_valid_keys ['fields', 'concatenate', 'conditions', 'include']
0
+ opts.assert_valid_keys ['fields', 'concatenate', 'conditions', 'include', 'delta']
0
+
0
+ # Single options
0
       
0
- Array(opts['fields']).each do |entry|
0
+ if opts['conditions']
0
+ # Do nothing
0
+ end
0
+
0
+ if opts['delta']
0
+ if opts['delta'] == true
0
+ opts['delta'] = {'field' => 'updated_at'}
0
+ elsif opts['delta'].is_a? String
0
+ opts['delta'] = {'field' => opts['delta']}
0
+ end
0
+ opts['delta'].stringify_keys!
0
+ opts['delta'].assert_valid_keys ['field']
0
+ end
0
+
0
+ # Enumerable options
0
+
0
+ opts['fields'] = Array(opts['fields'])
0
+ opts['concatenate'] = Array(opts['concatenate'])
0
+ opts['include'] = Array(opts['include'])
0
+
0
+ opts['fields'].each do |entry|
0
         if entry.is_a? Hash
0
           entry.stringify_keys!
0
           entry.assert_valid_keys ['field', 'as', 'facet', 'function_sql', 'sortable']
0
         end
0
       end
0
       
0
- Array(opts['concatenate']).each do |entry|
0
+ opts['concatenate'].each do |entry|
0
         entry.stringify_keys!
0
         entry.assert_valid_keys ['class_name', 'association_name', 'conditions', 'field', 'as', 'fields', 'association_sql', 'facet', 'function_sql', 'sortable']
0
         raise Ultrasphinx::ConfigurationError, "You can't mix regular concat and group concats" if entry['fields'] and (entry['field'] or entry['class_name'] or entry['association_name'])
0
@@ -155,11 +198,11 @@ If the associations weren't just <tt>has_many</tt> and <tt>belongs_to</tt>, you
0
         raise Ultrasphinx::ConfigurationError, "Regular concatenations should have multiple fields" if entry['fields'] and !entry['fields'].is_a?(Array)
0
       end
0
       
0
- Array(opts['include']).each do |entry|
0
+ opts['include'].each do |entry|
0
         entry.stringify_keys!
0
         entry.assert_valid_keys ['class_name', 'association_name', 'field', 'as', 'association_sql', 'facet', 'function_sql', 'sortable']
0
       end
0
-
0
+
0
       Ultrasphinx::MODEL_CONFIGURATION[self.name] = opts
0
     end
0
   end
...
42
43
44
 
45
46
47
...
100
101
102
 
 
 
 
103
104
105
...
125
126
127
128
 
 
129
130
131
...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
 
186
187
188
189
190
 
 
191
192
193
...
296
297
298
 
299
300
301
...
308
309
310
311
 
 
312
313
314
315
316
317
318
 
319
320
321
322
 
 
 
 
 
 
 
323
324
325
...
363
364
365
366
367
 
 
368
369
370
...
42
43
44
45
46
47
48
...
101
102
103
104
105
106
107
108
109
110
...
130
131
132
 
133
134
135
136
137
...
155
156
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
159
160
 
 
161
162
163
164
165
166
...
269
270
271
272
273
274
275
...
282
283
284
 
285
286
287
288
289
290
291
292
 
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
...
345
346
347
 
 
348
349
350
351
352
0
@@ -42,6 +42,7 @@ The hash lets you customize internal aspects of the search.
0
 <tt>:weights</tt>:: A hash. Text-field names and associated query weighting. The default weight for every field is 1.0. Example: <tt>:weights => {'title' => 2.0}</tt>
0
 <tt>:filters</tt>:: A hash. Names of numeric or date fields and associated values. You can use a single value, an array of values, or a range. (See the bottom of the ActiveRecord::Base page for an example.)
0
 <tt>:facets</tt>:: An array of fields for grouping/faceting. You can access the returned facet values and their result counts with the <tt>facets</tt> method.
0
+<tt>:indexes</tt>:: An array of indexes to search. Currently only <tt>Ultrasphinx::MAIN_INDEX</tt> and <tt>Ultrasphinx::DELTA_INDEX</tt> are available. Defaults to both; changing this is rarely needed.
0
 
0
 Note that you can set up your own query defaults in <tt>environment.rb</tt>:
0
   
0
@@ -100,6 +101,10 @@ Note that your database is never changed by anything Ultrasphinx does.
0
       :per_page => 20,
0
       :sort_by => nil,
0
       :sort_mode => 'relevance',
0
+ :indexes => [
0
+ MAIN_INDEX,
0
+ (DELTA_INDEX if Ultrasphinx.delta_index_present?)
0
+ ].compact,
0
       :weights => {},
0
       :class_names => [],
0
       :filters => {},
0
@@ -125,7 +130,8 @@ Note that your database is never changed by anything Ultrasphinx does.
0
       :max_missing_records => 5,
0
       :max_retries => 4,
0
       :retry_sleep_time => 0.5,
0
- :max_facets => 100,
0
+ :max_facets => 1000,
0
+ :max_matches_offset => 1000,
0
       # Whether to add an accessor to each returned result that specifies its global rank in
0
       # the search.
0
       :with_global_rank => false,
0
@@ -149,45 +155,12 @@ Note that your database is never changed by anything Ultrasphinx does.
0
     
0
     INTERNAL_KEYS = ['parsed_query'] #:nodoc:
0
 
0
- def self.get_models_to_class_ids #:nodoc:
0
- # Reading the conf file makes sure that we are in sync with the actual Sphinx index,
0
- # not whatever you happened to change your models to most recently
0
- unless File.exist? CONF_PATH
0
- Ultrasphinx.say "configuration file not found for #{RAILS_ENV.inspect} environment"
0
- Ultrasphinx.say "please run 'rake ultrasphinx:configure'"
0
- else
0
- begin
0
- lines = open(CONF_PATH).readlines
0
-
0
- sources = lines.select do |line|
0
- line =~ /^source \w/
0
- end.map do |line|
0
- line[/source ([\w\d_-]*)/, 1].gsub('__', '/').classify
0
- end
0
-
0
- ids = lines.select do |line|
0
- line =~ /^sql_query /
0
- end.map do |line|
0
- line[/(\d*) AS class_id/, 1].to_i
0
- end
0
-
0
- raise unless sources.size == ids.size
0
- Hash[*sources.zip(ids).flatten]
0
-
0
- rescue
0
- Ultrasphinx.say "#{CONF_PATH} file is corrupted"
0
- Ultrasphinx.say "please run 'rake ultrasphinx:configure'"
0
- end
0
-
0
- end
0
- end
0
-
0
- MODELS_TO_IDS = get_models_to_class_ids || {}
0
+ MODELS_TO_IDS = Ultrasphinx.get_models_to_class_ids || {}
0
 
0
     IDS_TO_MODELS = MODELS_TO_IDS.invert #:nodoc:
0
-
0
- MAX_MATCHES = DAEMON_SETTINGS["max_matches"].to_i
0
     
0
+ MAX_MATCHES = DAEMON_SETTINGS["max_matches"].to_i
0
+
0
     FACET_CACHE = {} #:nodoc:
0
     
0
     # Returns the options hash.
0
@@ -296,6 +269,7 @@ Note that your database is never changed by anything Ultrasphinx does.
0
       @options['query'] = @options['query'].to_s
0
       @options['class_names'] = Array(@options['class_names'])
0
       @options['facets'] = Array(@options['facets'])
0
+ @options['indexes'] = Array(@options['indexes']).join(" ")
0
             
0
       raise UsageError, "Weights must be a Hash" unless @options['weights'].is_a? Hash
0
       raise UsageError, "Filters must be a Hash" unless @options['filters'].is_a? Hash
0
@@ -308,18 +282,26 @@ Note that your database is never changed by anything Ultrasphinx does.
0
       say "discarded invalid keys: #{extra_keys * ', '}" if extra_keys.any? and RAILS_ENV != "test"
0
     end
0
     
0
- # Run the search, filling results with an array of ActiveRecord objects. Set the parame