ninjudd-model_set 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|