dpickett-thinking-sphinx 1.1.12 → 1.1.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/README.textile +19 -0
  2. data/lib/thinking_sphinx.rb +36 -2
  3. data/lib/thinking_sphinx/active_record.rb +18 -3
  4. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +9 -3
  5. data/lib/thinking_sphinx/association.rb +4 -1
  6. data/lib/thinking_sphinx/attribute.rb +85 -43
  7. data/lib/thinking_sphinx/configuration.rb +33 -12
  8. data/lib/thinking_sphinx/deltas.rb +9 -6
  9. data/lib/thinking_sphinx/deltas/datetime_delta.rb +3 -3
  10. data/lib/thinking_sphinx/deltas/default_delta.rb +4 -4
  11. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +1 -1
  12. data/lib/thinking_sphinx/deploy/capistrano.rb +82 -64
  13. data/lib/thinking_sphinx/facet.rb +58 -21
  14. data/lib/thinking_sphinx/facet_collection.rb +12 -13
  15. data/lib/thinking_sphinx/field.rb +3 -1
  16. data/lib/thinking_sphinx/index.rb +28 -353
  17. data/lib/thinking_sphinx/index/builder.rb +255 -232
  18. data/lib/thinking_sphinx/property.rb +29 -2
  19. data/lib/thinking_sphinx/search.rb +32 -96
  20. data/lib/thinking_sphinx/search/facets.rb +104 -0
  21. data/lib/thinking_sphinx/source.rb +150 -0
  22. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  23. data/lib/thinking_sphinx/source/sql.rb +128 -0
  24. data/lib/thinking_sphinx/tasks.rb +42 -8
  25. data/rails/init.rb +14 -0
  26. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +5 -5
  27. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +4 -4
  28. data/spec/unit/thinking_sphinx/active_record_spec.rb +52 -39
  29. data/spec/unit/thinking_sphinx/association_spec.rb +4 -5
  30. data/spec/unit/thinking_sphinx/attribute_spec.rb +209 -19
  31. data/spec/unit/thinking_sphinx/collection_spec.rb +7 -6
  32. data/spec/unit/thinking_sphinx/configuration_spec.rb +93 -7
  33. data/spec/unit/thinking_sphinx/facet_spec.rb +256 -0
  34. data/spec/unit/thinking_sphinx/field_spec.rb +26 -17
  35. data/spec/unit/thinking_sphinx/index/builder_spec.rb +351 -1
  36. data/spec/unit/thinking_sphinx/index_spec.rb +3 -102
  37. data/spec/unit/thinking_sphinx/rails_additions_spec.rb +13 -5
  38. data/spec/unit/thinking_sphinx/search_spec.rb +154 -29
  39. data/spec/unit/thinking_sphinx/source_spec.rb +217 -0
  40. data/spec/unit/thinking_sphinx_spec.rb +22 -4
  41. data/tasks/distribution.rb +19 -0
  42. data/vendor/riddle/lib/riddle.rb +1 -1
  43. data/vendor/riddle/lib/riddle/configuration/section.rb +7 -1
  44. metadata +26 -3
@@ -4,20 +4,23 @@ require 'thinking_sphinx/deltas/datetime_delta'
4
4
 
5
5
  module ThinkingSphinx
6
6
  module Deltas
7
- def self.parse(index, options)
8
- delta_option = options.delete(:delta)
7
+ def self.parse(index)
8
+ delta_option = index.local_options.delete(:delta)
9
9
  case delta_option
10
10
  when TrueClass, :default
11
- DefaultDelta.new index, options
11
+ DefaultDelta.new index, index.local_options
12
12
  when :delayed
13
- DelayedDelta.new index, options
13
+ DelayedDelta.new index, index.local_options
14
14
  when :datetime
15
- DatetimeDelta.new index, options
15
+ DatetimeDelta.new index, index.local_options
16
16
  when FalseClass, nil
17
17
  nil
18
18
  else
19
+ if delta_option.is_a?(String)
20
+ delta_option = Kernel.const_get(delta_option)
21
+ end
19
22
  if delta_option.ancestors.include?(ThinkingSphinx::Deltas::DefaultDelta)
