thinking-sphinx 2.0.6 → 2.0.7

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 (50) hide show
  1. data/HISTORY +157 -0
  2. data/lib/cucumber/thinking_sphinx/external_world.rb +12 -0
  3. data/lib/cucumber/thinking_sphinx/internal_world.rb +127 -0
  4. data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
  5. data/lib/thinking-sphinx.rb +1 -0
  6. data/lib/thinking_sphinx/action_controller.rb +31 -0
  7. data/lib/thinking_sphinx/active_record/attribute_updates.rb +53 -0
  8. data/lib/thinking_sphinx/active_record/collection_proxy.rb +40 -0
  9. data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +27 -0
  10. data/lib/thinking_sphinx/active_record/delta.rb +65 -0
  11. data/lib/thinking_sphinx/active_record/has_many_association.rb +37 -0
  12. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +21 -0
  13. data/lib/thinking_sphinx/active_record/log_subscriber.rb +61 -0
  14. data/lib/thinking_sphinx/active_record/scopes.rb +110 -0
  15. data/lib/thinking_sphinx/active_record.rb +383 -0
  16. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +87 -0
  17. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +62 -0
  18. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +171 -0
  19. data/lib/thinking_sphinx/association.rb +229 -0
  20. data/lib/thinking_sphinx/attribute.rb +407 -0
  21. data/lib/thinking_sphinx/auto_version.rb +38 -0
  22. data/lib/thinking_sphinx/bundled_search.rb +44 -0
  23. data/lib/thinking_sphinx/class_facet.rb +20 -0
  24. data/lib/thinking_sphinx/configuration.rb +335 -0
  25. data/lib/thinking_sphinx/context.rb +77 -0
  26. data/lib/thinking_sphinx/core/string.rb +15 -0
  27. data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
  28. data/lib/thinking_sphinx/deltas.rb +28 -0
  29. data/lib/thinking_sphinx/deploy/capistrano.rb +99 -0
  30. data/lib/thinking_sphinx/excerpter.rb +23 -0
  31. data/lib/thinking_sphinx/facet.rb +128 -0
  32. data/lib/thinking_sphinx/facet_search.rb +170 -0
  33. data/lib/thinking_sphinx/field.rb +98 -0
  34. data/lib/thinking_sphinx/index/builder.rb +312 -0
  35. data/lib/thinking_sphinx/index/faux_column.rb +118 -0
  36. data/lib/thinking_sphinx/index.rb +157 -0
  37. data/lib/thinking_sphinx/join.rb +37 -0
  38. data/lib/thinking_sphinx/property.rb +185 -0
  39. data/lib/thinking_sphinx/railtie.rb +46 -0
  40. data/lib/thinking_sphinx/search.rb +995 -0
  41. data/lib/thinking_sphinx/search_methods.rb +439 -0
  42. data/lib/thinking_sphinx/sinatra.rb +7 -0
  43. data/lib/thinking_sphinx/source/internal_properties.rb +51 -0
  44. data/lib/thinking_sphinx/source/sql.rb +157 -0
  45. data/lib/thinking_sphinx/source.rb +194 -0
  46. data/lib/thinking_sphinx/tasks.rb +132 -0
  47. data/lib/thinking_sphinx/test.rb +55 -0
  48. data/lib/thinking_sphinx/version.rb +3 -0
  49. data/lib/thinking_sphinx.rb +296 -0
  50. metadata +53 -4
