thinking-sphinx 2.0.11 → 2.0.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/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