thinking-sphinx 2.0.11 → 2.0.12

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -1,4 +1,8 @@
1
- 2.0.11 - January 2nd 2011
1
+ 2.0.12 - May 14th 2012
2
+ * STI fix when generating WHERE clauses for sql_query.
3
+ * 1.4.12 changes.
4
+
5
+ 2.0.11 - January 2nd 2012
2
6
  * Call #all on search results to force population of results, matching ActiveRecord::Relation#all in essence. (Adrian Macneil).
3
7
  * 1.4.11 changes.
4
8
 
@@ -57,7 +61,24 @@
57
61
  * Rails 3 support.
58
62
  * 1.4.0 changes.
59
63
 
60
- 1.4.11 - January 2nd 2011
64
+ 1.4.12 - May 14th 2012
65
+ * Updating Riddle references to 1.5.2.
66
+ * Can explicitly specify available types for STI tables instead of automatically discovering them with "SELECT DISTINCT type FROM <table>" (Cedric Maion).
67
+ * Don't try to run rake tasks for Capistrano if there's no Rakefile - eg. on fresh deploys (Nathan Smith).
68
+ * Populate search results before comparing with #==.
69
+ * Can indicate whether Sphinx should use a socket for connections instead of TCP (Simon Hürlimann).
70
+ * Can have just attribute values returned as search results using `:attributes_only => true` in a search call (Andrew Hunter).
71
+ * Can specify additional local indices for the generated distributed index (usually one per model) (Andrew Hunter).
72
+ * Supporting Sphinx 2.0.4 (Ilia Lobsanov).
73
+ * Load MySQL SSL settings from database.yml (James Brooks).
74
+ * Adding Sphinx 2.0.3 support (identical to 2.1.0).
75
+ * Dropping Rails 1.2 and 2.0.x support.
76
+ * Association keys now are the association stacks as arrays - more reliable in Rubinius.
77
+ * Can now determine PostgreSQL versions with JRuby.
78
+ * Many testing tweaks.
79
+ * Allow for rank_expr option being passed through to Riddle.
80
+
81
+ 1.4.11 - January 2nd 2012
61
82
  * Handle no results for total_pages and total_entries with defaults of 0.
62
83
  * No longer shuffle Sphinx addresses by default.
63
84
  * Fix coalescing of non-char values in PostgreSQL (Matthew Barnett).
@@ -227,3 +227,8 @@ Since I first released this library, there's been quite a few people who have su
227
227
  * Kenn Ejima
228
228
  * Matthew Barnett
229
229
  * Adrian Macneil
230
+ * Ilia Lobsanov
231
+ * Andrew Hunter
232
+ * Simon Hürlimann
233
+ * Nathan Smith
234
+ * 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
@@ -9,7 +9,6 @@ Dir[File.join(File.dirname(__FILE__), '../../vendor/*/lib')].each do |path|
9
9
  $:.unshift path
10
10
  end
11
11
 
12
- require 'active_support/core_ext/class/inheritable_attributes'
13
12
  require 'active_record'
14
13
  require 'cucumber/thinking_sphinx/internal_world'
15
14
 
@@ -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
@@ -18,9 +18,14 @@ module Cucumber
18
18
 
19
19
  @adapter = (ENV['DATABASE'] || 'mysql').gsub /^mysql$/, 'mysql2'
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 @adapter[/mysql/]
25
+ @username = 'root'
26
+ elsif ENV['TRAVIS']
27
+ @username = 'postgres'
28
+ end
24
29
  end
25
30
 
26
31
  def setup
@@ -6,10 +6,22 @@ module Cucumber
6
6
  /^SELECT @@ROWCOUNT/, /^SHOW FIELDS/
7
7
  ]
8
8
 
