thinking-sphinx 2.0.6 → 2.0.7

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