datastax_rails 1.1.0.3 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +13 -13
  3. data/Rakefile +1 -0
  4. data/config/schema.xml.erb +0 -1
  5. data/config/{solrconfig.xml → solrconfig.xml.erb} +1 -1
  6. data/lib/blankslate.rb +1 -1
  7. data/lib/datastax_rails/associations/collection_proxy.rb +6 -2
  8. data/lib/datastax_rails/attribute_assignment.rb +114 -0
  9. data/lib/datastax_rails/attribute_methods/definition.rb +8 -2
  10. data/lib/datastax_rails/attribute_methods/typecasting.rb +2 -5
  11. data/lib/datastax_rails/attribute_methods.rb +9 -7
  12. data/lib/datastax_rails/base.rb +127 -109
  13. data/lib/datastax_rails/callbacks.rb +11 -7
  14. data/lib/datastax_rails/cassandra_only_model.rb +27 -0
  15. data/lib/datastax_rails/collection.rb +3 -1
  16. data/lib/datastax_rails/cql/base.rb +4 -0
  17. data/lib/datastax_rails/cql/select.rb +12 -2
  18. data/lib/datastax_rails/identity/abstract_key_factory.rb +1 -0
  19. data/lib/datastax_rails/identity/custom_key_factory.rb +1 -0
  20. data/lib/datastax_rails/identity/natural_key_factory.rb +1 -0
  21. data/lib/datastax_rails/identity/uuid_key_factory.rb +4 -0
  22. data/lib/datastax_rails/identity.rb +2 -1
  23. data/lib/datastax_rails/inheritance.rb +61 -0
  24. data/lib/datastax_rails/payload_model.rb +2 -5
  25. data/lib/datastax_rails/persistence.rb +63 -20
  26. data/lib/datastax_rails/railtie.rb +5 -1
  27. data/lib/datastax_rails/relation/batches.rb +2 -2
  28. data/lib/datastax_rails/relation/facet_methods.rb +56 -5
  29. data/lib/datastax_rails/relation/finder_methods.rb +55 -1
  30. data/lib/datastax_rails/relation/search_methods.rb +103 -32
  31. data/lib/datastax_rails/relation/spawn_methods.rb +3 -1
  32. data/lib/datastax_rails/relation/stats_methods.rb +1 -1
  33. data/lib/datastax_rails/relation.rb +166 -30
  34. data/lib/datastax_rails/schema/cassandra.rb +165 -0
  35. data/lib/datastax_rails/schema/migrator.rb +85 -193
  36. data/lib/datastax_rails/schema/solr.rb +158 -0
  37. data/lib/datastax_rails/schema.rb +2 -30
  38. data/lib/datastax_rails/scoping/default.rb +142 -0
  39. data/lib/datastax_rails/scoping/named.rb +200 -0
  40. data/lib/datastax_rails/scoping.rb +106 -349
  41. data/lib/datastax_rails/tasks/ds.rake +41 -42
  42. data/lib/datastax_rails/types/array_type.rb +1 -1
  43. data/lib/datastax_rails/types/base_type.rb +2 -2
  44. data/lib/datastax_rails/types/binary_type.rb +1 -1
  45. data/lib/datastax_rails/types/boolean_type.rb +1 -1
  46. data/lib/datastax_rails/types/date_type.rb +1 -1
  47. data/lib/datastax_rails/types/float_type.rb +4 -4
  48. data/lib/datastax_rails/types/integer_type.rb +3 -3
  49. data/lib/datastax_rails/types/string_type.rb +1 -1
  50. data/lib/datastax_rails/types/text_type.rb +1 -1
  51. data/lib/datastax_rails/types/time_type.rb +3 -3
  52. data/lib/datastax_rails/validations/uniqueness.rb +1 -1
  53. data/lib/datastax_rails/version.rb +1 -1
  54. data/lib/datastax_rails/wide_storage_model.rb +44 -0
  55. data/lib/datastax_rails.rb +16 -18
  56. data/spec/datastax_rails/associations_spec.rb +7 -3
  57. data/spec/datastax_rails/attribute_methods_spec.rb +23 -0
  58. data/spec/datastax_rails/base_spec.rb +1 -6
  59. data/spec/datastax_rails/inheritance_spec.rb +41 -0
  60. data/spec/datastax_rails/persistence_spec.rb +13 -3
  61. data/spec/datastax_rails/relation/batches_spec.rb +1 -1
  62. data/spec/datastax_rails/relation/facet_methods_spec.rb +52 -0
  63. data/spec/datastax_rails/relation/finder_methods_spec.rb +22 -1
  64. data/spec/datastax_rails/relation/search_methods_spec.rb +51 -1
  65. data/spec/datastax_rails/relation_spec.rb +14 -3
  66. data/spec/datastax_rails/schema/migrator_spec.rb +92 -0
  67. data/spec/datastax_rails/schema/solr_spec.rb +34 -0
  68. data/spec/datastax_rails/scoping/default_spec.rb +17 -0
  69. data/spec/datastax_rails/types/float_type_spec.rb +5 -9
  70. data/spec/datastax_rails/types/integer_type_spec.rb +5 -9
  71. data/spec/datastax_rails/types/time_type_spec.rb +28 -0
  72. data/spec/datastax_rails/validations/uniqueness_spec.rb +3 -1
  73. data/spec/dummy/config/application.rb +1 -4
  74. data/spec/dummy/config/datastax.yml +1 -1
  75. data/spec/dummy/config/environments/test.rb +2 -0
  76. data/spec/dummy/config/solr/articles-schema.xml.erb +1 -0
  77. data/spec/dummy/db/test.sqlite3 +0 -0
  78. data/spec/dummy/log/development.log +2 -0
  79. data/spec/dummy/log/production.log +2 -0
  80. data/spec/dummy/log/test.log +523 -0
  81. data/spec/spec_helper.rb +11 -0
  82. data/spec/support/models.rb +14 -0
  83. metadata +66 -22
  84. data/lib/datastax_rails/log_subscriber.rb +0 -37
  85. data/lib/datastax_rails/migrations/migration.rb +0 -15
  86. data/lib/datastax_rails/migrations.rb +0 -36
  87. data/lib/datastax_rails/mocking.rb +0 -15
  88. data/lib/datastax_rails/schema/migration.rb +0 -106
  89. data/lib/datastax_rails/schema/migration_proxy.rb +0 -25
  90. data/lib/datastax_rails/tasks/column_family.rb +0 -329
  91. data/lib/datastax_rails/tasks/keyspace.rb +0 -57
  92. data/spec/support/connection_double.rb +0 -6
