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