9
- def log(sql, name = 'SQL', binds = [])
10
- $queries_executed ||= []
11
- $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
12
- super sql, name, binds
9
+ if ActiveRecord::VERSION::STRING.to_f > 3.0
10
+ def log(sql, name = 'SQL', binds = [])
11
+ $queries_executed ||= []
12
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
13
+ super sql, name, binds
14
+ end
15
+ else
16
+ def self.included(base)
17
+ base.send :alias_method_chain, :execute, :query_record
18
+ end
19
+
20
+ def execute_with_query_record(sql, name = 'SQL', &block)
21
+ $queries_executed ||= []
22
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
23
+ execute_without_query_record(sql, name, &block)
24
+ end
13
25
  end
14
26
  end
15
27
  end
@@ -24,7 +24,7 @@ module ThinkingSphinx
24
24
  extend ThinkingSphinx::ActiveRecord::ClassMethods
25
25
 
26
26
  class << self
27
- attr_accessor :sphinx_index_blocks
27
+ attr_accessor :sphinx_index_blocks, :sphinx_types
28
28
 
29
29
  def set_sphinx_primary_key(attribute)
30
30
  @sphinx_primary_key_attribute = attribute
@@ -56,6 +56,10 @@ module ThinkingSphinx
56
56
  sphinx_indexes.last.options
57
57
  end
58
58
 
59
+ def set_sphinx_types(types)
60
+ @sphinx_types = types
61
+ end
62
+
59
63
  # Generate a unique CRC value for the model's name, to use to
60
64
  # determine which Sphinx documents belong to which AR records.
61
65
  #
@@ -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
@@ -283,7 +283,7 @@ WHERE #{@source.index.delta_object.clause(model, true)})
283
283
  end
284
284
 
285
285
  def end_association_for_mva
