thinking-sphinx 2.0.6 → 2.0.7
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/HISTORY +157 -0
- data/lib/cucumber/thinking_sphinx/external_world.rb +12 -0
- data/lib/cucumber/thinking_sphinx/internal_world.rb +127 -0
- data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
- data/lib/thinking-sphinx.rb +1 -0
- data/lib/thinking_sphinx/action_controller.rb +31 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +53 -0
- data/lib/thinking_sphinx/active_record/collection_proxy.rb +40 -0
- data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +27 -0
- data/lib/thinking_sphinx/active_record/delta.rb +65 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +37 -0
- data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +21 -0
- data/lib/thinking_sphinx/active_record/log_subscriber.rb +61 -0
- data/lib/thinking_sphinx/active_record/scopes.rb +110 -0
- data/lib/thinking_sphinx/active_record.rb +383 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +87 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +62 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +171 -0
- data/lib/thinking_sphinx/association.rb +229 -0
- data/lib/thinking_sphinx/attribute.rb +407 -0
- data/lib/thinking_sphinx/auto_version.rb +38 -0
- data/lib/thinking_sphinx/bundled_search.rb +44 -0
- data/lib/thinking_sphinx/class_facet.rb +20 -0
- data/lib/thinking_sphinx/configuration.rb +335 -0
- data/lib/thinking_sphinx/context.rb +77 -0
- data/lib/thinking_sphinx/core/string.rb +15 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
- data/lib/thinking_sphinx/deltas.rb +28 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +99 -0
- data/lib/thinking_sphinx/excerpter.rb +23 -0
- data/lib/thinking_sphinx/facet.rb +128 -0
- data/lib/thinking_sphinx/facet_search.rb +170 -0
- data/lib/thinking_sphinx/field.rb +98 -0
- data/lib/thinking_sphinx/index/builder.rb +312 -0
- data/lib/thinking_sphinx/index/faux_column.rb +118 -0
- data/lib/thinking_sphinx/index.rb +157 -0
- data/lib/thinking_sphinx/join.rb +37 -0
- data/lib/thinking_sphinx/property.rb +185 -0
- data/lib/thinking_sphinx/railtie.rb +46 -0
- data/lib/thinking_sphinx/search.rb +995 -0
- data/lib/thinking_sphinx/search_methods.rb +439 -0
- data/lib/thinking_sphinx/sinatra.rb +7 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +51 -0
- data/lib/thinking_sphinx/source/sql.rb +157 -0
- data/lib/thinking_sphinx/source.rb +194 -0
- data/lib/thinking_sphinx/tasks.rb +132 -0
- data/lib/thinking_sphinx/test.rb +55 -0
- data/lib/thinking_sphinx/version.rb +3 -0
- data/lib/thinking_sphinx.rb +296 -0
- metadata +53 -4
@@ -0,0 +1,171 @@
|
|
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
|
+
if ThinkingSphinx::Configuration.instance.use_64_bit
|
32
|
+
"cast(extract(epoch from #{clause}) as bigint)"
|
33
|
+
else
|
34
|
+
"cast(extract(epoch from #{clause}) as int)"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def cast_to_unsigned(clause)
|
39
|
+
clause
|
40
|
+
end
|
41
|
+
|
42
|
+
def cast_to_int(clause)
|
43
|
+
"#{clause}::INT8"
|
44
|
+
end
|
45
|
+
|
46
|
+
def convert_nulls(clause, default = '')
|
47
|
+
default = case default
|
48
|
+
when String
|
49
|
+
"'#{default}'"
|
50
|
+
when NilClass
|
51
|
+
'NULL'
|
52
|
+
when Fixnum
|
53
|
+
"#{default}::bigint"
|
54
|
+
else
|
55
|
+
default
|
56
|
+
end
|
57
|
+
|
58
|
+
"COALESCE(#{clause}, #{default})"
|
59
|
+
end
|
60
|
+
|
61
|
+
def boolean(value)
|
62
|
+
value ? 'TRUE' : 'FALSE'
|
63
|
+
end
|
64
|
+
|
65
|
+
def crc(clause, blank_to_null = false)
|
66
|
+
clause = "NULLIF(#{clause},'')" if blank_to_null
|
67
|
+
"crc32(#{clause})"
|
68
|
+
end
|
69
|
+
|
70
|
+
def utf8_query_pre
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def time_difference(diff)
|
75
|
+
"current_timestamp - interval '#{diff} seconds'"
|
76
|
+
end
|
77
|
+
|
78
|
+
def utc_query_pre
|
79
|
+
"SET TIME ZONE 'UTC'"
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def execute(command, output_error = false)
|
85
|
+
if RUBY_PLATFORM == 'java'
|
86
|
+
connection.transaction do
|
87
|
+
execute_command command, output_error
|
88
|
+
end
|
89
|
+
else
|
90
|
+
execute_command command, output_error
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def execute_command(command, output_error = false)
|
95
|
+
connection.execute "begin"
|
96
|
+
connection.execute "savepoint ts"
|
97
|
+
begin
|
98
|
+
connection.execute command
|
99
|
+
rescue StandardError => err
|
100
|
+
puts err if output_error
|
101
|
+
connection.execute "rollback to savepoint ts"
|
102
|
+
end
|
103
|
+
connection.execute "release savepoint ts"
|
104
|
+
connection.execute "commit"
|
105
|
+
end
|
106
|
+
|
107
|
+
def create_array_accum_function
|
108
|
+
if connection.raw_connection.respond_to?(:server_version) && connection.raw_connection.server_version > 80200
|
109
|
+
execute <<-SQL
|
110
|
+
CREATE AGGREGATE array_accum (anyelement)
|
111
|
+
(
|
112
|
+
sfunc = array_append,
|
113
|
+
stype = anyarray,
|
114
|
+
initcond = '{}'
|
115
|
+
);
|
116
|
+
SQL
|
117
|
+
else
|
118
|
+
execute <<-SQL
|
119
|
+
CREATE AGGREGATE array_accum
|
120
|
+
(
|
121
|
+
basetype = anyelement,
|
122
|
+
sfunc = array_append,
|
123
|
+
stype = anyarray,
|
124
|
+
initcond = '{}'
|
125
|
+
);
|
126
|
+
SQL
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def create_crc32_function
|
131
|
+
execute "CREATE LANGUAGE 'plpgsql';"
|
132
|
+
function = <<-SQL
|
133
|
+
CREATE OR REPLACE FUNCTION crc32(word text)
|
134
|
+
RETURNS bigint AS $$
|
135
|
+
DECLARE tmp bigint;
|
136
|
+
DECLARE i int;
|
137
|
+
DECLARE j int;
|
138
|
+
DECLARE byte_length int;
|
139
|
+
DECLARE word_array bytea;
|
140
|
+
BEGIN
|
141
|
+
IF COALESCE(word, '') = '' THEN
|
142
|
+
return 0;
|
143
|
+
END IF;
|
144
|
+
|
145
|
+
i = 0;
|
146
|
+
tmp = 4294967295;
|
147
|
+
byte_length = bit_length(word) / 8;
|
148
|
+
word_array = decode(replace(word, E'\\\\', E'\\\\\\\\'), 'escape');
|
149
|
+
LOOP
|
150
|
+
tmp = (tmp # get_byte(word_array, i))::bigint;
|
151
|
+
i = i + 1;
|
152
|
+
j = 0;
|
153
|
+
LOOP
|
154
|
+
tmp = ((tmp >> 1) # (3988292384 * (tmp & 1)))::bigint;
|
155
|
+
j = j + 1;
|
156
|
+
IF j >= 8 THEN
|
157
|
+
EXIT;
|
158
|
+
END IF;
|
159
|
+
END LOOP;
|
160
|
+
IF i >= byte_length THEN
|
161
|
+
EXIT;
|
162
|
+
END IF;
|
163
|
+
END LOOP;
|
164
|
+
return (tmp # 4294967295);
|
165
|
+
END
|
166
|
+
$$ IMMUTABLE LANGUAGE plpgsql;
|
167
|
+
SQL
|
168
|
+
execute function, true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
# Association tracks a specific reflection and join to reference data that
|
3
|
+
# isn't in the base model. Very much an internal class for Thinking Sphinx -
|
4
|
+
# perhaps because I feel it's not as strong (or simple) as most of the rest.
|
5
|
+
#
|
6
|
+
class Association
|
7
|
+
attr_accessor :parent, :reflection, :join
|
8
|
+
|
9
|
+
# Create a new association by passing in the parent association, and the
|
10
|
+
# corresponding reflection instance. If there is no parent, pass in nil.
|
11
|
+
#
|
12
|
+
# top = Association.new nil, top_reflection
|
13
|
+
# child = Association.new top, child_reflection
|
14
|
+
#
|
15
|
+
def initialize(parent, reflection)
|
16
|
+
@parent, @reflection = parent, reflection
|
17
|
+
@children = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the children associations for a given association name. The only time
|
21
|
+
# that there'll actually be more than one association is when the
|
22
|
+
# relationship is polymorphic. To keep things simple though, it will always
|
23
|
+
# be an Array that gets returned (an empty one if no matches).
|
24
|
+
#
|
25
|
+
# # where pages is an association on the class tied to the reflection.
|
26
|
+
# association.children(:pages)
|
27
|
+
#
|
28
|
+
def children(assoc)
|
29
|
+
@children[assoc] ||= Association.children(@reflection.klass, assoc, self)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get the children associations for a given class, association name and
|
33
|
+
# parent association. Much like the instance method of the same name, it
|
34
|
+
# will return an empty array if no associations have the name, and only
|
35
|
+
# have multiple association instances if the underlying relationship is
|
36
|
+
# polymorphic.
|
37
|
+
#
|
38
|
+
# Association.children(User, :pages, user_association)
|
39
|
+
#
|
40
|
+
def self.children(klass, assoc, parent=nil)
|
41
|
+
ref = klass.reflect_on_association(assoc)
|
42
|
+
|
43
|
+
return [] if ref.nil?
|
44
|
+
return [Association.new(parent, ref)] unless ref.options[:polymorphic]
|
45
|
+
|
46
|
+
# association is polymorphic - create associations for each
|
47
|
+
# non-polymorphic reflection.
|
48
|
+
polymorphic_classes(ref).collect { |poly_class|
|
49
|
+
Association.new parent, depolymorphic_reflection(ref, klass, poly_class)
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Link up the join for this model from a base join - and set parent
|
54
|
+
# associations' joins recursively.
|
55
|
+
#
|
56
|
+
def join_to(base_join)
|
57
|
+
parent.join_to(base_join) if parent && parent.join.nil?
|
58
|
+
|
59
|
+
@join ||= join_association_class.new(
|
60
|
+
@reflection, base_join, parent ? parent.join : join_parent(base_join)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def arel_join
|
65
|
+
@join.join_type = Arel::OuterJoin
|
66
|
+
rewrite_conditions
|
67
|
+
|
68
|
+
@join
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns true if the association - or a parent - is a has_many or
|
72
|
+
# has_and_belongs_to_many.
|
73
|
+
#
|
74
|
+
def is_many?
|
75
|
+
case @reflection.macro
|
76
|
+
when :has_many, :has_and_belongs_to_many
|
77
|
+
true
|
78
|
+
else
|
79
|
+
@parent ? @parent.is_many? : false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns an array of all the associations that lead to this one - starting
|
84
|
+
# with the top level all the way to the current association object.
|
85
|
+
#
|
86
|
+
def ancestors
|
87
|
+
(parent ? parent.ancestors : []) << self
|
88
|
+
end
|
89
|
+
|
90
|
+
def has_column?(column)
|
91
|
+
@reflection.klass.column_names.include?(column.to_s)
|
92
|
+
end
|
93
|
+
|
94
|
+
def primary_key_from_reflection
|
95
|
+
if @reflection.options[:through]
|
96
|
+
if ThinkingSphinx.rails_3_1?
|
97
|
+
@reflection.source_reflection.foreign_key
|
98
|
+
else
|
99
|
+
@reflection.source_reflection.options[:foreign_key] ||
|
100
|
+
@reflection.source_reflection.primary_key_name
|
101
|
+
end
|
102
|
+
elsif @reflection.macro == :has_and_belongs_to_many
|
103
|
+
@reflection.association_foreign_key
|
104
|
+
else
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def table
|
110
|
+
if @reflection.options[:through] ||
|
111
|
+
@reflection.macro == :has_and_belongs_to_many
|
112
|
+
if ThinkingSphinx.rails_3_1?
|
113
|
+
@join.tables.first.name
|
114
|
+
else
|
115
|
+
@join.aliased_join_table_name
|
116
|
+
end
|
117
|
+
else
|
118
|
+
@join.aliased_table_name
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def self.depolymorphic_reflection(reflection, source_class, poly_class)
|
125
|
+
name = "#{reflection.name}_#{poly_class.name}".to_sym
|
126
|
+
|
127
|
+
source_class.reflections[name] ||=
|
128
|
+
::ActiveRecord::Reflection::AssociationReflection.new(
|
129
|
+
reflection.macro, name, casted_options(poly_class, reflection),
|
130
|
+
reflection.active_record
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns all the objects that could be currently instantiated from a
|
135
|
+
# polymorphic association. This is pretty damn fast if there's an index on
|
136
|
+
# the foreign type column - but if there isn't, it can take a while if you
|
137
|
+
# have a lot of data.
|
138
|
+
#
|
139
|
+
def self.polymorphic_classes(ref)
|
140
|
+
ref.active_record.connection.select_all(
|
141
|
+
"SELECT DISTINCT #{foreign_type(ref)} " +
|
142
|
+
"FROM #{ref.active_record.table_name} " +
|
143
|
+
"WHERE #{foreign_type(ref)} IS NOT NULL"
|
144
|
+
).collect { |row|
|
145
|
+
row[foreign_type(ref)].constantize
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns a new set of options for an association that mimics an existing
|
150
|
+
# polymorphic relationship for a specific class. It adds a condition to
|
151
|
+
# filter by the appropriate object.
|
152
|
+
#
|
153
|
+
def self.casted_options(klass, ref)
|
154
|
+
options = ref.options.clone
|
155
|
+
options[:polymorphic] = nil
|
156
|
+
options[:class_name] = klass.name
|
157
|
+
options[:foreign_key] ||= "#{ref.name}_id"
|
158
|
+
|
159
|
+
quoted_foreign_type = klass.connection.quote_column_name foreign_type(ref)
|
160
|
+
case options[:conditions]
|
161
|
+
when nil
|
162
|
+
options[:conditions] = "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
|
163
|
+
when Array
|
164
|
+
options[:conditions] << "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
|
165
|
+
when Hash
|
166
|
+
options[:conditions].merge!(foreign_type(ref) => klass.name)
|
167
|
+
else
|
168
|
+
options[:conditions] << " AND ::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
|
169
|
+
end
|
170
|
+
|
171
|
+
options
|
172
|
+
end
|
173
|
+
|
174
|
+
def join_association_class
|
175
|
+
if ThinkingSphinx.rails_3_1?
|
176
|
+
::ActiveRecord::Associations::JoinDependency::JoinAssociation
|
177
|
+
else
|
178
|
+
::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def join_parent(join)
|
183
|
+
if ThinkingSphinx.rails_3_1?
|
184
|
+
join.join_parts.first
|
185
|
+
else
|
186
|
+
join.joins.first
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.foreign_type(ref)
|
191
|
+
if ThinkingSphinx.rails_3_1?
|
192
|
+
ref.foreign_type
|
193
|
+
else
|
194
|
+
ref.options[:foreign_type]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def rewrite_conditions
|
199
|
+
@join.options[:conditions] = case @join.options[:conditions]
|
200
|
+
when String
|
201
|
+
rewrite_condition @join.options[:conditions]
|
202
|
+
when Array
|
203
|
+
@join.options[:conditions].collect { |condition|
|
204
|
+
rewrite_condition condition
|
205
|
+
}
|
206
|
+
else
|
207
|
+
@join.options[:conditions]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def rewrite_condition(condition)
|
212
|
+
return condition unless condition.is_a?(String)
|
213
|
+
|
214
|
+
if defined?(ActsAsTaggableOn) &&
|
215
|
+
@reflection.klass == ActsAsTaggableOn::Tagging &&
|
216
|
+
@reflection.name.to_s[/_taggings$/]
|
217
|
+
condition = condition.gsub /taggings\./, "#{quoted_alias @join}."
|
218
|
+
end
|
219
|
+
|
220
|
+
condition.gsub /::ts_join_alias::/, quoted_alias(@join.parent)
|
221
|
+
end
|
222
|
+
|
223
|
+
def quoted_alias(join)
|
224
|
+
@reflection.klass.connection.quote_table_name(
|
225
|
+
join.aliased_table_name
|
226
|
+
)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|