angelf-thinking-sphinx 1.3.18

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 (159) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +170 -0
  3. data/VERSION +1 -0
  4. data/features/abstract_inheritance.feature +10 -0
  5. data/features/alternate_primary_key.feature +27 -0
  6. data/features/attribute_transformation.feature +22 -0
  7. data/features/attribute_updates.feature +77 -0
  8. data/features/deleting_instances.feature +67 -0
  9. data/features/direct_attributes.feature +11 -0
  10. data/features/excerpts.feature +13 -0
  11. data/features/extensible_delta_indexing.feature +9 -0
  12. data/features/facets.feature +90 -0
  13. data/features/facets_across_model.feature +29 -0
  14. data/features/handling_edits.feature +92 -0
  15. data/features/retry_stale_indexes.feature +24 -0
  16. data/features/searching_across_models.feature +20 -0
  17. data/features/searching_by_index.feature +40 -0
  18. data/features/searching_by_model.feature +175 -0
  19. data/features/searching_with_find_arguments.feature +56 -0
  20. data/features/sphinx_detection.feature +25 -0
  21. data/features/sphinx_scopes.feature +42 -0
  22. data/features/step_definitions/alpha_steps.rb +16 -0
  23. data/features/step_definitions/beta_steps.rb +7 -0
  24. data/features/step_definitions/common_steps.rb +193 -0
  25. data/features/step_definitions/extensible_delta_indexing_steps.rb +7 -0
  26. data/features/step_definitions/facet_steps.rb +96 -0
  27. data/features/step_definitions/find_arguments_steps.rb +36 -0
  28. data/features/step_definitions/gamma_steps.rb +15 -0
  29. data/features/step_definitions/scope_steps.rb +15 -0
  30. data/features/step_definitions/search_steps.rb +89 -0
  31. data/features/step_definitions/sphinx_steps.rb +35 -0
  32. data/features/sti_searching.feature +19 -0
  33. data/features/support/env.rb +21 -0
  34. data/features/support/lib/generic_delta_handler.rb +8 -0
  35. data/features/thinking_sphinx/database.example.yml +3 -0
  36. data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -0
  37. data/features/thinking_sphinx/db/fixtures/authors.rb +1 -0
  38. data/features/thinking_sphinx/db/fixtures/betas.rb +11 -0
  39. data/features/thinking_sphinx/db/fixtures/boxes.rb +9 -0
  40. data/features/thinking_sphinx/db/fixtures/categories.rb +1 -0
  41. data/features/thinking_sphinx/db/fixtures/cats.rb +3 -0
  42. data/features/thinking_sphinx/db/fixtures/comments.rb +24 -0
  43. data/features/thinking_sphinx/db/fixtures/developers.rb +31 -0
  44. data/features/thinking_sphinx/db/fixtures/dogs.rb +3 -0
  45. data/features/thinking_sphinx/db/fixtures/extensible_betas.rb +10 -0
  46. data/features/thinking_sphinx/db/fixtures/foxes.rb +3 -0
  47. data/features/thinking_sphinx/db/fixtures/gammas.rb +10 -0
  48. data/features/thinking_sphinx/db/fixtures/music.rb +4 -0
  49. data/features/thinking_sphinx/db/fixtures/people.rb +1001 -0
  50. data/features/thinking_sphinx/db/fixtures/posts.rb +6 -0
  51. data/features/thinking_sphinx/db/fixtures/robots.rb +14 -0
  52. data/features/thinking_sphinx/db/fixtures/tags.rb +27 -0
  53. data/features/thinking_sphinx/db/migrations/create_alphas.rb +8 -0
  54. data/features/thinking_sphinx/db/migrations/create_animals.rb +5 -0
  55. data/features/thinking_sphinx/db/migrations/create_authors.rb +3 -0
  56. data/features/thinking_sphinx/db/migrations/create_authors_posts.rb +6 -0
  57. data/features/thinking_sphinx/db/migrations/create_betas.rb +5 -0
  58. data/features/thinking_sphinx/db/migrations/create_boxes.rb +5 -0
  59. data/features/thinking_sphinx/db/migrations/create_categories.rb +3 -0
  60. data/features/thinking_sphinx/db/migrations/create_comments.rb +10 -0
  61. data/features/thinking_sphinx/db/migrations/create_developers.rb +7 -0
  62. data/features/thinking_sphinx/db/migrations/create_extensible_betas.rb +5 -0
  63. data/features/thinking_sphinx/db/migrations/create_gammas.rb +3 -0
  64. data/features/thinking_sphinx/db/migrations/create_genres.rb +3 -0
  65. data/features/thinking_sphinx/db/migrations/create_music.rb +6 -0
  66. data/features/thinking_sphinx/db/migrations/create_people.rb +13 -0
  67. data/features/thinking_sphinx/db/migrations/create_posts.rb +5 -0
  68. data/features/thinking_sphinx/db/migrations/create_robots.rb +4 -0
  69. data/features/thinking_sphinx/db/migrations/create_taggings.rb +5 -0
  70. data/features/thinking_sphinx/db/migrations/create_tags.rb +4 -0
  71. data/features/thinking_sphinx/models/alpha.rb +22 -0
  72. data/features/thinking_sphinx/models/animal.rb +5 -0
  73. data/features/thinking_sphinx/models/author.rb +3 -0
  74. data/features/thinking_sphinx/models/beta.rb +8 -0
  75. data/features/thinking_sphinx/models/box.rb +8 -0
  76. data/features/thinking_sphinx/models/cat.rb +3 -0
  77. data/features/thinking_sphinx/models/category.rb +4 -0
  78. data/features/thinking_sphinx/models/comment.rb +10 -0
  79. data/features/thinking_sphinx/models/developer.rb +16 -0
  80. data/features/thinking_sphinx/models/dog.rb +3 -0
  81. data/features/thinking_sphinx/models/extensible_beta.rb +9 -0
  82. data/features/thinking_sphinx/models/fox.rb +5 -0
  83. data/features/thinking_sphinx/models/gamma.rb +5 -0
  84. data/features/thinking_sphinx/models/genre.rb +3 -0
  85. data/features/thinking_sphinx/models/medium.rb +5 -0
  86. data/features/thinking_sphinx/models/music.rb +8 -0
  87. data/features/thinking_sphinx/models/person.rb +23 -0
  88. data/features/thinking_sphinx/models/post.rb +21 -0
  89. data/features/thinking_sphinx/models/robot.rb +12 -0
  90. data/features/thinking_sphinx/models/tag.rb +3 -0
  91. data/features/thinking_sphinx/models/tagging.rb +4 -0
  92. data/lib/cucumber/thinking_sphinx/external_world.rb +8 -0
  93. data/lib/cucumber/thinking_sphinx/internal_world.rb +127 -0
  94. data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
  95. data/lib/thinking_sphinx.rb +242 -0
  96. data/lib/thinking_sphinx/active_record.rb +380 -0
  97. data/lib/thinking_sphinx/active_record/attribute_updates.rb +50 -0
  98. data/lib/thinking_sphinx/active_record/delta.rb +61 -0
  99. data/lib/thinking_sphinx/active_record/has_many_association.rb +51 -0
  100. data/lib/thinking_sphinx/active_record/scopes.rb +75 -0
  101. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +47 -0
  102. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +58 -0
  103. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +147 -0
  104. data/lib/thinking_sphinx/association.rb +164 -0
  105. data/lib/thinking_sphinx/attribute.rb +380 -0
  106. data/lib/thinking_sphinx/auto_version.rb +22 -0
  107. data/lib/thinking_sphinx/class_facet.rb +15 -0
  108. data/lib/thinking_sphinx/configuration.rb +292 -0
  109. data/lib/thinking_sphinx/context.rb +74 -0
  110. data/lib/thinking_sphinx/core/array.rb +7 -0
  111. data/lib/thinking_sphinx/core/string.rb +15 -0
  112. data/lib/thinking_sphinx/deltas.rb +28 -0
  113. data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
  114. data/lib/thinking_sphinx/deploy/capistrano.rb +100 -0
  115. data/lib/thinking_sphinx/excerpter.rb +22 -0
  116. data/lib/thinking_sphinx/facet.rb +125 -0
  117. data/lib/thinking_sphinx/facet_search.rb +146 -0
  118. data/lib/thinking_sphinx/field.rb +80 -0
  119. data/lib/thinking_sphinx/index.rb +157 -0
  120. data/lib/thinking_sphinx/index/builder.rb +302 -0
  121. data/lib/thinking_sphinx/index/faux_column.rb +118 -0
  122. data/lib/thinking_sphinx/join.rb +37 -0
  123. data/lib/thinking_sphinx/property.rb +168 -0
  124. data/lib/thinking_sphinx/rails_additions.rb +150 -0
  125. data/lib/thinking_sphinx/search.rb +785 -0
  126. data/lib/thinking_sphinx/search_methods.rb +439 -0
  127. data/lib/thinking_sphinx/source.rb +164 -0
  128. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  129. data/lib/thinking_sphinx/source/sql.rb +130 -0
  130. data/lib/thinking_sphinx/tasks.rb +121 -0
  131. data/lib/thinking_sphinx/test.rb +55 -0
  132. data/rails/init.rb +16 -0
  133. data/spec/thinking_sphinx/active_record/delta_spec.rb +128 -0
  134. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +71 -0
  135. data/spec/thinking_sphinx/active_record/scopes_spec.rb +177 -0
  136. data/spec/thinking_sphinx/active_record_spec.rb +618 -0
  137. data/spec/thinking_sphinx/association_spec.rb +239 -0
  138. data/spec/thinking_sphinx/attribute_spec.rb +548 -0
  139. data/spec/thinking_sphinx/auto_version_spec.rb +39 -0
  140. data/spec/thinking_sphinx/configuration_spec.rb +271 -0
  141. data/spec/thinking_sphinx/context_spec.rb +126 -0
  142. data/spec/thinking_sphinx/core/array_spec.rb +9 -0
  143. data/spec/thinking_sphinx/core/string_spec.rb +9 -0
  144. data/spec/thinking_sphinx/excerpter_spec.rb +49 -0
  145. data/spec/thinking_sphinx/facet_search_spec.rb +176 -0
  146. data/spec/thinking_sphinx/facet_spec.rb +333 -0
  147. data/spec/thinking_sphinx/field_spec.rb +113 -0
  148. data/spec/thinking_sphinx/index/builder_spec.rb +495 -0
  149. data/spec/thinking_sphinx/index/faux_column_spec.rb +36 -0
  150. data/spec/thinking_sphinx/index_spec.rb +183 -0
  151. data/spec/thinking_sphinx/rails_additions_spec.rb +203 -0
  152. data/spec/thinking_sphinx/search_methods_spec.rb +152 -0
  153. data/spec/thinking_sphinx/search_spec.rb +1206 -0
  154. data/spec/thinking_sphinx/source_spec.rb +243 -0
  155. data/spec/thinking_sphinx_spec.rb +204 -0
  156. data/tasks/distribution.rb +46 -0
  157. data/tasks/rails.rake +1 -0
  158. data/tasks/testing.rb +76 -0
  159. metadata +342 -0