@@ -1,6 +1,23 @@
1
1
  module DatastaxRails
2
2
  module SearchMethods
3
3
 
4
+ # By default, Cassandra will throw an error if you try to set a where condition
5
+ # on either a column with no index or on more than one column that isn't part
6
+ # of the primary key. If you are confident that the number of records that need
7
+ # to be searched is low, then you can instruct it to ignore the warning. Generally
8
+ # you only want to do this when either the number of records in the table is very
9
+ # small or when one of the other where conditions that has an index will reduce the
10
+ # number of records to a small number.
11
+ #
12
+ # Model.where(:name => 'johndoe', :active => true).allow_filtering
13
+ #
14
+ # @return [DatastaxRails::Relation] a new Relation object
15
+ def allow_filtering
16
+ clone.tap do |r|
17
+ r.allow_filtering_value = true
18
+ end
19
+ end
20
+
4
21
  # The default consistency level for DSR is QUORUM when searching by ID.
5
22
  # For all searches using SOLR, the default consistency is ONE. Use this
6
23
  # to override it in either case.
@@ -157,15 +174,41 @@ module DatastaxRails
157
174
  # Model.order(:name)
158
175
  # Model.order(:name => :desc)
159
176
  #
177
+ # WARNING: If this call is combined with #with_cassandra, you can only
178
+ # order on the clustering column of the primary key. If this doesn't
179
+ # mean anything to you, then you probably don't want to use these together.
180
+ #
160
181
  # @param attribute [Symbol, String, Hash] the attribute to sort by and optionally the direction to sort in
