thinking-sphinx 1.4.11 → 1.4.12

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.
data/README.textile CHANGED
@@ -204,3 +204,8 @@ Since I first released this library, there's been quite a few people who have su
204
204
  * Tony Pitale
205
205
  * Kenn Ejima
206
206
  * Matthew Barnett
207
+ * Ilia Lobsanov
208
+ * Andrew Hunter
209
+ * Simon Hürlimann
210
+ * Nathan Smith
211
+ * Cedric Maion
@@ -12,11 +12,11 @@ Feature: Handle not-quite-supported column types as attributes
12
12
  Scenario: Dates as Datetimes
13
13
  Given Sphinx is running
14
14
  And I am searching on alphas
15
- When I filter between 1 and 3 days ago on created_on by date
16
- Then I should get 2 results
15
+ When I filter between 2 and 4 days ago on created_on by date
16
+ Then I should get 3 results
17
17
 
18
18
  Scenario: Timestamps as Datetimes
19
19
  Given Sphinx is running
20
20
  And I am searching on alphas
21
- When I filter between 1 and 3 days ago on created_at
22
- Then I should get 2 results
21
+ When I filter between 2 and 4 days ago on created_at
22
+ Then I should get 2 results
@@ -1,10 +1,8 @@
1
- Alpha.create :name => "one", :value => 1, :cost => 1.51, :created_on => 1.day.ago.to_date, :created_at => 1.day.ago
2
- Alpha.create :name => "two", :value => 2, :cost => 2.52, :created_on => 2.day.ago.to_date, :created_at => 2.day.ago
3
- Alpha.create :name => "three", :value => 3, :cost => 3.53, :created_on => 3.day.ago.to_date, :created_at => 3.day.ago
4
- Alpha.create :name => "four", :value => 4, :cost => 4.54, :created_on => 4.day.ago.to_date, :created_at => 4.day.ago
5
- Alpha.create :name => "five", :value => 5, :cost => 5.55, :created_on => 5.day.ago.to_date, :created_at => 5.day.ago
6
- Alpha.create :name => "six", :value => 6, :cost => 6.56, :created_on => 6.day.ago.to_date, :created_at => 6.day.ago
7
- Alpha.create :name => "seven", :value => 7, :cost => 7.57, :created_on => 7.day.ago.to_date, :created_at => 7.day.ago
8
- Alpha.create :name => "eight", :value => 8, :cost => 8.58, :created_on => 8.day.ago.to_date, :created_at => 8.day.ago
9
- Alpha.create :name => "nine", :value => 9, :cost => 9.59, :created_on => 9.day.ago.to_date, :created_at => 9.day.ago
10
- Alpha.create :name => "ten", :value => 10, :cost => 10.50, :created_on => 10.day.ago.to_date, :created_at => 10.day.ago
1
+ %w(
2
+ one two three four five six seven eight nine ten
3
+ ).each_with_index do |number, index|
4
+ value = index + 1
5
+ cost = value.to_f + 0.5 + (value * 0.01)
6
+ Alpha.create :name => number, :value => value, :cost => cost,
7
+ :created_on => value.days.ago.to_date, :created_at => value.days.ago
8
+ end
@@ -16,11 +16,20 @@ module Cucumber
16
16
  @fixtures_directory = "#{pwd}/features/thinking_sphinx/db/fixtures"
17
17
  @database_file = "#{pwd}/features/thinking_sphinx/database.yml"
18
18
 
19
- @adapter = (ENV['DATABASE'] || 'mysql').gsub /^mysql$/, 'mysql2'
19
+ @adapter = (ENV['DATABASE'] || 'mysql')
20
20
  @database = 'thinking_sphinx'
21
- @username = @adapter[/mysql/] ? 'root' : 'postgres'
22
- # @password = 'thinking_sphinx'
21
+ @username = ENV['USER']
23
22
  @host = 'localhost'
23
+
24
+ if ActiveRecord.constants.include?('VERSION')
25
+ @adapter = @adapter.gsub /^mysql$/, 'mysql2'
26
+ end
27
+
28
+ if @adapter[/mysql/]
29
+ @username = 'root'
30
+ elsif ENV['TRAVIS']
31
+ @username = 'postgres'
32
+ end
24
33
  end
25
34
 
26
35
  def setup
