thinking-sphinx 2.0.5 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
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