161
182
  # @return [DatastaxRails::Relation] a new Relation object
162
183
  def order(attribute)
163
184
  return self if attribute.blank?
164
185
 
165
186
  clone.tap do |r|
166
- order_by = attribute.is_a?(Hash) ? attribute.dup : {attribute.to_sym => :asc}
167
-
168
- r.order_values << order_by
187
+ r.order_values << (attribute.is_a?(Hash) ? attribute : {attribute.to_sym => :asc})
188
+ end
189
+ end
190
+
191
+ # Orders the result set in memory after all matching records have been
192
+ # retrieved.
193
+ #
194
+ # This means that limit is ignored until the end. ALL matching records WILL
195
+ # be retrieved and sorted before taking #limit records and returning them to
196
+ # the caller.
197
+ #
198
+ # Why would you do this? If you are retrieving records from a cassandra index
199
+ # but don't have the appropriate clustering order you can use this, but you should
200
+ # only do so if you are confident that the number of records returned will be low.
201
+ #
202
+ # A warning will be printed to the log if this results in a very inefficient operation.
203
+ #
204
+ # USE WITH CARE!!!!!!
205
+ #
206
+ # @param attribute [Symbol, String, Hash] the attribute to sort by and optionally the direction to sort in
207
+ # @return [DatastaxRails::Relation] a new Relation object
208
+ def slow_order(attribute)
209
+ return self if attribute.blank?
210
+ clone.tap do |r|
211
+ r.slow_order_values << (attribute.is_a?(Hash) ? attribute : {attribute.to_sym => :asc})
169
212
  end
170
213
  end
171
214
 
@@ -404,15 +447,12 @@ module DatastaxRails
404
447
  # You can also pass in an options hash with the following options:
405
448
  #
406
449
  # * :fields => list of fields to search instead of the default of all fields
407
- # * :highlight => List of fields to retrieve highlights for. Note that highlighted fields *must* be +:stored+
408
450
  #
409
451
  # Model.fulltext("john smith", :fields => [:title])
410
- # Model.fulltext("john smith", :hightlight => [:body])
411
452
  #
412
453
  # @param query [String] a fulltext query to pass to solr
413
454
  # @param opts [Hash] an optional options hash to modify the fulltext query
414
455
  # @option opts [Array] :fields list of fields to search instead of the default of all text fields (not-implemented)
415
- # @option opts [Array] :highlight list of fields to retrieve highlights for (not-implemented)
416
456
  # @return [DatastaxRails::Relation] a new Relation object
417
457
  def fulltext(query, opts = {})
418
458
  return self if query.blank?
@@ -424,6 +464,56 @@ module DatastaxRails
424
464
  end
425
465
  end
426
466
 