@@ -0,0 +1,50 @@
1
+ module ThinkingSphinx
2
+ module ActiveRecord
3
+ module AttributeUpdates
4
+ def self.included(base)
5
+ base.class_eval do
6
+ after_commit :update_attribute_values
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ def update_attribute_values
13
+ return true unless ThinkingSphinx.updates_enabled? &&
14
+ ThinkingSphinx.sphinx_running?
15
+
16
+ self.class.sphinx_indexes.each do |index|
17
+ attribute_pairs = attribute_values_for_index(index)
18
+ attribute_names = attribute_pairs.keys
19
+ attribute_values = attribute_names.collect { |key|
20
+ attribute_pairs[key]
21
+ }
22
+
23
+ update_index index.core_name, attribute_names, attribute_values
24
+ next unless index.delta?
25
+ update_index index.delta_name, attribute_names, attribute_values
26
+ end
27
+
28
+ true
29
+ end
30
+
31
+ def updatable_attributes(index)
32
+ index.attributes.select { |attrib| attrib.updatable? }
33
+ end
34
+
35
+ def attribute_values_for_index(index)
36
+ updatable_attributes(index).inject({}) { |hash, attrib|
37
+ hash[attrib.unique_name.to_s] = attrib.live_value self
38
+ hash
39
+ }
40
+ end
41
+
42
+ def update_index(index_name, attribute_names, attribute_values)
43
+ config = ThinkingSphinx::Configuration.instance
44
+ config.client.update index_name, attribute_names, {
45
+ sphinx_document_id => attribute_values
46
+ } if self.class.search_for_id(sphinx_document_id, index_name)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,61 @@
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_object.index(self, instance)
20
+ end
21
+
22
+ def delta_object
23
+ self.sphinx_indexes.first.delta_object
24
+ end
25
+ end
26
+
27
+ def toggled_delta?
28
+ self.class.delta_object.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_object.toggle(self) if should_toggle_delta?
36
+ end
37
+
38
+ # Build the delta index for the related model. This won't be called
39
+ # if running in the test environment.
40
+ #
41
+ def index_delta
42
+ self.class.index_delta(self) if self.class.delta_object.toggled(self)
43
+ end
44
+
45
+ def should_toggle_delta?
46
+ self.new_record? || indexed_data_changed?
47
+ end
48
+
49
+ def indexed_data_changed?
50
+ sphinx_indexes.any? { |index|
51
+ index.fields.any? { |field| field.changed?(self) } ||
52
+ index.attributes.any? { |attrib|
53
+ attrib.public? && attrib.changed?(self) && !attrib.updatable?
54
+ }
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,51 @@
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
+ def method_missing(method, *args, &block)
14
+ if responds_to_scope(method)
15
+ @reflection.klass.
16
+ search(:with => default_filter).
17
+ send(method, *args, &block)
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def attribute_for_foreign_key
26
+ foreign_key = @reflection.primary_key_name
27
+ stack = [@reflection.options[:through]].compact
28
+
29
+ @reflection.klass.define_indexes
30
+ (@reflection.klass.sphinx_indexes || []).each do |index|
31
+ attribute = index.attributes.detect { |attrib|
32
+ attrib.columns.length == 1 &&
33
+ attrib.columns.first.__name == foreign_key.to_sym
34
+ }
35
+ return attribute unless attribute.nil?
36
+ end
37
+
38
+ raise "Missing Attribute for Foreign Key #{foreign_key}"
39
+ end
40
+
41
+ def default_filter
42
+ {attribute_for_foreign_key.unique_name => @owner.id}
43
+ end
44
+
45
+ def responds_to_scope(scope)
46
+ @reflection.klass.respond_to?(:sphinx_scopes) &&
47
+ @reflection.klass.sphinx_scopes.include?(scope)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,75 @@
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
+ @default_sphinx_scope = sphinx_scope_name
21
+ end
22
+
23
+ # Returns the default_sphinx_scope or nil if none is set.
24
+ def get_default_sphinx_scope
25
+ @default_sphinx_scope
26
+ end
27
+
28
+ # Returns true if the current Model has a default_sphinx_scope. Also checks if
29
+ # the default_sphinx_scope actually is a scope.
30
+ def has_default_sphinx_scope?
31
+ !@default_sphinx_scope.nil? && sphinx_scopes.include?(@default_sphinx_scope)
32
+ end
33
+
34
+ # Similar to ActiveRecord's named_scope method Thinking Sphinx supports
35
+ # scopes. For example:
36
+ #
37
+ # sphinx_scope(:latest_first) {
38
+ # {:order => 'created_at DESC, @relevance DESC'}
39
+ # }
40
+ #
41
+ # Usage:
42
+ #
43
+ # @articles = Article.latest_first.search 'pancakes'
44
+ #
45
+ def sphinx_scope(method, &block)
46
+ @sphinx_scopes ||= []
47
+ @sphinx_scopes << method
48
+
49
+ singleton_class.instance_eval do
50
+ define_method(method) do |*args|
51
+ options = {:classes => classes_option}
52
+ options.merge! block.call(*args)
53
+
54
+ ThinkingSphinx::Search.new(options)
55
+ end
56
+ end
57
+ end
58
+
59
+ # This returns an Array of all defined scopes. The default
60
+ # scope shows as :default.
61
+ def sphinx_scopes
62
+ @sphinx_scopes || []
63
+ end
64
+
65
+ def remove_sphinx_scopes
66
+ sphinx_scopes.each do |scope|
67
+ singleton_class.send(:undef_method, scope)
68
+ end
69
+
70
+ sphinx_scopes.clear
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,47 @@
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
+ case model.connection.class.name == "MultiDb::ConnectionProxy" ? model.connection.master.connection.class.name : model.connection.class.name
14
+ when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
15
+ "ActiveRecord::ConnectionAdapters::MysqlplusAdapter",
16
+ "ActiveRecord::ConnectionAdapters::Mysql2Adapter"
17
+ ThinkingSphinx::MysqlAdapter.new model
18
+ when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
19
+ ThinkingSphinx::PostgreSQLAdapter.new model
20
+ when "ActiveRecord::ConnectionAdapters::JdbcAdapter"
21
+ if model.connection.config[:adapter] == "jdbcmysql"
22
+ ThinkingSphinx::MysqlAdapter.new model
23
+ elsif model.connection.config[:adapter] == "jdbcpostgresql"
24
+ ThinkingSphinx::PostgreSQLAdapter.new model
25
+ else
26
+ raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
27
+ end
28
+ else
29
+ raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{model.connection.class.name}"
30
+ end
31
+ end
32
+
33
+ def quote_with_table(column)
34
+ "#{@model.quoted_table_name}.#{@model.connection.quote_column_name(column)}"
35
+ end
36
+
37
+ def bigint_pattern
38
+ /bigint/i
39
+ end
40
+
41
+ protected
42
+
43
+ def connection
44
+ @connection ||= @model.connection
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,58 @@
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 convert_nulls(clause, default = '')
32
+ default = "'#{default}'" if default.is_a?(String)
33
+
34
+ "IFNULL(#{clause}, #{default})"
35
+ end
36
+
37
+ def boolean(value)
38
+ value ? 1 : 0
39
+ end
40
+
41
+ def crc(clause, blank_to_null = false)
42
+ clause = "NULLIF(#{clause},'')" if blank_to_null
43
+ "CRC32(#{clause})"
44
+ end
45
+
46
+ def utf8_query_pre
47
+ "SET NAMES utf8"
48
+ end
49
+
50
+ def time_difference(diff)
51
+ "DATE_SUB(NOW(), INTERVAL #{diff} SECOND)"
52
+ end
53
+
54
+ def utc_query_pre
55
+ "SET TIME_ZONE = '+0:00'"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,147 @@
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 convert_nulls(clause, default = '')
39
+ default = case default
40
+ when String
41
+ "'#{default}'"
42
+ when NilClass
43
+ 'NULL'
44
+ when Fixnum
45
+ "#{default}::bigint"
46
+ else
47
+ default
48
+ end
49
+
50
+ "COALESCE(#{clause}, #{default})"
51
+ end
52
+
53
+ def boolean(value)
54
+ value ? 'TRUE' : 'FALSE'
55
+ end
56
+
57
+ def crc(clause, blank_to_null = false)
58
+ clause = "NULLIF(#{clause},'')" if blank_to_null
59
+ "crc32(#{clause})"
60
+ end
61
+
62
+ def utf8_query_pre
63
+ nil
64
+ end
65
+
66
+ def time_difference(diff)
67
+ "current_timestamp - interval '#{diff} seconds'"
68
+ end
69
+
70
+ def utc_query_pre
71
+ 'SET TIME ZONE UTC'
72
+ end
73
+
74
+ private
75
+
76
+ def execute(command, output_error = false)
77
+ connection.execute "begin"
78
+ connection.execute "savepoint ts"
79
+ begin
80
+ connection.execute command
81
+ rescue StandardError => err
82
+ puts err if output_error
83
+ connection.execute "rollback to savepoint ts"
84
+ end
85
+ connection.execute "release savepoint ts"
86
+ connection.execute "commit"
87
+ end
88
+
89
+ def create_array_accum_function
90
+ if connection.raw_connection.respond_to?(:server_version) && connection.raw_connection.server_version > 80200
91
+ execute <<-SQL
92
+ CREATE AGGREGATE array_accum (anyelement)
93
+ (
94
+ sfunc = array_append,
95
+ stype = anyarray,
96
+ initcond = '{}'
97
+ );
98
+ SQL
99
+ else
100
+ execute <<-SQL
101
+ CREATE AGGREGATE array_accum
102
+ (
103
+ basetype = anyelement,
104
+ sfunc = array_append,
105
+ stype = anyarray,
106
+ initcond = '{}'
107
+ );
108
+ SQL
109
+ end
110
+ end
111
+
112
+ def create_crc32_function
113
+ execute "CREATE LANGUAGE 'plpgsql';"
114
+ function = <<-SQL
115
+ CREATE OR REPLACE FUNCTION crc32(word text)
116
+ RETURNS bigint AS $$
117
+ DECLARE tmp bigint;
118
+ DECLARE i int;
119
+ DECLARE j int;
120
+ DECLARE word_array bytea;
121
+ BEGIN
122
+ i = 0;
123
+ tmp = 4294967295;
124
+ word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
125
+ LOOP
126
+ tmp = (tmp # get_byte(word_array, i))::bigint;
127
+ i = i + 1;
128
+ j = 0;
129
+ LOOP
130
+ tmp = ((tmp >> 1) # (3988292384 * (tmp & 1)))::bigint;
131
+ j = j + 1;
132
+ IF j >= 8 THEN
133
+ EXIT;
134
+ END IF;
135
+ END LOOP;
136
+ IF i >= char_length(word) THEN
137
+ EXIT;
138
+ END IF;
139
+ END LOOP;
140
+ return (tmp # 4294967295);
141
+ END
142
+ $$ IMMUTABLE STRICT LANGUAGE plpgsql;
143
+ SQL
144
+ execute function, true
145
+ end
146
+ end
147
+ end