riddle 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. data/.gitignore +6 -0
  2. data/.travis.yml +16 -0
  3. data/Gemfile +6 -0
  4. data/HISTORY +45 -0
  5. data/LICENCE +20 -0
  6. data/README.textile +5 -3
  7. data/Rakefile +23 -0
  8. data/lib/riddle.rb +1 -0
  9. data/lib/riddle/0.9.9/configuration/searchd.rb +10 -8
  10. data/lib/riddle/auto_version.rb +2 -2
  11. data/lib/riddle/client.rb +117 -118
  12. data/lib/riddle/configuration.rb +6 -6
  13. data/lib/riddle/configuration/distributed_index.rb +16 -16
  14. data/lib/riddle/configuration/sql_source.rb +5 -5
  15. data/lib/riddle/controller.rb +28 -25
  16. data/lib/riddle/query.rb +31 -20
  17. data/lib/riddle/query/select.rb +69 -8
  18. data/lib/riddle/version.rb +3 -0
  19. data/riddle.gemspec +25 -0
  20. data/spec/fixtures/.gitignore +2 -0
  21. data/spec/fixtures/data/0.9.9/anchor.bin +0 -0
  22. data/spec/fixtures/data/0.9.9/any.bin +0 -0
  23. data/spec/fixtures/data/0.9.9/boolean.bin +0 -0
  24. data/spec/fixtures/data/0.9.9/comment.bin +0 -0
  25. data/spec/fixtures/data/0.9.9/distinct.bin +0 -0
  26. data/spec/fixtures/data/0.9.9/field_weights.bin +0 -0
  27. data/spec/fixtures/data/0.9.9/filter.bin +0 -0
  28. data/spec/fixtures/data/0.9.9/filter_array.bin +0 -0
  29. data/spec/fixtures/data/0.9.9/filter_array_exclude.bin +0 -0
  30. data/spec/fixtures/data/0.9.9/filter_boolean.bin +0 -0
  31. data/spec/fixtures/data/0.9.9/filter_floats.bin +0 -0
  32. data/spec/fixtures/data/0.9.9/filter_floats_exclude.bin +0 -0
  33. data/spec/fixtures/data/0.9.9/filter_range.bin +0 -0
  34. data/spec/fixtures/data/0.9.9/filter_range_exclude.bin +0 -0
  35. data/spec/fixtures/data/0.9.9/group.bin +0 -0
  36. data/spec/fixtures/data/0.9.9/index.bin +0 -0
  37. data/spec/fixtures/data/0.9.9/index_weights.bin +0 -0
  38. data/spec/fixtures/data/0.9.9/keywords_with_hits.bin +0 -0
  39. data/spec/fixtures/data/0.9.9/keywords_without_hits.bin +0 -0
  40. data/spec/fixtures/data/0.9.9/overrides.bin +0 -0
  41. data/spec/fixtures/data/0.9.9/phrase.bin +0 -0
  42. data/spec/fixtures/data/0.9.9/rank_mode.bin +0 -0
  43. data/spec/fixtures/data/0.9.9/select.bin +0 -0
  44. data/spec/fixtures/data/0.9.9/simple.bin +0 -0
  45. data/spec/fixtures/data/0.9.9/sort.bin +0 -0
  46. data/spec/fixtures/data/0.9.9/update_simple.bin +0 -0
  47. data/spec/fixtures/data/0.9.9/weights.bin +0 -0
  48. data/spec/fixtures/data/1.10/anchor.bin +0 -0
  49. data/spec/fixtures/data/1.10/any.bin +0 -0
  50. data/spec/fixtures/data/1.10/boolean.bin +0 -0
  51. data/spec/fixtures/data/1.10/comment.bin +0 -0
  52. data/spec/fixtures/data/1.10/distinct.bin +0 -0
  53. data/spec/fixtures/data/1.10/field_weights.bin +0 -0
  54. data/spec/fixtures/data/1.10/filter.bin +0 -0
  55. data/spec/fixtures/data/1.10/filter_array.bin +0 -0
  56. data/spec/fixtures/data/1.10/filter_array_exclude.bin +0 -0
  57. data/spec/fixtures/data/1.10/filter_boolean.bin +0 -0
  58. data/spec/fixtures/data/1.10/filter_floats.bin +0 -0
  59. data/spec/fixtures/data/1.10/filter_floats_exclude.bin +0 -0
  60. data/spec/fixtures/data/1.10/filter_range.bin +0 -0
  61. data/spec/fixtures/data/1.10/filter_range_exclude.bin +0 -0
  62. data/spec/fixtures/data/1.10/group.bin +0 -0
  63. data/spec/fixtures/data/1.10/index.bin +0 -0
  64. data/spec/fixtures/data/1.10/index_weights.bin +0 -0
  65. data/spec/fixtures/data/1.10/keywords_with_hits.bin +0 -0
  66. data/spec/fixtures/data/1.10/keywords_without_hits.bin +0 -0
  67. data/spec/fixtures/data/1.10/overrides.bin +0 -0
  68. data/spec/fixtures/data/1.10/phrase.bin +0 -0
  69. data/spec/fixtures/data/1.10/rank_mode.bin +0 -0
  70. data/spec/fixtures/data/1.10/select.bin +0 -0
  71. data/spec/fixtures/data/1.10/simple.bin +0 -0
  72. data/spec/fixtures/data/1.10/sort.bin +0 -0
  73. data/spec/fixtures/data/1.10/update_simple.bin +0 -0
  74. data/spec/fixtures/data/1.10/weights.bin +0 -0
  75. data/spec/fixtures/data/2.0.1/anchor.bin +0 -0
  76. data/spec/fixtures/data/2.0.1/any.bin +0 -0
  77. data/spec/fixtures/data/2.0.1/boolean.bin +0 -0
  78. data/spec/fixtures/data/2.0.1/comment.bin +0 -0
  79. data/spec/fixtures/data/2.0.1/distinct.bin +0 -0
  80. data/spec/fixtures/data/2.0.1/field_weights.bin +0 -0
  81. data/spec/fixtures/data/2.0.1/filter.bin +0 -0
  82. data/spec/fixtures/data/2.0.1/filter_array.bin +0 -0
  83. data/spec/fixtures/data/2.0.1/filter_array_exclude.bin +0 -0
  84. data/spec/fixtures/data/2.0.1/filter_boolean.bin +0 -0
  85. data/spec/fixtures/data/2.0.1/filter_floats.bin +0 -0
  86. data/spec/fixtures/data/2.0.1/filter_floats_exclude.bin +0 -0
  87. data/spec/fixtures/data/2.0.1/filter_range.bin +0 -0
  88. data/spec/fixtures/data/2.0.1/filter_range_exclude.bin +0 -0
  89. data/spec/fixtures/data/2.0.1/group.bin +0 -0
  90. data/spec/fixtures/data/2.0.1/index.bin +0 -0
  91. data/spec/fixtures/data/2.0.1/index_weights.bin +0 -0
  92. data/spec/fixtures/data/2.0.1/keywords_with_hits.bin +0 -0
  93. data/spec/fixtures/data/2.0.1/keywords_without_hits.bin +0 -0
  94. data/spec/fixtures/data/2.0.1/overrides.bin +0 -0
  95. data/spec/fixtures/data/2.0.1/phrase.bin +0 -0
  96. data/spec/fixtures/data/2.0.1/rank_mode.bin +0 -0
  97. data/spec/fixtures/data/2.0.1/select.bin +0 -0
  98. data/spec/fixtures/data/2.0.1/simple.bin +0 -0
  99. data/spec/fixtures/data/2.0.1/sort.bin +0 -0
  100. data/spec/fixtures/data/2.0.1/update_simple.bin +0 -0
  101. data/spec/fixtures/data/2.0.1/weights.bin +0 -0
  102. data/spec/fixtures/data_generator.0.9.8.php +208 -0
  103. data/spec/fixtures/data_generator.0.9.9.php +5 -0
  104. data/spec/fixtures/data_generator.1.10.php +5 -0
  105. data/spec/fixtures/data_generator.2.0.1.php +5 -0
  106. data/spec/fixtures/data_generator.php +223 -0
  107. data/spec/fixtures/sphinxapi.0.9.8.php +1228 -0
  108. data/spec/fixtures/sphinxapi.0.9.9.php +1646 -0
  109. data/spec/fixtures/sphinxapi.1.10.php +1728 -0
  110. data/spec/fixtures/sphinxapi.2.0.1.php +1731 -0
  111. data/spec/fixtures/sql/conf.example.yml +3 -0
  112. data/spec/fixtures/sql/data.sql +25000 -0
  113. data/spec/fixtures/sql/data.tsv +25000 -0
  114. data/spec/fixtures/sql/structure.sql +16 -0
  115. data/spec/functional/connection_spec.rb +10 -12
  116. data/spec/functional/excerpt_spec.rb +1 -1
  117. data/spec/functional/keywords_spec.rb +1 -1
  118. data/spec/functional/persistance_spec.rb +1 -1
  119. data/spec/functional/search_spec.rb +1 -1
  120. data/spec/functional/status_spec.rb +1 -1
  121. data/spec/functional/update_spec.rb +1 -1
  122. data/spec/riddle/auto_version_spec.rb +18 -10
  123. data/spec/riddle/query/select_spec.rb +78 -14
  124. data/spec/riddle/query_spec.rb +5 -3
  125. data/spec/spec_helper.rb +13 -15
  126. data/spec/support/binary_fixtures.rb +18 -0
  127. data/spec/support/sphinx.rb +135 -0
  128. data/spec/unit/client_spec.rb +150 -142
  129. data/spec/unit/configuration/distributed_index_spec.rb +15 -15
  130. data/spec/unit/configuration/searchd_spec.rb +28 -3
  131. data/spec/unit/configuration_spec.rb +6 -6
  132. metadata +254 -68
  133. data/spec/sphinx_helper.rb +0 -96
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ *.tmproj
5
+ coverage
6
+ Gemfile.lock
@@ -0,0 +1,16 @@
1
+ rvm:
2
+ - 1.8.6
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - rbx
7
+ - rbx-2.0
8
+ - ree
9
+ - jruby
10
+ - ruby-head
11
+ env:
12
+ - SPHINX_BIN=/usr/local/sphinx-0.9.9/bin SPHINX_VERSION=0.9.9
13
+ - SPHINX_BIN=/usr/local/sphinx-1.10/bin SPHINX_VERSION=1.10
14
+ - SPHINX_BIN=/usr/local/sphinx-2.0.1/bin SPHINX_VERSION=2.0.1
15
+ before_script: killall searchd; echo ''
16
+ after_script: killall searchd; echo ''
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'mysql2', '0.3.2', :platform => :ruby
6
+ gem 'jdbc-mysql', '>= 5.1.13', :platform => :jruby
data/HISTORY ADDED
@@ -0,0 +1,45 @@
1
+ 1.5.0 - November 4th 2011
2
+ - Handle exclusive filters in SphinxQL SELECT commands.
3
+ - Allow for native Ruby objects in SphinxQL UPDATE commands.
4
+ - Handle options of hashes in SphinxQL SELECT commands.
5
+ - Allow for SphinxQL select clauses.
6
+ - Improving SphinxQL filter handling of native Ruby objects.
7
+ - Switch plural index references from indexes to indices, to distinguish beside indexes (the action).
8
+ - Rescue against timeouts and connection resets.
9
+ - Fixing reference to TCPSocket.
10
+ - Handle port numbers as integers for listen setting (Ngan Pham).
11
+ - Provide the option to start searchd with the nodetach flag (Aaron Gilbralter).
12
+ - Don't shuffle servers (if there's more than one) - let developers (or Thinking Sphinx) manage that (Ngan Pham).
13
+
14
+ 1.4.0 - August 2nd 2011
15
+ - Checking against both Windows platforms for Ruby (Paul Gibler)
16
+ - Encoding improvements (Alexey Artamonov)
17
+ - More Rubyish syntax (James Cook)
18
+ - Handling Ruby encodings (James Cook)
19
+ - Coreseek support (saberma)
20
+ - Section restructure for better inheritance (Alexey Artamonov)
21
+ - MySQL41 connection support
22
+ - requiring 'thread' for Mutex use
23
+
24
+ 1.3.3 - May 25th 2011
25
+ - Using MySQL2 library for SphinxQL interface
26
+ - Adding Sphinx 2.0.x settings
27
+ - SphinxQL support
28
+ - Speed improvements for hash lookups (Enrico Thierbach)
29
+ - Handle race conditions of segfaults while returning responses (Jason Lambert)
30
+ - 2.0.x support
31
+
32
+ 1.3.2 - May 12th 2011
33
+ - client_key support
34
+
35
+ 1.3.1 - May 9th 2011
36
+ - Don't output warnings or exit when version isn't detected - presume Thinking Sphinx will handle that.
37
+ - Confirm configuration file exists before attempting to start/stop Sphinx.
38
+ - Use a Mutex instead of the current Thread.
39
+
40
+ 1.3.0 - May 7th 2011
41
+ - Attempts at untested 2.0.x and client_key support
42
+ - Using Bundler, MySQL2 and Ruby 1.9.2 in development
43
+ - Allow for Sphinx versions compiled from source and SVN (Greg Weber)
44
+
45
+ 1.2.2 - December 22nd 2011
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2010 Pat Allan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -22,12 +22,12 @@ To get started, just instantiate a Client object:
22
22
 