20
- delta_option.new index, options
23
+ delta_option.new index, index.local_options
21
24
  else
22
25
  raise "Unknown delta type"
23
26
  end
@@ -18,8 +18,8 @@ module ThinkingSphinx
18
18
  config = ThinkingSphinx::Configuration.instance
19
19
  rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
20
20
 
21
- output = `#{config.bin_path}indexer --config #{config.config_file} #{rotate} #{delta_index_name model}`
22
- output += `#{config.bin_path}indexer --config #{config.config_file} #{rotate} --merge #{core_index_name model} #{delta_index_name model} --merge-dst-range sphinx_deleted 0 0`
21
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} #{delta_index_name model}`
22
+ output += `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} --merge #{core_index_name model} #{delta_index_name model} --merge-dst-range sphinx_deleted 0 0`
23
23
  puts output unless ThinkingSphinx.suppress_delta_output?
24
24
 
25
25
  true
@@ -39,7 +39,7 @@ module ThinkingSphinx
39
39
 
40
40
  def clause(model, toggled)
41
41
  if toggled
42
- "#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" +
42
+ "#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
43
43
  " > #{adapter.time_difference(@threshold)}"
44
44
  else
45
45
  nil
@@ -17,7 +17,7 @@ module ThinkingSphinx
17
17
  client = Riddle::Client.new config.address, config.port
18
18
  rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
19
19
 
20
- output = `#{config.bin_path}indexer --config #{config.config_file} #{rotate} #{delta_index_name model}`
20
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} #{delta_index_name model}`
21
21
  puts(output) unless ThinkingSphinx.suppress_delta_output?
22
22
 
23
23
  client.update(
@@ -39,12 +39,12 @@ module ThinkingSphinx
39
39
 
40
40
  def reset_query(model)
41
41
  "UPDATE #{model.quoted_table_name} SET " +
42
- "#{@index.quote_column(@column.to_s)} = #{adapter.boolean(false)} " +
43
- "WHERE #{@index.quote_column(@column.to_s)} = #{adapter.boolean(true)}"
42
+ "#{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(false)} " +
43
+ "WHERE #{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(true)}"
44
44
  end
45
45
 
46
46
  def clause(model, toggled)
47
- "#{model.quoted_table_name}.#{@index.quote_column(@column.to_s)}" +
47
+ "#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
48
48
  " = #{adapter.boolean(toggled)}"
49
49
  end
50
50
 
@@ -14,7 +14,7 @@ module ThinkingSphinx
14
14
  config = ThinkingSphinx::Configuration.instance
15
15
  client = Riddle::Client.new config.address, config.port
16
16
 
17
- output = `#{config.bin_path}indexer --config #{config.config_file} --rotate #{index}`
17
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} --rotate #{index}`
18
18
  puts output unless ThinkingSphinx.suppress_delta_output?
19
19
 
20
20
  true
@@ -1,80 +1,98 @@
1
- namespace :thinking_sphinx do
2
- namespace :install do
3
- desc "Install Sphinx by source"
4
- task :sphinx do
5
- with_postgres = false
6
- run "which pg_config" do |channel, stream, data|
7
- with_postgres = !(data.nil? || data == "")
8
- end
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ namespace :thinking_sphinx do
3
+ namespace :install do
4
+ desc <<-DESC
5
+ Install Sphinx by source
6
+
7
+ If Postgres is available, Sphinx will use it.
8
+
9
+ If the variable :thinking_sphinx_configure_args is set, it will
10
+ be passed to the Sphinx configure script. You can use this to
11
+ install Sphinx in a non-standard location:
12
+
13
+ set :thinking_sphinx_configure_args, "--prefix=$HOME/software"
14
+ DESC
15
+
16
+ task :sphinx do
17
+ with_postgres = false
18
+ begin
19
+ run "which pg_config" do |channel, stream, data|
20
+ with_postgres = !(data.nil? || data == "")
21
+ end
22
+ rescue Capistrano::CommandError => e
23
+ puts "Continuing despite error: #{e.message}"
24
+ end
9
25
 