@@ -17,7 +17,7 @@ module ThinkingSphinx
17
17
  extend ThinkingSphinx::ActiveRecord::ClassMethods
18
18
 
19
19
  class << self
20
- attr_accessor :sphinx_index_blocks
20
+ attr_accessor :sphinx_index_blocks, :sphinx_types
21
21
 
22
22
  def set_sphinx_primary_key(attribute)
23
23
  @sphinx_primary_key_attribute = attribute
@@ -49,6 +49,10 @@ module ThinkingSphinx
49
49
  sphinx_indexes.last.options
50
50
  end
51
51
 
52
+ def set_sphinx_types(types)
53
+ @sphinx_types = types
54
+ end
55
+
52
56
  # Generate a unique CRC value for the model's name, to use to
53
57
  # determine which Sphinx documents belong to which AR records.
54
58
  #
@@ -20,7 +20,7 @@ module ThinkingSphinx
20
20
  end
21
21
 
22
22
  def group_concatenate(clause, separator = ' ')
23
- if connection.raw_connection.server_version >= 80400
23
+ if server_version >= 80400
24
24
  "array_to_string(array_agg(COALESCE(#{clause}, '0')), '#{separator}')"
25
25
  else
26
26
  "array_to_string(array_accum(COALESCE(#{clause}, '0')), '#{separator}')"
@@ -109,11 +109,9 @@ module ThinkingSphinx
109
109
  end
110
110
 
111
111
  def create_array_accum_function
112
- if connection.raw_connection.respond_to?(:server_version) &&
113
- connection.raw_connection.server_version >= 80400
112
+ if server_version >= 80400
114
113
  return
115
- elsif connection.raw_connection.respond_to?(:server_version) &&
116
- connection.raw_connection.server_version > 80200
114
+ elsif server_version > 80200
117
115
  execute <<-SQL
118
116
  CREATE AGGREGATE array_accum (anyelement)