23
23
  client = Riddle::Client.new # defaults to localhost and port 3312
24
24
  client = Riddle::Client.new "sphinxserver.domain.tld", 3333 # custom settings
25
-
25
+
26
26
  And then set the parameters to what you want, before running a query:
27
27
 
28
28
  client.match_mode = :extended
29
29
  client.query "Pat Allan @state Victoria"
30
-
30
+
31
31
  The results from a query are similar to the other clients - but here's the details. It's a hash with
32
32
  the following keys:
33
33
 
@@ -53,7 +53,7 @@ documents. The key @:attributes@ will return a hash of attribute name and type p
53
53
  documents and hits for each, along the lines of:
54
54
 
55
55
  results[:words]["Pat"] #=> {:docs => 12, :hits => 15}
56
-
56
+
57
57
  @:total@, @:total_found@ and @:time@ return the number of matches available, the
58
58
  total number of matches (which may be greater than the maximum available), and the time in milliseconds
59
59
  that the query took to run.
@@ -91,3 +91,5 @@ Thanks to the following people who have contributed to Riddle in some shape or f
91
91
  * James Cook
92
92
  * Alexey Artamonov
93
93
  * Paul Gibler
94
+ * Ngan Pham
95
+ * Aaron Gilbralter
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+ Bundler.require :default, :development
6
+
7
+ require 'rspec/core/rake_task'
8
+
9
+ RSpec::Core::RakeTask.new
10
+
11
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
12
+ spec.rcov_opts = ['--exclude', 'spec', '--exclude', 'gems']
13
+ spec.rcov = true
14
+ end
15
+
16
+ YARD::Rake::YardocTask.new
17
+
18
+ task :default => :spec
19
+
20
+ task :fixtures do
21
+ require './spec/spec_helper'
22
+ BinaryFixtures.build_fixtures
23
+ end
@@ -70,6 +70,7 @@ require 'riddle/client'
70
70
  require 'riddle/configuration'