10
- args = []
11
- if with_postgres
12
- run "pg_config --pkgincludedir" do |channel, stream, data|
13
- args << "--with-pgsql=#{data}"
26
+ args = []
27
+ if with_postgres
28
+ run "pg_config --pkgincludedir" do |channel, stream, data|
29
+ args << "--with-pgsql=#{data}"
30
+ end
14
31
  end
32
+ args << fetch(:thinking_sphinx_configure_args, '')
33
+
34
+ commands = <<-CMD
35
+ wget -q http://www.sphinxsearch.com/downloads/sphinx-0.9.8.1.tar.gz >> sphinx.log
36
+ tar xzvf sphinx-0.9.8.1.tar.gz
37
+ cd sphinx-0.9.8.1
38
+ ./configure #{args.join(" ")}
39
+ make
40
+ #{try_sudo} make install
41
+ rm -rf sphinx-0.9.8.1 sphinx-0.9.8.1.tar.gz
42
+ CMD
43
+ run commands.split(/\n\s+/).join(" && ")
15
44
  end
16
-
17
- commands = <<-CMD
18
- wget -q http://www.sphinxsearch.com/downloads/sphinx-0.9.8.1.tar.gz >> sphinx.log
19
- tar xzvf sphinx-0.9.8.1.tar.gz
20
- cd sphinx-0.9.8.1
21
- ./configure #{args.join(" ")}
22
- make
23
- sudo make install
24
- rm -rf sphinx-0.9.8.1 sphinx-0.9.8.1.tar.gz
25
- CMD
26
- run commands.split(/\n\s+/).join(" && ")
27
- end
28
45
 
29
- desc "Install Thinking Sphinx as a gem from GitHub"
30
- task :ts do
31
- sudo "gem install freelancing-god-thinking-sphinx --source http://gems.github.com"
46
+ desc "Install Thinking Sphinx as a gem from GitHub"
47
+ task :ts do
48
+ run "#{try_sudo} gem install freelancing-god-thinking-sphinx --source http://gems.github.com"
49
+ end
32
50
  end
33
- end
34
51
 
35
- desc "Generate the Sphinx configuration file"
36
- task :configure do
37
- rake "thinking_sphinx:configure"
38
- end
52
+ desc "Generate the Sphinx configuration file"
53
+ task :configure do
54
+ rake "thinking_sphinx:configure"
55
+ end
39
56
 
40
- desc "Index data"
41
- task :index do
42
- rake "thinking_sphinx:index"
43
- end
57
+ desc "Index data"
58
+ task :index do
59
+ rake "thinking_sphinx:index"
60
+ end
44
61
 
45
- desc "Start the Sphinx daemon"
46
- task :start do
47
- configure
48
- rake "thinking_sphinx:start"
49
- end
62
+ desc "Start the Sphinx daemon"
63
+ task :start do
64
+ configure
65
+ rake "thinking_sphinx:start"
66
+ end
50
67
 
51
- desc "Stop the Sphinx daemon"
52
- task :stop do
53
- configure
54
- rake "thinking_sphinx:stop"
55
- end
68
+ desc "Stop the Sphinx daemon"
69
+ task :stop do
70
+ configure
71
+ rake "thinking_sphinx:stop"
72
+ end
56
73
 
57
- desc "Stop and then start the Sphinx daemon"
58
- task :restart do
59
- stop
60
- start
61
- end
74
+ desc "Stop and then start the Sphinx daemon"
75
+ task :restart do
76
+ stop
77
+ start
78
+ end
62
79
 
63
- desc "Stop, re-index and then start the Sphinx daemon"
64
- task :rebuild do
65
- stop
66
- index
67
- start
68
- end
80
+ desc "Stop, re-index and then start the Sphinx daemon"
81
+ task :rebuild do
82
+ stop
83
+ index
84
+ start
85
+ end
69
86
 
70
- desc "Add the shared folder for sphinx files for the production environment"
71
- task :shared_sphinx_folder, :roles => :web do
72
- sudo "mkdir -p #{shared_path}/db/sphinx/production"
73
- end
87
+ desc "Add the shared folder for sphinx files for the production environment"
88
+ task :shared_sphinx_folder, :roles => :web do
89
+ run "mkdir -p #{shared_path}/db/sphinx/production"
90
+ end
74
91
 