@@ -0,0 +1,171 @@
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
+ if ThinkingSphinx::Configuration.instance.use_64_bit
32
+ "cast(extract(epoch from #{clause}) as bigint)"
33
+ else
34
+ "cast(extract(epoch from #{clause}) as int)"
35
+ end
36
+ end
37
+
38
+ def cast_to_unsigned(clause)
39
+ clause
40
+ end
41
+
42
+ def cast_to_int(clause)
43
+ "#{clause}::INT8"
44
+ end
45
+
46
+ def convert_nulls(clause, default = '')
47
+ default = case default
48
+ when String
49
+ "'#{default}'"
50
+ when NilClass
51
+ 'NULL'
52
+ when Fixnum
53
+ "#{default}::bigint"
54
+ else
55
+ default
56
+ end
57
+
58
+ "COALESCE(#{clause}, #{default})"
59
+ end
60
+
61
+ def boolean(value)
62
+ value ? 'TRUE' : 'FALSE'
63
+ end
64
+
65
+ def crc(clause, blank_to_null = false)
66
+ clause = "NULLIF(#{clause},'')" if blank_to_null
67
+ "crc32(#{clause})"
68
+ end
69
+
70
+ def utf8_query_pre
71
+ nil
72
+ end
73
+
74
+ def time_difference(diff)
75
+ "current_timestamp - interval '#{diff} seconds'"
76
+ end
77
+
78
+ def utc_query_pre
79
+ "SET TIME ZONE 'UTC'"
80
+ end
81
+
82
+ private
83
+
84
+ def execute(command, output_error = false)
85
+ if RUBY_PLATFORM == 'java'
86
+ connection.transaction do
87
+ execute_command command, output_error
88
+ end
89
+ else
90
+ execute_command command, output_error
91
+ end
92
+ end
93
+
94
+ def execute_command(command, output_error = false)
95
+ connection.execute "begin"
96
+ connection.execute "savepoint ts"
97
+ begin
98
+ connection.execute command
99
+ rescue StandardError => err
100
+ puts err if output_error
101
+ connection.execute "rollback to savepoint ts"
102
+ end
103
+ connection.execute "release savepoint ts"
104
+ connection.execute "commit"
105
+ end
106
+
107
+ def create_array_accum_function
108
+ if connection.raw_connection.respond_to?(:server_version) && connection.raw_connection.server_version > 80200
109
+ execute <<-SQL
110
+ CREATE AGGREGATE array_accum (anyelement)
111
+ (
112
+ sfunc = array_append,
113
+ stype = anyarray,
114
+ initcond = '{}'
115
+ );
116
+ SQL
117
+ else
118
+ execute <<-SQL
119
+ CREATE AGGREGATE array_accum
120
+ (
121
+ basetype = anyelement,
122
+ sfunc = array_append,
123
+ stype = anyarray,
124
+ initcond = '{}'
125
+ );
126
+ SQL
127
+ end
128
+ end
129
+
130
+ def create_crc32_function
131
+ execute "CREATE LANGUAGE 'plpgsql';"
132
+ function = <<-SQL
133
+ CREATE OR REPLACE FUNCTION crc32(word text)
134
+ RETURNS bigint AS $$
135
+ DECLARE tmp bigint;
136
+ DECLARE i int;
137
+ DECLARE j int;
138
+ DECLARE byte_length int;
139
+ DECLARE word_array bytea;
140
+ BEGIN
141
+ IF COALESCE(word, '') = '' THEN
142
+ return 0;
143
+ END IF;
144
+
145
+ i = 0;
146
+ tmp = 4294967295;
147
+ byte_length = bit_length(word) / 8;
148
+ word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
149
+ LOOP
150
+ tmp = (tmp # get_byte(word_array, i))::bigint;
151
+ i = i + 1;
152
+ j = 0;
153
+ LOOP
154
+ tmp = ((tmp >> 1) # (3988292384 * (tmp & 1)))::bigint;
155
+ j = j + 1;
156
+ IF j >= 8 THEN
157
+ EXIT;
158
+ END IF;
159
+ END LOOP;
160
+ IF i >= byte_length THEN
161
+ EXIT;
162
+ END IF;
163
+ END LOOP;
164
+ return (tmp # 4294967295);
165
+ END
166
+ $$ IMMUTABLE LANGUAGE plpgsql;
167
+ SQL
168
+ execute function, true
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,229 @@
1
+ module ThinkingSphinx
2
+ # Association tracks a specific reflection and join to reference data that
3
+ # isn't in the base model. Very much an internal class for Thinking Sphinx -
4
+ # perhaps because I feel it's not as strong (or simple) as most of the rest.
5
+ #
6
+ class Association
7
+ attr_accessor :parent, :reflection, :join
8
+
9
+ # Create a new association by passing in the parent association, and the
10
+ # corresponding reflection instance. If there is no parent, pass in nil.
11
+ #
12
+ # top = Association.new nil, top_reflection
13
+ # child = Association.new top, child_reflection
14
+ #
15
+ def initialize(parent, reflection)
16
+ @parent, @reflection = parent, reflection
17
+ @children = {}
18
+ end
19
+
20
+ # Get the children associations for a given association name. The only time
21
+ # that there'll actually be more than one association is when the
22
+ # relationship is polymorphic. To keep things simple though, it will always
23
+ # be an Array that gets returned (an empty one if no matches).
24
+ #
25
+ # # where pages is an association on the class tied to the reflection.
26
+ # association.children(:pages)
27
+ #
28
+ def children(assoc)
29
+ @children[assoc] ||= Association.children(@reflection.klass, assoc, self)
30
+ end
31
+
32
+ # Get the children associations for a given class, association name and
33
+ # parent association. Much like the instance method of the same name, it
34
+ # will return an empty array if no associations have the name, and only
35
+ # have multiple association instances if the underlying relationship is
36
+ # polymorphic.
37
+ #
38
+ # Association.children(User, :pages, user_association)
39
+ #
40
+ def self.children(klass, assoc, parent=nil)
41
+ ref = klass.reflect_on_association(assoc)
42
+
43
+ return [] if ref.nil?
44
+ return [Association.new(parent, ref)] unless ref.options[:polymorphic]
45
+
46
+ # association is polymorphic - create associations for each
47
+ # non-polymorphic reflection.
48
+ polymorphic_classes(ref).collect { |poly_class|
49
+ Association.new parent, depolymorphic_reflection(ref, klass, poly_class)
50
+ }
51
+ end
52
+
53
+ # Link up the join for this model from a base join - and set parent
54
+ # associations' joins recursively.
55
+ #
56
+ def join_to(base_join)
57
+ parent.join_to(base_join) if parent && parent.join.nil?
58
+
59
+ @join ||= join_association_class.new(
60
+ @reflection, base_join, parent ? parent.join : join_parent(base_join)
61
+ )
62
+ end
63
+
64
+ def arel_join
65
+ @join.join_type = Arel::OuterJoin
66
+ rewrite_conditions
67
+
68
+ @join
69
+ end
70
+
71
+ # Returns true if the association - or a parent - is a has_many or
72
+ # has_and_belongs_to_many.
73
+ #
74
+ def is_many?
75
+ case @reflection.macro
76
+ when :has_many, :has_and_belongs_to_many
77
+ true
78
+ else
79
+ @parent ? @parent.is_many? : false
80
+ end
81
+ end
82
+
83
+ # Returns an array of all the associations that lead to this one - starting
84
+ # with the top level all the way to the current association object.
85
+ #
86
+ def ancestors
87
+ (parent ? parent.ancestors : []) << self
88
+ end
89
+
90
+ def has_column?(column)
91
+ @reflection.klass.column_names.include?(column.to_s)
92
+ end
93
+
94
+ def primary_key_from_reflection
95
+ if @reflection.options[:through]
96
+ if ThinkingSphinx.rails_3_1?
97
+ @reflection.source_reflection.foreign_key
98
+ else
99
+ @reflection.source_reflection.options[:foreign_key] ||
100
+ @reflection.source_reflection.primary_key_name
101
+ end
102
+ elsif @reflection.macro == :has_and_belongs_to_many
103
+ @reflection.association_foreign_key
104
+ else
105
+ nil
106
+ end
107
+ end
108
+
109
+ def table
110
+ if @reflection.options[:through] ||
111
+ @reflection.macro == :has_and_belongs_to_many
112
+ if ThinkingSphinx.rails_3_1?
113
+ @join.tables.first.name
114
+ else
115
+ @join.aliased_join_table_name
116
+ end
117
+ else
118
+ @join.aliased_table_name
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def self.depolymorphic_reflection(reflection, source_class, poly_class)
125
+ name = "#{reflection.name}_#{poly_class.name}".to_sym
126
+
127
+ source_class.reflections[name] ||=
128
+ ::ActiveRecord::Reflection::AssociationReflection.new(
129
+ reflection.macro, name, casted_options(poly_class, reflection),
130
+ reflection.active_record
131
+ )
132
+ end
133
+
134
+ # Returns all the objects that could be currently instantiated from a
135
+ # polymorphic association. This is pretty damn fast if there's an index on
136
+ # the foreign type column - but if there isn't, it can take a while if you
137
+ # have a lot of data.
138
+ #
139
+ def self.polymorphic_classes(ref)
140
+ ref.active_record.connection.select_all(
141
+ "SELECT DISTINCT #{foreign_type(ref)} " +
142
+ "FROM #{ref.active_record.table_name} " +
143
+ "WHERE #{foreign_type(ref)} IS NOT NULL"
144
+ ).collect { |row|
145
+ row[foreign_type(ref)].constantize
146
+ }
147
+ end
148
+
149
+ # Returns a new set of options for an association that mimics an existing
150
+ # polymorphic relationship for a specific class. It adds a condition to
151
+ # filter by the appropriate object.
152
+ #
153
+ def self.casted_options(klass, ref)
154
+ options = ref.options.clone
155
+ options[:polymorphic] = nil
156
+ options[:class_name] = klass.name
157
+ options[:foreign_key] ||= "#{ref.name}_id"
158
+
159
+ quoted_foreign_type = klass.connection.quote_column_name foreign_type(ref)
160
+ case options[:conditions]
161
+ when nil
162
+ options[:conditions] = "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
163
+ when Array
164
+ options[:conditions] << "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
165
+ when Hash
166
+ options[:conditions].merge!(foreign_type(ref) => klass.name)
167
+ else
168
+ options[:conditions] << " AND ::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
169
+ end
170
+
171
+ options
172
+ end
173
+
174
+ def join_association_class
175
+ if ThinkingSphinx.rails_3_1?
176
+ ::ActiveRecord::Associations::JoinDependency::JoinAssociation
177
+ else
178
+ ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
179
+ end
180
+ end
181
+
182
+ def join_parent(join)
183
+ if ThinkingSphinx.rails_3_1?
184
+ join.join_parts.first
185
+ else
186
+ join.joins.first
187
+ end
188
+ end
189
+
190
+ def self.foreign_type(ref)
191
+ if ThinkingSphinx.rails_3_1?
192
+ ref.foreign_type
193
+ else
194
+ ref.options[:foreign_type]
195
+ end
196
+ end
197
+
198
+ def rewrite_conditions
199
+ @join.options[:conditions] = case @join.options[:conditions]
200
+ when String
201
+ rewrite_condition @join.options[:conditions]
202
+ when Array
203
+ @join.options[:conditions].collect { |condition|
204
+ rewrite_condition condition
205
+ }
206
+ else
207
+ @join.options[:conditions]
208
+ end
209
+ end
210
+
211
+ def rewrite_condition(condition)
212
+ return condition unless condition.is_a?(String)
213
+
214
+ if defined?(ActsAsTaggableOn) &&
215
+ @reflection.klass == ActsAsTaggableOn::Tagging &&
216
+ @reflection.name.to_s[/_taggings$/]
217
+ condition = condition.gsub /taggings\./, "#{quoted_alias @join}."
218
+ end
219
+
220
+ condition.gsub /::ts_join_alias::/, quoted_alias(@join.parent)
221
+ end
222
+
223
+ def quoted_alias(join)
224
+ @reflection.klass.connection.quote_table_name(
225
+ join.aliased_table_name
226
+ )
227
+ end
228
+ end
229
+ end