71
71
  require 'riddle/controller'
72
72
  require 'riddle/query'
73
+ require 'riddle/version'
73
74
 
74
75
  Riddle.loaded_version = nil
75
76
  Riddle::AutoVersion.configure
@@ -4,29 +4,31 @@ module Riddle
4
4
  def valid?
5
5
  set_listen
6
6
  clear_deprecated
7
-
7
+
8
8
  !( @listen.nil? || @listen.empty? || @pid_file.nil? )
9
9
  end
10
-
10
+
11
11
  private
12
-
12
+
13
13
  def set_listen
14
+ @listen = @listen.to_s if @listen.is_a?(Fixnum)
15
+
14
16
  return unless @listen.nil? || @listen.empty?
15
-
17
+
16
18
  @listen = []
17
19
  @listen << @port.to_s if @port
18
20
  @listen << "9306:mysql41" if @mysql41.is_a?(TrueClass)
19
21
  @listen << "#{@mysql41}:mysql41" if @mysql41.is_a?(Fixnum)
20
-
22
+
21
23
  @listen.each { |l| l.insert(0, "#{@address}:") } if @address
22
24
  end
23
-
25
+
24
26
  def clear_deprecated
25
27
  return if @listen.nil?
26
-
28
+
27
29
  @address = nil
28
30
  @port = nil