75
- def rake(*tasks)
76
- tasks.each do |t|
77
- run "cd #{current_path} && rake #{t} RAILS_ENV=production"
92
+ def rake(*tasks)
93
+ tasks.each do |t|
94
+ run "cd #{current_path} && rake #{t} RAILS_ENV=production"
95
+ end
78
96
  end
79
97
  end
80
98
  end
@@ -1,11 +1,11 @@
1
1
  module ThinkingSphinx
2
2
  class Facet
3
- attr_reader :reference
3
+ attr_reader :property
4
4
 
5
- def initialize(reference)
6
- @reference = reference
5
+ def initialize(property)
6
+ @property = property
7
7
 
8
- if reference.columns.length != 1
8
+ if property.columns.length != 1
9
9
  raise "Can't translate Facets on multiple-column field or attribute"
10
10
  end
11
11
  end
@@ -19,29 +19,62 @@ module ThinkingSphinx
19
19
  end
20
20
  end
21
21
 
22
- def name
23
- reference.unique_name
24
- end
25
-
26
22
  def self.attribute_name_for(name)
27
23
  name.to_s == 'class' ? 'class_crc' : "#{name}_facet"
28
24
  end
29
25
 
26
+ def self.attribute_name_from_value(name, value)
27
+ case value
28
+ when String
29
+ attribute_name_for(name)
30
+ when Array
31
+ if value.all? { |val| val.is_a?(Integer) }
32
+ name
33
+ else
34
+ attribute_name_for(name)
35
+ end
36
+ else
37
+ name
38
+ end
39
+ end
40
+
41
+ def self.translate?(property)
42
+ return true if property.is_a?(Field)
43
+
44
+ case property.type
45
+ when :string
46
+ true
47
+ when :integer, :boolean, :datetime, :float
48
+ false
49
+ when :multi
50
+ !property.all_ints?
51
+ end
52
+ end
53
+
54
+ def name
55
+ property.unique_name
56
+ end
57
+
30
58
  def attribute_name
31
- # @attribute_name ||= case @reference
32
- # when Attribute
33
- # @reference.unique_name.to_s
34
- # when Field
35
- @attribute_name ||= @reference.unique_name.to_s + "_facet"
36
- # end
59
+ if translate?
60
+ Facet.attribute_name_for(@property.unique_name)
61
+ else
62
+ @property.unique_name.to_s
63
+ end
64
+ end
65
+
66
+ def translate?
67
+ Facet.translate?(@property)
68
+ end
69
+
70
+ def type
71
+ @property.is_a?(Field) ? :string : @property.type
37
72
  end
38
73
 
39
74
  def value(object, attribute_value)
40
- return translate(object, attribute_value) if @reference.is_a?(Field)
75
+ return translate(object, attribute_value) if translate?
41
76
 
42
- case @reference.type
43
- when :string
44
- translate(object, attribute_value)
77
+ case @property.type
45
78
  when :datetime
46
79
  Time.at(attribute_value)
47
80
  when :boolean
@@ -59,13 +92,17 @@ module ThinkingSphinx
59
92
 
60
93
  def translate(object, attribute_value)
61
94
  column.__stack.each { |method|
62
- object = object.send(method)
95
+ return nil unless object = object.send(method)
63
96
  }
64
- object.send(column.__name)
97
+ if object.is_a?(Array)
98
+ object.collect { |item| item.send(column.__name) }
99
+ else
100
+ object.send(column.__name)
101
+ end
65
102
  end
66
103
 
67
104
  def column
68
- @reference.columns.first
105
+ @property.columns.first
69
106
  end
70
107
  end
71
108
  end
@@ -3,28 +3,25 @@ module ThinkingSphinx
3
3
  attr_accessor :arguments
4
4
 
5
5
  def initialize(arguments)
6
- @arguments = arguments.clone
7
- @attribute_values = {}
8
- @facet_names = []
6
+ @arguments = arguments.clone
7
+ @facet_names = []
9
8
  end
10
9
 
11
10
  def add_from_results(facet, results)
12
11
  name = ThinkingSphinx::Facet.name_for(facet)