467
+ # Enables highlighting on specific fields when used with full
468
+ # text searching. In order for highlighting to work, the highlighted
469
+ # field(s) *must* be +:stored+
470
+ #
471
+ # Model.fulltext("ruby on rails").highlight(:tags, :body)
472
+ # Model.fulltext("pizza").highlight(:description, snippets: 3, fragsize: 150)
473
+ #
474
+ # In addition to the array of field names to highlight, you can pass in an
475
+ # options hash with the following options:
476
+ #
477
+ # * :snippets => number of highlight snippets to return
478
+ # * :fragsize => number of characters for each snippet length
479
+ # * :pre_tag => text which appears before a highlighted term
480
+ # * :post_tag => text which appears after a highlighted term
481
+ # * :merge_contiguous => collapse contiguous fragments into a single fragment
482
+ # * :use_fast_vector => enables the Solr FastVectorHighlighter
483
+ #
484
+ # Note: When enabling +:use_fast_vector+, the highlighted fields must be also have
485
+ # +:term_vectors+, +:term_positions+, and +:term_offsets+ enabled.
486
+ # For more information about these options, refer to Solr's wiki
487
+ # on HighlightingParameters[http://http://wiki.apache.org/solr/HighlightingParameters].
488
+ #
489
+ # @overload highlight(*args, opts)
490
+ # Highlights the full text search terms for the specified fields with the
491
+ # given options
492
+ # @param [Array] args list of field names to be highlighted
493
+ # @param [Hash] opts an options hash to configure the Solr highlighter
494
+ # @option opts [Integer] :snippets number of highlighted snippets to return
495
+ # @option opts [Integer] :fragsize number of characters for each snippet length
496
+ # @option opts [String] :pre_tag text which appears before a highlighted term
497
+ # @option opts [String] :post_tag text which appears after a highlighted term
498
+ # @option opts [true, false] :merge_contiguous collapse contiguous fragments into a single fragment
499
+ # @option opts [true, false] :use_fast_vector enables the Solr FastVectorHighlighter
500
+ # @return [DatastaxRails::Relation] a new Relation object
501
+ # @overload highlight(*args)
502
+ # Highlights the full text search terms for the specified fields
503
+ # @param [Array] args list of field names to be highlighted
504
+ # @return [DatastaxRails::Relation] a new Relation object
505
+ def highlight(*args)
506
+ return self if args.blank?
507
+
508
+ opts = args.last.is_a?(Hash) ? args.pop : {}
509
+
510
+ clone.tap do |r|
511
+ opts[:fields] = r.highlight_options[:fields] || []
512
+ opts[:fields] |= args # Union unique field names
513
+ r.highlight_options.merge! opts
514
+ end
515
+ end
516
+
427
517
  # @see where
428
518
  def less_than(value)
429
519
  raise ArgumentError, "#less_than can only be called after an appropriate where call. e.g. where(:created_at).less_than(1.day.ago)"
@@ -438,10 +528,14 @@ module DatastaxRails
438
528
  def solr_format(value)
439
529
  return value unless use_solr_value
440
530
  case
441
- when value.is_a?(Date), value.is_a?(Time)
442
- value.strftime('%Y-%m-%dT%H:%M:%SZ')
531
+ when value.is_a?(Time)
532
+ value.utc.strftime(DatastaxRails::Types::TimeType::FORMAT)
533
+ when value.is_a?(DateTime)
534
+ value.to_time.utc.strftime(DatastaxRails::Types::TimeType::FORMAT)
535
+ when value.is_a?(Date)
536
+ value.strftime(DatastaxRails::Types::TimeType::FORMAT)
443
537
  when value.is_a?(Array)
444
- value.collect {|v| v.gsub(/ /,"\\ ") }.join(" OR ")
538
+ value.collect {|v| v.to_s.gsub(/ /,"\\ ") }.join(" OR ")
445
539
  when value.is_a?(Fixnum)
446
540
  value < 0 ? "\\#{value}" : value
447
541
  when value.is_a?(Range)
@@ -455,29 +549,6 @@ module DatastaxRails
455
549
  end
456
550
  end
457
551
 
458
- protected
459
- def find_by_attributes(match, attributes, *args) #:nodoc:
460
- conditions = {}
461
- Hash[attributes.map {|a| [a, args[attributes.index(a)]]}].each do |k,v|
462
- if(v.is_a?(String))
463
- conditions[k] = v.gsub(/(\W)/, '\\\\\1')
464
- else
465
- conditions[k] = v
466
- end
467
- end
468
-
469
- self.where_values << conditions
470
- result = self.send(match.finder)
471
- #result = where(conditions).send(match.finder)
472
-
473
- if match.blank? && result.blank?
474
- raise RecordNotFound, "Couldn't find #{klass.name} with #{conditions.to_a.collect {|p| p.join('=')}.join(', ')}"
475
- else
476
- yield(result) if block_given?
477
- result
478
- end
479
- end
480
-
481
552
  class WhereProxy #:nodoc:
482
553
  def initialize(relation, attribute, invert = false) #:nodoc:
483
554
  @relation, @attribute, @invert = relation, attribute, invert
@@ -10,6 +10,8 @@ module DatastaxRails
10
10
 
11
11
  merged_relation = clone
12
12
 
13
+ r = r.with_default_scope if r.default_scoped? && r.klass != klass
14
+
13
15
  (Relation::MULTI_VALUE_METHODS - [:where, :where_not]).each do |method|