29
31
  end
30
32
  end
31
33
  end
32
- end
34
+ end
@@ -1,8 +1,8 @@
1
1
  class Riddle::AutoVersion
2
2
  def self.configure
3
3
  controller = Riddle::Controller.new nil, ''
4
- version = controller.sphinx_version
5
-
4
+ version = ENV['SPHINX_VERSION'] || controller.sphinx_version
5
+
6
6
  case version
7
7
  when '0.9.8', '0.9.9'
8
8
  require "riddle/#{version}"
@@ -6,7 +6,7 @@ module Riddle
6
6
  class VersionError < StandardError; end
7
7
  class ResponseError < StandardError; end
8
8
  class OutOfBoundsError < StandardError; end
9
-
9
+
10
10
  # This class was heavily based on the existing Client API by Dmytro Shteflyuk
11
11
  # and Alexy Kovyrin. Their code worked fine, I just wanted something a bit
12
12
  # more Ruby-ish (ie. lowercase and underscored method names). I also have
@@ -40,7 +40,7 @@ module Riddle
40
40
  :query => 6, # SEARCHD_COMMAND_QUERY
41
41
  :flushattrs => 7 # SEARCHD_COMMAND_FLUSHATTRS
42
42
  }
43
-
43
+
44
44
  Versions = {
45
45
  :search => 0x113, # VER_COMMAND_SEARCH
46
46
  :excerpt => 0x100, # VER_COMMAND_EXCERPT
@@ -50,14 +50,14 @@ module Riddle
50
50
  :query => 0x100, # VER_COMMAND_QUERY
51
51
  :flushattrs => 0x100 # VER_COMMAND_FLUSHATTRS
52
52
  }
53
-
53
+
54
54
  Statuses = {
55
55
  :ok => 0, # SEARCHD_OK
56
56
  :error => 1, # SEARCHD_ERROR
57
57
  :retry => 2, # SEARCHD_RETRY
58
58
  :warning => 3 # SEARCHD_WARNING
59
59
  }
60
-
60
+
61
61
  MatchModes = {
62
62
  :all => 0, # SPH_MATCH_ALL
63
63
  :any => 1, # SPH_MATCH_ANY
@@ -67,7 +67,7 @@ module Riddle
67
67
  :fullscan => 5, # SPH_MATCH_FULLSCAN
68
68
  :extended2 => 6 # SPH_MATCH_EXTENDED2
69
69
  }
70
-
70
+
71
71
  RankModes = {
72
72
  :proximity_bm25 => 0, # SPH_RANK_PROXIMITY_BM25
73
73
  :bm25 => 1, # SPH_RANK_BM25
@@ -79,7 +79,7 @@ module Riddle
79
79
  :sph04 => 7, # SPH_RANK_SPH04
80
80
  :total => 8 # SPH_RANK_TOTAL
81
81
  }
82
-
82
+
83
83
  SortModes = {
84
84
  :relevance => 0, # SPH_SORT_RELEVANCE
85
85
  :attr_desc => 1, # SPH_SORT_ATTR_DESC
@@ -88,7 +88,7 @@ module Riddle
88
88
  :extended => 4, # SPH_SORT_EXTENDED
89
89
  :expr => 5 # SPH_SORT_EXPR
90
90
  }
91
-
91
+
92
92
  AttributeTypes = {
93
93
  :integer => 1, # SPH_ATTR_INTEGER
94
94
  :timestamp => 2, # SPH_ATTR_TIMESTAMP
@@ -99,7 +99,7 @@ module Riddle
99
99
  :string => 7, # SPH_ATTR_STRING
100
100
  :multi => 0x40000000 # SPH_ATTR_MULTI
101
101
  }