13
-
14
- self[name] ||= {}
15
- @attribute_values[name] ||= {}
12
+
13
+ self[name] ||= {}
16
14
  @facet_names << name
17
-
15
+
18
16
  return if results.empty?
19
-
17
+
20
18
  facet = facet_from_object(results.first, facet) if facet.is_a?(String)
21
19
 
22
20
  results.each_with_groupby_and_count { |result, group, count|
23
21
  facet_value = facet.value(result, group)
24
22
 
25
- self[name][facet_value] ||= 0
26
- self[name][facet_value] += count
27
- @attribute_values[name][facet_value] = group
23
+ self[name][facet_value] ||= 0
24
+ self[name][facet_value] += count
28
25
  }
29
26
  end
30
27
 
@@ -34,7 +31,7 @@ module ThinkingSphinx
34
31
  options[:with] ||= {}
35
32
 
36
33
  hash.each do |key, value|
37
- attrib = ThinkingSphinx::Facet.attribute_name_for(key)
34
+ attrib = ThinkingSphinx::Facet.attribute_name_from_value(key, value)
38
35
  options[:with][attrib] = underlying_value key, value
39
36
  end
40
37
 
@@ -48,8 +45,10 @@ module ThinkingSphinx
48
45
  case value
49
46
  when Array
50
47
  value.collect { |item| underlying_value(key, item) }
48
+ when String
49
+ value.to_crc32
51
50
  else
52
- @attribute_values[key][value]
51
+ value
53
52
  end
54
53
  end
55
54
 
@@ -52,12 +52,14 @@ module ThinkingSphinx
52
52
  # :as => :posts, :prefixes => true
53
53
  # )
54
54
  #
55
- def initialize(columns, options = {})
55
+ def initialize(source, columns, options = {})
56
56
  super
57
57
 
58
58
  @sortable = options[:sortable] || false
59
59
  @infixes = options[:infixes] || false
60
60
  @prefixes = options[:prefixes] || false
61
+
62
+ source.fields << self
61
63
  end
62
64
 
63
65
  # Get the part of the SELECT clause related to this field. Don't forget
@@ -9,8 +9,7 @@ module ThinkingSphinx
9
9
  # Enjoy.
10
10
  #
11
11
  class Index
12
- attr_accessor :model, :fields, :attributes, :conditions, :groupings,
13
- :delta_object, :options
12
+ attr_accessor :model, :sources, :delta_object
14
13
 
15
14
  # Create a new index instance by passing in the model it is tied to, and
16
15
  # a block to build it with (optional but recommended). For documentation
@@ -28,129 +27,60 @@ module ThinkingSphinx
28
27
  #
29
28
  def initialize(model, &block)
30
29
  @model = model
31
- @associations = {}
32
- @fields = []
33
- @attributes = []
34
- @conditions = []
35
- @groupings = []
30
+ @sources = []
36
31
  @options = {}
37
32
  @delta_object = nil
38
-
39
- initialize_from_builder(&block) if block_given?
40
-
41
- add_internal_attributes_and_facets
42
-
43
- # We want to make sure that if the database doesn't exist, then Thinking
44
- # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
45
- # and db:migrate). It's a bit hacky, but I can't think of a better way.
46
- rescue StandardError => err
47
- case err.class.name
48
- when "Mysql::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
49
- return
50
- else
51
- raise err
52
- end
53
33
  end
54
34
 
55
- def name
56
- self.class.name(@model)
35
+ def fields
36
+ @sources.collect { |source| source.fields }.flatten
57
37
  end
58
38
 
59
- def self.name(model)
60
- model.name.underscore.tr(':/\\', '_')
39
+ def attributes
40
+ @sources.collect { |source| source.attributes }.flatten
61
41
  end
62
42
 
63
- def to_riddle_for_core(offset, index)
64
- link!
65
-
66
- source = Riddle::Configuration::SQLSource.new(
67
- "#{name}_core_#{index}", adapter.sphinx_identifier
68
- )
69
-
70
- set_source_database_settings source
71
- set_source_attributes source, offset
72
- set_source_sql source, offset
73
- set_source_settings source
74
-
75
- source
43
+ def name
44
+ self.class.name(@model)
76
45
  end
