datastax_rails 1.0.14.10 → 1.0.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ module DatastaxRails
2
+ class GroupedCollection < Hash
3
+ attr_accessor :total_entries, :total_groups, :total_for_all
4
+
5
+ def inspect
6
+ "<DatastaxRails::GroupedCollection##{object_id} contents: #{super} matching_records #{total_for_all}>"
7
+ end
8
+ end
9
+ end
@@ -96,15 +96,30 @@ module DatastaxRails
96
96
  end
97
97
  end
98
98
 
99
- # Group results by one or more attributes only returning the top result
100
- # for each group.
99
+ # Group results by a given attribute only returning the top results
100
+ # for each group. In Lucene, this is often referred to as Field Collapsing.
101
+ #
102
+ # This modifies the behavior of pagination. When using a group, +per_page+ will
103
+ # specify the number of results returned *for each group*. In addition, +page+
104
+ # will move all groups forward by one page possibly resulting in some groups
105
+ # getting dropped off if they have fewer matching entires than others.
106
+ #
107
+ # When grouping is being used, the sort values will be used to sort results within
108
+ # a given group. Any sorting of the groups themselves will need to be handled
109
+ # after-the-fact as the groups are returned as hash of Collection objects.
110
+ #
111
+ # Because SOLR is doing the grouping work, we can only group on single-valued
112
+ # fields (i.e., not +text+ or +array+ attributes). In the future, SOLR may
113
+ # support grouping on multi-valued fields.
114
+ #
115
+ # NOTE: Group names will be lower-cased
101
116
  #
102
117
  # Model.group(:program_id)
103
- def group(*attrs)
104
- return self if attrs.blank?
118
+ def group(attribute)
119
+ return self if attribute.blank?
105
120
 
106
121
  clone.tap do |r|
107
- r.group_values += attrs.flatten
122
+ r.group_value = attribute
108
123
  end
109
124
  end
110
125
 
@@ -2,8 +2,8 @@ require 'rsolr'
2
2
 
3
3
  module DatastaxRails
4
4
  class Relation
5
- MULTI_VALUE_METHODS = [:group, :order, :where, :where_not, :fulltext, :greater_than, :less_than, :select]
6
- SINGLE_VALUE_METHODS = [:page, :per_page, :reverse_order, :query_parser, :consistency, :ttl, :use_solr, :escape]
5
+ MULTI_VALUE_METHODS = [:order, :where, :where_not, :fulltext, :greater_than, :less_than, :select]
6
+ SINGLE_VALUE_METHODS = [:page, :per_page, :reverse_order, :query_parser, :consistency, :ttl, :use_solr, :escape, :group]
7
7
 