102
-
102
+
103
103
  GroupFunctions = {
104
104
  :day => 0, # SPH_GROUPBY_DAY
105
105
  :week => 1, # SPH_GROUPBY_WEEK
@@ -108,13 +108,13 @@ module Riddle
108
108
  :attr => 4, # SPH_GROUPBY_ATTR
109
109
  :attrpair => 5 # SPH_GROUPBY_ATTRPAIR
110
110
  }
111
-
111
+
112
112
  FilterTypes = {
113
113
  :values => 0, # SPH_FILTER_VALUES
114
114
  :range => 1, # SPH_FILTER_RANGE
115
115
  :float_range => 2 # SPH_FILTER_FLOATRANGE
116
116
  }
117
-
117
+
118
118
  attr_accessor :servers, :port, :offset, :limit, :max_matches,
119
119
  :match_mode, :sort_mode, :sort_by, :weights, :id_range, :filters,
120
120
  :group_by, :group_function, :group_clause, :group_distinct, :cut_off,
@@ -122,9 +122,9 @@ module Riddle
122
122
  :max_query_time, :field_weights, :timeout, :overrides, :select,
123
123
  :connection, :key
124
124
  attr_reader :queue
125
-
125
+
126
126
  @@connection = nil
127
-
127
+
128
128
  def self.connection=(value)
129
129
  Riddle.mutex.synchronize do
130
130
  @@connection = value
@@ -134,24 +134,23 @@ module Riddle
134
134
  def self.connection
135
135
  @@connection
136
136
  end
137
-
137
+
138
138
  # Can instantiate with a specific server and port - otherwise it assumes
139
139
  # defaults of localhost and 3312 respectively. All other settings can be
140
140
  # accessed and changed via the attribute accessors.
141
141
  def initialize(servers = nil, port = nil, key = nil)
142
142
  Riddle.version_warning
143
143
 
144
- servers = Array(servers || "localhost")
145
- @servers = servers.respond_to?(:shuffle) ? servers.shuffle : servers.sort_by{ rand }
146
- @port = port || 9312
144
+ @servers = Array(servers || "localhost")
145
+ @port = port || 9312
147
146
  @socket = nil
148
147
  @key = key
149
-
148
+
150
149
  reset
151
-
150
+
152
151
  @queue = []
153
152
  end
154
-
153
+
155
154
  # Reset attributes and settings to defaults.
156
155
  def reset
157
156
  # defaults
@@ -182,7 +181,7 @@ module Riddle
182
181
  @overrides = {}
183
182
  @select = "*"
184
183
  end
185
-
184
+
186
185
  # The searchd server to query. Servers are removed from @server after a
187
186
  # Timeout::Error is hit to allow for fail-over.
188
187
  def server
@@ -213,18 +212,18 @@ module Riddle
213
212
  :longitude => long
214
213
  }
215
214
  end
216
-
215
+
217
216
  # Append a query to the queue. This uses the same parameters as the query
218
217
  # method.
219
218
  def append_query(search, index = '*', comments = '')
220
219
  @queue << query_message(search, index, comments)
221
220
  end
222
-
221
+
223
222
  # Run all the queries currently in the queue. This will return an array of
224
223
  # results hashes.
225
224
  def run
226
225
  response = Response.new request(:search, @queue)
227
-
226
+
228
227
  results = @queue.collect do