14
16
  value = r.send(:"#{method}_values")
15
17
  merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present?
@@ -31,7 +33,7 @@ module DatastaxRails
31
33
 
32
34
  (Relation::SINGLE_VALUE_METHODS).each do |method|
33
35
  value = r.send(:"#{method}_value")
34
- merged_relation.send(:"#{method}_value=", value) unless value.nil?
36
+ merged_relation.send(:"#{method}_value=", value) unless value.nil? || value == :default
35
37
  end
36
38
 
37
39
  merged_relation
@@ -62,7 +62,7 @@ module DatastaxRails
62
62
  private
63
63
  def calculate_stats(field)
64
64
  unless @stats[field]
65
- @stats[field] = limit(0).compute_stats(field).stats[field]
65
+ @stats[field] = limit(1).compute_stats(field).stats[field]
66
66
  end
67
67
  end
68
68
  end
@@ -1,9 +1,10 @@
1
1
  require 'rsolr'
2
+ require 'pp' if ENV['DEBUG_SOLR'] == 'true'
2
3
 
3
4
  module DatastaxRails
4
5
  class Relation
5
- MULTI_VALUE_METHODS = [:order, :where, :where_not, :fulltext, :greater_than, :less_than, :select, :stats]
6
- SINGLE_VALUE_METHODS = [:page, :per_page, :reverse_order, :query_parser, :consistency, :ttl, :use_solr, :escape, :group]
6
+ MULTI_VALUE_METHODS = [:order, :where, :where_not, :fulltext, :greater_than, :less_than, :select, :stats, :field_facet, :range_facet, :slow_order]
7
+ SINGLE_VALUE_METHODS = [:page, :per_page, :reverse_order, :query_parser, :consistency, :ttl, :use_solr, :escape, :group, :allow_filtering]
7
8
 