77
46
 
78
- def to_riddle_for_delta(offset, index)
79
- link!
80
-
81
- source = Riddle::Configuration::SQLSource.new(
82
- "#{name}_delta_#{index}", adapter.sphinx_identifier
83
- )
84
- source.parent = "#{name}_core_#{index}"
85
-
86
- set_source_database_settings source
87
- set_source_attributes source, offset
88
- set_source_sql source, offset, true
89
-
90
- source
47
+ def self.name(model)
48
+ model.name.underscore.tr(':/\\', '_')
91
49
  end
92
50
 
93
- # Link all the fields and associations to their corresponding
94
- # associations and joins. This _must_ be called before interrogating
95
- # the index's fields and associations for anything that may reference
96
- # their SQL structure.
97
- #
98
- def link!
99
- base = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(
100
- @model, [], nil
101
- )
102
-
103
- @fields.each { |field|
104
- field.model ||= @model
105
- field.columns.each { |col|
106
- field.associations[col] = associations(col.__stack.clone)
107
- field.associations[col].each { |assoc| assoc.join_to(base) }
108
- }
109
- }
110
-
111
- @attributes.each { |attribute|
112
- attribute.model ||= @model
113
- attribute.columns.each { |col|
114
- attribute.associations[col] = associations(col.__stack.clone)
115
- attribute.associations[col].each { |assoc| assoc.join_to(base) }
116
- }
117
- }
51
+ def prefix_fields
52
+ fields.select { |field| field.prefixes }
118
53
  end
119
54
 
120
- # Flag to indicate whether this index has a corresponding delta index.
121
- #
122
- def delta?
123
- !@delta_object.nil?
55
+ def infix_fields
56
+ fields.select { |field| field.infixes }
124
57
  end
125
58
 
126
- def adapter
127
- @adapter ||= @model.sphinx_database_adapter
128
- end
129
-
130
- def prefix_fields
131
- @fields.select { |field| field.prefixes }
59
+ def local_options
60
+ @options
132
61
  end
133
62
 
134
- def infix_fields
135
- @fields.select { |field| field.infixes }
136
- end
137
-
138
- def index_options
63
+ def options
139
64
  all_index_options = ThinkingSphinx::Configuration.instance.index_options.clone
140
65
  @options.keys.select { |key|
141
- ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s)
66
+ ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) ||
67
+ ThinkingSphinx::Configuration::CustomOptions.include?(key.to_s)
142
68
  }.each { |key| all_index_options[key.to_sym] = @options[key] }
143
69
  all_index_options
144
70
  end
145
-
146
- def quote_column(column)
147
- @model.connection.quote_column_name(column)
71
+
72
+ def delta?
73
+ !@delta_object.nil?
148
74
  end
149
75
 
150
76
  private
151
77
 
78
+ def adapter
79
+ @adapter ||= @model.sphinx_database_adapter
80
+ end
81
+
152
82
  def utf8?
153
- self.index_options[:charset_type] == "utf-8"
83
+ options[:charset_type] == "utf-8"
154
84
  end
155
85
 
156
86
  # Does all the magic with the block provided to the base #initialize.
@@ -158,267 +88,12 @@ module ThinkingSphinx
158
88
  # on it, then pulls all relevant settings - fields, attributes, conditions,
159
89
  # properties - into the new index.
160
90
  #
161
- # Also creates a CRC attribute for the model.
162
- #
163
91
  def initialize_from_builder(&block)