229
228
  result = {
230
229
  :matches => [],
@@ -242,7 +241,7 @@ module Riddle
242
241
  result[:error] = response.next
243
242
  next result
244
243
  end
245
-
244
+
246
245
  result[:fields] = response.next_array
247
246
 
248
247
  attributes = response.next_int
@@ -259,7 +258,7 @@ module Riddle
259
258
 
260
259
  matches = response.next_int
261
260
  is_64_bit = response.next_int
262
-
261
+
263
262
  result[:matches] = (0...matches).map do |i|
264
263
  doc = is_64_bit > 0 ? response.next_64bit_int : response.next_int
265
264
  weight = response.next_int
@@ -269,7 +268,7 @@ module Riddle
269
268
  result_attribute_names_and_types.each do |attr, type|
270
269
  current_match_attributes[attr] = attribute_from_type(type, response)
271
270
  end
272
-
271
+
273
272
  {:doc => doc, :weight => weight, :index => i, :attributes => current_match_attributes}
274
273
  end
275
274
 
@@ -287,17 +286,17 @@ module Riddle
287
286
 
288
287
  result
289
288
  end
290
-
289
+
291
290
  @queue.clear
292
291
  results
293
292
  end
294
-
295
- # Query the Sphinx daemon - defaulting to all indexes, but you can specify
293
+
294
+ # Query the Sphinx daemon - defaulting to all indices, but you can specify
296
295
  # a specific one if you wish. The search parameter should be a string
297
296
  # following Sphinx's expectations.
298
297
  #
299
298
  # The object returned from this method is a hash with the following keys:
300
- #
299
+ #
301
300
  # * :matches
302
301
  # * :fields
303
302
  # * :attributes
@@ -311,24 +310,24 @@ module Riddle
311
310
  # * :error (if appropriate)
312
311
  #
313
312
  # The key <tt>:matches</tt> returns an array of hashes - the actual search
314
- # results. Each hash has the document id (<tt>:doc</tt>), the result
313
+ # results. Each hash has the document id (<tt>:doc</tt>), the result
315
314
  # weighting (<tt>:weight</tt>), and a hash of the attributes for the
316
315
  # document (<tt>:attributes</tt>).
317
- #
316
+ #
318
317
  # The <tt>:fields</tt> and <tt>:attribute_names</tt> keys return list of
319
318
  # fields and attributes for the documents. The key <tt>:attributes</tt>
320
319
  # will return a hash of attribute name and type pairs, and <tt>:words</tt>
321
320
  # returns a hash of hashes representing the words from the search, with the
322
321
  # number of documents and hits for each, along the lines of:
323
- #
322
+ #
324
323
  # results[:words]["Pat"] #=> {:docs => 12, :hits => 15}
325
- #
324
+ #
326
325
  # <tt>:total</tt>, <tt>:total_found</tt> and <tt>:time</tt> return the
327
326
  # number of matches available, the total number of matches (which may be
328
327
  # greater than the maximum available, depending on the number of matches
329
328
  # and your sphinx configuration), and the time in milliseconds that the
330
329
  # query took to run.
331
- #
330
+ #
332
331
  # <tt>:status</tt> is the error code for the query - and if there was a
333
332
  # related warning, it will be under the <tt>:warning</tt> key. Fatal errors
334
333
  # will be described under <tt>:error</tt>.
@@ -337,7 +336,7 @@ module Riddle
337
336
  @queue << query_message(search, index, comments)
338
337
  self.run.first
339
338
  end
340
-
339
+
341
340
  # Build excerpts from search terms (the +words+) and the text of documents. Excerpts are bodies of text that have the +words+ highlighted.
342
341
  # They may also be abbreviated to fit within a word limit.
343
342
  #
@@ -368,13 +367,13 @@ module Riddle
368
367
  #
369
368
  # client.excerpts(:docs => ["Pat Allan, #{lorem_lipsum} Pat Cash"], :words => 'Pat', :index => 'pats')
370
369
  # #=> ["<span class=\"match\">Pat</span> Allan, Lorem ipsum dolor sit amet, consectetur adipisicing
371
- # elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua &#8230; . Excepteur
372
- # sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
373
- # laborum. <span class=\"match\">Pat</span> Cash"]
370
+ # elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua &#8230; . Excepteur
371
+ # sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
372
+ # laborum. <span class=\"match\">Pat</span> Cash"]
374
373
  #
375
374
  # Workflow:
376
375
  #
377
- # Excerpt creation is completely isolated from searching the index. The nominated index is only used to
376
+ # Excerpt creation is completely isolated from searching the index. The nominated index is only used to
378
377
  # discover encoding and charset information.
379
378
  #
380
379
  # Therefore, the workflow goes:
@@ -402,30 +401,30 @@ module Riddle
402
401
  options[:allow_empty] ||= false
403
402
  options[:passage_boundary] ||= 'none'
404
403
  options[:emit_zones] ||= false
405
-
404
+
406
405
  response = Response.new request(:excerpt, excerpts_message(options))
407
-
406
+
408
407
  options[:docs].collect { response.next }
409
408
  end
410
-
409
+
411
410
  # Update attributes - first parameter is the relevant index, second is an
412
411
  # array of attributes to be updated, and the third is a hash, where the
413
412
  # keys are the document ids, and the values are arrays with the attribute
414
413
  # values - in the same order as the second parameter.
415
414
  #
416
415
  # Example:
417
- #
416
+ #
418
417
  # client.update('people', ['birthday'], {1 => [Time.at(1982, 20, 8).to_i]})
419
- #
418
+ #
420
419
  def update(index, attributes, values_by_doc)
421
420
  response = Response.new request(
422
421
  :update,
423
422
  update_message(index, attributes, values_by_doc)
424
423
  )
425
-
424
+
426
425
  response.next_int
427
426
  end
428
-
427
+
429
428
  # Generates a keyword list for a given query. Each keyword is represented
430
429
  # by a hash, with keys :tokenised and :normalised. If return_hits is set to
431
430
  # true it will also report on the number of hits and documents for each
@@ -435,65 +434,65 @@ module Riddle
435
434
  :keywords,
436
435
  keywords_message(query, index, return_hits)
437
436
  )
438
-
437
+
439
438
  (0...response.next_int).collect do
440
439
  hash = {}
441
440
  hash[:tokenised] = response.next
442
441
  hash[:normalised] = response.next
443
-
442
+
444
443
  if return_hits
445
444
  hash[:docs] = response.next_int
446
445
  hash[:hits] = response.next_int
447
446
  end
448
-
447
+
449
448
  hash
450
449
  end
451
450
  end
452
-
451
+
453
452
  def status
454
453
  response = Response.new request(
455
454
  :status, Message.new
456
455
  )
457
-
456
+
458
457
  rows, cols = response.next_int, response.next_int
459
-
458
+
460
459
  (0...rows).inject({}) do |hash, row|
461
460
  hash[response.next.to_sym] = response.next
462
461
  hash
463
462
  end
464
463
  end
465
-
464
+
466
465
  def flush_attributes
467
466
  response = Response.new request(
468
467
  :flushattrs, Message.new
469
468
  )
470
-
469
+
471
470
  response.next_int
