ninjudd-model_set 0.9.2
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/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/VERSION.yml +4 -0
- data/lib/model_set.rb +712 -0
- data/lib/model_set/conditioned.rb +33 -0
- data/lib/model_set/conditions.rb +103 -0
- data/lib/model_set/query.rb +128 -0
- data/lib/model_set/raw_query.rb +41 -0
- data/lib/model_set/raw_sql_query.rb +19 -0
- data/lib/model_set/set_query.rb +34 -0
- data/lib/model_set/solr_query.rb +70 -0
- data/lib/model_set/sphinx_query.rb +148 -0
- data/lib/model_set/sql_base_query.rb +52 -0
- data/lib/model_set/sql_query.rb +75 -0
- data/lib/multi_set.rb +67 -0
- data/test/model_set_test.rb +283 -0
- data/test/multi_set_test.rb +65 -0
- data/test/test_helper.rb +23 -0
- data/vendor/sphinx_client/README.rdoc +41 -0
- data/vendor/sphinx_client/Rakefile +21 -0
- data/vendor/sphinx_client/init.rb +1 -0
- data/vendor/sphinx_client/install.rb +5 -0
- data/vendor/sphinx_client/lib/sphinx.rb +6 -0
- data/vendor/sphinx_client/lib/sphinx/client.rb +1093 -0
- data/vendor/sphinx_client/lib/sphinx/request.rb +50 -0
- data/vendor/sphinx_client/lib/sphinx/response.rb +69 -0
- data/vendor/sphinx_client/spec/client_response_spec.rb +112 -0
- data/vendor/sphinx_client/spec/client_spec.rb +469 -0
- data/vendor/sphinx_client/spec/fixtures/default_search.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/default_search_index.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/excerpt_custom.php +11 -0
- data/vendor/sphinx_client/spec/fixtures/excerpt_default.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/excerpt_flags.php +11 -0
- data/vendor/sphinx_client/spec/fixtures/field_weights.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_exclude.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_float_range.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_float_range_exclude.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_range.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_range_exclude.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/filter_range_int64.php +10 -0
- data/vendor/sphinx_client/spec/fixtures/filter_ranges.php +10 -0
- data/vendor/sphinx_client/spec/fixtures/filters.php +10 -0
- data/vendor/sphinx_client/spec/fixtures/filters_different.php +13 -0
- data/vendor/sphinx_client/spec/fixtures/geo_anchor.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_attr.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_attrpair.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_day.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_day_sort.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_month.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_week.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_by_year.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/group_distinct.php +10 -0
- data/vendor/sphinx_client/spec/fixtures/id_range.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/id_range64.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/index_weights.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/keywords.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/limits.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/limits_cutoff.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/limits_max.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/limits_max_cutoff.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_all.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_any.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_boolean.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_extended.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_extended2.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_fullscan.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/match_phrase.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/max_query_time.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/miltiple_queries.php +12 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_bm25.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_none.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_proximity.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_proximity_bm25.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/ranking_wordcount.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/retries.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/retries_delay.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/select.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/set_override.php +11 -0
- data/vendor/sphinx_client/spec/fixtures/sort_attr_asc.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_attr_desc.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_expr.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_extended.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_relevance.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sort_time_segments.php +9 -0
- data/vendor/sphinx_client/spec/fixtures/sphinxapi.php +1269 -0
- data/vendor/sphinx_client/spec/fixtures/update_attributes.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/update_attributes_mva.php +8 -0
- data/vendor/sphinx_client/spec/fixtures/weights.php +9 -0
- data/vendor/sphinx_client/spec/sphinx/sphinx-id64.conf +67 -0
- data/vendor/sphinx_client/spec/sphinx/sphinx.conf +67 -0
- data/vendor/sphinx_client/spec/sphinx/sphinx_test.sql +86 -0
- data/vendor/sphinx_client/sphinx.yml.tpl +3 -0
- data/vendor/sphinx_client/tasks/sphinx.rake +75 -0
- metadata +154 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class ModelSet
|
|
2
|
+
module Conditioned
|
|
3
|
+
# Shared methods for dealing with conditions.
|
|
4
|
+
attr_reader :conditions
|
|
5
|
+
|
|
6
|
+
def add_conditions!(*conditions)
|
|
7
|
+
operator = conditions.shift if conditions.first.kind_of?(Symbol)
|
|
8
|
+
operator ||= :and
|
|
9
|
+
|
|
10
|
+
# Sanitize conditions.
|
|
11
|
+
conditions.collect! do |condition|
|
|
12
|
+
condition.kind_of?(Conditions) ? condition : Conditions.new( sanitize_condition(condition) )
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if operator == :not
|
|
16
|
+
# In this case, :not actually means :and :not.
|
|
17
|
+
conditions = ~Conditions.new(:and, *conditions)
|
|
18
|
+
operator = :and
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
conditions << @conditions if @conditions
|
|
22
|
+
@conditions = Conditions.new(operator, *conditions)
|
|
23
|
+
|
|
24
|
+
clear_cache!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def invert!
|
|
28
|
+
raise 'cannot invert without conditions' if @conditions.nil?
|
|
29
|
+
@conditions = ~@conditions
|
|
30
|
+
clear_cache!
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
class ModelSet
|
|
2
|
+
class Conditions
|
|
3
|
+
deep_clonable
|
|
4
|
+
|
|
5
|
+
attr_reader :operator, :conditions
|
|
6
|
+
|
|
7
|
+
def self.new(*args)
|
|
8
|
+
if args.size == 1 and args.first.kind_of?(self)
|
|
9
|
+
# Just clone if the only argument is a Conditions object.
|
|
10
|
+
args.first.clone
|
|
11
|
+
elsif args.size == 2 and [:and, :or].include?(args.first)
|
|
12
|
+
# The operator is not necessary if there is only one subcondition.
|
|
13
|
+
new(args.last)
|
|
14
|
+
else
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def new(*args)
|
|
20
|
+
self.class.new(*args)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(*args)
|
|
24
|
+
if args.size == 1 and not args.first.kind_of?(Symbol)
|
|
25
|
+
# Terminal.
|
|
26
|
+
@conditions = args
|
|
27
|
+
else
|
|
28
|
+
@operator = args.shift
|
|
29
|
+
raise "invalid operator :#{operator}" unless [:and, :or, :not].include?(operator)
|
|
30
|
+
|
|
31
|
+
if operator == :not
|
|
32
|
+
raise "unary operator :not cannot have multiple conditions" if args.size > 1
|
|
33
|
+
@conditions = [self.class.new(args.first)]
|
|
34
|
+
else
|
|
35
|
+
# Compact the conditions if possible.
|
|
36
|
+
@conditions = []
|
|
37
|
+
args.each do |clause|
|
|
38
|
+
self << clause
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def terminal?
|
|
45
|
+
operator.nil?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def <<(clause)
|
|
49
|
+
raise 'cannot append conditions to a terminal' if terminal?
|
|
50
|
+
|
|
51
|
+
clause = self.class.new(clause)
|
|
52
|
+
if clause.operator == operator
|
|
53
|
+
@conditions.concat(clause.conditions)
|
|
54
|
+
else
|
|
55
|
+
@conditions << clause
|
|
56
|
+
end
|
|
57
|
+
@conditions.uniq!
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ~
|
|
61
|
+
if operator == :not
|
|
62
|
+
conditions.first.clone
|
|
63
|
+
else
|
|
64
|
+
new(:not, self)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def |(other)
|
|
69
|
+
new(:or, self, other)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def &(other)
|
|
73
|
+
new(:and, self, other)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def to_s
|
|
77
|
+
return conditions.first if terminal?
|
|
78
|
+
|
|
79
|
+
condition_strings = conditions.collect do |condition|
|
|
80
|
+
condition.operator == :not ? condition.to_s : "(#{condition.to_s})"
|
|
81
|
+
end.sort_by {|s| s.size}
|
|
82
|
+
|
|
83
|
+
case operator
|
|
84
|
+
when :not
|
|
85
|
+
"NOT #{condition_strings.first}"
|
|
86
|
+
when :and
|
|
87
|
+
"#{condition_strings.join(' AND ')}"
|
|
88
|
+
when :or
|
|
89
|
+
"#{condition_strings.join(' OR ')}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def hash
|
|
94
|
+
# for uniq
|
|
95
|
+
[operator, conditions].hash
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def eql?(other)
|
|
99
|
+
# for uniq
|
|
100
|
+
self.hash == other.hash
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
class ModelSet
|
|
2
|
+
class Query
|
|
3
|
+
deep_clonable
|
|
4
|
+
|
|
5
|
+
def initialize(model_set = ModelSet)
|
|
6
|
+
if model_set.kind_of?(Class)
|
|
7
|
+
@set_class = model_set
|
|
8
|
+
else
|
|
9
|
+
@set_class = model_set.class
|
|
10
|
+
anchor!(model_set.query) if model_set.query
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def order_by!(order)
|
|
15
|
+
@sort_order = order
|
|
16
|
+
clear_cache!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def unsorted!
|
|
20
|
+
@sort_order = nil
|
|
21
|
+
clear_cache!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def page!(page)
|
|
25
|
+
@page = page ? page.to_i : nil
|
|
26
|
+
@offset = nil
|
|
27
|
+
clear_limited_cache!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def limit!(limit, offset = nil)
|
|
31
|
+
@limit = limit ? limit.to_i : nil
|
|
32
|
+
@offset = offset ? offset.to_i : nil
|
|
33
|
+
@page = nil if offset
|
|
34
|
+
clear_limited_cache!
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def unlimited!
|
|
38
|
+
@limit = nil
|
|
39
|
+
@offset = nil
|
|
40
|
+
@page = nil
|
|
41
|
+
clear_limited_cache!
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def clear_limited_cache!
|
|
45
|
+
@ids = nil
|
|
46
|
+
@size = nil
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def clear_cache!
|
|
51
|
+
@count = nil
|
|
52
|
+
clear_limited_cache!
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
attr_reader :set_class
|
|
56
|
+
delegate :id_field, :id_field_with_prefix, :to => :set_class
|
|
57
|
+
|
|
58
|
+
def model_class
|
|
59
|
+
set_class.query_model_class
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def model_name
|
|
63
|
+
model_class.name
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def table_name
|
|
67
|
+
model_class.table_name
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
attr_reader :limit, :sort_order
|
|
71
|
+
|
|
72
|
+
def offset
|
|
73
|
+
if limit
|
|
74
|
+
@offset ||= @page ? (@page - 1) * limit : 0
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def page
|
|
79
|
+
if limit
|
|
80
|
+
@page ||= @offset ? (@offset / limit) : 1
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def pages
|
|
85
|
+
limit ? (1.0 * count / limit).ceil : 1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def before_query(*args)
|
|
89
|
+
proc = self.class.before_query
|
|
90
|
+
proc.bind(self).call(*args) if proc
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.before_query(&block)
|
|
94
|
+
if block
|
|
95
|
+
@before_query = block
|
|
96
|
+
else
|
|
97
|
+
@before_query
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def on_exception(*args)
|
|
102
|
+
proc = self.class.on_exception
|
|
103
|
+
proc ? proc.bind(self).call(*args) : raise(args.first)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.on_exception(&block)
|
|
107
|
+
if block
|
|
108
|
+
@on_exception = block
|
|
109
|
+
else
|
|
110
|
+
@on_exception
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def after_query(*args)
|
|
115
|
+
proc = self.class.after_query
|
|
116
|
+
proc.bind(self).call(*args) if proc
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.after_query(*args, &block)
|
|
120
|
+
if block
|
|
121
|
+
@after_query = block
|
|
122
|
+
else
|
|
123
|
+
@after_query
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class ModelSet
|
|
2
|
+
class RawQuery < Query
|
|
3
|
+
attr_reader :records
|
|
4
|
+
|
|
5
|
+
def anchor!(query, raw_method = 'find_raw_by_id')
|
|
6
|
+
@records = model_class.send(raw_method, query.ids.to_a)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def select!(&block)
|
|
10
|
+
records.select!(&block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def reject!(&block)
|
|
14
|
+
records.reject!(&block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def sort_by!(&block)
|
|
18
|
+
@records = records.sort_by(&block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ids
|
|
22
|
+
if limit
|
|
23
|
+
(records[offset, limit] || []).collect {|r| r['id'].to_i}
|
|
24
|
+
else
|
|
25
|
+
records.collect {|r| r['id'].to_i}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def size
|
|
30
|
+
if limit
|
|
31
|
+
[count - offset, limit].min
|
|
32
|
+
else
|
|
33
|
+
count
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def count
|
|
38
|
+
records.size
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class ModelSet
|
|
2
|
+
class RawSQLQuery < SQLBaseQuery
|
|
3
|
+
def sql=(sql)
|
|
4
|
+
@sql = sanitize_condition(sql)
|
|
5
|
+
['LIMIT', 'OFFSET'].each do |term|
|
|
6
|
+
raise "#{term} not permitted in raw sql" if @sql.match(/ #{term} \d+/i)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def sql
|
|
11
|
+
"#{@sql} #{limit_clause}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def count
|
|
15
|
+
# The only way to get the count if there is a limit is to fetch all ids without the limit.
|
|
16
|
+
@count ||= limit ? fetch_id_set(@sql).size : size
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class ModelSet
|
|
2
|
+
class SetQuery < Query
|
|
3
|
+
delegate :add!, :unshift!, :subtract!, :intersect!, :reorder!, :to => :set
|
|
4
|
+
|
|
5
|
+
def anchor!(query)
|
|
6
|
+
@set = query.ids.to_ordered_set
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def set
|
|
10
|
+
@set ||= [].to_ordered_set
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def ids
|
|
14
|
+
if limit
|
|
15
|
+
set.limit(limit, offset)
|
|
16
|
+
else
|
|
17
|
+
set.clone
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def size
|
|
22
|
+
if limit
|
|
23
|
+
[count - offset, limit].min
|
|
24
|
+
else
|
|
25
|
+
count
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def count
|
|
30
|
+
set.size
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
class ModelSet
|
|
2
|
+
class SolrQuery < Query
|
|
3
|
+
include Conditioned
|
|
4
|
+
|
|
5
|
+
MAX_SOLR_RESULTS = 1000
|
|
6
|
+
|
|
7
|
+
def anchor!(query)
|
|
8
|
+
add_conditions!( ids_clause(query.ids) )
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def size
|
|
12
|
+
fetch_results if @size.nil?
|
|
13
|
+
@size
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def count
|
|
17
|
+
fetch_results if @count.nil?
|
|
18
|
+
@count
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ids
|
|
22
|
+
fetch_results if @ids.nil?
|
|
23
|
+
@ids
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def fetch_results
|
|
29
|
+
query = "#{conditions.to_s};#{@sort_order.to_s}"
|
|
30
|
+
|
|
31
|
+
solr_params = []
|
|
32
|
+
solr_params << "q=#{ ERB::Util::url_encode(query) }"
|
|
33
|
+
solr_params << "wt=ruby"
|
|
34
|
+
solr_params << "fl=pk_i"
|
|
35
|
+
|
|
36
|
+
if limit
|
|
37
|
+
solr_params << "rows=#{limit}"
|
|
38
|
+
solr_params << "start=#{offset}"
|
|
39
|
+
else
|
|
40
|
+
solr_params << "rows=#{MAX_SOLR_RESULTS}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
solr_params = solr_params.join('&')
|
|
44
|
+
before_query(solr_params)
|
|
45
|
+
|
|
46
|
+
# Catch any errors when calling solr so we can log the params.
|
|
47
|
+
begin
|
|
48
|
+
resp = eval ActsAsSolr::Post.execute(solr_params)
|
|
49
|
+
rescue Exception => e
|
|
50
|
+
on_exception(e, solr_params)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
after_query(solr_params)
|
|
54
|
+
|
|
55
|
+
@count = resp['response']['numFound']
|
|
56
|
+
@ids = resp['response']['docs'].collect {|doc| doc['pk_i'].to_i}.to_ordered_set
|
|
57
|
+
@size = @ids.size
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ids_clause(ids, field = nil)
|
|
61
|
+
return 'pk_i:(false)' if ids.empty?
|
|
62
|
+
field ||= 'pk_i'
|
|
63
|
+
"#{field}:(#{ids.join(' OR ')})"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def sanitize_condition(condition)
|
|
67
|
+
condition
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../vendor/sphinx_client/lib/sphinx'
|
|
2
|
+
|
|
3
|
+
class ModelSet
|
|
4
|
+
class SphinxQuery < Query
|
|
5
|
+
MAX_SPHINX_RESULTS = 1000
|
|
6
|
+
|
|
7
|
+
class SphinxError < StandardError; end
|
|
8
|
+
|
|
9
|
+
attr_reader :conditions, :filters
|
|
10
|
+
|
|
11
|
+
def anchor!(query)
|
|
12
|
+
add_filters!( id_field => query.ids.to_a )
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_filters!(filters)
|
|
16
|
+
@filters ||= {}
|
|
17
|
+
@filters.merge!(filters)
|
|
18
|
+
clear_cache!
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_conditions!(conditions)
|
|
22
|
+
if conditions.kind_of?(Hash)
|
|
23
|
+
conditions.each do |field, value|
|
|
24
|
+
next if value.nil?
|
|
25
|
+
field = field.join(',') if field.kind_of?(Array)
|
|
26
|
+
add_conditions!("@(#{field}) #{value}")
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
@conditions ||= []
|
|
30
|
+
@conditions << conditions
|
|
31
|
+
@conditions.uniq!
|
|
32
|
+
clear_cache!
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def index
|
|
37
|
+
@index ||= '*'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def use_index!(index)
|
|
41
|
+
@index = index
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
SORT_MODES = {
|
|
45
|
+
:relevance => Sphinx::Client::SPH_SORT_RELEVANCE,
|
|
46
|
+
:descending => Sphinx::Client::SPH_SORT_ATTR_DESC,
|
|
47
|
+
:ascending => Sphinx::Client::SPH_SORT_ATTR_ASC,
|
|
48
|
+
:time => Sphinx::Client::SPH_SORT_TIME_SEGMENTS,
|
|
49
|
+
:extending => Sphinx::Client::SPH_SORT_EXTENDED,
|
|
50
|
+
:expression => Sphinx::Client::SPH_SORT_EXPR,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def order_by!(field, mode = :ascending)
|
|
54
|
+
raise "invalid mode: :#{mode}" unless SORT_MODES[mode]
|
|
55
|
+
@sort_order = [SORT_MODES[mode], field.to_s]
|
|
56
|
+
clear_cache!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def size
|
|
60
|
+
fetch_results if @size.nil?
|
|
61
|
+
@size
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def count
|
|
65
|
+
fetch_results if @count.nil?
|
|
66
|
+
@count
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def ids
|
|
70
|
+
fetch_results if @ids.nil?
|
|
71
|
+
@ids
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def fetch_results
|
|
77
|
+
if @conditions.nil? or (@filters and @filters[id_field] and @filters[id_field].empty?)
|
|
78
|
+
@count = 0
|
|
79
|
+
@size = 0
|
|
80
|
+
@ids = []
|
|
81
|
+
else
|
|
82
|
+
opts = {
|
|
83
|
+
:filters => @filters,
|
|
84
|
+
:query => conditions_clause,
|
|
85
|
+
}
|
|
86
|
+
before_query(opts)
|
|
87
|
+
|
|
88
|
+
search = Sphinx::Client.new
|
|
89
|
+
search.SetServer(self.class.server_host, self.class.server_port)
|
|
90
|
+
search.SetMatchMode(Sphinx::Client::SPH_MATCH_EXTENDED2)
|
|
91
|
+
if limit
|
|
92
|
+
search.SetLimits(offset, limit, offset + limit)
|
|
93
|
+
else
|
|
94
|
+
search.SetLimits(0, MAX_SPHINX_RESULTS, MAX_SPHINX_RESULTS)
|
|
95
|
+
end
|
|
96
|
+
search.SetSortMode(*@sort_order) if @sort_order
|
|
97
|
+
search.SetFilter('class_id', model_class.class_id) if model_class.respond_to?(:class_id)
|
|
98
|
+
|
|
99
|
+
@filters and @filters.each do |field, value|
|
|
100
|
+
next if value.nil?
|
|
101
|
+
|
|
102
|
+
exclude = defined?(AntiObject) && value.kind_of?(AntiObject)
|
|
103
|
+
value = ~value if exclude
|
|
104
|
+
|
|
105
|
+
if value.kind_of?(Range)
|
|
106
|
+
min, max = filter_values([value.begin, value.end])
|
|
107
|
+
search.SetFilterRange(field.to_s, min, max, exclude)
|
|
108
|
+
else
|
|
109
|
+
search.SetFilter(field.to_s, filter_values(value), exclude)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
begin
|
|
114
|
+
response = search.Query(opts[:query], index)
|
|
115
|
+
raise SphinxError, search.GetLastError unless response
|
|
116
|
+
rescue Exception => e
|
|
117
|
+
on_exception(e, opts)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
@count = response['total_found']
|
|
121
|
+
@ids = response['matches'].collect {|r| r['id']}.to_ordered_set
|
|
122
|
+
@size = @ids.size
|
|
123
|
+
|
|
124
|
+
after_query(opts)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def filter_values(values)
|
|
129
|
+
Array(values).collect do |value|
|
|
130
|
+
case value
|
|
131
|
+
when Date : value.to_time.to_i
|
|
132
|
+
when TrueClass : 1
|
|
133
|
+
when FalseClass : 0
|
|
134
|
+
else
|
|
135
|
+
value.to_i
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
class << self
|
|
141
|
+
attr_accessor :server_host, :server_port
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def conditions_clause
|
|
145
|
+
@conditions ? @conditions.join(' ') : ''
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|