angelf-thinking-sphinx 1.3.18
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 +170 -0
- data/VERSION +1 -0
- data/features/abstract_inheritance.feature +10 -0
- data/features/alternate_primary_key.feature +27 -0
- data/features/attribute_transformation.feature +22 -0
- data/features/attribute_updates.feature +77 -0
- data/features/deleting_instances.feature +67 -0
- data/features/direct_attributes.feature +11 -0
- data/features/excerpts.feature +13 -0
- data/features/extensible_delta_indexing.feature +9 -0
- data/features/facets.feature +90 -0
- data/features/facets_across_model.feature +29 -0
- data/features/handling_edits.feature +92 -0
- data/features/retry_stale_indexes.feature +24 -0
- data/features/searching_across_models.feature +20 -0
- data/features/searching_by_index.feature +40 -0
- data/features/searching_by_model.feature +175 -0
- data/features/searching_with_find_arguments.feature +56 -0
- data/features/sphinx_detection.feature +25 -0
- data/features/sphinx_scopes.feature +42 -0
- data/features/step_definitions/alpha_steps.rb +16 -0
- data/features/step_definitions/beta_steps.rb +7 -0
- data/features/step_definitions/common_steps.rb +193 -0
- data/features/step_definitions/extensible_delta_indexing_steps.rb +7 -0
- data/features/step_definitions/facet_steps.rb +96 -0
- data/features/step_definitions/find_arguments_steps.rb +36 -0
- data/features/step_definitions/gamma_steps.rb +15 -0
- data/features/step_definitions/scope_steps.rb +15 -0
- data/features/step_definitions/search_steps.rb +89 -0
- data/features/step_definitions/sphinx_steps.rb +35 -0
- data/features/sti_searching.feature +19 -0
- data/features/support/env.rb +21 -0
- data/features/support/lib/generic_delta_handler.rb +8 -0
- data/features/thinking_sphinx/database.example.yml +3 -0
- data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -0
- data/features/thinking_sphinx/db/fixtures/authors.rb +1 -0
- data/features/thinking_sphinx/db/fixtures/betas.rb +11 -0
- data/features/thinking_sphinx/db/fixtures/boxes.rb +9 -0
- data/features/thinking_sphinx/db/fixtures/categories.rb +1 -0
- data/features/thinking_sphinx/db/fixtures/cats.rb +3 -0
- data/features/thinking_sphinx/db/fixtures/comments.rb +24 -0
- data/features/thinking_sphinx/db/fixtures/developers.rb +31 -0
- data/features/thinking_sphinx/db/fixtures/dogs.rb +3 -0
- data/features/thinking_sphinx/db/fixtures/extensible_betas.rb +10 -0
- data/features/thinking_sphinx/db/fixtures/foxes.rb +3 -0
- data/features/thinking_sphinx/db/fixtures/gammas.rb +10 -0
- data/features/thinking_sphinx/db/fixtures/music.rb +4 -0
- data/features/thinking_sphinx/db/fixtures/people.rb +1001 -0
- data/features/thinking_sphinx/db/fixtures/posts.rb +6 -0
- data/features/thinking_sphinx/db/fixtures/robots.rb +14 -0
- data/features/thinking_sphinx/db/fixtures/tags.rb +27 -0
- data/features/thinking_sphinx/db/migrations/create_alphas.rb +8 -0
- data/features/thinking_sphinx/db/migrations/create_animals.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_authors.rb +3 -0
- data/features/thinking_sphinx/db/migrations/create_authors_posts.rb +6 -0
- data/features/thinking_sphinx/db/migrations/create_betas.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_boxes.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_categories.rb +3 -0
- data/features/thinking_sphinx/db/migrations/create_comments.rb +10 -0
- data/features/thinking_sphinx/db/migrations/create_developers.rb +7 -0
- data/features/thinking_sphinx/db/migrations/create_extensible_betas.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_gammas.rb +3 -0
- data/features/thinking_sphinx/db/migrations/create_genres.rb +3 -0
- data/features/thinking_sphinx/db/migrations/create_music.rb +6 -0
- data/features/thinking_sphinx/db/migrations/create_people.rb +13 -0
- data/features/thinking_sphinx/db/migrations/create_posts.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_robots.rb +4 -0
- data/features/thinking_sphinx/db/migrations/create_taggings.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_tags.rb +4 -0
- data/features/thinking_sphinx/models/alpha.rb +22 -0
- data/features/thinking_sphinx/models/animal.rb +5 -0
- data/features/thinking_sphinx/models/author.rb +3 -0
- data/features/thinking_sphinx/models/beta.rb +8 -0
- data/features/thinking_sphinx/models/box.rb +8 -0
- data/features/thinking_sphinx/models/cat.rb +3 -0
- data/features/thinking_sphinx/models/category.rb +4 -0
- data/features/thinking_sphinx/models/comment.rb +10 -0
- data/features/thinking_sphinx/models/developer.rb +16 -0
- data/features/thinking_sphinx/models/dog.rb +3 -0
- data/features/thinking_sphinx/models/extensible_beta.rb +9 -0
- data/features/thinking_sphinx/models/fox.rb +5 -0
- data/features/thinking_sphinx/models/gamma.rb +5 -0
- data/features/thinking_sphinx/models/genre.rb +3 -0
- data/features/thinking_sphinx/models/medium.rb +5 -0
- data/features/thinking_sphinx/models/music.rb +8 -0
- data/features/thinking_sphinx/models/person.rb +23 -0
- data/features/thinking_sphinx/models/post.rb +21 -0
- data/features/thinking_sphinx/models/robot.rb +12 -0
- data/features/thinking_sphinx/models/tag.rb +3 -0
- data/features/thinking_sphinx/models/tagging.rb +4 -0
- data/lib/cucumber/thinking_sphinx/external_world.rb +8 -0
- data/lib/cucumber/thinking_sphinx/internal_world.rb +127 -0
- data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
- data/lib/thinking_sphinx.rb +242 -0
- data/lib/thinking_sphinx/active_record.rb +380 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +50 -0
- data/lib/thinking_sphinx/active_record/delta.rb +61 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +51 -0
- data/lib/thinking_sphinx/active_record/scopes.rb +75 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +47 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +58 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +147 -0
- data/lib/thinking_sphinx/association.rb +164 -0
- data/lib/thinking_sphinx/attribute.rb +380 -0
- data/lib/thinking_sphinx/auto_version.rb +22 -0
- data/lib/thinking_sphinx/class_facet.rb +15 -0
- data/lib/thinking_sphinx/configuration.rb +292 -0
- data/lib/thinking_sphinx/context.rb +74 -0
- data/lib/thinking_sphinx/core/array.rb +7 -0
- data/lib/thinking_sphinx/core/string.rb +15 -0
- data/lib/thinking_sphinx/deltas.rb +28 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +100 -0
- data/lib/thinking_sphinx/excerpter.rb +22 -0
- data/lib/thinking_sphinx/facet.rb +125 -0
- data/lib/thinking_sphinx/facet_search.rb +146 -0
- data/lib/thinking_sphinx/field.rb +80 -0
- data/lib/thinking_sphinx/index.rb +157 -0
- data/lib/thinking_sphinx/index/builder.rb +302 -0
- data/lib/thinking_sphinx/index/faux_column.rb +118 -0
- data/lib/thinking_sphinx/join.rb +37 -0
- data/lib/thinking_sphinx/property.rb +168 -0
- data/lib/thinking_sphinx/rails_additions.rb +150 -0
- data/lib/thinking_sphinx/search.rb +785 -0
- data/lib/thinking_sphinx/search_methods.rb +439 -0
- data/lib/thinking_sphinx/source.rb +164 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
- data/lib/thinking_sphinx/source/sql.rb +130 -0
- data/lib/thinking_sphinx/tasks.rb +121 -0
- data/lib/thinking_sphinx/test.rb +55 -0
- data/rails/init.rb +16 -0
- data/spec/thinking_sphinx/active_record/delta_spec.rb +128 -0
- data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +71 -0
- data/spec/thinking_sphinx/active_record/scopes_spec.rb +177 -0
- data/spec/thinking_sphinx/active_record_spec.rb +618 -0
- data/spec/thinking_sphinx/association_spec.rb +239 -0
- data/spec/thinking_sphinx/attribute_spec.rb +548 -0
- data/spec/thinking_sphinx/auto_version_spec.rb +39 -0
- data/spec/thinking_sphinx/configuration_spec.rb +271 -0
- data/spec/thinking_sphinx/context_spec.rb +126 -0
- data/spec/thinking_sphinx/core/array_spec.rb +9 -0
- data/spec/thinking_sphinx/core/string_spec.rb +9 -0
- data/spec/thinking_sphinx/excerpter_spec.rb +49 -0
- data/spec/thinking_sphinx/facet_search_spec.rb +176 -0
- data/spec/thinking_sphinx/facet_spec.rb +333 -0
- data/spec/thinking_sphinx/field_spec.rb +113 -0
- data/spec/thinking_sphinx/index/builder_spec.rb +495 -0
- data/spec/thinking_sphinx/index/faux_column_spec.rb +36 -0
- data/spec/thinking_sphinx/index_spec.rb +183 -0
- data/spec/thinking_sphinx/rails_additions_spec.rb +203 -0
- data/spec/thinking_sphinx/search_methods_spec.rb +152 -0
- data/spec/thinking_sphinx/search_spec.rb +1206 -0
- data/spec/thinking_sphinx/source_spec.rb +243 -0
- data/spec/thinking_sphinx_spec.rb +204 -0
- data/tasks/distribution.rb +46 -0
- data/tasks/rails.rake +1 -0
- data/tasks/testing.rb +76 -0
- metadata +342 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module AttributeUpdates
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.class_eval do
|
|
6
|
+
after_commit :update_attribute_values
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def update_attribute_values
|
|
13
|
+
return true unless ThinkingSphinx.updates_enabled? &&
|
|
14
|
+
ThinkingSphinx.sphinx_running?
|
|
15
|
+
|
|
16
|
+
self.class.sphinx_indexes.each do |index|
|
|
17
|
+
attribute_pairs = attribute_values_for_index(index)
|
|
18
|
+
attribute_names = attribute_pairs.keys
|
|
19
|
+
attribute_values = attribute_names.collect { |key|
|
|
20
|
+
attribute_pairs[key]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
update_index index.core_name, attribute_names, attribute_values
|
|
24
|
+
next unless index.delta?
|
|
25
|
+
update_index index.delta_name, attribute_names, attribute_values
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def updatable_attributes(index)
|
|
32
|
+
index.attributes.select { |attrib| attrib.updatable? }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def attribute_values_for_index(index)
|
|
36
|
+
updatable_attributes(index).inject({}) { |hash, attrib|
|
|
37
|
+
hash[attrib.unique_name.to_s] = attrib.live_value self
|
|
38
|
+
hash
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def update_index(index_name, attribute_names, attribute_values)
|
|
43
|
+
config = ThinkingSphinx::Configuration.instance
|
|
44
|
+
config.client.update index_name, attribute_names, {
|
|
45
|
+
sphinx_document_id => attribute_values
|
|
46
|
+
} if self.class.search_for_id(sphinx_document_id, index_name)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
# This module contains all the delta-related code for models. There isn't
|
|
4
|
+
# really anything you need to call manually in here - except perhaps
|
|
5
|
+
# index_delta, but not sure what reason why.
|
|
6
|
+
#
|
|
7
|
+
module Delta
|
|
8
|
+
# Code for after_commit callback is written by Eli Miller:
|
|
9
|
+
# http://elimiller.blogspot.com/2007/06/proper-cache-expiry-with-aftercommit.html
|
|
10
|
+
# with slight modification from Joost Hietbrink.
|
|
11
|
+
#
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.class_eval do
|
|
14
|
+
class << self
|
|
15
|
+
# Build the delta index for the related model. This won't be called
|
|
16
|
+
# if running in the test environment.
|
|
17
|
+
#
|
|
18
|
+
def index_delta(instance = nil)
|
|
19
|
+
delta_object.index(self, instance)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delta_object
|
|
23
|
+
self.sphinx_indexes.first.delta_object
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def toggled_delta?
|
|
28
|
+
self.class.delta_object.toggled(self)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Set the delta value for the model to be true.
|
|
34
|
+
def toggle_delta
|
|
35
|
+
self.class.delta_object.toggle(self) if should_toggle_delta?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Build the delta index for the related model. This won't be called
|
|
39
|
+
# if running in the test environment.
|
|
40
|
+
#
|
|
41
|
+
def index_delta
|
|
42
|
+
self.class.index_delta(self) if self.class.delta_object.toggled(self)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def should_toggle_delta?
|
|
46
|
+
self.new_record? || indexed_data_changed?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def indexed_data_changed?
|
|
50
|
+
sphinx_indexes.any? { |index|
|
|
51
|
+
index.fields.any? { |field| field.changed?(self) } ||
|
|
52
|
+
index.attributes.any? { |attrib|
|
|
53
|
+
attrib.public? && attrib.changed?(self) && !attrib.updatable?
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module HasManyAssociation
|
|
4
|
+
def search(*args)
|
|
5
|
+
options = args.extract_options!
|
|
6
|
+
options[:with] ||= {}
|
|
7
|
+
options[:with].merge! default_filter
|
|
8
|
+
|
|
9
|
+
args << options
|
|
10
|
+
@reflection.klass.search(*args)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def method_missing(method, *args, &block)
|
|
14
|
+
if responds_to_scope(method)
|
|
15
|
+
@reflection.klass.
|
|
16
|
+
search(:with => default_filter).
|
|
17
|
+
send(method, *args, &block)
|
|
18
|
+
else
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def attribute_for_foreign_key
|
|
26
|
+
foreign_key = @reflection.primary_key_name
|
|
27
|
+
stack = [@reflection.options[:through]].compact
|
|
28
|
+
|
|
29
|
+
@reflection.klass.define_indexes
|
|
30
|
+
(@reflection.klass.sphinx_indexes || []).each do |index|
|
|
31
|
+
attribute = index.attributes.detect { |attrib|
|
|
32
|
+
attrib.columns.length == 1 &&
|
|
33
|
+
attrib.columns.first.__name == foreign_key.to_sym
|
|
34
|
+
}
|
|
35
|
+
return attribute unless attribute.nil?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
raise "Missing Attribute for Foreign Key #{foreign_key}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def default_filter
|
|
42
|
+
{attribute_for_foreign_key.unique_name => @owner.id}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def responds_to_scope(scope)
|
|
46
|
+
@reflection.klass.respond_to?(:sphinx_scopes) &&
|
|
47
|
+
@reflection.klass.sphinx_scopes.include?(scope)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module Scopes
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.class_eval do
|
|
6
|
+
extend ThinkingSphinx::ActiveRecord::Scopes::ClassMethods
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
|
|
12
|
+
# Similar to ActiveRecord's default_scope method Thinking Sphinx supports
|
|
13
|
+
# a default_sphinx_scope. For example:
|
|
14
|
+
#
|
|
15
|
+
# default_sphinx_scope :some_sphinx_named_scope
|
|
16
|
+
#
|
|
17
|
+
# The scope is automatically applied when the search method is called. It
|
|
18
|
+
# will only be applied if it is an existing sphinx_scope.
|
|
19
|
+
def default_sphinx_scope(sphinx_scope_name)
|
|
20
|
+
@default_sphinx_scope = sphinx_scope_name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns the default_sphinx_scope or nil if none is set.
|
|
24
|
+
def get_default_sphinx_scope
|
|
25
|
+
@default_sphinx_scope
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns true if the current Model has a default_sphinx_scope. Also checks if
|
|
29
|
+
# the default_sphinx_scope actually is a scope.
|
|
30
|
+
def has_default_sphinx_scope?
|
|
31
|
+
!@default_sphinx_scope.nil? && sphinx_scopes.include?(@default_sphinx_scope)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Similar to ActiveRecord's named_scope method Thinking Sphinx supports
|
|
35
|
+
# scopes. For example:
|
|
36
|
+
#
|
|
37
|
+
# sphinx_scope(:latest_first) {
|
|
38
|
+
# {:order => 'created_at DESC, @relevance DESC'}
|
|
39
|
+
# }
|
|
40
|
+
#
|
|
41
|
+
# Usage:
|
|
42
|
+
#
|
|
43
|
+
# @articles = Article.latest_first.search 'pancakes'
|
|
44
|
+
#
|
|
45
|
+
def sphinx_scope(method, &block)
|
|
46
|
+
@sphinx_scopes ||= []
|
|
47
|
+
@sphinx_scopes << method
|
|
48
|
+
|
|
49
|
+
singleton_class.instance_eval do
|
|
50
|
+
define_method(method) do |*args|
|
|
51
|
+
options = {:classes => classes_option}
|
|
52
|
+
options.merge! block.call(*args)
|
|
53
|
+
|
|
54
|
+
ThinkingSphinx::Search.new(options)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# This returns an Array of all defined scopes. The default
|
|
60
|
+
# scope shows as :default.
|
|
61
|
+
def sphinx_scopes
|
|
62
|
+
@sphinx_scopes || []
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def remove_sphinx_scopes
|
|
66
|
+
sphinx_scopes.each do |scope|
|
|
67
|
+
singleton_class.send(:undef_method, scope)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
sphinx_scopes.clear
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class AbstractAdapter
|
|
3
|
+
def initialize(model)
|
|
4
|
+
@model = model
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def setup
|
|
8
|
+
# Deliberately blank - subclasses should do something though. Well, if
|
|
9
|
+
# they need to.
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.detect(model)
|
|
13
|
+
case model.connection.class.name == "MultiDb::ConnectionProxy" ? model.connection.master.connection.class.name : model.connection.class.name
|
|
14
|
+
when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
|
|
15
|
+
"ActiveRecord::ConnectionAdapters::MysqlplusAdapter",
|
|
16
|
+
"ActiveRecord::ConnectionAdapters::Mysql2Adapter"
|
|
17
|
+
ThinkingSphinx::MysqlAdapter.new model
|
|
18
|
+
when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
|
|
19
|
+
ThinkingSphinx::PostgreSQLAdapter.new model
|
|
20
|
+
when "ActiveRecord::ConnectionAdapters::JdbcAdapter"
|
|
21
|
+
if model.connection.config[:adapter] == "jdbcmysql"
|
|
22
|
+
ThinkingSphinx::MysqlAdapter.new model
|
|
23
|
+
elsif model.connection.config[:adapter] == "jdbcpostgresql"
|
|
24
|
+
ThinkingSphinx::PostgreSQLAdapter.new model
|
|
25
|
+
else
|
|
26
|
+
raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{model.connection.class.name}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def quote_with_table(column)
|
|
34
|
+
"#{@model.quoted_table_name}.#{@model.connection.quote_column_name(column)}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def bigint_pattern
|
|
38
|
+
/bigint/i
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
def connection
|
|
44
|
+
@connection ||= @model.connection
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class MysqlAdapter < AbstractAdapter
|
|
3
|
+
def setup
|
|
4
|
+
# Does MySQL actually need to do anything?
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def sphinx_identifier
|
|
8
|
+
"mysql"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def concatenate(clause, separator = ' ')
|
|
12
|
+
"CONCAT_WS('#{separator}', #{clause})"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def group_concatenate(clause, separator = ' ')
|
|
16
|
+
"GROUP_CONCAT(DISTINCT IFNULL(#{clause}, '0') SEPARATOR '#{separator}')"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def cast_to_string(clause)
|
|
20
|
+
"CAST(#{clause} AS CHAR)"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def cast_to_datetime(clause)
|
|
24
|
+
"UNIX_TIMESTAMP(#{clause})"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cast_to_unsigned(clause)
|
|
28
|
+
"CAST(#{clause} AS UNSIGNED)"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def convert_nulls(clause, default = '')
|
|
32
|
+
default = "'#{default}'" if default.is_a?(String)
|
|
33
|
+
|
|
34
|
+
"IFNULL(#{clause}, #{default})"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def boolean(value)
|
|
38
|
+
value ? 1 : 0
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def crc(clause, blank_to_null = false)
|
|
42
|
+
clause = "NULLIF(#{clause},'')" if blank_to_null
|
|
43
|
+
"CRC32(#{clause})"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def utf8_query_pre
|
|
47
|
+
"SET NAMES utf8"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def time_difference(diff)
|
|
51
|
+
"DATE_SUB(NOW(), INTERVAL #{diff} SECOND)"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def utc_query_pre
|
|
55
|
+
"SET TIME_ZONE = '+0:00'"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class PostgreSQLAdapter < AbstractAdapter
|
|
3
|
+
def setup
|
|
4
|
+
create_array_accum_function
|
|
5
|
+
create_crc32_function
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def sphinx_identifier
|
|
9
|
+
"pgsql"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def concatenate(clause, separator = ' ')
|
|
13
|
+
if clause[/^COALESCE/]
|
|
14
|
+
clause.split('), ').join(") || '#{separator}' || ")
|
|
15
|
+
else
|
|
16
|
+
clause.split(', ').collect { |field|
|
|
17
|
+
"CAST(COALESCE(#{field}, '') as varchar)"
|
|
18
|
+
}.join(" || '#{separator}' || ")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def group_concatenate(clause, separator = ' ')
|
|
23
|
+
"array_to_string(array_accum(COALESCE(#{clause}, '0')), '#{separator}')"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def cast_to_string(clause)
|
|
27
|
+
clause
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def cast_to_datetime(clause)
|
|
31
|
+
"cast(extract(epoch from #{clause}) as int)"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def cast_to_unsigned(clause)
|
|
35
|
+
clause
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def convert_nulls(clause, default = '')
|
|
39
|
+
default = case default
|
|
40
|
+
when String
|
|
41
|
+
"'#{default}'"
|
|
42
|
+
when NilClass
|
|
43
|
+
'NULL'
|
|
44
|
+
when Fixnum
|
|
45
|
+
"#{default}::bigint"
|
|
46
|
+
else
|
|
47
|
+
default
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
"COALESCE(#{clause}, #{default})"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def boolean(value)
|
|
54
|
+
value ? 'TRUE' : 'FALSE'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def crc(clause, blank_to_null = false)
|
|
58
|
+
clause = "NULLIF(#{clause},'')" if blank_to_null
|
|
59
|
+
"crc32(#{clause})"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def utf8_query_pre
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def time_difference(diff)
|
|
67
|
+
"current_timestamp - interval '#{diff} seconds'"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def utc_query_pre
|
|
71
|
+
'SET TIME ZONE UTC'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def execute(command, output_error = false)
|
|
77
|
+
connection.execute "begin"
|
|
78
|
+
connection.execute "savepoint ts"
|
|
79
|
+
begin
|
|
80
|
+
connection.execute command
|
|
81
|
+
rescue StandardError => err
|
|
82
|
+
puts err if output_error
|
|
83
|
+
connection.execute "rollback to savepoint ts"
|
|
84
|
+
end
|
|
85
|
+
connection.execute "release savepoint ts"
|
|
86
|
+
connection.execute "commit"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def create_array_accum_function
|
|
90
|
+
if connection.raw_connection.respond_to?(:server_version) && connection.raw_connection.server_version > 80200
|
|
91
|
+
execute <<-SQL
|
|
92
|
+
CREATE AGGREGATE array_accum (anyelement)
|
|
93
|
+
(
|
|
94
|
+
sfunc = array_append,
|
|
95
|
+
stype = anyarray,
|
|
96
|
+
initcond = '{}'
|
|
97
|
+
);
|
|
98
|
+
SQL
|
|
99
|
+
else
|
|
100
|
+
execute <<-SQL
|
|
101
|
+
CREATE AGGREGATE array_accum
|
|
102
|
+
(
|
|
103
|
+
basetype = anyelement,
|
|
104
|
+
sfunc = array_append,
|
|
105
|
+
stype = anyarray,
|
|
106
|
+
initcond = '{}'
|
|
107
|
+
);
|
|
108
|
+
SQL
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def create_crc32_function
|
|
113
|
+
execute "CREATE LANGUAGE 'plpgsql';"
|
|
114
|
+
function = <<-SQL
|
|
115
|
+
CREATE OR REPLACE FUNCTION crc32(word text)
|
|
116
|
+
RETURNS bigint AS $$
|
|
117
|
+
DECLARE tmp bigint;
|
|
118
|
+
DECLARE i int;
|
|
119
|
+
DECLARE j int;
|
|
120
|
+
DECLARE word_array bytea;
|
|
121
|
+
BEGIN
|
|
122
|
+
i = 0;
|
|
123
|
+
tmp = 4294967295;
|
|
124
|
+
word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
|
|
125
|
+
LOOP
|
|
126
|
+
tmp = (tmp # get_byte(word_array, i))::bigint;
|
|
127
|
+
i = i + 1;
|
|
128
|
+
j = 0;
|
|
129
|
+
LOOP
|
|
130
|
+
tmp = ((tmp >> 1) # (3988292384 * (tmp & 1)))::bigint;
|
|
131
|
+
j = j + 1;
|
|
132
|
+
IF j >= 8 THEN
|
|
133
|
+
EXIT;
|
|
134
|
+
END IF;
|
|
135
|
+
END LOOP;
|
|
136
|
+
IF i >= char_length(word) THEN
|
|
137
|
+
EXIT;
|
|
138
|
+
END IF;
|
|
139
|
+
END LOOP;
|
|
140
|
+
return (tmp # 4294967295);
|
|
141
|
+
END
|
|
142
|
+
$$ IMMUTABLE STRICT LANGUAGE plpgsql;
|
|
143
|
+
SQL
|
|
144
|
+
execute function, true
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|