initforthe-thinking-sphinx 1.1.21
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.
- data/LICENCE +20 -0
- data/README.textile +141 -0
- data/lib/thinking_sphinx.rb +215 -0
- data/lib/thinking_sphinx/active_record.rb +278 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
- data/lib/thinking_sphinx/active_record/delta.rb +87 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
- data/lib/thinking_sphinx/active_record/search.rb +57 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +135 -0
- data/lib/thinking_sphinx/association.rb +164 -0
- data/lib/thinking_sphinx/attribute.rb +268 -0
- data/lib/thinking_sphinx/class_facet.rb +15 -0
- data/lib/thinking_sphinx/collection.rb +148 -0
- data/lib/thinking_sphinx/configuration.rb +262 -0
- data/lib/thinking_sphinx/core/string.rb +15 -0
- data/lib/thinking_sphinx/deltas.rb +30 -0
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +27 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +82 -0
- data/lib/thinking_sphinx/facet.rb +108 -0
- data/lib/thinking_sphinx/facet_collection.rb +59 -0
- data/lib/thinking_sphinx/field.rb +82 -0
- data/lib/thinking_sphinx/index.rb +99 -0
- data/lib/thinking_sphinx/index/builder.rb +287 -0
- data/lib/thinking_sphinx/index/faux_column.rb +110 -0
- data/lib/thinking_sphinx/property.rb +160 -0
- data/lib/thinking_sphinx/rails_additions.rb +136 -0
- data/lib/thinking_sphinx/search.rb +727 -0
- data/lib/thinking_sphinx/search/facets.rb +104 -0
- data/lib/thinking_sphinx/source.rb +150 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
- data/lib/thinking_sphinx/source/sql.rb +126 -0
- data/lib/thinking_sphinx/tasks.rb +162 -0
- data/rails/init.rb +14 -0
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
- data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
- data/spec/unit/thinking_sphinx/active_record_spec.rb +329 -0
- data/spec/unit/thinking_sphinx/association_spec.rb +246 -0
- data/spec/unit/thinking_sphinx/attribute_spec.rb +338 -0
- data/spec/unit/thinking_sphinx/collection_spec.rb +15 -0
- data/spec/unit/thinking_sphinx/configuration_spec.rb +222 -0
- data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
- data/spec/unit/thinking_sphinx/facet_collection_spec.rb +64 -0
- data/spec/unit/thinking_sphinx/facet_spec.rb +302 -0
- data/spec/unit/thinking_sphinx/field_spec.rb +154 -0
- data/spec/unit/thinking_sphinx/index/builder_spec.rb +355 -0
- data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +45 -0
- data/spec/unit/thinking_sphinx/rails_additions_spec.rb +191 -0
- data/spec/unit/thinking_sphinx/search_spec.rb +228 -0
- data/spec/unit/thinking_sphinx/source_spec.rb +217 -0
- data/spec/unit/thinking_sphinx_spec.rb +151 -0
- data/tasks/distribution.rb +67 -0
- data/tasks/rails.rake +1 -0
- data/tasks/testing.rb +78 -0
- data/vendor/after_commit/LICENSE +20 -0
- data/vendor/after_commit/README +16 -0
- data/vendor/after_commit/Rakefile +22 -0
- data/vendor/after_commit/init.rb +8 -0
- data/vendor/after_commit/lib/after_commit.rb +45 -0
- data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
- data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
- data/vendor/after_commit/test/after_commit_test.rb +53 -0
- data/vendor/delayed_job/lib/delayed/job.rb +251 -0
- data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
- data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
- data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
- data/vendor/riddle/lib/riddle.rb +30 -0
- data/vendor/riddle/lib/riddle/client.rb +619 -0
- data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
- data/vendor/riddle/lib/riddle/client/message.rb +65 -0
- data/vendor/riddle/lib/riddle/client/response.rb +84 -0
- data/vendor/riddle/lib/riddle/configuration.rb +33 -0
- data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
- data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
- data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
- data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
- data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
- data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
- data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
- data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
- data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
- data/vendor/riddle/lib/riddle/controller.rb +44 -0
- metadata +190 -0
|
@@ -0,0 +1,48 @@
|
|
|
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 unless ThinkingSphinx.updates_enabled? && ThinkingSphinx.sphinx_running?
|
|
14
|
+
|
|
15
|
+
config = ThinkingSphinx::Configuration.instance
|
|
16
|
+
client = Riddle::Client.new config.address, config.port
|
|
17
|
+
|
|
18
|
+
self.sphinx_indexes.each do |index|
|
|
19
|
+
attribute_pairs = attribute_values_for_index(index)
|
|
20
|
+
attribute_names = attribute_pairs.keys
|
|
21
|
+
attribute_values = attribute_names.collect { |key|
|
|
22
|
+
attribute_pairs[key]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
client.update "#{index.name}_core", attribute_names, {
|
|
26
|
+
sphinx_document_id => attribute_values
|
|
27
|
+
} if in_core_index?
|
|
28
|
+
end
|
|
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
|
+
if attrib.type == :datetime
|
|
38
|
+
hash[attrib.unique_name.to_s] = attrib.live_value(self).to_time.to_i
|
|
39
|
+
else
|
|
40
|
+
hash[attrib.unique_name.to_s] = attrib.live_value self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
hash
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
# Temporarily disable delta indexing inside a block, then perform a single
|
|
16
|
+
# rebuild of index at the end.
|
|
17
|
+
#
|
|
18
|
+
# Useful when performing updates to batches of models to prevent
|
|
19
|
+
# the delta index being rebuilt after each individual update.
|
|
20
|
+
#
|
|
21
|
+
# In the following example, the delta index will only be rebuilt once,
|
|
22
|
+
# not 10 times.
|
|
23
|
+
#
|
|
24
|
+
# SomeModel.suspended_delta do
|
|
25
|
+
# 10.times do
|
|
26
|
+
# SomeModel.create( ... )
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
def suspended_delta(reindex_after = true, &block)
|
|
31
|
+
original_setting = ThinkingSphinx.deltas_enabled?
|
|
32
|
+
ThinkingSphinx.deltas_enabled = false
|
|
33
|
+
begin
|
|
34
|
+
yield
|
|
35
|
+
ensure
|
|
36
|
+
ThinkingSphinx.deltas_enabled = original_setting
|
|
37
|
+
self.index_delta if reindex_after
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Build the delta index for the related model. This won't be called
|
|
42
|
+
# if running in the test environment.
|
|
43
|
+
#
|
|
44
|
+
def index_delta(instance = nil)
|
|
45
|
+
delta_object.index(self, instance)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def delta_object
|
|
49
|
+
self.sphinx_indexes.first.delta_object
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def toggled_delta?
|
|
54
|
+
self.class.delta_object.toggled(self)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Set the delta value for the model to be true.
|
|
60
|
+
def toggle_delta
|
|
61
|
+
self.class.delta_object.toggle(self) if should_toggle_delta?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Build the delta index for the related model. This won't be called
|
|
65
|
+
# if running in the test environment.
|
|
66
|
+
#
|
|
67
|
+
def index_delta
|
|
68
|
+
self.class.index_delta(self) if self.class.delta_object.toggled(self)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def should_toggle_delta?
|
|
72
|
+
self.new_record? || indexed_data_changed?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def indexed_data_changed?
|
|
76
|
+
sphinx_indexes.any? { |index|
|
|
77
|
+
index.fields.any? { |field| field.changed?(self) } ||
|
|
78
|
+
index.attributes.any? { |attrib|
|
|
79
|
+
attrib.public? && attrib.changed?(self) && !attrib.updatable?
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module HasManyAssociation
|
|
4
|
+
def search(*args)
|
|
5
|
+
foreign_key = @reflection.primary_key_name
|
|
6
|
+
stack = [@reflection.options[:through]].compact
|
|
7
|
+
|
|
8
|
+
attribute = nil
|
|
9
|
+
(@reflection.klass.sphinx_indexes || []).each do |index|
|
|
10
|
+
attribute = index.attributes.detect { |attrib|
|
|
11
|
+
attrib.columns.length == 1 &&
|
|
12
|
+
attrib.columns.first.__name == foreign_key.to_sym &&
|
|
13
|
+
attrib.columns.first.__stack == stack
|
|
14
|
+
}
|
|
15
|
+
break if attribute
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
raise "Missing Attribute for Foreign Key #{foreign_key}" unless attribute
|
|
19
|
+
|
|
20
|
+
options = args.extract_options!
|
|
21
|
+
options[:with] ||= {}
|
|
22
|
+
options[:with][attribute.unique_name] = @owner.id
|
|
23
|
+
|
|
24
|
+
args << options
|
|
25
|
+
@reflection.klass.search(*args)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
# This module covers the specific model searches - but the syntax is
|
|
4
|
+
# exactly the same as the core Search class - so use that as your refence
|
|
5
|
+
# point.
|
|
6
|
+
#
|
|
7
|
+
module Search
|
|
8
|
+
def self.included(base)
|
|
9
|
+
base.class_eval do
|
|
10
|
+
class << self
|
|
11
|
+
# Searches for results that match the parameters provided. Will only
|
|
12
|
+
# return the ids for the matching objects. See
|
|
13
|
+
# ThinkingSphinx::Search#search for syntax examples.
|
|
14
|
+
#
|
|
15
|
+
def search_for_ids(*args)
|
|
16
|
+
options = args.extract_options!
|
|
17
|
+
options[:class] = self
|
|
18
|
+
args << options
|
|
19
|
+
ThinkingSphinx::Search.search_for_ids(*args)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Searches for results limited to a single model. See
|
|
23
|
+
# ThinkingSphinx::Search#search for syntax examples.
|
|
24
|
+
#
|
|
25
|
+
def search(*args)
|
|
26
|
+
options = args.extract_options!
|
|
27
|
+
options[:class] = self
|
|
28
|
+
args << options
|
|
29
|
+
ThinkingSphinx::Search.search(*args)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def search_count(*args)
|
|
33
|
+
options = args.extract_options!
|
|
34
|
+
options[:class] = self
|
|
35
|
+
args << options
|
|
36
|
+
ThinkingSphinx::Search.count(*args)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def search_for_id(*args)
|
|
40
|
+
options = args.extract_options!
|
|
41
|
+
options[:class] = self
|
|
42
|
+
args << options
|
|
43
|
+
ThinkingSphinx::Search.search_for_id(*args)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def facets(*args)
|
|
47
|
+
options = args.extract_options!
|
|
48
|
+
options[:class] = self
|
|
49
|
+
args << options
|
|
50
|
+
ThinkingSphinx::Search.facets(*args)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
|
14
|
+
when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
|
|
15
|
+
"ActiveRecord::ConnectionAdapters::MysqlplusAdapter"
|
|
16
|
+
ThinkingSphinx::MysqlAdapter.new model
|
|
17
|
+
when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
|
|
18
|
+
ThinkingSphinx::PostgreSQLAdapter.new model
|
|
19
|
+
when "ActiveRecord::ConnectionAdapters::JdbcAdapter"
|
|
20
|
+
if model.connection.config[:adapter] == "jdbcmysql"
|
|
21
|
+
ThinkingSphinx::MysqlAdapter.new model
|
|
22
|
+
elsif model.connection.config[:adapter] == "jdbcpostgresql"
|
|
23
|
+
ThinkingSphinx::PostgreSQLAdapter.new model
|
|
24
|
+
else
|
|
25
|
+
raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL"
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
raise "Invalid Database Adapter: Sphinx only supports MySQL and PostgreSQL, not #{model.connection.class.name}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def quote_with_table(column)
|
|
33
|
+
"#{@model.quoted_table_name}.#{@model.connection.quote_column_name(column)}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
def connection
|
|
39
|
+
@connection ||= @model.connection
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
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 #{clause} 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
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
clause.split(', ').collect { |field|
|
|
14
|
+
case field
|
|
15
|
+
when /COALESCE/, "'')"
|
|
16
|
+
field
|
|
17
|
+
else
|
|
18
|
+
"COALESCE(CAST(#{field} as varchar), '')"
|
|
19
|
+
end
|
|
20
|
+
}.join(" || '#{separator}' || ")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def group_concatenate(clause, separator = ' ')
|
|
24
|
+
"array_to_string(array_accum(#{clause}), '#{separator}')"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cast_to_string(clause)
|
|
28
|
+
clause
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def cast_to_datetime(clause)
|
|
32
|
+
"cast(extract(epoch from #{clause}) as int)"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def cast_to_unsigned(clause)
|
|
36
|
+
clause
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def convert_nulls(clause, default = '')
|
|
40
|
+
default = "'#{default}'" if default.is_a?(String)
|
|
41
|
+
|
|
42
|
+
"COALESCE(#{clause}, #{default})"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def boolean(value)
|
|
46
|
+
value ? 'TRUE' : 'FALSE'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def crc(clause, blank_to_null = false)
|
|
50
|
+
clause = "NULLIF(#{clause},'')" if blank_to_null
|
|
51
|
+
"crc32(#{clause})"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def utf8_query_pre
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def time_difference(diff)
|
|
59
|
+
"current_timestamp - interval '#{diff} seconds'"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def execute(command, output_error = false)
|
|
65
|
+
connection.execute "begin"
|
|
66
|
+
connection.execute "savepoint ts"
|
|
67
|
+
begin
|
|
68
|
+
connection.execute command
|
|
69
|
+
rescue StandardError => err
|
|
70
|
+
puts err if output_error
|
|
71
|
+
connection.execute "rollback to savepoint ts"
|
|
72
|
+
end
|
|
73
|
+
connection.execute "release savepoint ts"
|
|
74
|
+
connection.execute "commit"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def create_array_accum_function
|
|
78
|
+
if connection.raw_connection.respond_to?(:server_version) && connection.raw_connection.server_version > 80200
|
|
79
|
+
execute <<-SQL
|
|
80
|
+
CREATE AGGREGATE array_accum (anyelement)
|
|
81
|
+
(
|
|
82
|
+
sfunc = array_append,
|
|
83
|
+
stype = anyarray,
|
|
84
|
+
initcond = '{}'
|
|
85
|
+
);
|
|
86
|
+
SQL
|
|
87
|
+
else
|
|
88
|
+
execute <<-SQL
|
|
89
|
+
CREATE AGGREGATE array_accum
|
|
90
|
+
(
|
|
91
|
+
basetype = anyelement,
|
|
92
|
+
sfunc = array_append,
|
|
93
|
+
stype = anyarray,
|
|
94
|
+
initcond = '{}'
|
|
95
|
+
);
|
|
96
|
+
SQL
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def create_crc32_function
|
|
101
|
+
execute "CREATE LANGUAGE 'plpgsql';"
|
|
102
|
+
function = <<-SQL
|
|
103
|
+
CREATE OR REPLACE FUNCTION crc32(word text)
|
|
104
|
+
RETURNS bigint AS $$
|
|
105
|
+
DECLARE tmp bigint;
|
|
106
|
+
DECLARE i int;
|
|
107
|
+
DECLARE j int;
|
|
108
|
+
DECLARE word_array bytea;
|
|
109
|
+
BEGIN
|
|
110
|
+
i = 0;
|
|
111
|
+
tmp = 4294967295;
|
|
112
|
+
word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
|
|
113
|
+
LOOP
|
|
114
|
+
tmp = (tmp # get_byte(word_array, i))::bigint;
|
|
115
|
+
i = i + 1;
|
|
116
|
+
j = 0;
|
|
117
|
+
LOOP
|
|
118
|
+
tmp = ((tmp >> 1) # (3988292384 * (tmp & 1)))::bigint;
|
|
119
|
+
j = j + 1;
|
|
120
|
+
IF j >= 8 THEN
|
|
121
|
+
EXIT;
|
|
122
|
+
END IF;
|
|
123
|
+
END LOOP;
|
|
124
|
+
IF i >= char_length(word) THEN
|
|
125
|
+
EXIT;
|
|
126
|
+
END IF;
|
|
127
|
+
END LOOP;
|
|
128
|
+
return (tmp # 4294967295);
|
|
129
|
+
END
|
|
130
|
+
$$ IMMUTABLE STRICT LANGUAGE plpgsql;
|
|
131
|
+
SQL
|
|
132
|
+
execute function, true
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|