ebeigarts-thinking-sphinx 1.1.21 → 1.1.22
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/lib/thinking_sphinx.rb +1 -1
- data/lib/thinking_sphinx/adapters/oracle_adapter.rb +122 -0
- data/lib/thinking_sphinx/adapters/sqlite3_adapter.rb +91 -0
- data/lib/thinking_sphinx/attribute.rb +32 -9
- data/spec/unit/thinking_sphinx/attribute_spec.rb +20 -0
- data/spec/unit/thinking_sphinx/configuration_spec.rb +1 -1
- metadata +5 -3
data/lib/thinking_sphinx.rb
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class OracleAdapter < AbstractAdapter
|
|
3
|
+
def setup
|
|
4
|
+
create_group_concat_function
|
|
5
|
+
create_crc32_function
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def sphinx_identifier
|
|
9
|
+
"odbc"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def concatenate(clause, separator = ' ')
|
|
13
|
+
clause.split(', ').collect { |field| field }.join(" || '#{separator}' || ")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def group_concatenate(clause, separator = ' ')
|
|
17
|
+
"TO_STRING(CAST(COLLECT(TO_CHAR(#{clause})) AS SYS.ODCIVARCHAR2LIST), '#{separator}')"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def cast_to_string(clause)
|
|
21
|
+
"TO_CHAR(#{clause})"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def cast_to_datetime(clause)
|
|
25
|
+
"(TO_DATE(TO_CHAR(#{clause}, 'YYYY-MON-DD HH24.MI.SS'), 'YYYY-MON-DD HH24.MI.SS') - TO_DATE('01-JAN-1970','DD-MON-YYYY')) * (86400)"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cast_to_unsigned(clause)
|
|
29
|
+
"CAST(#{clause} AS NUMBER(10,0))"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def convert_nulls(clause, default = '')
|
|
33
|
+
return clause if default == ''
|
|
34
|
+
default = "'#{default}'" if default.is_a?(String)
|
|
35
|
+
"COALESCE(#{clause}, #{default})"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def boolean(value)
|
|
39
|
+
value ? '1' : '0'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# TODO
|
|
43
|
+
def crc(clause, blank_to_null = false)
|
|
44
|
+
"CRC32(#{clause})"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def utf8_query_pre
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def time_difference(diff)
|
|
52
|
+
"SYSDATE - #{diff}/(86400)"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def select_each(query)
|
|
56
|
+
cursor = connection.raw_connection.exec(query)
|
|
57
|
+
col_names = cursor.get_col_names.collect(&:downcase)
|
|
58
|
+
while values = cursor.fetch
|
|
59
|
+
hash_values = Hash[*col_names.zip(values).flatten]
|
|
60
|
+
yield hash_values
|
|
61
|
+
end
|
|
62
|
+
cursor.close
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# Requires Oracle 10g+
|
|
68
|
+
# Return only first 4000 bytes
|
|
69
|
+
def create_group_concat_function
|
|
70
|
+
connection.execute <<-SQL
|
|
71
|
+
CREATE OR REPLACE FUNCTION to_string (
|
|
72
|
+
nt_in IN SYS.ODCIVARCHAR2LIST,
|
|
73
|
+
delimiter_in IN VARCHAR2 DEFAULT ','
|
|
74
|
+
) RETURN VARCHAR2 IS
|
|
75
|
+
v_idx PLS_INTEGER;
|
|
76
|
+
v_str VARCHAR2(4000);
|
|
77
|
+
v_dlm VARCHAR2(1);
|
|
78
|
+
BEGIN
|
|
79
|
+
v_idx := nt_in.FIRST;
|
|
80
|
+
WHILE v_idx IS NOT NULL LOOP
|
|
81
|
+
v_str := SUBSTRB(v_str || v_dlm || nt_in(v_idx), 1, 4000);
|
|
82
|
+
v_dlm := delimiter_in;
|
|
83
|
+
v_idx := nt_in.NEXT(v_idx);
|
|
84
|
+
END LOOP;
|
|
85
|
+
RETURN v_str;
|
|
86
|
+
END to_string;
|
|
87
|
+
SQL
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Requires Oracle 10g+
|
|
91
|
+
def create_crc32_function
|
|
92
|
+
connection.execute <<-SQL
|
|
93
|
+
CREATE OR REPLACE FUNCTION crc32(
|
|
94
|
+
word IN VARCHAR2
|
|
95
|
+
) RETURN NUMBER IS
|
|
96
|
+
code NUMBER(4,0);
|
|
97
|
+
i NUMBER(10,0);
|
|
98
|
+
j NUMBER(1,0);
|
|
99
|
+
tmp NUMBER(10,0);
|
|
100
|
+
tmp_a NUMBER(10,0);
|
|
101
|
+
tmp_b NUMBER(10,0);
|
|
102
|
+
BEGIN
|
|
103
|
+
tmp := 4294967295;
|
|
104
|
+
i := 0;
|
|
105
|
+
WHILE i < length(word) LOOP
|
|
106
|
+
code := ascii(SUBSTR(word, i + 1, 1));
|
|
107
|
+
tmp := tmp - 2 * to_number(bitand(tmp, code)) + code;
|
|
108
|
+
j := 0;
|
|
109
|
+
WHILE j < 8 LOOP
|
|
110
|
+
tmp_a := floor(tmp / 2);
|
|
111
|
+
tmp_b := 3988292384 * to_number(bitand(tmp, 1));
|
|
112
|
+
tmp := tmp_a - 2 * to_number(bitand(tmp_a, tmp_b)) + tmp_b;
|
|
113
|
+
j := j + 1;
|
|
114
|
+
END LOOP;
|
|
115
|
+
i := i + 1;
|
|
116
|
+
END LOOP;
|
|
117
|
+
RETURN tmp - 2 * to_number(bitand(tmp, 4294967295)) + 4294967295;
|
|
118
|
+
END crc32;
|
|
119
|
+
SQL
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class SQLite3Adapter < AbstractAdapter
|
|
3
|
+
def setup
|
|
4
|
+
create_group_concat_function
|
|
5
|
+
create_crc32
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def sphinx_identifier
|
|
9
|
+
"sqlite3"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def concatenate(clause, separator = ' ')
|
|
13
|
+
clause.split(', ').collect { |field| field }.join(" || '#{separator}' || ")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def group_concatenate(clause, separator = ' ')
|
|
17
|
+
"GROUP_CONCAT(#{clause}, '#{separator}')"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def cast_to_string(clause)
|
|
21
|
+
"CAST(#{clause} AS TEXT)"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def cast_to_datetime(clause)
|
|
25
|
+
"STRFTIME('%s', #{clause})"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cast_to_unsigned(clause)
|
|
29
|
+
"CAST(#{clause} AS INTEGER)"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def convert_nulls(clause, default = '')
|
|
33
|
+
return clause if default == ''
|
|
34
|
+
default = "'#{default}'" if default.is_a?(String)
|
|
35
|
+
"COALESCE(#{clause}, #{default})"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def boolean(value)
|
|
39
|
+
value ? "'t'" : "'f'"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def crc(clause, blank_to_null = false)
|
|
43
|
+
"CRC32(#{clause})"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def utf8_query_pre
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def time_difference(diff)
|
|
51
|
+
"STRFTIME('%s','now') - STRFTIME('%s', #{diff})"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def create_group_concat_function
|
|
57
|
+
connection.raw_connection.create_aggregate("group_concat", 2) do
|
|
58
|
+
step do |func, value, separator|
|
|
59
|
+
value = value.respond_to?(:to_s) ? value.to_s : ''
|
|
60
|
+
if String(func[:concat]).empty? then
|
|
61
|
+
func[:concat] = value
|
|
62
|
+
else
|
|
63
|
+
func[:concat] = func[:concat].to_s + separator.to_s + value
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
finalize do |func|
|
|
67
|
+
func.result = func[:concat]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def create_crc32
|
|
73
|
+
connection.raw_connection.create_function("crc32", 1) do |func, value|
|
|
74
|
+
c = value.respond_to?(:to_s) ? value.to_s : ''
|
|
75
|
+
n = c.length
|
|
76
|
+
r = 0xFFFFFFFF
|
|
77
|
+
n.times do |i|
|
|
78
|
+
r ^= c[i]
|
|
79
|
+
8.times do
|
|
80
|
+
if (r & 1) != 0
|
|
81
|
+
r = (r >> 1) ^ 0xEDB88320
|
|
82
|
+
else
|
|
83
|
+
r >>= 1
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
func.result = r ^ 0xFFFFFFFF
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -128,7 +128,7 @@ module ThinkingSphinx
|
|
|
128
128
|
def config_value(offset = nil)
|
|
129
129
|
if type == :multi && ThinkingSphinx::Configuration.instance.type != "xml"
|
|
130
130
|
multi_config = include_as_association? ? "field" :
|
|
131
|
-
source_value(offset).gsub(/\
|
|
131
|
+
source_value(offset).gsub(/\s+/m, " ").strip
|
|
132
132
|
"uint #{unique_name} from #{multi_config}"
|
|
133
133
|
else
|
|
134
134
|
unique_name
|
|
@@ -195,24 +195,25 @@ module ThinkingSphinx
|
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
def query(offset)
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
base_assoc = base_association_for_mva
|
|
199
|
+
end_assoc = end_association_for_mva
|
|
200
|
+
raise "Could not determine SQL for MVA" if base_assoc.nil?
|
|
200
201
|
|
|
201
202
|
<<-SQL
|
|
202
|
-
SELECT #{foreign_key_for_mva
|
|
203
|
+
SELECT #{foreign_key_for_mva base_assoc}
|
|
203
204
|
#{ThinkingSphinx.unique_id_expression(offset)} AS #{quote_column('id')},
|
|
204
|
-
#{primary_key_for_mva(
|
|
205
|
-
FROM #{quote_table_name
|
|
205
|
+
#{primary_key_for_mva(end_assoc)} AS #{quote_column(unique_name)}
|
|
206
|
+
FROM #{quote_table_name base_assoc.table} #{association_joins}
|
|
206
207
|
SQL
|
|
207
208
|
end
|
|
208
209
|
|
|
209
210
|
def query_clause
|
|
210
|
-
foreign_key = foreign_key_for_mva
|
|
211
|
+
foreign_key = foreign_key_for_mva base_association_for_mva
|
|
211
212
|
"WHERE #{foreign_key} >= $start AND #{foreign_key} <= $end"
|
|
212
213
|
end
|
|
213
214
|
|
|
214
215
|
def range_query
|
|
215
|
-
assoc =
|
|
216
|
+
assoc = base_association_for_mva
|
|
216
217
|
foreign_key = foreign_key_for_mva assoc
|
|
217
218
|
"SELECT MIN(#{foreign_key}), MAX(#{foreign_key}) FROM #{quote_table_name assoc.table}"
|
|
218
219
|
end
|
|
@@ -227,12 +228,34 @@ FROM #{quote_table_name assoc.table}
|
|
|
227
228
|
quote_with_table assoc.table, assoc.reflection.primary_key_name
|
|
228
229
|
end
|
|
229
230
|
|
|
230
|
-
def
|
|
231
|
+
def end_association_for_mva
|
|
231
232
|
@association_for_mva ||= associations[columns.first].detect { |assoc|
|
|
232
233
|
assoc.has_column?(columns.first.__name)
|
|
233
234
|
}
|
|
234
235
|
end
|
|
235
236
|
|
|
237
|
+
def base_association_for_mva
|
|
238
|
+
@first_association_for_mva ||= begin
|
|
239
|
+
assoc = end_association_for_mva
|
|
240
|
+
while !assoc.parent.nil?
|
|
241
|
+
assoc = assoc.parent
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
assoc
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def association_joins
|
|
249
|
+
joins = []
|
|
250
|
+
assoc = end_association_for_mva
|
|
251
|
+
while assoc != base_association_for_mva
|
|
252
|
+
joins << assoc.to_sql
|
|
253
|
+
assoc = assoc.parent
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
joins.join(' ')
|
|
257
|
+
end
|
|
258
|
+
|
|
236
259
|
def is_many_ints?
|
|
237
260
|
concat_ws? && all_ints?
|
|
238
261
|
end
|
|
@@ -321,6 +321,26 @@ describe ThinkingSphinx::Attribute do
|
|
|
321
321
|
end
|
|
322
322
|
end
|
|
323
323
|
|
|
324
|
+
describe "MVA via two has-many associations with a ranged source query" do
|
|
325
|
+
before :each do
|
|
326
|
+
@index = ThinkingSphinx::Index.new(Alpha)
|
|
327
|
+
@source = ThinkingSphinx::Source.new(@index)
|
|
328
|
+
@attribute = ThinkingSphinx::Attribute.new(@source,
|
|
329
|
+
[ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
|
|
330
|
+
:as => :gamma_values, :source => :ranged_query
|
|
331
|
+
)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
it "should use a ranged query" do
|
|
335
|
+
@attribute.type_to_config.should == :sql_attr_multi
|
|
336
|
+
|
|
337
|
+
declaration, query, range_query = @attribute.config_value.split('; ')
|
|
338
|
+
declaration.should == "uint gamma_values from ranged-query"
|
|
339
|
+
query.should == "SELECT `betas`.`alpha_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `gammas`.`value` AS `gamma_values` FROM `betas` LEFT OUTER JOIN `gammas` ON gammas.beta_id = betas.id WHERE `betas`.`alpha_id` >= $start AND `betas`.`alpha_id` <= $end"
|
|
340
|
+
range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
324
344
|
describe "with custom queries" do
|
|
325
345
|
before :each do
|
|
326
346
|
index = CricketTeam.sphinx_indexes.first
|
|
@@ -12,7 +12,7 @@ describe ThinkingSphinx::Configuration do
|
|
|
12
12
|
unless defined?(Merb)
|
|
13
13
|
module ::Merb; end
|
|
14
14
|
end
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
ThinkingSphinx::Configuration.stub_method(:defined? => true)
|
|
17
17
|
Merb.stub!(:environment => "merb_production")
|
|
18
18
|
ThinkingSphinx::Configuration.environment.should == "merb_production"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ebeigarts-thinking-sphinx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pat Allan
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-06-
|
|
12
|
+
date: 2009-06-22 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies: []
|
|
15
15
|
|
|
@@ -30,6 +30,8 @@ files:
|
|
|
30
30
|
- lib/thinking_sphinx/active_record.rb
|
|
31
31
|
- lib/thinking_sphinx/adapters/abstract_adapter.rb
|
|
32
32
|
- lib/thinking_sphinx/adapters/mysql_adapter.rb
|
|
33
|
+
- lib/thinking_sphinx/adapters/sqlite3_adapter.rb
|
|
34
|
+
- lib/thinking_sphinx/adapters/oracle_adapter.rb
|
|
33
35
|
- lib/thinking_sphinx/adapters/postgresql_adapter.rb
|
|
34
36
|
- lib/thinking_sphinx/association.rb
|
|
35
37
|
- lib/thinking_sphinx/attribute.rb
|
|
@@ -128,7 +130,7 @@ has_rdoc: true
|
|
|
128
130
|
homepage: http://ts.freelancing-gods.com
|
|
129
131
|
licenses:
|
|
130
132
|
post_install_message: |+
|
|
131
|
-
|
|
133
|
+
With the release of Thinking Sphinx 1.1.18, there is one important change to
|
|
132
134
|
note: previously, the default morphology for indexing was 'stem_en'. The new
|
|
133
135
|
default is nil, to avoid any unexpected behavior. If you wish to keep the old
|
|
134
136
|
value though, you will need to add the following settings to your
|