119
117
  (
@@ -175,5 +173,16 @@ module ThinkingSphinx
175
173
  SQL
176
174
  execute function, true
177
175
  end
176
+
177
+ def server_version
178
+ if RUBY_PLATFORM == 'java'
179
+ (connection.raw_connection.connection.server_major_version * 10000) +
180
+ (connection.raw_connection.connection.server_minor_version * 100)
181
+ elsif connection.raw_connection.respond_to?(:server_version)
182
+ connection.raw_connection.server_version
183
+ else
184
+ 0
185
+ end
186
+ end
178
187
  end
179
188
  end
@@ -269,7 +269,7 @@ WHERE #{@source.index.delta_object.clause(model, true)})
269
269
  end
270
270
 
271
271
  def end_association_for_mva
272
- @association_for_mva ||= associations[columns.first].detect { |assoc|
272
+ @association_for_mva ||= associations[columns.first.__stack].detect { |assoc|
273
273
  assoc.has_column?(columns.first.__name)
274
274
  }
275
275
  end
@@ -358,8 +358,8 @@ block:
358
358
 
359
359
  def all_of_type?(*column_types)
360
360
  @columns.all? { |col|
361
- klasses = @associations[col].empty? ? [@model] :
362
- @associations[col].collect { |assoc| assoc.reflection.klass }
361
+ klasses = @associations[col.__stack].empty? ? [@model] :
362
+ @associations[col.__stack].collect { |assoc| assoc.reflection.klass }
363
363
  klasses.all? { |klass|
364
364
  column = klass.columns.detect { |column| column.name == col.__name.to_s }
365
365
  !column.nil? && column_types.include?(column.type)
@@ -7,9 +7,9 @@ module ThinkingSphinx
7
7
  require "riddle/#{version}"
8
8
  when /1.10/
9
9
  require 'riddle/1.10'
10
- when /2.0.\d/
10
+ when /2.0.[12]/
11
11
  require 'riddle/2.0.1'
12
- when /2.1.\d/
12
+ when /2.0.3/, /2.0.4/, /2.1.\d/
13
13
  require 'riddle/2.1.0'
14
14
  else
15
15
  documentation_link = %Q{
@@ -195,6 +195,14 @@ module ThinkingSphinx
195
195
  @configuration.searchd.port = port
196
196
  end
197
197
 
198
+ def use_socket=(use_socket)
199
+ if use_socket
200
+ socket = "#{app_root}/tmp/sockets/searchd.#{self.environment}.sock"
201
+ @configuration.searchd.listen = socket
202
+ self.address = socket
203
+ end
204
+ end
205
+
198
206
  def pid_file
199
207
  @configuration.searchd.pid_file
200
208
  end
@@ -92,7 +92,7 @@ DESC
92
92
  rails_env = fetch(:rails_env, "production")
93
93
  rake = fetch(:rake, "rake")
94
94
  tasks.each do |t|
95
- run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; #{rake} RAILS_ENV=#{rails_env} #{t}"
95
+ run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; if [ -f Rakefile ]; then #{rake} RAILS_ENV=#{rails_env} #{t}; fi;"
96
96
  end
97
97
  end
98
98
  end
@@ -1,11 +1,11 @@
1
1
  module ThinkingSphinx
2
2
  class Facet
3
3
  attr_reader :property, :value_source
4
-
4
+
5
5
  def initialize(property, value_source = nil)
6
6
  @property = property
7
7
  @value_source = value_source
8
-
8
+
9
9
  if property.columns.length != 1
10
10
  raise "Can't translate Facets on multiple-column field or attribute"
11
11
  end
@@ -20,11 +20,11 @@ module ThinkingSphinx
20
20
  facet.to_s.gsub(/(_facet|_crc)$/,'').to_sym
21
21
  end
22
22
  end
23
-
23
+
24
24
  def self.attribute_name_for(name)
25
25
  name.to_s == 'class' ? 'class_crc' : "#{name}_facet"
26
26
  end
27
-
27
+
28
28
  def self.attribute_name_from_value(name, value)
29
29
  case value
30
30
  when String
@@ -39,10 +39,10 @@ module ThinkingSphinx
39
39
  name
40
40
  end
41
41
  end
42
-
42
+
43
43
  def self.translate?(property)
44
44
  return true if property.is_a?(Field)
45
-
45
+
46
46
  case property.type
47
47
  when :string
48
48
  true
@@ -52,11 +52,11 @@ module ThinkingSphinx
52
52
  !property.all_ints?
53
53
  end
54
54
  end
55
-
55
+
56
56
  def name
57
57
  property.unique_name
58
58
  end
59
-
59
+
60
60
  def attribute_name
61
61
  if translate?
62
62
  Facet.attribute_name_for(@property.unique_name)
@@ -64,23 +64,23 @@ module ThinkingSphinx
64
64
  @property.unique_name.to_s
65
65
  end
66
66
  end
67
-
67
+
68
68
  def translate?
69
69
  Facet.translate?(@property)
70
70
  end
71
-
71
+
72
72
  def type
73
73
  @property.is_a?(Field) ? :string : @property.type
74
74
  end
75
-
75
+
76
76
  def float?
77
77
  @property.type == :float
78
78
  end
79
-
79
+
80
80
  def value(object, attribute_hash)
81
81
  attribute_value = attribute_hash['@groupby']
82
82
  return translate(object, attribute_value) if translate? || float?
83
-
83
+
84
84
  case @property.type
85
85
  when :datetime
86
86
  Time.at(attribute_value)
@@ -90,13 +90,13 @@ module ThinkingSphinx
90
90
  attribute_value
91
91
  end
92
92
  end
93
-
93
+
94
94
  def to_s
95
95
  name
96
96
  end
97
-
97
+
98
98
  private
99
-
99
+
100
100
  def translate(object, attribute_value)
101
101
  objects = source_objects(object)
102
102
  return if objects.blank?
@@ -107,20 +107,20 @@ module ThinkingSphinx
107
107
  result && result.to_crc32 == attribute_value
108
108
  }
109
109
 
110
- object.try(method)
110
+ object ? object.send(method) : nil
111
111
  end
112
-
112
+
113
113
  def source_objects(object)
114
114
  column.__stack.each { |method|
115
115
  object = Array(object).collect { |item|
116
116
  item.send(method)
117
117
  }.flatten.compact
118
-
118
+
119
119
  return nil if object.empty?
120
120
  }
121
121
  Array(object)
122
122
  end
123
-
123
+
124
124
  def column
125
125
  @property.columns.first
126
126
  end
@@ -3,7 +3,7 @@ require 'thinking_sphinx/index/faux_column'
3
3
 
4
4
  module ThinkingSphinx
5
5
  class Index
6
- attr_accessor :name, :model, :sources, :delta_object
6
+ attr_accessor :name, :model, :sources, :delta_object, :additional_indices
7
7
 
8
8
  # Create a new index instance by passing in the model it is tied to, and
9
9
  # a block to build it with (optional but recommended). For documentation
@@ -25,6 +25,7 @@ module ThinkingSphinx
25
25
  @sources = []
26
26
  @options = {}
27
27
  @delta_object = nil
28
+ @additional_indices = []
28
29
  end
29
30
 
30
31
  def fields
@@ -132,6 +133,7 @@ module ThinkingSphinx
132
133
  def to_riddle_for_distributed
133
134
  index = Riddle::Configuration::DistributedIndex.new name
134
135
  index.local_indices << core_name
136
+ index.local_indices += additional_indices
135
137
  index.local_indices.unshift delta_name if delta?
136
138
  index
137
139
  end
@@ -52,6 +52,11 @@ module ThinkingSphinx
52
52
  self.instance_eval &block
53
53
  end
54
54
 
55
+ def use_local_indices(*indexes)
56
+ @index.additional_indices += indexes.map {|index_name| "#{index_name.to_s}_core"}
57
+ end
58
+ alias_method :use_local_index, :use_local_indices
59
+
55
60
  # This is how you add fields - the strings Sphinx looks at - to your
56
61
  # index. Technically, to use this method, you need to pass in some
57
62
  # columns and options - but there's some neat method_missing stuff
@@ -1,7 +1,7 @@
1
1
  module ThinkingSphinx
2
2
  class Property
3
3
  attr_accessor :alias, :columns, :associations, :model, :faceted, :admin
4
-
4
+
5
5
  def initialize(source, columns, options = {})
6
6
  @source = source
7
7
  @model = source.model
@@ -9,27 +9,27 @@ module ThinkingSphinx
9
9
  @associations = {}
10
10
 
11
11
  raise "Cannot define a field or attribute in #{source.model.name} with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) }
12
-
12
+
13
13
  @alias = options[:as]
14
14
  @faceted = options[:facet]
15
15
  @admin = options[:admin]
16
16
  @sortable = options[:sortable] || false
17
17
  @value_source = options[:value]
18
-
18
+
19
19
  @alias = @alias.to_sym unless @alias.blank?
20
-
20
+
21
21
  @columns.each { |col|
22
- @associations[col] = association_stack(col.__stack.clone).each { |assoc|
22
+ @associations[col.__stack] = association_stack(col.__stack.clone).each { |assoc|
23
23
  assoc.join_to(source.base)
24
24
  }
25
25
  }
26
26
  end
27
-
27
+
28
28
  # Returns the unique name of the attribute - which is either the alias of
29
29
  # the attribute, or the name of the only column - if there is only one. If
30
30
  # there isn't, there should be an alias. Else things probably won't work.
31
31
  # Consider yourself warned.
32
- #
32
+ #
33
33
  def unique_name
34
34
  if @columns.length == 1
35
35
  @alias || @columns.first.__name
@@ -37,19 +37,19 @@ module ThinkingSphinx
37
37
  @alias
38
38
  end
39
39
  end
40
-
40
+
41
41
  def to_facet
42
42
  return nil unless @faceted
43
-
43
+
44
44
  ThinkingSphinx::Facet.new(self, @value_source)
45
45
  end
46
-
46
+
47
47
  # Get the part of the GROUP BY clause related to this attribute - if one is
48
48
  # needed. If not, all you'll get back is nil. The latter will happen if
49
49
  # there isn't actually a real column to get data from, or if there's
50
50
  # multiple data values (read: a has_many or has_and_belongs_to_many
51
51
  # association).
52
- #
52
+ #
53
53
  def to_group_sql
54
54
  case
55
55
  when is_many?, is_string?, ThinkingSphinx.use_group_by_shortcut?
@@ -60,126 +60,128 @@ module ThinkingSphinx
60
60
  }
61
61
  end
62
62
  end
63
-
63
+
64
64
  def changed?(instance)
65
65
  return true if is_string? || @columns.any? { |col| !col.__stack.empty? }
66
-
66
+
67
67
  !@columns.all? { |col|
68
68
  instance.respond_to?("#{col.__name.to_s}_changed?") &&
69
69
  !instance.send("#{col.__name.to_s}_changed?")
70
70
  }
71
71
  end
72
-
72
+
73
73
  def admin?
74
74
  admin
75
75
  end
76
-
76
+
77
77
  def public?
78
78
  !admin
79
79
  end
80
-
80
+
81
81
  def available?
82
82
  columns.any? { |column| column_available?(column) }
83
83
  end
84
-
84
+
85
85
  private
86
-
86
+
87
87
  # Could there be more than one value related to the parent record? If so,
88
88
  # then this will return true. If not, false. It's that simple.
89
- #
89
+ #
90
90
  def is_many?
91
91
  associations.values.flatten.any? { |assoc| assoc.is_many? }
92
92
  end
93
-
93
+
94
94
  # Returns true if any of the columns are string values, instead of database
95
95
  # column references.
96
96
  def is_string?
97
97
  columns.all? { |col| col.is_string? }
98
98
  end
99
-
99
+
100
100
  def adapter
101
101
  @adapter ||= @model.sphinx_database_adapter
102
102
  end
103
-
103
+
104
104
  def quote_with_table(table, column)
105
105
  "#{quote_table_name(table)}.#{quote_column(column)}"
106
106
  end
107
-
107
+
108
108
  def quote_column(column)
109
109
  @model.connection.quote_column_name(column)
110
110
  end
111
-
111
+
112
112
  def quote_table_name(table_name)
113
113
  @model.connection.quote_table_name(table_name)
114
114
  end
115
-
115
+
116
116
  # Indication of whether the columns should be concatenated with a space
117
117
  # between each value. True if there's either multiple sources or multiple
118
118
  # associations.
119
- #
119
+ #
120
120
  def concat_ws?
121
121
  multiple_associations? || @columns.length > 1
122
122
  end
123
-
123
+
124
124
  # Checks whether any column requires multiple associations (which only
125
125
  # happens for polymorphic situations).
126
- #
126
+ #
127
127
  def multiple_associations?
128
- associations.any? { |col,assocs| assocs.length > 1 }
128
+ associations.values.any? { |assocs| assocs.length > 1 }
129
129
  end
130
-
130
+
131
131
  # Builds a column reference tied to the appropriate associations. This
132
132
  # dives into the associations hash and their corresponding joins to
133
133
  # figure out how to correctly reference a column in SQL.
134
- #
134
+ #
135
135
  def column_with_prefix(column)
136
136
  return nil unless column_available?(column)
137
-
137
+
138
138
  if column.is_string?
139
139
  column.__name
140
140
  elsif column.__stack.empty?
141
141
  "#{@model.quoted_table_name}.#{quote_column(column.__name)}"
142
142
  else
143
- associations[column].collect { |assoc|
143
+ associations[column.__stack].collect { |assoc|
144
144
  assoc.has_column?(column.__name) ?
145
145
  "#{quote_with_table(assoc.join.aliased_table_name, column.__name)}" :
146
146
  nil
147
147
  }.compact
148
148
  end
149
149
  end
150
-
150
+
151
151
  def columns_with_prefixes
152
152
  @columns.collect { |column|
153
153
  column_with_prefix column
154
154
  }.flatten.compact
155
155
  end
156
-
156
+
157
157
  def column_available?(column)
158
158
  if column.is_string?
159
159
  true
160
160
  elsif column.__stack.empty?
161
161
  @model.column_names.include?(column.__name.to_s)
162
162
  else
163
- associations[column].any? { |assoc| assoc.has_column?(column.__name) }
163
+ associations[column.__stack].any? { |assoc|
164
+ assoc.has_column?(column.__name)
165
+ }
164
166
  end
165
167
  end
166
-
168
+
167
169
  # Gets a stack of associations for a specific path.
168
- #
170
+ #
169
171
  def association_stack(path, parent = nil)
170
172
  assocs = []
171
-
173
+
172
174
  if parent.nil?
173
175
  assocs = @source.association(path.shift)
174
176
  else
175
177
  assocs = parent.children(path.shift)
176
178
  end
177
-
179
+
178
180
  until path.empty?
179
181
  point = path.shift
180
182
  assocs = assocs.collect { |assoc| assoc.children(point) }.flatten
181
183
  end
182
-
184
+
183
185
  assocs
184
186
  end
185
187
  end