thinking-sphinx 2.0.5 → 2.0.6

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.
Files changed (70) hide show
  1. data/README.textile +7 -1
  2. data/features/searching_by_model.feature +24 -30
  3. data/features/step_definitions/common_steps.rb +5 -5
  4. data/features/thinking_sphinx/db/.gitignore +1 -0
  5. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +1 -0
  6. data/spec/fixtures/data.sql +32 -0
  7. data/spec/fixtures/database.yml.default +3 -0
  8. data/spec/fixtures/models.rb +161 -0
  9. data/spec/fixtures/structure.sql +146 -0
  10. data/spec/spec_helper.rb +62 -0
  11. data/spec/sphinx_helper.rb +61 -0
  12. data/spec/support/rails.rb +18 -0
  13. data/spec/thinking_sphinx/active_record/delta_spec.rb +24 -24
  14. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +27 -0
  15. data/spec/thinking_sphinx/active_record/scopes_spec.rb +25 -25
  16. data/spec/thinking_sphinx/active_record_spec.rb +108 -107
  17. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +38 -38
  18. data/spec/thinking_sphinx/association_spec.rb +69 -35
  19. data/spec/thinking_sphinx/context_spec.rb +61 -64
  20. data/spec/thinking_sphinx/search_spec.rb +7 -0
  21. data/spec/thinking_sphinx_spec.rb +47 -46
  22. metadata +49 -141
  23. data/VERSION +0 -1
  24. data/lib/cucumber/thinking_sphinx/external_world.rb +0 -12
  25. data/lib/cucumber/thinking_sphinx/internal_world.rb +0 -127
  26. data/lib/cucumber/thinking_sphinx/sql_logger.rb +0 -20
  27. data/lib/thinking-sphinx.rb +0 -1
  28. data/lib/thinking_sphinx.rb +0 -301
  29. data/lib/thinking_sphinx/action_controller.rb +0 -31
  30. data/lib/thinking_sphinx/active_record.rb +0 -384
  31. data/lib/thinking_sphinx/active_record/attribute_updates.rb +0 -52
  32. data/lib/thinking_sphinx/active_record/delta.rb +0 -65
  33. data/lib/thinking_sphinx/active_record/has_many_association.rb +0 -36
  34. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  35. data/lib/thinking_sphinx/active_record/log_subscriber.rb +0 -61
  36. data/lib/thinking_sphinx/active_record/scopes.rb +0 -93
  37. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +0 -87
  38. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -62
  39. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +0 -157
  40. data/lib/thinking_sphinx/association.rb +0 -219
  41. data/lib/thinking_sphinx/attribute.rb +0 -396
  42. data/lib/thinking_sphinx/auto_version.rb +0 -38
  43. data/lib/thinking_sphinx/bundled_search.rb +0 -44
  44. data/lib/thinking_sphinx/class_facet.rb +0 -20
  45. data/lib/thinking_sphinx/configuration.rb +0 -339
  46. data/lib/thinking_sphinx/context.rb +0 -76
  47. data/lib/thinking_sphinx/core/string.rb +0 -15
  48. data/lib/thinking_sphinx/deltas.rb +0 -28
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +0 -62
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +0 -101
  51. data/lib/thinking_sphinx/excerpter.rb +0 -23
  52. data/lib/thinking_sphinx/facet.rb +0 -128
  53. data/lib/thinking_sphinx/facet_search.rb +0 -170
  54. data/lib/thinking_sphinx/field.rb +0 -98
  55. data/lib/thinking_sphinx/index.rb +0 -157
  56. data/lib/thinking_sphinx/index/builder.rb +0 -312
  57. data/lib/thinking_sphinx/index/faux_column.rb +0 -118
  58. data/lib/thinking_sphinx/join.rb +0 -37
  59. data/lib/thinking_sphinx/property.rb +0 -185
  60. data/lib/thinking_sphinx/railtie.rb +0 -46
  61. data/lib/thinking_sphinx/search.rb +0 -972
  62. data/lib/thinking_sphinx/search_methods.rb +0 -439
  63. data/lib/thinking_sphinx/sinatra.rb +0 -7
  64. data/lib/thinking_sphinx/source.rb +0 -194
  65. data/lib/thinking_sphinx/source/internal_properties.rb +0 -51
  66. data/lib/thinking_sphinx/source/sql.rb +0 -157
  67. data/lib/thinking_sphinx/tasks.rb +0 -130
  68. data/lib/thinking_sphinx/test.rb +0 -55
  69. data/tasks/distribution.rb +0 -33
  70. data/tasks/testing.rb +0 -80