472
471
  end
473
-
472
+
474
473
  def add_override(attribute, type, values)
475
474
  @overrides[attribute] = {:type => type, :values => values}
476
475
  end
477
-
476
+
478
477
  def open
479
478
  open_socket
480
-
479
+
481
480
  return if Versions[:search] < 0x116
482
-
481
+
483
482
  @socket.send [
484
483
  Commands[:persist], 0, 4, 1
485
484
  ].pack("nnNN"), 0
486
485
  end
487
-
486
+
488
487
  def close
489
488
  close_socket
490
489
  end
491
-
490
+
492
491
  private
493
-
492
+
494
493
  def open_socket
495
494
  raise "Already Connected" unless @socket.nil?
496
-
495
+
497
496
  if @timeout == 0
498
497
  @socket = initialise_connection
499
498
  else
@@ -513,19 +512,19 @@ module Riddle
513
512
  end
514
513
  end
515
514
  end
516
-
515
+
517
516
  true
518
517
  end
519
-
518
+
520
519
  def close_socket
521
520
  raise "Not Connected" if @socket.nil?
522
-
521
+
523
522
  @socket.close
524
523
  @socket = nil
525
-
524
+
526
525
  true
527
526
  end
528
-
527
+
529
528
  # If there's an active connection to the Sphinx daemon, this will yield the
530
529
  # socket. If there's no active connection, then it will connect, yield the
531
530
  # new socket, then close it.
@@ -542,23 +541,23 @@ module Riddle
542
541
  yield @socket
543
542
  end
544
543
  end
545
-
544
+
546
545
  def initialise_connection
547
546
  socket = initialise_socket
548
-
547
+
549
548
  # Checking version
550
549
  version = socket.recv(4).unpack('N*').first
551
550
  if version < 1
552
551
  socket.close
553
552
  raise VersionError, "Can only connect to searchd version 1.0 or better, not version #{version}"
554
553
  end
555
-
554
+
556
555
  # Send version
557
556
  socket.send [1].pack('N'), 0
558
-
557
+
559
558
  socket
560
559
  end
561
-
560
+
562
561
  def initialise_socket
563
562
  tries = 0
564
563
  begin
@@ -571,15 +570,15 @@ module Riddle
571
570
  else
572
571
  TCPSocket.new server, @port
573
572
  end
574
- rescue Errno::ECONNREFUSED => e
573
+ rescue Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::ECONNREFUSED => e
575
574
  retry if (tries += 1) < 5
576
575
  raise Riddle::ConnectionError,
577
576
  "Connection to #{server} on #{@port} failed. #{e.message}"
578
577
  end
579
-
578
+
580
579
  socket
581
580
  end
582
-
581
+
583
582
  def request_header(command, length = 0)
584
583
  length += key_message.length if key
585
584
  core_header = case command
@@ -596,10 +595,10 @@ module Riddle
596
595
  else
597
596
  [Commands[command], Versions[command], length].pack("nnN")
598
597
  end
599
-
598
+
600
599
  key ? core_header + key_message : core_header
601
600
  end
602
-
601
+
603
602
  def key_message
604
603
  @key_message ||= begin
605
604
  message = Message.new
@@ -607,7 +606,7 @@ module Riddle
607
606
  message.to_s
608
607
  end
609
608
  end
610
-
609
+
611
610
  # Send a collection of messages, for a command type (eg, search, excerpts,
612
611
  # update), to the Sphinx daemon.
613
612
  def request(command, messages)
@@ -616,12 +615,12 @@ module Riddle
616
615
  version = 0
617
616
  length = 0
618
617
  message = Riddle.encode(Array(messages).join(""), 'ASCII-8BIT')
619
-
618
+
620
619
  connect do |socket|
621
620
  case command
622
621
  when :search
623
622
  if Versions[command] >= 0x118
624
- socket.send request_header(command, message.length) +
623
+ socket.send request_header(command, message.length) +
625
624
  [0, messages.length].pack('NN') + message, 0
626
625
  else
627
626
  socket.send request_header(command, message.length) +
@@ -632,24 +631,24 @@ module Riddle
632
631
  else
633
632
  socket.send request_header(command, message.length) + message, 0
634
633
  end
635
-
634
+
636
635
  header = socket.recv(8)
637
636
  status, version, length = header.unpack('n2N')
638
-
637
+
639
638
  while response.length < (length || 0)
640
639
  part = socket.recv(length - response.length)
641
640
 
642
641
  # will return 0 bytes if remote side closed TCP connection, e.g, searchd segfaulted.
643
- break if part.length == 0 && socket.is_a?(TcpSocket)
642
+ break if part.length == 0 && socket.is_a?(TCPSocket)
644
643
 
645
644
  response << part if part
646
645
  end
647
646
  end
648
-
647
+
649
648
  if response.empty? || response.length != length
650
649
  raise ResponseError, "No response from searchd (status: #{status}, version: #{version})"
651
650
  end
652
-
651
+
653
652
  case status
654
653
  when Statuses[:ok]
655
654
  if version < Versions[command]
@@ -670,34 +669,34 @@ module Riddle
670
669
  raise ResponseError, "Unknown searchd error (status: #{status})"