8
8
  SOLR_CHAR_RX = /([\+\!\(\)\[\]\^\"\~\:\'\=]+)/
9
9
 
@@ -74,6 +74,9 @@ module DatastaxRails
74
74
  # If the relation has not been populated yet, a limit of 1 will be
75
75
  # placed on the query before it is executed.
76
76
  #
77
+ # For a grouped query, this still returns the total number of
78
+ # matching documents
79
+ #
77
80
  # Compare with #size.
78
81
  #
79
82
  # XXX: Count via CQL is useless unless criteria has been applied.
@@ -164,9 +167,12 @@ module DatastaxRails
164
167
  # the number of results in the current page. DatastaxRails models
165
168
  # can have a +default_page_size+ set which will cause them to be
166
169
  # paginated all the time.
170
+ #
171
+ # For a grouped query, this returns the size of the largest group.
172
+ #
167
173
  # Compare with #count
168
174
  def size
169
- return @results.size if loaded?
175
+ return @results.size if loaded? && !@group_value
170
176
  total_entries = count
171
177
  (per_page_value && total_entries > per_page_value) ? per_page_value : total_entries
172
178
  end
@@ -184,7 +190,7 @@ module DatastaxRails
184
190
  return @results if loaded?
185
191
  if use_solr_value
186
192
  @results = query_via_solr
187
- @count = @results.total_entries
193
+ @count = @group_value ? @results.total_for_all : @results.total_entries
188
194
  else
189
195
  @results = query_via_cql
190
196
  end
@@ -245,7 +251,8 @@ module DatastaxRails
245
251
  # Runs the query with a limit of 1 just to grab the total results attribute off
246
252
  # the result set.
247
253
  def count_via_solr
248
- limit(1).select(:id).to_a.total_entries
254
+ results = limit(1).select(:id).to_a
255
+ @group_value ? results.total_for_all : results.total_entries
249
256
  end
250
257
 
251
258
  # Escapes values that might otherwise mess up the URL or confuse SOLR.
@@ -325,24 +332,44 @@ module DatastaxRails
325
332
 
326
333
  select_columns = select_values.empty? ? (@klass.attribute_definitions.keys - @klass.lazy_attributes) : select_values.flatten
327
334
 
328
- #TODO Need to escape URL stuff (I think)
329
- response = rsolr.paginate(@page_value, @per_page_value, 'select', :params => params)["response"]
335
+ if(@group_value)
336
+ results = DatastaxRails::GroupedCollection.new
337
+ params[:group] = 'true'
338
+ params['group.field'] = @group_value
339
+ params['group.limit'] = @per_page_value
340
+ params['group.offset'] = (@page_value - 1) * @per_page_value
341
+ params['group.ngroups'] = 'true'
342
+ response = rsolr.post('select', :params => params)["grouped"][@group_value.to_s]
343
+ results.total_groups = response['ngroups'].to_i
344
+ results.total_for_all = response['matches'].to_i
345
+ results.total_entries = 0
346
+ response['groups'].each do |group|
347
+ results[group['groupValue']] = parse_docs(group['doclist'], select_columns)
348
+ results.total_entries = results[group['groupValue']].total_entries if results[group['groupValue']].total_entries > results.total_entries
349
+ end
350
+ else
351
+ response = rsolr.paginate(@page_value, @per_page_value, 'select', :params => params)["response"]
352
+ results = parse_docs(response, select_columns)
353
+ end
354
+ results
355
+ end
356
+
357
+
358
+ def parse_docs(response, select_columns)
330
359
  results = DatastaxRails::Collection.new
331
360
  results.total_entries = response['numFound'].to_i
332
- if @consistency_value
333
- response['docs'].each do |doc|
334
- id = doc['id']
361
+ response['docs'].each do |doc|
362
+ id = doc['id']
363
+ if(@consistency_value)
335
364
  obj = @klass.with_cassandra.consistency(@consistency_value).find_by_id(id)
336
365
  results << obj if obj
337
- end
338
- else
339
- response['docs'].each do |doc|
340
- key = doc.delete('id')
341
- results << @klass.instantiate(key,doc, select_columns)
366
+ else
367
+ results << @klass.instantiate(id, doc, select_columns)
342
368
  end
343
369
  end
344
370
  results
345
371
  end
372
+ protected(:parse_docs)
346
373
 
347
374
  def inspect(just_me = false)
348
375
  just_me ? super() : to_a.inspect
@@ -109,6 +109,7 @@ module DatastaxRails
109
109
  solr_url = "#{DatastaxRails::Base.solr_base_url}/resource/#{DatastaxRails::Base.config[:keyspace]}.#{model.column_family}"
110
110
  uri = URI.parse(solr_url)
111
111
  Net::HTTP.start(uri.host, uri.port) do |http|
112
+ http.read_timeout(300)
112
113
  if force || solrconfig_digest != sm_digests['solrconfig']
113
114
  puts "Posting Solr Config file to '#{solr_url}/solrconfig.xml'"
114
115
  http.post(uri.path+"/solrconfig.xml", solrconfig)
@@ -1,4 +1,4 @@
1
1
  module DatastaxRails
2
2
  # The current version of the gem
3
- VERSION = "1.0.14.10"
3
+ VERSION = "1.0.15"
4
4
  end
@@ -15,6 +15,7 @@ module DatastaxRails
15
15
  autoload :Collection
16
16
  autoload :Connection
17
17
  autoload :Cql
18
+ autoload :GroupedCollection
18
19
  autoload :Identity
19
20
  autoload :Migrations
20
21
  autoload :Persistence
@@ -8,7 +8,7 @@ describe DatastaxRails::Cql::Update do
8
8
  it "should generate valid CQL" do
9
9
  cql = DatastaxRails::Cql::Update.new(@model_class, "12345")
10
10
  cql.using(DatastaxRails::Cql::Consistency::QUORUM).columns(:name => 'John', :age => '23')
11
- cql.to_cql.should == "update users using consistency QUORUM SET name = 'John', age = '23' WHERE KEY IN ('12345')"
11
+ cql.to_cql.should match(/update users using consistency QUORUM SET (name = 'John', age = '23'|age = '23', name = 'John') WHERE KEY IN \('12345'\)/)
12
12
  end
13
13
 
14
14
  it_has_behavior "default_consistency"
@@ -127,4 +127,32 @@ describe DatastaxRails::Relation do
127
127
  relation.total_pages.should == 4
128
128
  end
129
129
  end
130
+
131
+ describe "grouped queries" do
132
+ before(:each) do
133
+ Person.create(:name => 'John', :nickname => 'J')
134
+ Person.create(:name => 'Jason', :nickname => 'J')
135
+ Person.create(:name => 'James', :nickname => 'J')
136
+ Person.create(:name => 'Kathrine', :nickname => 'Kat')
137
+ Person.create(:name => 'Kathy', :nickname => 'Kat')
138
+ Person.create(:name => 'Steven', :nickname => 'Steve')
139
+ Person.commit_solr
140
+ end
141
+
142
+ it "should return matching documents grouped by an attribute" do
143
+ results = Person.group(:nickname).all
144
+ results['j'].should have(3).items
145
+ results['kat'].should have(2).items
146
+ results['steve'].should have(1).item
147
+ end
148
+
149
+ it "should return total_entires as the highest value of any group" do
150
+ results = Person.group(:nickname).all
151
+ results.total_entries.should eq(3)
152
+ end
153
+
154
+ it "should still return a total count when using the count method" do
155
+ results = Person.group(:nickname).count.should eq(6)
156
+ end
157
+ end
130
158
  end