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 +23 -2
- data/README.textile +5 -0
- data/features/attribute_transformation.feature +4 -4
- data/features/support/env.rb +0 -1
- data/features/thinking_sphinx/db/fixtures/alphas.rb +8 -10
- data/lib/cucumber/thinking_sphinx/internal_world.rb +7 -2
- data/lib/cucumber/thinking_sphinx/sql_logger.rb +16 -4
- data/lib/thinking_sphinx/active_record.rb +5 -1
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +14 -5
- data/lib/thinking_sphinx/attribute.rb +3 -3
- data/lib/thinking_sphinx/auto_version.rb +2 -2
- data/lib/thinking_sphinx/configuration.rb +8 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +1 -1
- data/lib/thinking_sphinx/facet.rb +19 -19
- data/lib/thinking_sphinx/index.rb +3 -1
- data/lib/thinking_sphinx/index/builder.rb +5 -0
- data/lib/thinking_sphinx/property.rb +43 -41
- data/lib/thinking_sphinx/search.rb +23 -4
- data/lib/thinking_sphinx/source.rb +5 -5
- data/lib/thinking_sphinx/source/sql.rb +33 -16
- data/lib/thinking_sphinx/version.rb +1 -1
- data/spec/fixtures/models.rb +3 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/sphinx_helper.rb +7 -1
- data/spec/thinking_sphinx/active_record_spec.rb +15 -0
- data/spec/thinking_sphinx/auto_version_spec.rb +8 -0
- data/spec/thinking_sphinx/index/builder_spec.rb +126 -105
- data/spec/thinking_sphinx/index_spec.rb +6 -0
- data/spec/thinking_sphinx/search_spec.rb +27 -6
- data/spec/thinking_sphinx/source_spec.rb +17 -3
- metadata +63 -64
data/HISTORY
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
2.0.
|
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.
|
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).
|
data/README.textile
CHANGED
@@ -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
|
16
|
-
Then I should get
|
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
|
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
|
data/features/support/env.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
Alpha.create :name =>
|
7
|
-
|
8
|
-
|
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 =
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
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
|
113
|
-
connection.raw_connection.server_version >= 80400
|
112
|
+
if server_version >= 80400
|
114
113
|
return
|
115
|
-
elsif
|
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
|
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? { |
|
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|
|
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
|