286
- @association_for_mva ||= associations[columns.first].detect { |assoc|
286
+ @association_for_mva ||= associations[columns.first.__stack].detect { |assoc|
287
287
  assoc.has_column?(columns.first.__name)
288
288
  }
289
289
  end
@@ -372,8 +372,8 @@ block:
372
372
 
373
373
  def all_of_type?(*column_types)
374
374
  @columns.all? { |col|
375
- klasses = @associations[col].empty? ? [@model] :
376
- @associations[col].collect { |assoc| assoc.reflection.klass }
375
+ klasses = @associations[col.__stack].empty? ? [@model] :
376
+ @associations[col.__stack].collect { |assoc| assoc.reflection.klass }
377
377
  klasses.all? { |klass|
378
378
  column = klass.columns.detect { |column| column.name == col.__name.to_s }
379
379
  !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{
@@ -194,6 +194,14 @@ module ThinkingSphinx
194
194
  @configuration.searchd.port = port
195
195
  end
196
196
 
197
+ def use_socket=(use_socket)
198
+ if use_socket
199
+ socket = "#{app_root}/tmp/sockets/searchd.#{self.environment}.sock"
200
+ @configuration.searchd.listen = socket
201
+ self.address = socket
202
+ end
203
+ end
204
+
197
205
  def pid_file
198
206
  @configuration.searchd.pid_file
199
207
  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?
@@ -109,18 +109,18 @@ module ThinkingSphinx
109
109
 
110
110
  object.try(method)
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
@@ -47,6 +47,11 @@ module ThinkingSphinx
47
47
  self.instance_eval &block
48
48
  end
49
49
 
50
+ def use_local_indices(*indexes)
51
+ @index.additional_indices += indexes.map {|index_name| "#{index_name.to_s}_core"}
52
+ end
53
+ alias_method :use_local_index, :use_local_indices
54
+
50
55
  # This is how you add fields - the strings Sphinx looks at - to your
51
56
  # index. Technically, to use this method, you need to pass in some
52
57
  # 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,125 +60,127 @@ 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.any? { |col|
68
68
  instance.send("#{col.__name.to_s}_changed?")
69
69
  }
70
70
  end
71
-
71
+
72
72
  def admin?
73
73
  admin
74
74
  end
75
-
75
+
76
76
  def public?
77
77
  !admin
78
78
  end
79
-
79
+
80
80
  def available?
81
81
  columns.any? { |column| column_available?(column) }
82
82
  end
83
-
83
+
84
84
  private
85
-
85
+
86
86
  # Could there be more than one value related to the parent record? If so,
87
87
  # then this will return true. If not, false. It's that simple.
88
- #
88
+ #
89
89
  def is_many?
90
90
  associations.values.flatten.any? { |assoc| assoc.is_many? }
91
91
  end
92
-
92
+
93
93
  # Returns true if any of the columns are string values, instead of database
94
94
  # column references.
95
95
  def is_string?
96
96
  columns.all? { |col| col.is_string? }
97
97
  end
98
-
98
+
99
99
  def adapter
100
100
  @adapter ||= @model.sphinx_database_adapter
101
101
  end
102
-
102
+
103
103
  def quote_with_table(table, column)
104
104
  "#{quote_table_name(table)}.#{quote_column(column)}"
105
105
  end
106
-
106
+
107
107
  def quote_column(column)
108
108
  @model.connection.quote_column_name(column)
109
109
  end
110
-
110
+
111
111
  def quote_table_name(table_name)
112
112
  @model.connection.quote_table_name(table_name)
113
113
  end
114
-
114
+
115
115
  # Indication of whether the columns should be concatenated with a space
116
116
  # between each value. True if there's either multiple sources or multiple
117
117
  # associations.
118
- #
118
+ #
119
119
  def concat_ws?
120
120
  multiple_associations? || @columns.length > 1
121
121
  end
122
-
122
+
123
123
  # Checks whether any column requires multiple associations (which only
124
124
  # happens for polymorphic situations).
125
- #
125
+ #
126
126
  def multiple_associations?
127
- associations.any? { |col,assocs| assocs.length > 1 }
127
+ associations.values.any? { |assocs| assocs.length > 1 }
128
128
  end
129
-
129
+
130
130
  # Builds a column reference tied to the appropriate associations. This
131
131
  # dives into the associations hash and their corresponding joins to
132
132
  # figure out how to correctly reference a column in SQL.
133
- #
133
+ #
134
134
  def column_with_prefix(column)
135
135
  return nil unless column_available?(column)
136
-
136
+
137
137
  if column.is_string?
138
138
  column.__name
139
139
  elsif column.__stack.empty?
140
140
  "#{@model.quoted_table_name}.#{quote_column(column.__name)}"
141
141
  else
142
- associations[column].collect { |assoc|
142
+ associations[column.__stack].collect { |assoc|
143
143
  assoc.has_column?(column.__name) ?
144
144
  "#{quote_with_table(assoc.join.aliased_table_name, column.__name)}" :
145
145
  nil
146
146
  }.compact
147
147
  end
148
148
  end
149
-
149
+
150
150
  def columns_with_prefixes
151
151
  @columns.collect { |column|
152
152
  column_with_prefix column
153
153
  }.flatten.compact
154
154
  end
155
-
155
+
156
156
  def column_available?(column)
157
157
  if column.is_string?
158
158
  true
159
159
  elsif column.__stack.empty?
160
160
  @model.column_names.include?(column.__name.to_s)
161
161
  else
162
- associations[column].any? { |assoc| assoc.has_column?(column.__name) }
162
+ associations[column.__stack].any? { |assoc|
163
+ assoc.has_column?(column.__name)
164
+ }
163
165
  end
164
166
  end
165
-
167
+
166
168
  # Gets a stack of associations for a specific path.
167
- #
169
+ #
168
170
  def association_stack(path, parent = nil)
169
171
  assocs = []
170
-
172
+
171
173
  if parent.nil?
172
174
  assocs = @source.association(path.shift)
173
175
  else
174
176
  assocs = parent.children(path.shift)
175
177
  end
176
-
178
+
177
179
  until path.empty?
178
180
  point = path.shift
179
181
  assocs = assocs.collect { |assoc| assoc.children(point) }.flatten
180
182
  end
181
-
183
+
182
184
  assocs
183
185
  end
184
186
  end