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 +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
|