164
- builder = Class.new(Builder)
165
- builder.setup
166
-
167
- builder.instance_eval &block
168
-
169
- unless @model.descends_from_active_record?
170
- stored_class = @model.store_full_sti_class ? @model.name : @model.name.demodulize
171
- builder.where("#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)} = '#{stored_class}'")
172
- end
173
-
174
- set_model = Proc.new { |item| item.model = @model }
175
-
176
- @fields = builder.fields &set_model
177
- @attributes = builder.attributes.each &set_model
178
- @conditions = builder.conditions
179
- @groupings = builder.groupings
180
- @delta_object = ThinkingSphinx::Deltas.parse self, builder.properties
181
- @options = builder.properties
182
-
183
- is_faceted = Proc.new { |item| item.faceted }
184
- add_facet = Proc.new { |item| @model.sphinx_facets << item.to_facet }
185
-
186
- @model.sphinx_facets ||= []
187
- @fields.select( &is_faceted).each &add_facet
188
- @attributes.select(&is_faceted).each &add_facet
189
- end
190
-
191
- # Returns all associations used amongst all the fields and attributes.
192
- # This includes all associations between the model and what the actual
193
- # columns are from.
194
- #
195
- def all_associations
196
- @all_associations ||= (
197
- # field associations
198
- @fields.collect { |field|
199
- field.associations.values
200
- }.flatten +
201
- # attribute associations
202
- @attributes.collect { |attrib|
203
- attrib.associations.values if attrib.include_as_association?
204
- }.compact.flatten
205
- ).uniq.collect { |assoc|
206
- # get ancestors as well as column-level associations
207
- assoc.ancestors
208
- }.flatten.uniq
209
- end
210
-
211
- # Gets a stack of associations for a specific path.
212
- #
213
- def associations(path, parent = nil)
214
- assocs = []
215
-
216
- if parent.nil?
217
- assocs = association(path.shift)
218
- else
219
- assocs = parent.children(path.shift)
220
- end
221
-
222
- until path.empty?
223
- point = path.shift
224
- assocs = assocs.collect { |assoc|
225
- assoc.children(point)
226
- }.flatten
227
- end
228
-
229
- assocs
230
- end
231
-
232
- # Gets the association stack for a specific key.
233
- #
234
- def association(key)
235
- @associations[key] ||= Association.children(@model, key)
236
- end
237
-
238
- def crc_column
239
- if @model.column_names.include?(@model.inheritance_column)
240
- adapter.cast_to_unsigned(adapter.convert_nulls(
241
- adapter.crc(adapter.quote_with_table(@model.inheritance_column), true),
242
- @model.to_crc32
243
- ))
244
- else
245
- @model.to_crc32.to_s
246
- end
247
- end
248
-
249
- def add_internal_attributes_and_facets
250
- add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key.to_sym
251
- add_internal_attribute :class_crc, :integer, crc_column, true
252
- add_internal_attribute :subclass_crcs, :multi, subclasses_to_s
253
- add_internal_attribute :sphinx_deleted, :integer, "0"
254
-
255
- add_internal_facet :class_crc
256
- end
257
-
258
- def add_internal_attribute(name, type, contents, facet = false)
259
- return unless attribute_by_alias(name).nil?
260
-
261
- @attributes << Attribute.new(
262
- FauxColumn.new(contents),
263
- :type => type,
264
- :as => name,
265
- :facet => facet,
266
- :admin => true
267
- )
268
- end
269
-
270
- def add_internal_facet(name)
271
- return unless facet_by_alias(name).nil?
272
-
273
- @model.sphinx_facets << ClassFacet.new(attribute_by_alias(name))
274
- end
275
-
276
- def attribute_by_alias(attr_alias)
277
- @attributes.detect { |attrib| attrib.alias == attr_alias }
278
- end
279
-
280
- def facet_by_alias(name)
281
- @model.sphinx_facets.detect { |facet| facet.name == name }
282
- end
283
-
284
- def subclasses_to_s
285
- "'" + (@model.send(:subclasses).collect { |klass|
286
- klass.to_crc32.to_s
287
- } << @model.to_crc32.to_s).join(",") + "'"
288
- end
289
-
290
- def set_source_database_settings(source)
291
- config = @model.connection.instance_variable_get(:@config)
292
-
293
- source.sql_host = config[:host] || "localhost"
294
- source.sql_user = config[:username] || config[:user] || ""
295
- source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
296
- source.sql_db = config[:database]
297
- source.sql_port = config[:port]
298
- source.sql_sock = config[:socket]
299
- end
300
-
301
- def set_source_attributes(source, offset = nil)
302
- attributes.each do |attrib|
303
- source.send(attrib.type_to_config) << attrib.config_value(offset)
304
- end
305
- end
306
-
307
- def set_source_sql(source, offset, delta = false)
308
- source.sql_query = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
309
- source.sql_query_range = to_sql_query_range(:delta => delta)
310
- source.sql_query_info = to_sql_query_info(offset)
311
-
312
- source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
313
-
314
- if @options[:group_concat_max_len]
315
- source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
316
- end
317
-
318
- source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
319
- end
320
-
321
- def set_source_settings(source)
322
- ThinkingSphinx::Configuration.instance.source_options.each do |key, value|
323
- source.send("#{key}=".to_sym, value)
324
- end
325
-
326
- @options.each do |key, value|
327
- source.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::SourceOptions.include?(key.to_s) && !value.nil?
328
- end
329
- end
330
-
331
- def sql_query_pre_for_core
332
- if self.delta? && !@delta_object.reset_query(@model).blank?
333
- [@delta_object.reset_query(@model)]
334
- else
335
- []
336
- end
92
+ #
337
93
  end