671
670
  end
672
671
  end
673
-
672
+
674
673
  # Generation of the message to send to Sphinx for a search.
675
674
  def query_message(search, index, comments = '')
676
675
  message = Message.new
677
-
676
+
678
677
  # Mode, Limits, Sort Mode
679
678
  message.append_ints @offset, @limit, MatchModes[@match_mode],
680
679
  RankModes[@rank_mode], SortModes[@sort_mode]
681
680
  message.append_string @sort_by
682
-
681
+
683
682
  # Query
684
683
  message.append_string search
685
-
684
+
686
685
  # Weights
687
686
  message.append_int @weights.length
688
687
  message.append_ints *@weights
689
-
688
+
690
689
  # Index
691
690
  message.append_string index
692
-
691
+
693
692
  # ID Range
694
693
  message.append_int 1
695
694
  message.append_64bit_ints @id_range.first, @id_range.last
696
-
695
+
697
696
  # Filters
698
697
  message.append_int @filters.length
699
698
  @filters.each { |filter| message.append filter.query_message }
700
-
699
+
701
700
  # Grouping
702
701
  message.append_int GroupFunctions[@group_function]
703
702
  message.append_string @group_by
@@ -705,7 +704,7 @@ module Riddle
705
704
  message.append_string @group_clause
706
705
  message.append_ints @cut_off, @retry_count, @retry_delay
707
706
  message.append_string @group_distinct
708
-
707
+
709
708
  # Anchor Point
710
709
  if @anchor.empty?
711
710
  message.append_int 0
@@ -715,29 +714,29 @@ module Riddle
715
714
  message.append_string @anchor[:longitude_attribute]
716
715
  message.append_floats @anchor[:latitude], @anchor[:longitude]
717
716
  end
718
-
717
+
719
718
  # Per Index Weights
720
719
  message.append_int @index_weights.length
721
720
  @index_weights.each do |key,val|
722
721
  message.append_string key.to_s
723
722
  message.append_int val
724
723
  end
725
-
724
+
726
725
  # Max Query Time
727
726
  message.append_int @max_query_time
728
-
727
+
729
728
  # Per Field Weights
730
729
  message.append_int @field_weights.length
731
730
  @field_weights.each do |key,val|
732
731
  message.append_string key.to_s
733
732
  message.append_int val
734
733
  end
735
-
734
+
736
735
  message.append_string comments
737
-
736
+
738
737
  return message.to_s if Versions[:search] < 0x116
739
-
740
- # Overrides
738
+
739
+ # Overrides
741
740
  message.append_int @overrides.length
742
741
  @overrides.each do |key,val|
743
742
  message.append_string key.to_s
@@ -756,56 +755,56 @@ module Riddle
756
755
  message.send method, map
757
756
  end
758
757
  end
759
-
758
+
760
759
  message.append_string @select
761
-
760
+
762
761
  message.to_s
763
762
  end
764
-
763
+
765
764
  # Generation of the message to send to Sphinx for an excerpts request.
766
765
  def excerpts_message(options)
767
766
  message = Message.new
768
-
767
+
769
768
  message.append [0, excerpt_flags(options)].pack('N2') # 0 = mode
770
769
  message.append_string options[:index]
771
770
  message.append_string options[:words]
772
-
771
+
773
772
  # options
774
773
  message.append_string options[:before_match]
775
774
  message.append_string options[:after_match]
776
775
  message.append_string options[:chunk_separator]
777
776
  message.append_ints options[:limit], options[:around]
778
-
777
+
779
778
  message.append_array options[:docs]
780
-
779
+
781
780
  message.to_s
782
781
  end
783
-
782
+
784
783
  # Generation of the message to send to Sphinx to update attributes of a
785
784
  # document.
786
785
  def update_message(index, attributes, values_by_doc)
787
786
  message = Message.new
788
-
787
+
789
788
  message.append_string index
790
789
  message.append_array attributes
791
-
790
+
792
791
  message.append_int values_by_doc.length
793
792
  values_by_doc.each do |key,values|
794
793
  message.append_64bit_int key # document ID
795
794
  message.append_ints *values # array of new values (integers)
796
795
  end
797
-
796
+
798
797
  message.to_s
799
798
  end
800
-
799
+
801
800
  # Generates the simple message to send to the daemon for a keywords request.
802
801
  def keywords_message(query, index, return_hits)
803
802
  message = Message.new
804
-
803
+
805
804
  message.append_string query
806
805
  message.append_string index
807
806
  message.append_int return_hits ? 1 : 0
808
-
807
+
809
808
  message.to_s
810
809
  end
811
810
 
@@ -826,12 +825,12 @@ module Riddle
826
825
  AttributeTypes[:multi] + AttributeTypes[:bigint] => :next_64bit_int_array,
827
826
  AttributeTypes[:multi] + AttributeTypes[:string] => :next_array
828
827
  }
829
-
828
+
830
829
  def attribute_from_type(type, response)
831
830
  handler = AttributeHandlers[type]
832
831
  response.send handler
833
832
  end
834
-
833
+
835
834
  def excerpt_flags(options)
836
835
  flags = 1
837
836
  flags |= 2 if options[:exact_phrase]