thinking-sphinx 2.0.5 → 2.0.6
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/README.textile +7 -1
- data/features/searching_by_model.feature +24 -30
- data/features/step_definitions/common_steps.rb +5 -5
- data/features/thinking_sphinx/db/.gitignore +1 -0
- data/features/thinking_sphinx/db/fixtures/post_keywords.txt +1 -0
- data/spec/fixtures/data.sql +32 -0
- data/spec/fixtures/database.yml.default +3 -0
- data/spec/fixtures/models.rb +161 -0
- data/spec/fixtures/structure.sql +146 -0
- data/spec/spec_helper.rb +62 -0
- data/spec/sphinx_helper.rb +61 -0
- data/spec/support/rails.rb +18 -0
- data/spec/thinking_sphinx/active_record/delta_spec.rb +24 -24
- data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +27 -0
- data/spec/thinking_sphinx/active_record/scopes_spec.rb +25 -25
- data/spec/thinking_sphinx/active_record_spec.rb +108 -107
- data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +38 -38
- data/spec/thinking_sphinx/association_spec.rb +69 -35
- data/spec/thinking_sphinx/context_spec.rb +61 -64
- data/spec/thinking_sphinx/search_spec.rb +7 -0
- data/spec/thinking_sphinx_spec.rb +47 -46
- metadata +49 -141
- data/VERSION +0 -1
- data/lib/cucumber/thinking_sphinx/external_world.rb +0 -12
- data/lib/cucumber/thinking_sphinx/internal_world.rb +0 -127
- data/lib/cucumber/thinking_sphinx/sql_logger.rb +0 -20
- data/lib/thinking-sphinx.rb +0 -1
- data/lib/thinking_sphinx.rb +0 -301
- data/lib/thinking_sphinx/action_controller.rb +0 -31
- data/lib/thinking_sphinx/active_record.rb +0 -384
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +0 -52
- data/lib/thinking_sphinx/active_record/delta.rb +0 -65
- data/lib/thinking_sphinx/active_record/has_many_association.rb +0 -36
- data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
- data/lib/thinking_sphinx/active_record/log_subscriber.rb +0 -61
- data/lib/thinking_sphinx/active_record/scopes.rb +0 -93
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +0 -87
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -62
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +0 -157
- data/lib/thinking_sphinx/association.rb +0 -219
- data/lib/thinking_sphinx/attribute.rb +0 -396
- data/lib/thinking_sphinx/auto_version.rb +0 -38
- data/lib/thinking_sphinx/bundled_search.rb +0 -44
- data/lib/thinking_sphinx/class_facet.rb +0 -20
- data/lib/thinking_sphinx/configuration.rb +0 -339
- data/lib/thinking_sphinx/context.rb +0 -76
- data/lib/thinking_sphinx/core/string.rb +0 -15
- data/lib/thinking_sphinx/deltas.rb +0 -28
- data/lib/thinking_sphinx/deltas/default_delta.rb +0 -62
- data/lib/thinking_sphinx/deploy/capistrano.rb +0 -101
- data/lib/thinking_sphinx/excerpter.rb +0 -23
- data/lib/thinking_sphinx/facet.rb +0 -128
- data/lib/thinking_sphinx/facet_search.rb +0 -170
- data/lib/thinking_sphinx/field.rb +0 -98
- data/lib/thinking_sphinx/index.rb +0 -157
- data/lib/thinking_sphinx/index/builder.rb +0 -312
- data/lib/thinking_sphinx/index/faux_column.rb +0 -118
- data/lib/thinking_sphinx/join.rb +0 -37
- data/lib/thinking_sphinx/property.rb +0 -185
- data/lib/thinking_sphinx/railtie.rb +0 -46
- data/lib/thinking_sphinx/search.rb +0 -972
- data/lib/thinking_sphinx/search_methods.rb +0 -439
- data/lib/thinking_sphinx/sinatra.rb +0 -7
- data/lib/thinking_sphinx/source.rb +0 -194
- data/lib/thinking_sphinx/source/internal_properties.rb +0 -51
- data/lib/thinking_sphinx/source/sql.rb +0 -157
- data/lib/thinking_sphinx/tasks.rb +0 -130
- data/lib/thinking_sphinx/test.rb +0 -55
- data/tasks/distribution.rb +0 -33
- data/tasks/testing.rb +0 -80
@@ -1,65 +0,0 @@
|
|
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_objects.each { |obj| obj.index(self, instance) }
|
20
|
-
end
|
21
|
-
|
22
|
-
def delta_objects
|
23
|
-
self.sphinx_indexes.collect(&:delta_object).compact
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def toggled_delta?
|
28
|
-
self.class.delta_objects.any? { |obj| obj.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_objects.each { |obj|
|
36
|
-
obj.toggle(self)
|
37
|
-
} if should_toggle_delta?
|
38
|
-
end
|
39
|
-
|
40
|
-
# Build the delta index for the related model. This won't be called
|
41
|
-
# if running in the test environment.
|
42
|
-
#
|
43
|
-
def index_delta
|
44
|
-
self.class.index_delta(self) if self.class.delta_objects.any? { |obj|
|
45
|
-
obj.toggled(self)
|
46
|
-
}
|
47
|
-
end
|
48
|
-
|
49
|
-
def should_toggle_delta?
|
50
|
-
self.new_record? || indexed_data_changed?
|
51
|
-
end
|
52
|
-
|
53
|
-
def indexed_data_changed?
|
54
|
-
sphinx_indexes.any? { |index|
|
55
|
-
index.fields.any? { |field| field.changed?(self) } ||
|
56
|
-
index.attributes.any? { |attrib|
|
57
|
-
attrib.public? && attrib.changed?(self) && !attrib.updatable?
|
58
|
-
}
|
59
|
-
}
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,36 +0,0 @@
|
|
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
|
-
private
|
14
|
-
|
15
|
-
def attribute_for_foreign_key
|
16
|
-
foreign_key = @reflection.primary_key_name
|
17
|
-
stack = [@reflection.options[:through]].compact
|
18
|
-
|
19
|
-
@reflection.klass.define_indexes
|
20
|
-
(@reflection.klass.sphinx_indexes || []).each do |index|
|
21
|
-
attribute = index.attributes.detect { |attrib|
|
22
|
-
attrib.columns.length == 1 &&
|
23
|
-
attrib.columns.first.__name == foreign_key.to_sym
|
24
|
-
}
|
25
|
-
return attribute unless attribute.nil?
|
26
|
-
end
|
27
|
-
|
28
|
-
raise "Missing Attribute for Foreign Key #{foreign_key}"
|
29
|
-
end
|
30
|
-
|
31
|
-
def default_filter
|
32
|
-
{attribute_for_foreign_key.unique_name => @owner.id}
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module ThinkingSphinx
|
2
|
-
module ActiveRecord
|
3
|
-
module HasManyAssociationWithScopes
|
4
|
-
def method_missing(method, *args, &block)
|
5
|
-
if responds_to_scope(method)
|
6
|
-
@reflection.klass.
|
7
|
-
search(:with => default_filter).
|
8
|
-
send(method, *args, &block)
|
9
|
-
else
|
10
|
-
super
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
def responds_to_scope(scope)
|
16
|
-
@reflection.klass.respond_to?(:sphinx_scopes) &&
|
17
|
-
@reflection.klass.sphinx_scopes.include?(scope)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
require 'active_support/log_subscriber'
|
2
|
-
|
3
|
-
module ThinkingSphinx
|
4
|
-
module ActiveRecord
|
5
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
6
|
-
def self.runtime=(value)
|
7
|
-
Thread.current['thinking_sphinx_query_runtime'] = value
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.runtime
|
11
|
-
Thread.current['thinking_sphinx_query_runtime'] ||= 0
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.reset_runtime
|
15
|
-
rt, self.runtime = runtime, 0
|
16
|
-
rt
|
17
|
-
end
|
18
|
-
|
19
|
-
def initialize
|
20
|
-
super
|
21
|
-
@odd_or_even = false
|
22
|
-
end
|
23
|
-
|
24
|
-
def query(event)
|
25
|
-
self.class.runtime += event.duration
|
26
|
-
return unless logger.debug?
|
27
|
-
|
28
|
-
identifier = color('Sphinx Query (%.1fms)' % event.duration, GREEN, true)
|
29
|
-
query = event.payload[:query]
|
30
|
-
query = color query, nil, true if odd?
|
31
|
-
|
32
|
-
debug " #{identifier} #{query}"
|
33
|
-
end
|
34
|
-
|
35
|
-
def message(event)
|
36
|
-
return unless logger.debug?
|
37
|
-
|
38
|
-
identifier = color 'Sphinx', GREEN, true
|
39
|
-
message = event.payload[:message]
|
40
|
-
message = color message, nil, true if odd?
|
41
|
-
|
42
|
-
debug " #{identifier} #{message}"
|
43
|
-
end
|
44
|
-
|
45
|
-
def odd?
|
46
|
-
@odd_or_even = !@odd_or_even
|
47
|
-
end
|
48
|
-
|
49
|
-
def logger
|
50
|
-
return @logger if defined? @logger
|
51
|
-
self.logger = ::ActiveRecord::Base.logger
|
52
|
-
end
|
53
|
-
|
54
|
-
def logger=(logger)
|
55
|
-
@logger = logger
|
56
|
-
end
|
57
|
-
|
58
|
-
attach_to :thinking_sphinx
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
@@ -1,93 +0,0 @@
|
|
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
|
-
scope_mixin = ::ThinkingSphinx::ActiveRecord::HasManyAssociationWithScopes
|
85
|
-
|
86
|
-
::ActiveRecord::Associations::HasManyAssociation.send(:include, scope_mixin)
|
87
|
-
::ActiveRecord::Associations::HasManyThroughAssociation.send(:include, scope_mixin)
|
88
|
-
end
|
89
|
-
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
@@ -1,87 +0,0 @@
|
|
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]
|
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
|
@@ -1,62 +0,0 @@
|
|
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
|
@@ -1,157 +0,0 @@
|
|
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 cast_to_int(clause)
|
39
|
-
"#{clause}::INT8"
|
40
|
-
end
|
41
|
-
|
42
|
-
def convert_nulls(clause, default = '')
|
43
|
-
default = case default
|
44
|
-
when String
|
45
|
-
"'#{default}'"
|
46
|
-
when NilClass
|
47
|
-
'NULL'
|
48
|
-
when Fixnum
|
49
|
-
"#{default}::bigint"
|
50
|
-
else
|
51
|
-
default
|
52
|
-
end
|
53
|
-
|
54
|
-
"COALESCE(#{clause}, #{default})"
|
55
|
-
end
|
56
|
-
|
57
|
-
def boolean(value)
|
58
|
-
value ? 'TRUE' : 'FALSE'
|
59
|
-
end
|
60
|
-
|
61
|
-
def crc(clause, blank_to_null = false)
|
62
|
-
clause = "NULLIF(#{clause},'')" if blank_to_null
|
63
|
-
"crc32(#{clause})"
|
64
|
-
end
|
65
|
-
|
66
|
-
def utf8_query_pre
|
67
|
-
nil
|
68
|
-
end
|
69
|
-
|
70
|
-
def time_difference(diff)
|
71
|
-
"current_timestamp - interval '#{diff} seconds'"
|
72
|
-
end
|
73
|
-
|
74
|
-
def utc_query_pre
|
75
|
-
"SET TIME ZONE 'UTC'"
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def execute(command, output_error = false)
|
81
|
-
connection.execute "begin"
|
82
|
-
connection.execute "savepoint ts"
|
83
|
-
begin
|
84
|
-
connection.execute command
|
85
|
-
rescue StandardError => err
|
86
|
-
puts err if output_error
|
87
|
-
connection.execute "rollback to savepoint ts"
|
88
|
-
end
|
89
|
-
connection.execute "release savepoint ts"
|
90
|
-
connection.execute "commit"
|
91
|
-
end
|
92
|
-
|
93
|
-
def create_array_accum_function
|
94
|
-
if connection.raw_connection.respond_to?(:server_version) && connection.raw_connection.server_version > 80200
|
95
|
-
execute <<-SQL
|
96
|
-
CREATE AGGREGATE array_accum (anyelement)
|
97
|
-
(
|
98
|
-
sfunc = array_append,
|
99
|
-
stype = anyarray,
|
100
|
-
initcond = '{}'
|
101
|
-
);
|
102
|
-
SQL
|
103
|
-
else
|
104
|
-
execute <<-SQL
|
105
|
-
CREATE AGGREGATE array_accum
|
106
|
-
(
|
107
|
-
basetype = anyelement,
|
108
|
-
sfunc = array_append,
|
109
|
-
stype = anyarray,
|
110
|
-
initcond = '{}'
|
111
|
-
);
|
112
|
-
SQL
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def create_crc32_function
|
117
|
-
execute "CREATE LANGUAGE 'plpgsql';"
|
118
|
-
function = <<-SQL
|
119
|
-
CREATE OR REPLACE FUNCTION crc32(word text)
|
120
|
-
RETURNS bigint AS $$
|
121
|
-
DECLARE tmp bigint;
|
122
|
-
DECLARE i int;
|
123
|
-
DECLARE j int;
|
124
|
-
DECLARE byte_length int;
|
125
|
-
DECLARE word_array bytea;
|
126
|
-
BEGIN
|
127
|
-
IF COALESCE(word, '') = '' THEN
|
128
|
-
return 0;
|
129
|
-
END IF;
|
130
|
-
|
131
|
-
i = 0;
|
132
|
-
tmp = 4294967295;
|
133
|
-
byte_length = bit_length(word) / 8;
|
134
|
-
word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
|
135
|
-
LOOP
|
136
|
-
tmp = (tmp # get_byte(word_array, i))::bigint;
|
137
|
-
i = i + 1;
|
138
|
-
j = 0;
|
139
|
-
LOOP
|
140
|
-
tmp = ((tmp >> 1) # (3988292384 * (tmp & 1)))::bigint;
|
141
|
-
j = j + 1;
|
142
|
-
IF j >= 8 THEN
|
143
|
-
EXIT;
|
144
|
-
END IF;
|
145
|
-
END LOOP;
|
146
|
-
IF i >= byte_length THEN
|
147
|
-
EXIT;
|
148
|
-
END IF;
|
149
|
-
END LOOP;
|
150
|
-
return (tmp # 4294967295);
|
151
|
-
END
|
152
|
-
$$ IMMUTABLE LANGUAGE plpgsql;
|
153
|
-
SQL
|
154
|
-
execute function, true
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|