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,110 @@
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
+ mixin = sphinx_scopes_support_mixin
85
+ sphinx_scopes_support_classes.each do |klass|
86
+ klass.send(:include, mixin) unless klass.ancestors.include?(mixin)
87
+ end
88
+ end
89
+
90
+ def sphinx_scopes_support_classes
91
+ if ThinkingSphinx.rails_3_1?
92
+ [::ActiveRecord::Associations::CollectionProxy]
93
+ else
94
+ [::ActiveRecord::Associations::HasManyAssociation,
95
+ ::ActiveRecord::Associations::HasManyThroughAssociation]
96
+ end
97
+ end
98
+
99
+ def sphinx_scopes_support_mixin
100
+ if ThinkingSphinx.rails_3_1?
101
+ ::ThinkingSphinx::ActiveRecord::CollectionProxyWithScopes
102
+ else
103
+ ::ThinkingSphinx::ActiveRecord::HasManyAssociationWithScopes
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,383 @@
1
+ require 'thinking_sphinx/active_record/attribute_updates'
2
+ require 'thinking_sphinx/active_record/collection_proxy'
3
+ require 'thinking_sphinx/active_record/collection_proxy_with_scopes'
4
+ require 'thinking_sphinx/active_record/delta'
5
+ require 'thinking_sphinx/active_record/has_many_association'
6
+ require 'thinking_sphinx/active_record/log_subscriber'
7
+ require 'thinking_sphinx/active_record/has_many_association_with_scopes'
8
+ require 'thinking_sphinx/active_record/scopes'
9
+
10
+ module ThinkingSphinx
11
+ # Core additions to ActiveRecord models - define_index for creating indexes
12
+ # for models. If you want to interrogate the index objects created for the
13
+ # model, you can use the class-level accessor :sphinx_indexes.
14
+ #
15
+ module ActiveRecord
16
+ def self.included(base)
17
+ base.class_eval do
18
+ if defined?(class_attribute)
19
+ class_attribute :sphinx_indexes, :sphinx_facets
20
+ else
21
+ class_inheritable_array :sphinx_indexes, :sphinx_facets
22
+ end
23
+
24
+ extend ThinkingSphinx::ActiveRecord::ClassMethods
25
+
26
+ class << self
27
+ attr_accessor :sphinx_index_blocks
28
+
29
+ def set_sphinx_primary_key(attribute)
30
+ @sphinx_primary_key_attribute = attribute
31
+ end
32
+
33
+ def primary_key_for_sphinx
34
+ @primary_key_for_sphinx ||= begin
35
+ if custom_primary_key_for_sphinx?
36
+ @sphinx_primary_key_attribute ||
37
+ superclass.primary_key_for_sphinx
38
+ else
39
+ primary_key
40
+ end
41
+ end
42
+ end
43
+
44
+ def custom_primary_key_for_sphinx?
45
+ (
46
+ superclass.respond_to?(:custom_primary_key_for_sphinx?) &&
47
+ superclass.custom_primary_key_for_sphinx?
48
+ ) || !@sphinx_primary_key_attribute.nil?
49
+ end
50
+
51
+ def clear_primary_key_for_sphinx
52
+ @primary_key_for_sphinx = nil
53
+ end
54
+
55
+ def sphinx_index_options
56
+ sphinx_indexes.last.options
57
+ end
58
+
59
+ # Generate a unique CRC value for the model's name, to use to
60
+ # determine which Sphinx documents belong to which AR records.
61
+ #
62
+ # Really only written for internal use - but hey, if it's useful to
63
+ # you in some other way, awesome.
64
+ #
65
+ def to_crc32
66
+ self.name.to_crc32
67
+ end
68
+
69
+ def to_crc32s
70
+ (descendants << self).collect { |klass| klass.to_crc32 }
71
+ end
72
+
73
+ def sphinx_database_adapter
74
+ ThinkingSphinx::AbstractAdapter.detect(self)
75
+ end
76
+
77
+ def sphinx_name
78
+ self.name.underscore.tr(':/\\', '_')
79
+ end
80
+
81
+ private
82
+
83
+ def defined_indexes?
84
+ @defined_indexes
85
+ end
86
+
87
+ def defined_indexes=(value)
88
+ @defined_indexes = value
89
+ end
90
+
91
+ def sphinx_delta?
92
+ self.sphinx_indexes.any? { |index| index.delta? }
93
+ end
94
+ end
95
+ end
96
+
97
+ if ThinkingSphinx.rails_3_1?
98
+ assoc_mixin = ThinkingSphinx::ActiveRecord::CollectionProxy
99
+ ::ActiveRecord::Associations::CollectionProxy.send(:include, assoc_mixin)
100
+ else
101
+ assoc_mixin = ThinkingSphinx::ActiveRecord::HasManyAssociation
102
+ ::ActiveRecord::Associations::HasManyAssociation.send(:include, assoc_mixin)
103
+ ::ActiveRecord::Associations::HasManyThroughAssociation.send(:include, assoc_mixin)
104
+ end
105
+ end
106
+
107
+ module ClassMethods
108
+ # Allows creation of indexes for Sphinx. If you don't do this, there
109
+ # isn't much point trying to search (or using this plugin at all,
110
+ # really).
111
+ #
112
+ # An example or two:
113
+ #
114
+ # define_index
115
+ # indexes :id, :as => :model_id
116
+ # indexes name
117
+ # end
118
+ #
119
+ # You can also grab fields from associations - multiple levels deep
120
+ # if necessary.
121
+ #
122
+ # define_index do
123
+ # indexes tags.name, :as => :tag
124
+ # indexes articles.content
125
+ # indexes orders.line_items.product.name, :as => :product
126
+ # end
127
+ #
128
+ # And it will automatically concatenate multiple fields:
129
+ #
130
+ # define_index do
131
+ # indexes [author.first_name, author.last_name], :as => :author
132
+ # end
133
+ #
134
+ # The #indexes method is for fields - if you want attributes, use
135
+ # #has instead. All the same rules apply - but keep in mind that
136
+ # attributes are for sorting, grouping and filtering, not searching.
137
+ #
138
+ # define_index do
139
+ # # fields ...
140
+ #
141
+ # has created_at, updated_at
142
+ # end
143
+ #
144
+ # One last feature is the delta index. This requires the model to
145
+ # have a boolean field named 'delta', and is enabled as follows:
146
+ #
147
+ # define_index do
148
+ # # fields ...
149
+ # # attributes ...
150
+ #
151
+ # set_property :delta => true
152
+ # end
153
+ #
154
+ # Check out the more detailed documentation for each of these methods
155
+ # at ThinkingSphinx::Index::Builder.
156
+ #
157
+ def define_index(name = nil, &block)
158
+ self.sphinx_index_blocks ||= []
159
+ self.sphinx_indexes ||= []
160
+ self.sphinx_facets ||= []
161
+
162
+ ThinkingSphinx.context.add_indexed_model self
163
+
164
+ if sphinx_index_blocks.empty?
165
+ before_validation :define_indexes
166
+ before_destroy :define_indexes
167
+ end
168
+
169
+ self.sphinx_index_blocks << lambda {
170
+ add_sphinx_index name, &block
171
+ }
172
+
173
+ include ThinkingSphinx::ActiveRecord::Scopes
174
+ include ThinkingSphinx::SearchMethods
175
+ end
176
+
177
+ def define_indexes
178
+ superclass.define_indexes unless superclass == ::ActiveRecord::Base
179
+
180
+ return if sphinx_index_blocks.nil? ||
181
+ defined_indexes? ||
182
+ !ThinkingSphinx.define_indexes?
183
+
184
+ sphinx_index_blocks.each do |block|
185
+ block.call
186
+ end
187
+
188
+ self.defined_indexes = true
189
+
190
+ # We want to make sure that if the database doesn't exist, then Thinking
191
+ # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
192
+ # and db:migrate). It's a bit hacky, but I can't think of a better way.
193
+ rescue StandardError => err
194
+ case err.class.name
195
+ when "Mysql::Error", "Mysql2::Error", "Java::JavaSql::SQLException", "ActiveRecord::StatementInvalid"
196
+ return
197
+ else
198
+ raise err
199
+ end
200
+ end
201
+
202
+ def add_sphinx_index(name, &block)
203
+ index = ThinkingSphinx::Index::Builder.generate self, name, &block
204
+
205
+ unless sphinx_indexes.any? { |i| i.name == index.name }
206
+ add_sphinx_callbacks_and_extend(index.delta?)
207
+ insert_sphinx_index index
208
+ end
209
+ end
210
+
211
+ def insert_sphinx_index(index)
212
+ self.sphinx_indexes += [index]
213
+ end
214
+
215
+ def has_sphinx_indexes?
216
+ sphinx_indexes &&
217
+ sphinx_index_blocks &&
218
+ (sphinx_indexes.length > 0 || sphinx_index_blocks.length > 0)
219
+ end
220
+
221
+ def indexed_by_sphinx?
222
+ sphinx_indexes && sphinx_indexes.length > 0
223
+ end
224
+
225
+ def delta_indexed_by_sphinx?
226
+ sphinx_indexes && sphinx_indexes.any? { |index| index.delta? }
227
+ end
228
+
229
+ def sphinx_index_names
230
+ define_indexes
231
+ sphinx_indexes.collect(&:all_names).flatten
232
+ end
233
+
234
+ def core_index_names
235
+ define_indexes
236
+ sphinx_indexes.collect(&:core_name)
237
+ end
238
+
239
+ def delta_index_names
240
+ define_indexes
241
+ sphinx_indexes.select(&:delta?).collect(&:delta_name)
242
+ end
243
+
244
+ def to_riddle
245
+ define_indexes
246
+ sphinx_database_adapter.setup
247
+
248
+ local_sphinx_indexes.collect { |index|
249
+ index.to_riddle(sphinx_offset)
250
+ }.flatten
251
+ end
252
+
253
+ def source_of_sphinx_index
254
+ define_indexes
255
+ possible_models = self.sphinx_indexes.collect { |index| index.model }
256
+ return self if possible_models.include?(self)
257
+
258
+ parent = self.superclass
259
+ while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
260
+ parent = parent.superclass
261
+ end
262
+
263
+ return parent
264
+ end
265
+
266
+ def delete_in_index(index, document_id)
267
+ return unless ThinkingSphinx.sphinx_running? &&
268
+ search_for_id(document_id, index)
269
+
270
+ ThinkingSphinx::Configuration.instance.client.update(
271
+ index, ['sphinx_deleted'], {document_id => [1]}
272
+ )
273
+ rescue Riddle::ConnectionError, Riddle::ResponseError,
274
+ ThinkingSphinx::SphinxError, Errno::ETIMEDOUT
275
+ # Not the end of the world if Sphinx isn't running.
276
+ end
277
+
278
+ def sphinx_offset
279
+ ThinkingSphinx.context.superclass_indexed_models.
280
+ index eldest_indexed_ancestor
281
+ end
282
+
283
+ # Temporarily disable delta indexing inside a block, then perform a
284
+ # single rebuild of index at the end.
285
+ #
286
+ # Useful when performing updates to batches of models to prevent
287
+ # the delta index being rebuilt after each individual update.
288
+ #
289
+ # In the following example, the delta index will only be rebuilt
290
+ # once, not 10 times.
291
+ #
292
+ # SomeModel.suspended_delta do
293
+ # 10.times do
294
+ # SomeModel.create( ... )
295
+ # end
296
+ # end
297
+ #
298
+ def suspended_delta(reindex_after = true, &block)
299
+ define_indexes
300
+ original_setting = ThinkingSphinx.deltas_suspended?
301
+ ThinkingSphinx.deltas_suspended = true
302
+ begin
303
+ yield
304
+ ensure
305
+ ThinkingSphinx.deltas_suspended = original_setting
306
+ self.index_delta if reindex_after && !original_setting
307
+ end
308
+ end
309
+
310
+ private
311
+
312
+ def local_sphinx_indexes
313
+ sphinx_indexes.select { |index|
314
+ index.model == self
315
+ }
316
+ end
317
+
318
+ def add_sphinx_callbacks_and_extend(delta = false)
319
+ unless indexed_by_sphinx?
320
+ after_destroy :toggle_deleted
321
+
322
+ include ThinkingSphinx::ActiveRecord::AttributeUpdates
323
+ end
324
+
325
+ if delta && !delta_indexed_by_sphinx?
326
+ include ThinkingSphinx::ActiveRecord::Delta
327
+
328
+ before_save :toggle_delta
329
+ after_commit :index_delta
330
+ end
331
+ end
332
+
333
+ def eldest_indexed_ancestor
334
+ ancestors.reverse.detect { |ancestor|
335
+ ThinkingSphinx.context.indexed_models.include?(ancestor.name)
336
+ }.name
337
+ end
338
+ end
339
+
340
+ attr_accessor :excerpts
341
+ attr_accessor :sphinx_attributes
342
+ attr_accessor :matching_fields
343
+
344
+ def toggle_deleted
345
+ return unless ThinkingSphinx.updates_enabled?
346
+
347
+ self.class.core_index_names.each do |index_name|
348
+ self.class.delete_in_index index_name, self.sphinx_document_id
349
+ end
350
+ self.class.delta_index_names.each do |index_name|
351
+ self.class.delete_in_index index_name, self.sphinx_document_id
352
+ end if self.class.delta_indexed_by_sphinx? && toggled_delta?
353
+
354
+ rescue ::ThinkingSphinx::ConnectionError
355
+ # nothing
356
+ end
357
+
358
+ # Returns the unique integer id for the object. This method uses the
359
+ # attribute hash to get around ActiveRecord always mapping the #id method
360
+ # to whatever the real primary key is (which may be a unique string hash).
361
+ #
362
+ # @return [Integer] Unique record id for the purposes of Sphinx.
363
+ #
364
+ def primary_key_for_sphinx
365
+ read_attribute(self.class.primary_key_for_sphinx)
366
+ end
367
+
368
+ def sphinx_document_id
369
+ primary_key_for_sphinx * ThinkingSphinx.context.indexed_models.size +
370
+ self.class.sphinx_offset
371
+ end
372
+
373
+ private
374
+
375
+ def sphinx_index_name(suffix)
376
+ "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
377
+ end
378
+
379
+ def define_indexes
380
+ self.class.define_indexes
381
+ end
382
+ end
383
+ end
@@ -0,0 +1,87 @@
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].to_sym
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
@@ -0,0 +1,62 @@
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