@@ -1,65 +0,0 @@
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_objects.each { |obj| obj.index(self, instance) }
20
- end
21
-
22
- def delta_objects
23
- self.sphinx_indexes.collect(&:delta_object).compact
24
- end
25
- end
26
-
27
- def toggled_delta?
28
- self.class.delta_objects.any? { |obj| obj.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_objects.each { |obj|
36
- obj.toggle(self)
37
- } if should_toggle_delta?
38
- end
39
-
40
- # Build the delta index for the related model. This won't be called
41
- # if running in the test environment.
42
- #
43
- def index_delta
44
- self.class.index_delta(self) if self.class.delta_objects.any? { |obj|
45
- obj.toggled(self)
46
- }
47
- end
48
-
49
- def should_toggle_delta?
50
- self.new_record? || indexed_data_changed?
51
- end
52
-
53
- def indexed_data_changed?
54
- sphinx_indexes.any? { |index|
55
- index.fields.any? { |field| field.changed?(self) } ||
56
- index.attributes.any? { |attrib|
57
- attrib.public? && attrib.changed?(self) && !attrib.updatable?
58
- }
59
- }
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,36 +0,0 @@
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
- private
14
-
15
- def attribute_for_foreign_key
16
- foreign_key = @reflection.primary_key_name
17
- stack = [@reflection.options[:through]].compact
18
-
19
- @reflection.klass.define_indexes
20
- (@reflection.klass.sphinx_indexes || []).each do |index|
21
- attribute = index.attributes.detect { |attrib|
22
- attrib.columns.length == 1 &&
23
- attrib.columns.first.__name == foreign_key.to_sym
24
- }
25
- return attribute unless attribute.nil?
26
- end
27
-
28
- raise "Missing Attribute for Foreign Key #{foreign_key}"
29
- end
30
-
31
- def default_filter
32
- {attribute_for_foreign_key.unique_name => @owner.id}
33
- end
34
- end
35
- end
36
- end
@@ -1,21 +0,0 @@
1
- module ThinkingSphinx
2
- module ActiveRecord
3
- module HasManyAssociationWithScopes
4
- def method_missing(method, *args, &block)
5
- if responds_to_scope(method)
6
- @reflection.klass.
7
- search(:with => default_filter).
8
- send(method, *args, &block)
9
- else
10
- super
11
- end
12
- end
13
-
14
- private
15
- def responds_to_scope(scope)
16
- @reflection.klass.respond_to?(:sphinx_scopes) &&
17
- @reflection.klass.sphinx_scopes.include?(scope)
18
- end
19
- end
20
- end
21
- end
@@ -1,61 +0,0 @@
1
- require 'active_support/log_subscriber'
2
-
3
- module ThinkingSphinx
4
- module ActiveRecord
5
- class LogSubscriber < ActiveSupport::LogSubscriber
6
- def self.runtime=(value)
7
- Thread.current['thinking_sphinx_query_runtime'] = value
8
- end
9
-
10
- def self.runtime
11
- Thread.current['thinking_sphinx_query_runtime'] ||= 0
12
- end
13
-
14
- def self.reset_runtime
15
- rt, self.runtime = runtime, 0
16
- rt
17
- end
18
-
19
- def initialize
20
- super
21
- @odd_or_even = false
22
- end
23
-
24
- def query(event)
25
- self.class.runtime += event.duration
26
- return unless logger.debug?
27
-
28
- identifier = color('Sphinx Query (%.1fms)' % event.duration, GREEN, true)
29
- query = event.payload[:query]
30
- query = color query, nil, true if odd?
31
-
32
- debug " #{identifier} #{query}"
33
- end
34
-
35
- def message(event)
36
- return unless logger.debug?
37
-
38
- identifier = color 'Sphinx', GREEN, true
39
- message = event.payload[:message]
40
- message = color message, nil, true if odd?
41
-
42
- debug " #{identifier} #{message}"
43
- end
44
-
45
- def odd?
46
- @odd_or_even = !@odd_or_even
47
- end
48
-
49
- def logger
50
- return @logger if defined? @logger
51
- self.logger = ::ActiveRecord::Base.logger
52
- end
53
-
54
- def logger=(logger)
55
- @logger = logger
56
- end
57
-
58
- attach_to :thinking_sphinx
59
- end
60
- end
61
- end
@@ -1,93 +0,0 @@
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
- add_sphinx_scopes_support_to_has_many_associations
21
- @default_sphinx_scope = sphinx_scope_name
22
- end
23
-
24
- # Returns the default_sphinx_scope or nil if none is set.
25
- def get_default_sphinx_scope
26
- @default_sphinx_scope
27
- end
28
-
29
- # Returns true if the current Model has a default_sphinx_scope. Also checks if
30
- # the default_sphinx_scope actually is a scope.
31
- def has_default_sphinx_scope?
32
- !@default_sphinx_scope.nil? && sphinx_scopes.include?(@default_sphinx_scope)
33
- end
34
-
35
- # Similar to ActiveRecord's named_scope method Thinking Sphinx supports
36
- # scopes. For example:
37
- #
38
- # sphinx_scope(:latest_first) {
39
- # {:order => 'created_at DESC, @relevance DESC'}
40
- # }
41
- #
42
- # Usage:
43
- #
44
- # @articles = Article.latest_first.search 'pancakes'
45
- #
46
- def sphinx_scope(method, &block)
47
- add_sphinx_scopes_support_to_has_many_associations
48
-
49
- @sphinx_scopes ||= []
50
- @sphinx_scopes << method
51
-
52
- singleton_class.instance_eval do
53
- define_method(method) do |*args|
54
- options = {:classes => classes_option}
55
- options.merge! block.call(*args)
56
-
57
- ThinkingSphinx::Search.new(options)
58
- end
59
-
60
- define_method("#{method}_without_default".to_sym) do |*args|
61
- options = {:classes => classes_option, :ignore_default => true}
62
- options.merge! block.call(*args)
63
-
64
- ThinkingSphinx::Search.new(options)
65
- end
66
- end
67
- end
68
-
69
- # This returns an Array of all defined scopes. The default
70
- # scope shows as :default.
71
- def sphinx_scopes
72
- @sphinx_scopes || []
73
- end
74
-
75
- def remove_sphinx_scopes
76
- sphinx_scopes.each do |scope|
77
- singleton_class.send(:undef_method, scope)
78
- end
79
-
80
- sphinx_scopes.clear
81
- end
82
-
83
- def add_sphinx_scopes_support_to_has_many_associations
84
- scope_mixin = ::ThinkingSphinx::ActiveRecord::HasManyAssociationWithScopes
85
-
86
- ::ActiveRecord::Associations::HasManyAssociation.send(:include, scope_mixin)
87
- ::ActiveRecord::Associations::HasManyThroughAssociation.send(:include, scope_mixin)
88
- end
89
-
90
- end
91
- end
92
- end
93
- end
@@ -1,87 +0,0 @@
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
- adapter = adapter_for_model model
14
- case adapter
15
- when :mysql
16
- ThinkingSphinx::MysqlAdapter.new model
17
- when :postgresql
18
- ThinkingSphinx::PostgreSQLAdapter.new model
19
- when Class
20
- adapter.new model
21
- else
22
- raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{adapter}"
23
- end
24
- end
25
-
26
- def self.adapter_for_model(model)
27
- case ThinkingSphinx.database_adapter
28
- when String
29
- ThinkingSphinx.database_adapter.to_sym
30
- when NilClass
31
- standard_adapter_for_model model
32
- when Proc
33
- ThinkingSphinx.database_adapter.call model
34
- else
35
- ThinkingSphinx.database_adapter
36
- end
37
- end
38
-
39
- def self.standard_adapter_for_model(model)
40
- case model.connection.class.name
41
- when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
42
- "ActiveRecord::ConnectionAdapters::MysqlplusAdapter",
43
- "ActiveRecord::ConnectionAdapters::Mysql2Adapter",
44
- "ActiveRecord::ConnectionAdapters::NullDBAdapter"
45
- :mysql
46
- when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
47
- :postgresql
48
- when "ActiveRecord::ConnectionAdapters::JdbcAdapter"
49
- case model.connection.config[:adapter]
50
- when "jdbcmysql"
51
- :mysql
52
- when "jdbcpostgresql"
53
- :postgresql
54
- else
55
- model.connection.config[:adapter]
56
- end
57
- else
58
- model.connection.class.name
59
- end
60
- end
61
-
62
- def quote_with_table(column)
63
- "#{@model.quoted_table_name}.#{@model.connection.quote_column_name(column)}"
64
- end
65
-
66
- def bigint_pattern
67
- /bigint/i
68
- end
69
-
70
- def downcase(clause)
71
- "LOWER(#{clause})"
72
- end
73
-
74
- def case(expression, pairs, default)
75
- "CASE #{expression} " +
76
- pairs.keys.inject('') { |string, key|
77
- string + "WHEN '#{key}' THEN #{pairs[key]} "
78
- } + "ELSE #{default} END"
79
- end
80
-
81
- protected
82
-
83
- def connection
84
- @connection ||= @model.connection
85
- end
86
- end
87
- end
@@ -1,62 +0,0 @@
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 cast_to_int(clause)
32
- "CAST(#{clause} AS SIGNED)"
33
- end
34
-
35
- def convert_nulls(clause, default = '')
36
- default = "'#{default}'" if default.is_a?(String)
37
-
38
- "IFNULL(#{clause}, #{default})"
39
- end
40
-
41
- def boolean(value)
42
- value ? 1 : 0
43
- end
44
-
45
- def crc(clause, blank_to_null = false)
46
- clause = "NULLIF(#{clause},'')" if blank_to_null
47
- "CRC32(#{clause})"
48
- end
49
-
50
- def utf8_query_pre
51
- "SET NAMES utf8"
52
- end
53
-
54
- def time_difference(diff)
55
- "DATE_SUB(NOW(), INTERVAL #{diff} SECOND)"
56
- end
57
-
58
- def utc_query_pre
59
- "SET TIME_ZONE = '+0:00'"
60
- end
61
- end
62
- end
@@ -1,157 +0,0 @@
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 cast_to_int(clause)
39
- "#{clause}::INT8"
40
- end
41
-
42
- def convert_nulls(clause, default = '')
43
- default = case default
44
- when String
45
- "'#{default}'"
46
- when NilClass
47
- 'NULL'
48
- when Fixnum
49
- "#{default}::bigint"
50
- else
51
- default
52
- end
53
-
54
- "COALESCE(#{clause}, #{default})"
55
- end
56
-
57
- def boolean(value)
58
- value ? 'TRUE' : 'FALSE'
59
- end
60
-
61
- def crc(clause, blank_to_null = false)
62
- clause = "NULLIF(#{clause},'')" if blank_to_null
63
- "crc32(#{clause})"
64
- end
65
-
66
- def utf8_query_pre
67
- nil
68
- end
69
-
70
- def time_difference(diff)
71
- "current_timestamp - interval '#{diff} seconds'"
72
- end
73
-
74
- def utc_query_pre
75
- "SET TIME ZONE 'UTC'"
76
- end
77
-
78
- private
79
-
80
- def execute(command, output_error = false)
81
- connection.execute "begin"
82
- connection.execute "savepoint ts"
83
- begin
84
- connection.execute command
85
- rescue StandardError => err
86
- puts err if output_error
87
- connection.execute "rollback to savepoint ts"
88
- end
89
- connection.execute "release savepoint ts"
90
- connection.execute "commit"
91
- end
92
-
93
- def create_array_accum_function
94
- if connection.raw_connection.respond_to?(:server_version) && connection.raw_connection.server_version > 80200
95
- execute <<-SQL
96
- CREATE AGGREGATE array_accum (anyelement)
97
- (
98
- sfunc = array_append,
99
- stype = anyarray,
100
- initcond = '{}'
101
- );
102
- SQL
103
- else
104
- execute <<-SQL
105
- CREATE AGGREGATE array_accum
106
- (
107
- basetype = anyelement,
108
- sfunc = array_append,
109
- stype = anyarray,
110
- initcond = '{}'
111
- );
112
- SQL
113
- end
114
- end
115
-
116
- def create_crc32_function
117
- execute "CREATE LANGUAGE 'plpgsql';"
118
- function = <<-SQL
119
- CREATE OR REPLACE FUNCTION crc32(word text)
120
- RETURNS bigint AS $$
121
- DECLARE tmp bigint;
122
- DECLARE i int;
123
- DECLARE j int;
124
- DECLARE byte_length int;
125
- DECLARE word_array bytea;
126
- BEGIN
127
- IF COALESCE(word, '') = '' THEN
128
- return 0;
129
- END IF;
130
-
131
- i = 0;
132
- tmp = 4294967295;
133
- byte_length = bit_length(word) / 8;
134
- word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
135
- LOOP
136
- tmp = (tmp # get_byte(word_array, i))::bigint;
137
- i = i + 1;
138
- j = 0;
139
- LOOP
140
- tmp = ((tmp >> 1) # (3988292384 * (tmp & 1)))::bigint;
141
- j = j + 1;
142
- IF j >= 8 THEN
143
- EXIT;
144
- END IF;
145
- END LOOP;
146
- IF i >= byte_length THEN
147
- EXIT;
148
- END IF;
149
- END LOOP;
150
- return (tmp # 4294967295);
151
- END
152
- $$ IMMUTABLE LANGUAGE plpgsql;
153
- SQL
154
- execute function, true
155
- end
156
- end
157
- end