338
94
 
339
95
  def sql_query_pre_for_delta
340
96
  [""]
341
97
  end
342
-
343
- # Generates the big SQL statement to get the data back for all the fields
344
- # and attributes, using all the relevant association joins. If you want
345
- # the version filtered for delta values, send through :delta => true in the
346
- # options. Won't do much though if the index isn't set up to support a
347
- # delta sibling.
348
- #
349
- # Examples:
350
- #
351
- # index.to_sql
352
- # index.to_sql(:delta => true)
353
- #
354
- def to_sql(options={})
355
- assocs = all_associations
356
-
357
- where_clause = ""
358
- if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
359
- where_clause << " AND #{@delta_object.clause(@model, options[:delta])}"
360
- end
361
- unless @conditions.empty?
362
- where_clause << " AND " << @conditions.join(" AND ")
363
- end
364
-
365
- internal_groupings = []
366
- if @model.column_names.include?(@model.inheritance_column)
367
- internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
368
- end
369
-
370
- unique_id_expr = ThinkingSphinx.unique_id_expression(options[:offset])
371
-
372
- sql = <<-SQL
373
- SELECT #{ (
374
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
375
- @fields.collect { |field| field.to_select_sql } +
376
- @attributes.collect { |attribute| attribute.to_select_sql }
377
- ).compact.join(", ") }
378
- FROM #{ @model.table_name }
379
- #{ assocs.collect { |assoc| assoc.to_sql }.join(' ') }
380
- WHERE #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start
381
- AND #{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end
382
- #{ where_clause }
383
- GROUP BY #{ (
384
- ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
385
- @fields.collect { |field| field.to_group_sql }.compact +
386
- @attributes.collect { |attribute| attribute.to_group_sql }.compact +
387
- @groupings + internal_groupings
388
- ).join(", ") }
389
- SQL
390
-
391
- sql += " ORDER BY NULL" if adapter.sphinx_identifier == "mysql"
392
- sql
393
- end
394
-
395
- # Simple helper method for the query info SQL - which is a statement that
396
- # returns the single row for a corresponding id.
397
- #
398
- def to_sql_query_info(offset)
399
- "SELECT * FROM #{@model.quoted_table_name} WHERE " +
400
- " #{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
401
- end
402
-
403
- # Simple helper method for the query range SQL - which is a statement that
404
- # returns minimum and maximum id values. These can be filtered by delta -
405
- # so pass in :delta => true to get the delta version of the SQL.
406
- #
407
- def to_sql_query_range(options={})
408
- min_statement = adapter.convert_nulls(
409
- "MIN(#{quote_column(@model.primary_key)})", 1
410
- )
411
- max_statement = adapter.convert_nulls(
412
- "MAX(#{quote_column(@model.primary_key)})", 1
413
- )
414
-
415
- sql = "SELECT #{min_statement}, #{max_statement} " +
416
- "FROM #{@model.quoted_table_name} "
417
- if self.delta? && !@delta_object.clause(@model, options[:delta]).blank?
418
- sql << "WHERE #{@delta_object.clause(@model, options[:delta])}"
419
- end
420
-
421
- sql
422
- end
423
98
  end
424
99
  end