initforthe-thinking-sphinx 1.1.21
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/LICENCE +20 -0
- data/README.textile +141 -0
- data/lib/thinking_sphinx.rb +215 -0
- data/lib/thinking_sphinx/active_record.rb +278 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
- data/lib/thinking_sphinx/active_record/delta.rb +87 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
- data/lib/thinking_sphinx/active_record/search.rb +57 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +135 -0
- data/lib/thinking_sphinx/association.rb +164 -0
- data/lib/thinking_sphinx/attribute.rb +268 -0
- data/lib/thinking_sphinx/class_facet.rb +15 -0
- data/lib/thinking_sphinx/collection.rb +148 -0
- data/lib/thinking_sphinx/configuration.rb +262 -0
- data/lib/thinking_sphinx/core/string.rb +15 -0
- data/lib/thinking_sphinx/deltas.rb +30 -0
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +27 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +82 -0
- data/lib/thinking_sphinx/facet.rb +108 -0
- data/lib/thinking_sphinx/facet_collection.rb +59 -0
- data/lib/thinking_sphinx/field.rb +82 -0
- data/lib/thinking_sphinx/index.rb +99 -0
- data/lib/thinking_sphinx/index/builder.rb +287 -0
- data/lib/thinking_sphinx/index/faux_column.rb +110 -0
- data/lib/thinking_sphinx/property.rb +160 -0
- data/lib/thinking_sphinx/rails_additions.rb +136 -0
- data/lib/thinking_sphinx/search.rb +727 -0
- data/lib/thinking_sphinx/search/facets.rb +104 -0
- data/lib/thinking_sphinx/source.rb +150 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
- data/lib/thinking_sphinx/source/sql.rb +126 -0
- data/lib/thinking_sphinx/tasks.rb +162 -0
- data/rails/init.rb +14 -0
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
- data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
- data/spec/unit/thinking_sphinx/active_record_spec.rb +329 -0
- data/spec/unit/thinking_sphinx/association_spec.rb +246 -0
- data/spec/unit/thinking_sphinx/attribute_spec.rb +338 -0
- data/spec/unit/thinking_sphinx/collection_spec.rb +15 -0
- data/spec/unit/thinking_sphinx/configuration_spec.rb +222 -0
- data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
- data/spec/unit/thinking_sphinx/facet_collection_spec.rb +64 -0
- data/spec/unit/thinking_sphinx/facet_spec.rb +302 -0
- data/spec/unit/thinking_sphinx/field_spec.rb +154 -0
- data/spec/unit/thinking_sphinx/index/builder_spec.rb +355 -0
- data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +45 -0
- data/spec/unit/thinking_sphinx/rails_additions_spec.rb +191 -0
- data/spec/unit/thinking_sphinx/search_spec.rb +228 -0
- data/spec/unit/thinking_sphinx/source_spec.rb +217 -0
- data/spec/unit/thinking_sphinx_spec.rb +151 -0
- data/tasks/distribution.rb +67 -0
- data/tasks/rails.rake +1 -0
- data/tasks/testing.rb +78 -0
- data/vendor/after_commit/LICENSE +20 -0
- data/vendor/after_commit/README +16 -0
- data/vendor/after_commit/Rakefile +22 -0
- data/vendor/after_commit/init.rb +8 -0
- data/vendor/after_commit/lib/after_commit.rb +45 -0
- data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
- data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
- data/vendor/after_commit/test/after_commit_test.rb +53 -0
- data/vendor/delayed_job/lib/delayed/job.rb +251 -0
- data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
- data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
- data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
- data/vendor/riddle/lib/riddle.rb +30 -0
- data/vendor/riddle/lib/riddle/client.rb +619 -0
- data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
- data/vendor/riddle/lib/riddle/client/message.rb +65 -0
- data/vendor/riddle/lib/riddle/client/response.rb +84 -0
- data/vendor/riddle/lib/riddle/configuration.rb +33 -0
- data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
- data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
- data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
- data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
- data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
- data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
- data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
- data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
- data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
- data/vendor/riddle/lib/riddle/controller.rb +44 -0
- metadata +190 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class Search
|
|
3
|
+
module Facets
|
|
4
|
+
# Model.facets *args
|
|
5
|
+
# ThinkingSphinx::Search.facets *args
|
|
6
|
+
# ThinkingSphinx::Search.facets *args, :all_attributes => true
|
|
7
|
+
# ThinkingSphinx::Search.facets *args, :class_facet => false
|
|
8
|
+
#
|
|
9
|
+
def facets(*args)
|
|
10
|
+
options = args.extract_options!
|
|
11
|
+
|
|
12
|
+
if options[:class]
|
|
13
|
+
facets_for_model options[:class], args, options
|
|
14
|
+
else
|
|
15
|
+
facets_for_all_models args, options
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def facets_for_model(klass, args, options)
|
|
22
|
+
hash = ThinkingSphinx::FacetCollection.new args + [options]
|
|
23
|
+
options = options.clone.merge! facet_query_options
|
|
24
|
+
|
|
25
|
+
facets = klass.sphinx_facets
|
|
26
|
+
facets = Array(options.delete(:facets)).collect { |name|
|
|
27
|
+
klass.sphinx_facets.detect { |facet| facet.name.to_s == name.to_s }
|
|
28
|
+
}.compact if options[:facets]
|
|
29
|
+
|
|
30
|
+
facets.inject(hash) do |hash, facet|
|
|
31
|
+
unless facet.name == :class && !options[:class_facet]
|
|
32
|
+
options[:group_by] = facet.attribute_name
|
|
33
|
+
hash.add_from_results facet, search(*(args + [options]))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
hash
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def facets_for_all_models(args, options)
|
|
41
|
+
options = GlobalFacetOptions.merge(options)
|
|
42
|
+
hash = ThinkingSphinx::FacetCollection.new args + [options]
|
|
43
|
+
options = options.merge! facet_query_options
|
|
44
|
+
|
|
45
|
+
facet_names(options).inject(hash) do |hash, name|
|
|
46
|
+
options[:group_by] = name
|
|
47
|
+
hash.add_from_results name, search(*(args + [options]))
|
|
48
|
+
hash
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def facet_query_options
|
|
53
|
+
config = ThinkingSphinx::Configuration.instance
|
|
54
|
+
max = config.configuration.searchd.max_matches || 1000
|
|
55
|
+
|
|
56
|
+
{
|
|
57
|
+
:group_function => :attr,
|
|
58
|
+
:limit => max,
|
|
59
|
+
:max_matches => max,
|
|
60
|
+
:page => 1
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def facet_classes(options)
|
|
65
|
+
options[:classes] || ThinkingSphinx.indexed_models.collect { |model|
|
|
66
|
+
model.constantize
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def facet_names(options)
|
|
71
|
+
classes = facet_classes(options)
|
|
72
|
+
names = options[:all_attributes] ?
|
|
73
|
+
facet_names_for_all_classes(classes) :
|
|
74
|
+
facet_names_common_to_all_classes(classes)
|
|
75
|
+
|
|
76
|
+
names.delete "class_crc" unless options[:class_facet]
|
|
77
|
+
names
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def facet_names_for_all_classes(classes)
|
|
81
|
+
all_facets = classes.collect { |klass| klass.sphinx_facets }.flatten
|
|
82
|
+
|
|
83
|
+
all_facets.group_by { |facet|
|
|
84
|
+
facet.name
|
|
85
|
+
}.collect { |name, facets|
|
|
86
|
+
if facets.collect { |facet| facet.type }.uniq.length > 1
|
|
87
|
+
raise "Facet #{name} exists in more than one model with different types"
|
|
88
|
+
end
|
|
89
|
+
facets.first.attribute_name
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def facet_names_common_to_all_classes(classes)
|
|
94
|
+
facet_names_for_all_classes(classes).select { |name|
|
|
95
|
+
classes.all? { |klass|
|
|
96
|
+
klass.sphinx_facets.detect { |facet|
|
|
97
|
+
facet.attribute_name == name
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
require 'thinking_sphinx/source/internal_properties'
|
|
2
|
+
require 'thinking_sphinx/source/sql'
|
|
3
|
+
|
|
4
|
+
module ThinkingSphinx
|
|
5
|
+
class Source
|
|
6
|
+
include ThinkingSphinx::Source::InternalProperties
|
|
7
|
+
include ThinkingSphinx::Source::SQL
|
|
8
|
+
|
|
9
|
+
attr_accessor :model, :fields, :attributes, :conditions, :groupings,
|
|
10
|
+
:options
|
|
11
|
+
attr_reader :base
|
|
12
|
+
|
|
13
|
+
def initialize(index, options = {})
|
|
14
|
+
@index = index
|
|
15
|
+
@model = index.model
|
|
16
|
+
@fields = []
|
|
17
|
+
@attributes = []
|
|
18
|
+
@conditions = []
|
|
19
|
+
@groupings = []
|
|
20
|
+
@options = options
|
|
21
|
+
@associations = {}
|
|
22
|
+
|
|
23
|
+
@base = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(
|
|
24
|
+
@model, [], nil
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
unless @model.descends_from_active_record?
|
|
28
|
+
stored_class = @model.store_full_sti_class ? @model.name : @model.name.demodulize
|
|
29
|
+
@conditions << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)} = '#{stored_class}'"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
add_internal_attributes_and_facets
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def name
|
|
36
|
+
@model.name.underscore.tr(':/\\', '_')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_riddle_for_core(offset, index)
|
|
40
|
+
source = Riddle::Configuration::SQLSource.new(
|
|
41
|
+
"#{name}_core_#{index}", adapter.sphinx_identifier
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
set_source_database_settings source
|
|
45
|
+
set_source_attributes source, offset
|
|
46
|
+
set_source_sql source, offset
|
|
47
|
+
set_source_settings source
|
|
48
|
+
|
|
49
|
+
source
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_riddle_for_delta(offset, index)
|
|
53
|
+
source = Riddle::Configuration::SQLSource.new(
|
|
54
|
+
"#{name}_delta_#{index}", adapter.sphinx_identifier
|
|
55
|
+
)
|
|
56
|
+
source.parent = "#{name}_core_#{index}"
|
|
57
|
+
|
|
58
|
+
set_source_database_settings source
|
|
59
|
+
set_source_attributes source, offset
|
|
60
|
+
set_source_sql source, offset, true
|
|
61
|
+
|
|
62
|
+
source
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def delta?
|
|
66
|
+
!@index.delta_object.nil?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Gets the association stack for a specific key.
|
|
70
|
+
#
|
|
71
|
+
def association(key)
|
|
72
|
+
@associations[key] ||= Association.children(@model, key)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def adapter
|
|
78
|
+
@adapter ||= @model.sphinx_database_adapter
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def set_source_database_settings(source)
|
|
82
|
+
config = @model.connection.instance_variable_get(:@config)
|
|
83
|
+
|
|
84
|
+
source.sql_host = config[:host] || "localhost"
|
|
85
|
+
source.sql_user = config[:username] || config[:user] || ""
|
|
86
|
+
source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
|
|
87
|
+
source.sql_db = config[:database]
|
|
88
|
+
source.sql_port = config[:port]
|
|
89
|
+
source.sql_sock = config[:socket]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def set_source_attributes(source, offset)
|
|
93
|
+
attributes.each do |attrib|
|
|
94
|
+
source.send(attrib.type_to_config) << attrib.config_value(offset)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def set_source_sql(source, offset, delta = false)
|
|
99
|
+
source.sql_query = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
|
|
100
|
+
source.sql_query_range = to_sql_query_range(:delta => delta)
|
|
101
|
+
source.sql_query_info = to_sql_query_info(offset)
|
|
102
|
+
|
|
103
|
+
source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
|
|
104
|
+
|
|
105
|
+
if @index.local_options[:group_concat_max_len]
|
|
106
|
+
source.sql_query_pre << "SET SESSION group_concat_max_len = #{@index.local_options[:group_concat_max_len]}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def set_source_settings(source)
|
|
113
|
+
config = ThinkingSphinx::Configuration.instance
|
|
114
|
+
config.source_options.each do |key, value|
|
|
115
|
+
source.send("#{key}=".to_sym, value)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
source_options = ThinkingSphinx::Configuration::SourceOptions
|
|
119
|
+
@options.each do |key, value|
|
|
120
|
+
if source_options.include?(key.to_s) && !value.nil?
|
|
121
|
+
source.send("#{key}=".to_sym, value)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Returns all associations used amongst all the fields and attributes.
|
|
127
|
+
# This includes all associations between the model and what the actual
|
|
128
|
+
# columns are from.
|
|
129
|
+
#
|
|
130
|
+
def all_associations
|
|
131
|
+
@all_associations ||= (
|
|
132
|
+
# field associations
|
|
133
|
+
@fields.collect { |field|
|
|
134
|
+
field.associations.values
|
|
135
|
+
}.flatten +
|
|
136
|
+
# attribute associations
|
|
137
|
+
@attributes.collect { |attrib|
|
|
138
|
+
attrib.associations.values if attrib.include_as_association?
|
|
139
|
+
}.compact.flatten
|
|
140
|
+
).uniq.collect { |assoc|
|
|
141
|
+
# get ancestors as well as column-level associations
|
|
142
|
+
assoc.ancestors
|
|
143
|
+
}.flatten.uniq
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def utf8?
|
|
147
|
+
@index.options[:charset_type] == "utf-8"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class Source
|
|
3
|
+
module InternalProperties
|
|
4
|
+
def add_internal_attributes_and_facets
|
|
5
|
+
add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key.to_sym
|
|
6
|
+
add_internal_attribute :class_crc, :integer, crc_column, true
|
|
7
|
+
add_internal_attribute :subclass_crcs, :multi, subclasses_to_s
|
|
8
|
+
add_internal_attribute :sphinx_deleted, :integer, "0"
|
|
9
|
+
|
|
10
|
+
add_internal_facet :class_crc
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add_internal_attribute(name, type, contents, facet = false)
|
|
14
|
+
return unless attribute_by_alias(name).nil?
|
|
15
|
+
|
|
16
|
+
Attribute.new(self,
|
|
17
|
+
ThinkingSphinx::Index::FauxColumn.new(contents),
|
|
18
|
+
:type => type,
|
|
19
|
+
:as => name,
|
|
20
|
+
:facet => facet,
|
|
21
|
+
:admin => true
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def add_internal_facet(name)
|
|
26
|
+
return unless facet_by_alias(name).nil?
|
|
27
|
+
|
|
28
|
+
@model.sphinx_facets << ClassFacet.new(attribute_by_alias(name))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def attribute_by_alias(attr_alias)
|
|
32
|
+
@attributes.detect { |attrib| attrib.alias == attr_alias }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def facet_by_alias(name)
|
|
36
|
+
@model.sphinx_facets.detect { |facet| facet.name == name }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def subclasses_to_s
|
|
40
|
+
"'" + (@model.send(:subclasses).collect { |klass|
|
|
41
|
+
klass.to_crc32.to_s
|
|
42
|
+
} << @model.to_crc32.to_s).join(",") + "'"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class Source
|
|
3
|
+
module SQL
|
|
4
|
+
# Generates the big SQL statement to get the data back for all the fields
|
|
5
|
+
# and attributes, using all the relevant association joins. If you want
|
|
6
|
+
# the version filtered for delta values, send through :delta => true in the
|
|
7
|
+
# options. Won't do much though if the index isn't set up to support a
|
|
8
|
+
# delta sibling.
|
|
9
|
+
#
|
|
10
|
+
# Examples:
|
|
11
|
+
#
|
|
12
|
+
# source.to_sql
|
|
13
|
+
# source.to_sql(:delta => true)
|
|
14
|
+
#
|
|
15
|
+
def to_sql(options={})
|
|
16
|
+
sql = <<-SQL
|
|
17
|
+
SELECT #{ sql_select_clause options[:offset] }
|
|
18
|
+
FROM #{ @model.quoted_table_name }
|
|
19
|
+
#{ all_associations.collect { |assoc| assoc.to_sql }.join(' ') }
|
|
20
|
+
#{ sql_where_clause(options) }
|
|
21
|
+
GROUP BY #{ sql_group_clause }
|
|
22
|
+
SQL
|
|
23
|
+
|
|
24
|
+
sql += " ORDER BY NULL" if adapter.sphinx_identifier == "mysql"
|
|
25
|
+
sql
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Simple helper method for the query range SQL - which is a statement that
|
|
29
|
+
# returns minimum and maximum id values. These can be filtered by delta -
|
|
30
|
+
# so pass in :delta => true to get the delta version of the SQL.
|
|
31
|
+
#
|
|
32
|
+
def to_sql_query_range(options={})
|
|
33
|
+
return nil if @index.options[:disable_range]
|
|
34
|
+
|
|
35
|
+
min_statement = adapter.convert_nulls(
|
|
36
|
+
"MIN(#{quote_column(@model.primary_key)})", 1
|
|
37
|
+
)
|
|
38
|
+
max_statement = adapter.convert_nulls(
|
|
39
|
+
"MAX(#{quote_column(@model.primary_key)})", 1
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
sql = "SELECT #{min_statement}, #{max_statement} " +
|
|
43
|
+
"FROM #{@model.quoted_table_name} "
|
|
44
|
+
if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
|
|
45
|
+
sql << "WHERE #{@index.delta_object.clause(@model, options[:delta])}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
sql
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Simple helper method for the query info SQL - which is a statement that
|
|
52
|
+
# returns the single row for a corresponding id.
|
|
53
|
+
#
|
|
54
|
+
def to_sql_query_info(offset)
|
|
55
|
+
"SELECT * FROM #{@model.quoted_table_name} WHERE " +
|
|
56
|
+
"#{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def sql_select_clause(offset)
|
|
60
|
+
unique_id_expr = ThinkingSphinx.unique_id_expression(offset)
|
|
61
|
+
|
|
62
|
+
(
|
|
63
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
|
|
64
|
+
@fields.collect { |field| field.to_select_sql } +
|
|
65
|
+
@attributes.collect { |attribute| attribute.to_select_sql }
|
|
66
|
+
).compact.join(", ")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def sql_where_clause(options)
|
|
70
|
+
logic = []
|
|
71
|
+
logic += [
|
|
72
|
+
"#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start",
|
|
73
|
+
"#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end"
|
|
74
|
+
] unless @index.options[:disable_range]
|
|
75
|
+
|
|
76
|
+
if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
|
|
77
|
+
logic << "#{@index.delta_object.clause(@model, options[:delta])}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
logic += (@conditions || [])
|
|
81
|
+
logic.empty? ? "" : "WHERE #{logic.join(' AND ')}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def sql_group_clause
|
|
85
|
+
internal_groupings = []
|
|
86
|
+
if @model.column_names.include?(@model.inheritance_column)
|
|
87
|
+
internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
(
|
|
91
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
|
|
92
|
+
@fields.collect { |field| field.to_group_sql }.compact +
|
|
93
|
+
@attributes.collect { |attribute| attribute.to_group_sql }.compact +
|
|
94
|
+
@groupings + internal_groupings
|
|
95
|
+
).join(", ")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def sql_query_pre_for_core
|
|
99
|
+
if self.delta? && !@index.delta_object.reset_query(@model).blank?
|
|
100
|
+
[@index.delta_object.reset_query(@model)]
|
|
101
|
+
else
|
|
102
|
+
[]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def sql_query_pre_for_delta
|
|
107
|
+
[""]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def quote_column(column)
|
|
111
|
+
@model.connection.quote_column_name(column)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def crc_column
|
|
115
|
+
if @model.column_names.include?(@model.inheritance_column)
|
|
116
|
+
adapter.cast_to_unsigned(adapter.convert_nulls(
|
|
117
|
+
adapter.crc(adapter.quote_with_table(@model.inheritance_column), true),
|
|
118
|
+
@model.to_crc32
|
|
119
|
+
))
|
|
120
|
+
else
|
|
121
|
+
@model.to_crc32.to_s
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
namespace :thinking_sphinx do
|
|
4
|
+
task :app_env do
|
|
5
|
+
if defined?(RAILS_ROOT)
|
|
6
|
+
Rake::Task[:environment].invoke
|
|
7
|
+
Rails.configuration.cache_classes = false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
Rake::Task[:merb_env].invoke if defined?(Merb)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc "Output the current Thinking Sphinx version"
|
|
14
|
+
task :version => :app_env do
|
|
15
|
+
puts "Thinking Sphinx v" + ThinkingSphinx::Version::String
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings"
|
|
19
|
+
task :running_start => :app_env do
|
|
20
|
+
Rake::Task["thinking_sphinx:stop"].invoke if sphinx_running?
|
|
21
|
+
Rake::Task["thinking_sphinx:start"].invoke
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc "Start a Sphinx searchd daemon using Thinking Sphinx's settings"
|
|
25
|
+
task :start => :app_env do
|
|
26
|
+
config = ThinkingSphinx::Configuration.instance
|
|
27
|
+
|
|
28
|
+
FileUtils.mkdir_p config.searchd_file_path
|
|
29
|
+
raise RuntimeError, "searchd is already running." if sphinx_running?
|
|
30
|
+
|
|
31
|
+
Dir["#{config.searchd_file_path}/*.spl"].each { |file| File.delete(file) }
|
|
32
|
+
|
|
33
|
+
system! "#{config.bin_path}#{config.searchd_binary_name} --pidfile --config #{config.config_file}"
|
|
34
|
+
|
|
35
|
+
sleep(2)
|
|
36
|
+
|
|
37
|
+
if sphinx_running?
|
|
38
|
+
puts "Started successfully (pid #{sphinx_pid})."
|
|
39
|
+
else
|
|
40
|
+
puts "Failed to start searchd daemon. Check #{config.searchd_log_file}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc "Stop Sphinx using Thinking Sphinx's settings"
|
|
45
|
+
task :stop => :app_env do
|
|
46
|
+
raise RuntimeError, "searchd is not running." unless sphinx_running?
|
|
47
|
+
config = ThinkingSphinx::Configuration.instance
|
|
48
|
+
pid = sphinx_pid
|
|
49
|
+
system! "#{config.bin_path}#{searchd_binary_name} --stop --config #{config.config_file}"
|
|
50
|
+
puts "Stopped search daemon (pid #{pid})."
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
desc "Restart Sphinx"
|
|
54
|
+
task :restart => [:app_env, :stop, :start]
|
|
55
|
+
|
|
56
|
+
desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
|
|
57
|
+
task :configure => :app_env do
|
|
58
|
+
config = ThinkingSphinx::Configuration.instance
|
|
59
|
+
puts "Generating Configuration to #{config.config_file}"
|
|
60
|
+
config.build
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
desc "Index data for Sphinx using Thinking Sphinx's settings"
|
|
64
|
+
task :index => :app_env do
|
|
65
|
+
ThinkingSphinx::Deltas::Job.cancel_thinking_sphinx_jobs
|
|
66
|
+
|
|
67
|
+
config = ThinkingSphinx::Configuration.instance
|
|
68
|
+
unless ENV["INDEX_ONLY"] == "true"
|
|
69
|
+
puts "Generating Configuration to #{config.config_file}"
|
|
70
|
+
config.build
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
FileUtils.mkdir_p config.searchd_file_path
|
|
74
|
+
cmd = "#{config.bin_path}#{indexer_binary_name} --config #{config.config_file} --all"
|
|
75
|
+
cmd << " --rotate" if sphinx_running?
|
|
76
|
+
|
|
77
|
+
system! cmd
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
desc "Stop Sphinx (if it's running), rebuild the indexes, and start Sphinx"
|
|
81
|
+
task :rebuild => :app_env do
|
|
82
|
+
Rake::Task["thinking_sphinx:stop"].invoke if sphinx_running?
|
|
83
|
+
Rake::Task["thinking_sphinx:index"].invoke
|
|
84
|
+
Rake::Task["thinking_sphinx:start"].invoke
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
namespace :index do
|
|
88
|
+
task :delta => :app_env do
|
|
89
|
+
ThinkingSphinx.indexed_models.select { |model|
|
|
90
|
+
model.constantize.sphinx_indexes.any? { |index| index.delta? }
|
|
91
|
+
}.each do |model|
|
|
92
|
+
model.constantize.sphinx_indexes.select { |index|
|
|
93
|
+
index.delta? && index.delta_object.respond_to?(:delayed_index)
|
|
94
|
+
}.each { |index|
|
|
95
|
+
index.delta_object.delayed_index(index.model)
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
desc "Process stored delta index requests"
|
|
102
|
+
task :delayed_delta => :app_env do
|
|
103
|
+
require 'delayed/worker'
|
|
104
|
+
|
|
105
|
+
Delayed::Worker.new(
|
|
106
|
+
:min_priority => ENV['MIN_PRIORITY'],
|
|
107
|
+
:max_priority => ENV['MAX_PRIORITY']
|
|
108
|
+
).start
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
namespace :ts do
|
|
113
|
+
desc "Output the current Thinking Sphinx version"
|
|
114
|
+
task :version => "thinking_sphinx:version"
|
|
115
|
+
desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings"
|
|
116
|
+
task :run => "thinking_sphinx:running_start"
|
|
117
|
+
desc "Start a Sphinx searchd daemon using Thinking Sphinx's settings"
|
|
118
|
+
task :start => "thinking_sphinx:start"
|
|
119
|
+
desc "Stop Sphinx using Thinking Sphinx's settings"
|
|
120
|
+
task :stop => "thinking_sphinx:stop"
|
|
121
|
+
desc "Index data for Sphinx using Thinking Sphinx's settings"
|
|
122
|
+
task :in => "thinking_sphinx:index"
|
|
123
|
+
namespace :in do
|
|
124
|
+
desc "Index Thinking Sphinx datetime delta indexes"
|
|
125
|
+
task :delta => "thinking_sphinx:index:delta"
|
|
126
|
+
end
|
|
127
|
+
task :index => "thinking_sphinx:index"
|
|
128
|
+
desc "Restart Sphinx"
|
|
129
|
+
task :restart => "thinking_sphinx:restart"
|
|
130
|
+
desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
|
|
131
|
+
task :conf => "thinking_sphinx:configure"
|
|
132
|
+
desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
|
|
133
|
+
task :config => "thinking_sphinx:configure"
|
|
134
|
+
desc "Stop Sphinx (if it's running), rebuild the indexes, and start Sphinx"
|
|
135
|
+
task :rebuild => "thinking_sphinx:rebuild"
|
|
136
|
+
desc "Process stored delta index requests"
|
|
137
|
+
task :dd => "thinking_sphinx:delayed_delta"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def sphinx_pid
|
|
141
|
+
ThinkingSphinx.sphinx_pid
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def sphinx_running?
|
|
145
|
+
ThinkingSphinx.sphinx_running?
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# a fail-fast, hopefully helpful version of system
|
|
149
|
+
def system!(cmd)
|
|
150
|
+
unless system(cmd)
|
|
151
|
+
raise <<-SYSTEM_CALL_FAILED
|
|
152
|
+
The following command failed:
|
|
153
|
+
#{cmd}
|
|
154
|
+
|
|
155
|
+
This could be caused by a PATH issue in the environment of cron/passenger/etc. Your current PATH:
|
|
156
|
+
#{ENV['PATH']}
|
|
157
|
+
You can set the path to your indexer and searchd binaries using the bin_path property in config/sphinx.yml:
|
|
158
|
+
production:
|
|
159
|
+
bin_path: '/usr/local/bin'
|
|
160
|
+
SYSTEM_CALL_FAILED
|
|
161
|
+
end
|
|
162
|
+
end
|