ninjudd-model_set 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +39 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/model_set.rb +712 -0
  5. data/lib/model_set/conditioned.rb +33 -0
  6. data/lib/model_set/conditions.rb +103 -0
  7. data/lib/model_set/query.rb +128 -0
  8. data/lib/model_set/raw_query.rb +41 -0
  9. data/lib/model_set/raw_sql_query.rb +19 -0
  10. data/lib/model_set/set_query.rb +34 -0
  11. data/lib/model_set/solr_query.rb +70 -0
  12. data/lib/model_set/sphinx_query.rb +148 -0
  13. data/lib/model_set/sql_base_query.rb +52 -0
  14. data/lib/model_set/sql_query.rb +75 -0
  15. data/lib/multi_set.rb +67 -0
  16. data/test/model_set_test.rb +283 -0
  17. data/test/multi_set_test.rb +65 -0
  18. data/test/test_helper.rb +23 -0
  19. data/vendor/sphinx_client/README.rdoc +41 -0
  20. data/vendor/sphinx_client/Rakefile +21 -0
  21. data/vendor/sphinx_client/init.rb +1 -0
  22. data/vendor/sphinx_client/install.rb +5 -0
  23. data/vendor/sphinx_client/lib/sphinx.rb +6 -0
  24. data/vendor/sphinx_client/lib/sphinx/client.rb +1093 -0
  25. data/vendor/sphinx_client/lib/sphinx/request.rb +50 -0
  26. data/vendor/sphinx_client/lib/sphinx/response.rb +69 -0
  27. data/vendor/sphinx_client/spec/client_response_spec.rb +112 -0
  28. data/vendor/sphinx_client/spec/client_spec.rb +469 -0
  29. data/vendor/sphinx_client/spec/fixtures/default_search.php +8 -0
  30. data/vendor/sphinx_client/spec/fixtures/default_search_index.php +8 -0
  31. data/vendor/sphinx_client/spec/fixtures/excerpt_custom.php +11 -0
  32. data/vendor/sphinx_client/spec/fixtures/excerpt_default.php +8 -0
  33. data/vendor/sphinx_client/spec/fixtures/excerpt_flags.php +11 -0
  34. data/vendor/sphinx_client/spec/fixtures/field_weights.php +9 -0
  35. data/vendor/sphinx_client/spec/fixtures/filter.php +9 -0
  36. data/vendor/sphinx_client/spec/fixtures/filter_exclude.php +9 -0
  37. data/vendor/sphinx_client/spec/fixtures/filter_float_range.php +9 -0
  38. data/vendor/sphinx_client/spec/fixtures/filter_float_range_exclude.php +9 -0
  39. data/vendor/sphinx_client/spec/fixtures/filter_range.php +9 -0
  40. data/vendor/sphinx_client/spec/fixtures/filter_range_exclude.php +9 -0
  41. data/vendor/sphinx_client/spec/fixtures/filter_range_int64.php +10 -0
  42. data/vendor/sphinx_client/spec/fixtures/filter_ranges.php +10 -0
  43. data/vendor/sphinx_client/spec/fixtures/filters.php +10 -0
  44. data/vendor/sphinx_client/spec/fixtures/filters_different.php +13 -0
  45. data/vendor/sphinx_client/spec/fixtures/geo_anchor.php +9 -0
  46. data/vendor/sphinx_client/spec/fixtures/group_by_attr.php +9 -0
  47. data/vendor/sphinx_client/spec/fixtures/group_by_attrpair.php +9 -0
  48. data/vendor/sphinx_client/spec/fixtures/group_by_day.php +9 -0
  49. data/vendor/sphinx_client/spec/fixtures/group_by_day_sort.php +9 -0
  50. data/vendor/sphinx_client/spec/fixtures/group_by_month.php +9 -0
  51. data/vendor/sphinx_client/spec/fixtures/group_by_week.php +9 -0
  52. data/vendor/sphinx_client/spec/fixtures/group_by_year.php +9 -0
  53. data/vendor/sphinx_client/spec/fixtures/group_distinct.php +10 -0
  54. data/vendor/sphinx_client/spec/fixtures/id_range.php +9 -0
  55. data/vendor/sphinx_client/spec/fixtures/id_range64.php +9 -0
  56. data/vendor/sphinx_client/spec/fixtures/index_weights.php +9 -0
  57. data/vendor/sphinx_client/spec/fixtures/keywords.php +8 -0
  58. data/vendor/sphinx_client/spec/fixtures/limits.php +9 -0
  59. data/vendor/sphinx_client/spec/fixtures/limits_cutoff.php +9 -0
  60. data/vendor/sphinx_client/spec/fixtures/limits_max.php +9 -0
  61. data/vendor/sphinx_client/spec/fixtures/limits_max_cutoff.php +9 -0
  62. data/vendor/sphinx_client/spec/fixtures/match_all.php +9 -0
  63. data/vendor/sphinx_client/spec/fixtures/match_any.php +9 -0
  64. data/vendor/sphinx_client/spec/fixtures/match_boolean.php +9 -0
  65. data/vendor/sphinx_client/spec/fixtures/match_extended.php +9 -0
  66. data/vendor/sphinx_client/spec/fixtures/match_extended2.php +9 -0
  67. data/vendor/sphinx_client/spec/fixtures/match_fullscan.php +9 -0
  68. data/vendor/sphinx_client/spec/fixtures/match_phrase.php +9 -0
  69. data/vendor/sphinx_client/spec/fixtures/max_query_time.php +9 -0
  70. data/vendor/sphinx_client/spec/fixtures/miltiple_queries.php +12 -0
  71. data/vendor/sphinx_client/spec/fixtures/ranking_bm25.php +9 -0
  72. data/vendor/sphinx_client/spec/fixtures/ranking_none.php +9 -0
  73. data/vendor/sphinx_client/spec/fixtures/ranking_proximity.php +9 -0
  74. data/vendor/sphinx_client/spec/fixtures/ranking_proximity_bm25.php +9 -0
  75. data/vendor/sphinx_client/spec/fixtures/ranking_wordcount.php +9 -0
  76. data/vendor/sphinx_client/spec/fixtures/retries.php +9 -0
  77. data/vendor/sphinx_client/spec/fixtures/retries_delay.php +9 -0
  78. data/vendor/sphinx_client/spec/fixtures/select.php +9 -0
  79. data/vendor/sphinx_client/spec/fixtures/set_override.php +11 -0
  80. data/vendor/sphinx_client/spec/fixtures/sort_attr_asc.php +9 -0
  81. data/vendor/sphinx_client/spec/fixtures/sort_attr_desc.php +9 -0
  82. data/vendor/sphinx_client/spec/fixtures/sort_expr.php +9 -0
  83. data/vendor/sphinx_client/spec/fixtures/sort_extended.php +9 -0
  84. data/vendor/sphinx_client/spec/fixtures/sort_relevance.php +9 -0
  85. data/vendor/sphinx_client/spec/fixtures/sort_time_segments.php +9 -0
  86. data/vendor/sphinx_client/spec/fixtures/sphinxapi.php +1269 -0
  87. data/vendor/sphinx_client/spec/fixtures/update_attributes.php +8 -0
  88. data/vendor/sphinx_client/spec/fixtures/update_attributes_mva.php +8 -0
  89. data/vendor/sphinx_client/spec/fixtures/weights.php +9 -0
  90. data/vendor/sphinx_client/spec/sphinx/sphinx-id64.conf +67 -0
  91. data/vendor/sphinx_client/spec/sphinx/sphinx.conf +67 -0
  92. data/vendor/sphinx_client/spec/sphinx/sphinx_test.sql +86 -0
  93. data/vendor/sphinx_client/sphinx.yml.tpl +3 -0
  94. data/vendor/sphinx_client/tasks/sphinx.rake +75 -0
  95. 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