8
9
  SOLR_CHAR_RX = /([\+\!\(\)\[\]\^\"\~\:\'\=\/]+)/
9
10
 
@@ -14,6 +15,7 @@ module DatastaxRails
14
15
  attr_accessor :"#{m}_value"
15
16
  end
16
17
  attr_accessor :create_with_value, :default_scoped
18
+ attr_accessor :highlight_options
17
19
 
18
20
  include SearchMethods
19
21
  include ModificationMethods
@@ -21,6 +23,7 @@ module DatastaxRails
21
23
  include SpawnMethods
22
24
  include StatsMethods
23
25
  include Batches
26
+ include FacetMethods
24
27
 
25
28
  attr_reader :klass, :column_family, :loaded, :cql
26
29
  alias :loaded? :loaded
@@ -41,14 +44,14 @@ module DatastaxRails
41
44
 
42
45
  SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
43
46
  MULTI_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_values", [])}
47
+ @highlight_options = {}
44
48
  @per_page_value = @klass.default_page_size
45
49
  @page_value = 1
46
- @use_solr_value = true
50
+ @use_solr_value = :default
47
51
  @extensions = []
48
52
  @create_with_value = {}
49
- @escape_value = true
53
+ @escape_value = :default
50
54
  @stats = {}
51
- apply_default_scope
52
55
  end
53
56
 
54
57
  # Returns true if the two relations have the same query parameters
@@ -87,11 +90,8 @@ module DatastaxRails
87
90
  # matching documents
88
91
  #
89
92
  # Compare with #size.
90
- #
91
- # XXX: Count via CQL is useless unless criteria has been applied.
92
- # Otherwise you get everything that has ever been in the CF.
93
93
  def count
94
- @count ||= self.use_solr_value ? count_via_solr : count_via_cql
94
+ @count ||= (with_default_scope.path_decision == :solr) ? with_default_scope.count_via_solr : with_default_scope.count_via_cql
95
95
  end
96
96
 
97
97
  def stats
@@ -118,10 +118,16 @@ module DatastaxRails
118
118
 
119
119
  # Gets a default scope with no conditions or search attributes set.
120
120
  def default_scope
121
- clone.tap do |r|
122
- SINGLE_VALUE_METHODS.each {|v| r.instance_variable_set(:"@#{v}_value", nil)}
123
- MULTI_VALUE_METHODS.each {|v| r.instance_variable_set(:"@#{v}_values", [])}
124
- apply_default_scope
121
+ klass.scoped.with_default_scope
122
+ end
123
+
124
+ def with_default_scope #:nodoc:
125
+ if default_scoped? && default_scope = klass.send(:build_default_scope)
126
+ default_scope = default_scope.merge(self)
127
+ default_scope.default_scoped = false
128
+ default_scope
129
+ else
130
+ self
125
131
  end
126
132
  end
127
133
 
@@ -207,11 +213,11 @@ module DatastaxRails
207
213
  # Returns a standard array thus no more methods may be chained.
208
214
  def to_a
209
215
  return @results if loaded?
210
- if use_solr_value
211
- @results = query_via_solr
216
+ if with_default_scope.path_decision == :solr
217
+ @results = with_default_scope.query_via_solr
212
218
  @count = @group_value ? @results.total_for_all : @results.total_entries
213
219
  else
214
- @results = query_via_cql
220
+ @results = with_default_scope.query_via_cql
215
221
  end
216
222
  @loaded = true
217
223
  @results
@@ -236,16 +242,56 @@ module DatastaxRails
236
242
  super
237
243
  end
238
244
 
239
- # NOTE: This method does not actually run a count via CQL because it only
240
- # works if you run against a secondary index. So this currently just
241
- # delegates to the count_via_solr method.
245
+ def path_decision
246
+ return :cassandra if klass <= DatastaxRails::CassandraOnlyModel
247
+ case use_solr_value
248
+ when false
249
+ return :cassandra
250
+ when true
251
+ return :solr
252
+ else
253
+ # If we've already decided to use cassandra, just go with it.
254
+ return :cassandra unless use_solr_value
255
+ [order_values, where_not_values, fulltext_values, greater_than_values, less_than_values, field_facet_values,
256
+ range_facet_values, group_value].each do |solr_only_stuff|
257
+ return :solr unless solr_only_stuff.blank?
258
+ end
259
+ return :solr unless group_value.blank?
260
+ return :solr unless page_value == 1
261
+ @where_values.each do |wv|
262
+ wv.each do |k,v|
263
+ next if k.to_sym == :id
264
+ if(klass.attribute_definitions[k].indexed == :solr || !klass.attribute_definitions[k].indexed)
265
+ return :solr
266
+ end
267
+ end
268
+ end
269
+ # If we get here, we can safely run this query via Cassandra
270
+ return :cassandra
271
+ end
272
+ end
273
+
274
+ # If we index something into both cassandra and solr, we rename the cassandra
275
+ # column. This method maps the column names as necessary
276
+ def map_cassandra_columns(conditions)
277
+ {}.tap do |mapped|
278
+ conditions.each do |k,v|
279
+ if(klass.attribute_definitions[k].indexed == :both)
280
+ mapped["__#{k}"] = v
281
+ else
282
+ mapped[k] = v
283
+ end
284
+ end
285
+ end
286
+ end
287
+
242
288
  def count_via_cql
243
- select_columns = ['count(*)']
244
- cql = @cql.select(select_columns)
289
+ cql = @cql.select(['count(*)'])
245
290
  cql.using(@consistency_value) if @consistency_value
246
291
  @where_values.each do |wv|
247
292
  cql.conditions(wv)
248
293
  end
294
+ cql.allow_filtering if @allow_filtering_value
249
295
  CassandraCQL::Result.new(cql.execute).fetch['count']
250
296
  end
251
297
 
@@ -254,10 +300,10 @@ module DatastaxRails
254
300
  # For ad-hoc queries, you will have to use Solr.
255
301
  def query_via_cql
256
302
  select_columns = select_values.empty? ? (@klass.attribute_definitions.keys - @klass.lazy_attributes) : select_values.flatten
257
- cql = @cql.select(select_columns + ['key'])
303
+ cql = @cql.select((select_columns + @klass.key_factory.key_columns).uniq)
258
304
  cql.using(@consistency_value) if @consistency_value
259
305
  @where_values.each do |wv|
260
- cql.conditions(wv)
306
+ cql.conditions(Hash[wv.map {|k,v| [(k.to_sym == :id ? :key : k), v]}])
261
307
  end
262
308
  @greater_than_values.each do |gtv|
263
309
  gtv.each do |k,v|
@@ -270,13 +316,47 @@ module DatastaxRails
270
316
  if(@per_page_value)
271
317
  cql.limit(@per_page_value)
272
318
  end
319
+ cql.allow_filtering if @allow_filtering_value
273
320
  results = []
274
- CassandraCQL::Result.new(cql.execute).fetch do |row|
275
- results << @klass.instantiate(row['key'], row.to_hash, select_columns)
321
+ begin
322
+ CassandraCQL::Result.new(cql.execute).fetch do |row|
323
+ results << @klass.instantiate(row['key'], row.to_hash, select_columns)
324
+ end
325
+ rescue CassandraCQL::Error::InvalidRequestException => e
326
+ # If we get an exception about an empty key, ignore it. We'll return an empty set.
327
+ if e.message =~ /Key may not be empty/
328
+ # No-Op
329
+ else
330
+ raise
331
+ end
332
+ end
333
+ if(@slow_order_values.any?)
334
+ results.sort! do |a,b|
335
+ values = slow_ordering(a,b)
336
+ values[0] <=> values[1]
337
+ end
276
338
  end
277
339
  results
278
340
  end
279
341
 
342
+ def slow_ordering(obj1, obj2)
343
+ [[],[]].tap do |values|
344
+ i=0
345
+ @slow_order_values.each do |ordering|
346
+ ordering.each do |k,v|
347
+ if v == :asc
348
+ values[0][i] = obj1.send(k)
349
+ values[1][i] = obj2.send(k)
350
+ else
351
+ values[1][i] = obj1.send(k)
352
+ values[0][i] = obj2.send(k)
353
+ end
354
+ end
355
+ i += 1
356
+ end
357
+ end
358
+ end
359
+
280
360
  # Runs the query with a limit of 1 just to grab the total results attribute off
281
361
  # the result set.
282
362
  def count_via_solr
@@ -354,10 +434,9 @@ module DatastaxRails
354
434
  q = "*:*"
355
435
  else
356
436
  q = @fulltext_values.collect {|ftv| "(" + ftv[:query] + ")"}.join(' AND ')
437
+ hl_fields = @fulltext_values.collect { |ftv| ftv[:highlight].join(",") if ftv[:highlight].present? }.join(",")
357
438
  end
358
439
 
359
- #TODO highlighting and fielded queries of fulltext
360
-
361
440
  params = {:q => q}
362
441
  unless sort.empty?
363
442
  params[:sort] = sort
@@ -366,6 +445,54 @@ module DatastaxRails
366
445
  unless filter_queries.empty?
367
446
  params[:fq] = filter_queries
368
447
  end
448
+
449
+ # Facets
450
+ # facet=true to enable faceting, facet.field=<field_name> (can appear more than once for multiple fields)
451
+ # Additional options: f.<field_name>.facet.<option> [e.g. f.author.facet.sort=index]
452
+
453
+ # Facet Fields
454
+ unless field_facet_values.empty?
455
+ params['facet'] = 'true'
456
+ facet_fields = []
457
+ field_facet_values.each do |facet|
458
+ facet_field = facet[:field]
459
+ facet_fields << facet_field
460
+ facet[:options].each do |key,value|
461
+ params["f.#{facet_field}.facet.#{key}"] = value.to_s
462
+ end
463
+ end
464
+ params['facet.field'] = facet_fields
465
+ end
466
+
467
+ # Facet Ranges
468
+ unless range_facet_values.empty?
469
+ params['facet'] = 'true'
470
+ facet_fields = []
471
+ range_facet_values.each do |facet|
472
+ facet_field = facet[:field]
473
+ facet_fields << facet_field
474
+ facet[:options].each do |key,value|
475
+ params["f.#{facet_field}.facet.range.#{key}"] = value.to_s
476
+ end
477
+ end
478
+ params['facet.range'] = facet_fields
479
+ end
480
+
481
+ if @highlight_options[:fields].present?
482
+ params[:hl] = true
483
+ params['hl.fl'] = @highlight_options[:fields]
484
+ params['hl.snippets'] = @highlight_options[:snippets] if @highlight_options[:snippets]
485
+ params['hl.fragsize'] = @highlight_options[:fragsize] if @highlight_options[:fragsize]
486
+ if @highlight_options[:use_fast_vector]
487
+ params['hl.useFastVectorHighlighter'] = true
488
+ params['hl.tag.pre'] = @highlight_options[:pre_tag] if @highlight_options[:pre_tag].present?
489
+ params['hl.tag.post'] = @highlight_options[:post_tag] if @highlight_options[:post_tag].present?
490
+ else
491
+ params['hl.mergeContiguous'] = !!@highlight_options[:merge_contiguous]
492
+ params['hl.simple.pre'] = @highlight_options[:pre_tag] if @highlight_options[:pre_tag].present?
493
+ params['hl.simple.post'] = @highlight_options[:post_tag] if @highlight_options[:post_tag].present?
494
+ end
495
+ end
369
496
 
370
497
  select_columns = select_values.empty? ? (@klass.attribute_definitions.keys - @klass.lazy_attributes) : select_values.flatten
371
498
  select_columns << "id"
@@ -387,10 +514,10 @@ module DatastaxRails
387
514
  params['group.field'] = @group_value
388
515
  params['group.limit'] = @per_page_value
389
516
  params['group.offset'] = (@page_value - 1) * @per_page_value
390
- params['group.ngroups'] = 'true'
517
+ params['group.ngroups'] = 'false' # must be false due to issues with solr sharding
391
518
  solr_response = rsolr.post('select', :data => params)
392
519
  response = solr_response["grouped"][@group_value.to_s]
393
- results.total_groups = response['ngroups'].to_i
520
+ results.total_groups = response['groups'].size
394
521
  results.total_for_all = response['matches'].to_i
395
522
  results.total_entries = 0
396
523
  response['groups'].each do |group|
@@ -400,11 +527,19 @@ module DatastaxRails
400
527
  else
401
528
  solr_response = rsolr.paginate(@page_value, @per_page_value, 'select', :data => params, :method => :post)
402
529
  response = solr_response["response"]
530
+ pp solr_response if ENV['DEBUG_SOLR'] == 'true'
403
531
  results = parse_docs(response, select_columns)
532
+ results.highlights = solr_response['highlighting']
404
533
  end
405
534
  if solr_response["stats"]
406
535
  @stats = solr_response["stats"]["stats_fields"].with_indifferent_access
407
536
  end
537
+ # Apply Facets if they exist
538
+ if solr_response['facet_counts']
539
+ results.facets = {}
540
+ results.facets = results.facets.merge(solr_response['facet_counts']['facet_fields'].to_h)
541
+ results.facets = results.facets.merge(solr_response['facet_counts']['facet_ranges'].to_h)
542
+ end
408
543
  pp params if ENV['DEBUG_SOLR'] == 'true'
409
544
  results
410
545
  end
@@ -426,6 +561,7 @@ module DatastaxRails
426
561
  obj = @klass.with_cassandra.consistency(@consistency_value).find_by_id(id)
427
562
  results << obj if obj
428
563
  else
564
+ #byebug
429
565
  results << @klass.instantiate(id, doc, select_columns)
430
566
  end
431
567
  end
@@ -493,7 +629,7 @@ module DatastaxRails
493
629
  protected
494
630
 
495
631
  def method_missing(method, *args, &block) #:nodoc:
496
- if Array.method_defined?(method)
632
+ if DatastaxRails::Collection.method_defined?(method)
497
633
  to_a.send(method, *args, &block)
498
634
  elsif @klass.respond_to?(method, true)
499
635
  scoping { @klass.send(method, *args, &block) }