mongo 1.9.2 → 1.10.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +1 -1
  5. data/README.md +94 -334
  6. data/Rakefile +6 -4
  7. data/VERSION +1 -1
  8. data/bin/mongo_console +13 -6
  9. data/lib/mongo.rb +22 -27
  10. data/lib/mongo/bulk_write_collection_view.rb +352 -0
  11. data/lib/mongo/collection.rb +128 -188
  12. data/lib/mongo/collection_writer.rb +348 -0
  13. data/lib/mongo/connection.rb +19 -0
  14. data/lib/mongo/{util → connection}/node.rb +15 -1
  15. data/lib/mongo/{util → connection}/pool.rb +34 -19
  16. data/lib/mongo/{util → connection}/pool_manager.rb +8 -2
  17. data/lib/mongo/{util → connection}/sharding_pool_manager.rb +1 -1
  18. data/lib/mongo/connection/socket.rb +18 -0
  19. data/lib/mongo/{util → connection/socket}/socket_util.rb +5 -2
  20. data/lib/mongo/{util → connection/socket}/ssl_socket.rb +3 -4
  21. data/lib/mongo/{util → connection/socket}/tcp_socket.rb +25 -15
  22. data/lib/mongo/{util → connection/socket}/unix_socket.rb +6 -4
  23. data/lib/mongo/cursor.rb +113 -47
  24. data/lib/mongo/db.rb +203 -131
  25. data/lib/mongo/{exceptions.rb → exception.rb} +7 -1
  26. data/lib/mongo/functional.rb +19 -0
  27. data/lib/mongo/functional/authentication.rb +303 -0
  28. data/lib/mongo/{util → functional}/logging.rb +1 -1
  29. data/lib/mongo/{util → functional}/read_preference.rb +49 -1
  30. data/lib/mongo/{util → functional}/uri_parser.rb +81 -69
  31. data/lib/mongo/{util → functional}/write_concern.rb +2 -1
  32. data/{test/unit/pool_test.rb → lib/mongo/gridfs.rb} +5 -10
  33. data/lib/mongo/gridfs/grid.rb +1 -3
  34. data/lib/mongo/gridfs/grid_ext.rb +1 -1
  35. data/lib/mongo/gridfs/grid_file_system.rb +1 -1
  36. data/lib/mongo/gridfs/grid_io.rb +1 -1
  37. data/lib/mongo/legacy.rb +63 -8
  38. data/lib/mongo/mongo_client.rb +128 -154
  39. data/lib/mongo/mongo_replica_set_client.rb +17 -11
  40. data/lib/mongo/mongo_sharded_client.rb +2 -1
  41. data/lib/mongo/networking.rb +19 -10
  42. data/lib/mongo/utils.rb +19 -0
  43. data/lib/mongo/{util → utils}/conversions.rb +1 -1
  44. data/lib/mongo/{util → utils}/core_ext.rb +1 -1
  45. data/lib/mongo/{util → utils}/server_version.rb +1 -1
  46. data/lib/mongo/{util → utils}/support.rb +10 -57
  47. data/lib/mongo/{util → utils}/thread_local_variable_manager.rb +1 -1
  48. data/test/functional/authentication_test.rb +8 -21
  49. data/test/functional/bulk_write_collection_view_test.rb +782 -0
  50. data/test/functional/{connection_test.rb → client_test.rb} +153 -78
  51. data/test/functional/collection_test.rb +343 -97
  52. data/test/functional/collection_writer_test.rb +83 -0
  53. data/test/functional/conversions_test.rb +1 -3
  54. data/test/functional/cursor_fail_test.rb +3 -3
  55. data/test/functional/cursor_message_test.rb +3 -3
  56. data/test/functional/cursor_test.rb +38 -3
  57. data/test/functional/db_api_test.rb +5 -5
  58. data/test/functional/db_connection_test.rb +2 -2
  59. data/test/functional/db_test.rb +35 -11
  60. data/test/functional/grid_file_system_test.rb +2 -2
  61. data/test/functional/grid_io_test.rb +2 -2
  62. data/test/functional/grid_test.rb +2 -2
  63. data/test/functional/pool_test.rb +2 -3
  64. data/test/functional/safe_test.rb +5 -5
  65. data/test/functional/ssl_test.rb +22 -102
  66. data/test/functional/support_test.rb +1 -1
  67. data/test/functional/timeout_test.rb +6 -22
  68. data/test/functional/uri_test.rb +113 -12
  69. data/test/functional/write_concern_test.rb +6 -6
  70. data/test/helpers/general.rb +50 -0
  71. data/test/helpers/test_unit.rb +309 -0
  72. data/test/replica_set/authentication_test.rb +8 -23
  73. data/test/replica_set/basic_test.rb +41 -14
  74. data/test/replica_set/client_test.rb +179 -117
  75. data/test/replica_set/complex_connect_test.rb +6 -7
  76. data/test/replica_set/connection_test.rb +46 -38
  77. data/test/replica_set/count_test.rb +2 -2
  78. data/test/replica_set/cursor_test.rb +8 -8
  79. data/test/replica_set/insert_test.rb +64 -2
  80. data/test/replica_set/max_values_test.rb +59 -10
  81. data/test/replica_set/pinning_test.rb +2 -2
  82. data/test/replica_set/query_test.rb +2 -2
  83. data/test/replica_set/read_preference_test.rb +6 -6
  84. data/test/replica_set/refresh_test.rb +7 -7
  85. data/test/replica_set/replication_ack_test.rb +5 -5
  86. data/test/replica_set/ssl_test.rb +24 -106
  87. data/test/sharded_cluster/basic_test.rb +43 -15
  88. data/test/shared/authentication/basic_auth_shared.rb +215 -0
  89. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  90. data/test/shared/ssl_shared.rb +173 -0
  91. data/test/test_helper.rb +31 -199
  92. data/test/threading/basic_test.rb +29 -3
  93. data/test/tools/mongo_config.rb +45 -20
  94. data/test/tools/mongo_config_test.rb +1 -1
  95. data/test/unit/client_test.rb +136 -57
  96. data/test/unit/collection_test.rb +31 -55
  97. data/test/unit/connection_test.rb +135 -72
  98. data/test/unit/cursor_test.rb +2 -2
  99. data/test/unit/db_test.rb +19 -15
  100. data/test/unit/grid_test.rb +2 -2
  101. data/test/unit/mongo_sharded_client_test.rb +17 -15
  102. data/test/unit/node_test.rb +2 -2
  103. data/test/unit/pool_manager_test.rb +7 -5
  104. data/test/unit/read_pref_test.rb +82 -2
  105. data/test/unit/read_test.rb +14 -14
  106. data/test/unit/safe_test.rb +9 -9
  107. data/test/unit/sharding_pool_manager_test.rb +11 -5
  108. data/test/unit/write_concern_test.rb +9 -9
  109. metadata +71 -56
  110. metadata.gz.sig +0 -0
  111. data/test/functional/threading_test.rb +0 -109
  112. data/test/shared/authentication.rb +0 -121
  113. data/test/unit/util_test.rb +0 -69
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013 10gen Inc.
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -24,7 +24,9 @@ module Mongo
24
24
  :pk_factory,
25
25
  :hint,
26
26
  :write_concern,
27
- :capped
27
+ :capped,
28
+ :operation_writer,
29
+ :command_writer
28
30
 
29
31
  # Read Preference
30
32
  attr_accessor :read,
@@ -62,8 +64,6 @@ module Mongo
62
64
  # if collection name is not a string or symbol
63
65
  #
64
66
  # @return [Collection]
65
- #
66
- # @core collections constructor_details
67
67
  def initialize(name, db, opts={})
68
68
  if db.is_a?(String) && name.is_a?(Mongo::DB)
69
69
  warn "Warning: the order of parameters to initialize a collection have changed. " +
@@ -109,6 +109,8 @@ module Mongo
109
109
  end
110
110
  @pk_factory = pk_factory || opts[:pk] || BSON::ObjectId
111
111
  @hint = nil
112
+ @operation_writer = CollectionOperationWriter.new(self)
113
+ @command_writer = CollectionCommandWriter.new(self)
112
114
  end
113
115
 
114
116
  # Indicate whether this is a capped collection.
@@ -215,14 +217,14 @@ module Mongo
215
217
  # @option opts [Block] :transformer (nil) a block for transforming returned documents.
216
218
  # This is normally used by object mappers to convert each returned document to an instance of a class.
217
219
  # @option opts [String] :comment (nil) a comment to include in profiling logs
220
+ # @option opts [Boolean] :compile_regex (true) whether BSON regex objects should be compiled into Ruby regexes.
221
+ # If false, a BSON::Regex object will be returned instead.
218
222
  #
219
223
  # @raise [ArgumentError]
220
224
  # if timeout is set to false and find is not invoked in a block
221
225
  #
222
226
  # @raise [RuntimeError]
223
227
  # if given unknown options
224
- #
225
- # @core find find-instance_method
226
228
  def find(selector={}, opts={})
227
229
  opts = opts.dup
228
230
  fields = opts.delete(:fields)
@@ -243,6 +245,7 @@ module Mongo
243
245
  read = opts.delete(:read) || @read
244
246
  tag_sets = opts.delete(:tag_sets) || @tag_sets
245
247
  acceptable_latency = opts.delete(:acceptable_latency) || @acceptable_latency
248
+ compile_regex = opts.key?(:compile_regex) ? opts.delete(:compile_regex) : true
246
249
 
247
250
  if timeout == false && !block_given?
248
251
  raise ArgumentError, "Collection#find must be invoked with a block when timeout is disabled."
@@ -273,7 +276,8 @@ module Mongo
273
276
  :read => read,
274
277
  :tag_sets => tag_sets,
275
278
  :comment => comment,
276
- :acceptable_latency => acceptable_latency
279
+ :acceptable_latency => acceptable_latency,
280
+ :compile_regex => compile_regex
277
281
  })
278
282
 
279
283
  if block_given?
@@ -314,7 +318,9 @@ module Mongo
314
318
  else
315
319
  raise TypeError, "spec_or_object_id must be an instance of ObjectId or Hash, or nil"
316
320
  end
317
- find(spec, opts.merge(:limit => -1)).next_document
321
+ timeout = opts.delete(:max_time_ms)
322
+ cursor = find(spec, opts.merge(:limit => -1))
323
+ timeout ? cursor.max_time_ms(timeout).next_document : cursor.next_document
318
324
  end
319
325
 
320
326
  # Save a document to this collection.
@@ -380,14 +386,19 @@ module Mongo
380
386
  # collects invalid documents as an array. Note that this option changes the result format.
381
387
  #
382
388
  # @raise [Mongo::OperationFailure] will be raised iff :w > 0 and the operation fails.
383
- #
384
- # @core insert insert-instance_method
385
389
  def insert(doc_or_docs, opts={})
386
- doc_or_docs = [doc_or_docs] unless doc_or_docs.is_a?(Array)
387
- doc_or_docs.collect! { |doc| @pk_factory.create_pk(doc) }
388
- write_concern = get_write_concern(opts, self)
389
- result = insert_documents(doc_or_docs, @name, true, write_concern, opts)
390
- result.size > 1 ? result : result.first
390
+ if doc_or_docs.respond_to?(:collect!)
391
+ doc_or_docs.collect! { |doc| @pk_factory.create_pk(doc) }
392
+ error_docs, errors, rest_ignored = batch_write(:insert, doc_or_docs, true, opts)
393
+ raise errors.last if !opts[:collect_on_error] && !errors.empty?
394
+ inserted_docs = doc_or_docs - error_docs
395
+ inserted_ids = inserted_docs.collect {|o| o[:_id] || o['_id']}
396
+ opts[:collect_on_error] ? [inserted_ids, error_docs] : inserted_ids
397
+ else
398
+ @pk_factory.create_pk(doc_or_docs)
399
+ send_write(:insert, nil, doc_or_docs, true, opts)
400
+ return doc_or_docs[:_id] || doc_or_docs['_id']
401
+ end
391
402
  end
392
403
  alias_method :<<, :insert
393
404
 
@@ -401,6 +412,7 @@ module Mongo
401
412
  # @option opts [Boolean] :j (false) Set journal acknowledgement
402
413
  # @option opts [Integer] :wtimeout (nil) Set replica set acknowledgement timeout
403
414
  # @option opts [Boolean] :fsync (false) Set fsync acknowledgement.
415
+ # @option opts [Integer] :limit (0) Set limit option, currently only 0 for all or 1 for just one.
404
416
  #
405
417
  # Notes on write concern:
406
418
  # Options provided here will override any write concern options set on this collection,
@@ -417,23 +429,8 @@ module Mongo
417
429
  # Otherwise, returns true.
418
430
  #
419
431
  # @raise [Mongo::OperationFailure] will be raised iff :w > 0 and the operation fails.
420
- #
421
- # @core remove remove-instance_method
422
432
  def remove(selector={}, opts={})
423
- write_concern = get_write_concern(opts, self)
424
- message = BSON::ByteBuffer.new("\0\0\0\0", @connection.max_message_size)
425
- BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
426
- message.put_int(0)
427
- message.put_binary(BSON::BSON_CODER.serialize(selector, false, true, @connection.max_bson_size).to_s)
428
-
429
- instrument(:remove, :database => @db.name, :collection => @name, :selector => selector) do
430
- if Mongo::WriteConcern.gle?(write_concern)
431
- @connection.send_message_with_gle(Mongo::Constants::OP_DELETE, message, @db.name, nil, write_concern)
432
- else
433
- @connection.send_message(Mongo::Constants::OP_DELETE, message)
434
- true
435
- end
436
- end
433
+ send_write(:delete, selector, nil, nil, opts)
437
434
  end
438
435
 
439
436
  # Update one or more documents in this collection.
@@ -464,31 +461,8 @@ module Mongo
464
461
  # Otherwise, returns true.
465
462
  #
466
463
  # @raise [Mongo::OperationFailure] will be raised iff :w > 0 and the operation fails.
467
- #
468
- # @core update update-instance_method
469
464
  def update(selector, document, opts={})
470
- # Initial byte is 0.
471
- write_concern = get_write_concern(opts, self)
472
- message = BSON::ByteBuffer.new("\0\0\0\0", @connection.max_message_size)
473
- BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
474
- update_options = 0
475
- update_options += 1 if opts[:upsert]
476
- update_options += 2 if opts[:multi]
477
-
478
- # Determine if update document has modifiers and check keys if so
479
- check_keys = document.keys.first.to_s.start_with?("$") ? false : true
480
-
481
- message.put_int(update_options)
482
- message.put_binary(BSON::BSON_CODER.serialize(selector, false, true, @connection.max_bson_size).to_s)
483
- message.put_binary(BSON::BSON_CODER.serialize(document, check_keys, true, @connection.max_bson_size).to_s)
484
-
485
- instrument(:update, :database => @db.name, :collection => @name, :selector => selector, :document => document) do
486
- if Mongo::WriteConcern.gle?(write_concern)
487
- @connection.send_message_with_gle(Mongo::Constants::OP_UPDATE, message, @db.name, nil, write_concern)
488
- else
489
- @connection.send_message(Mongo::Constants::OP_UPDATE, message)
490
- end
491
- end
465
+ send_write(:update, selector, document, !document.keys.first.to_s.start_with?("$"), opts)
492
466
  end
493
467
 
494
468
  # Create a new index.
@@ -540,8 +514,6 @@ module Mongo
540
514
  # @restaurants.create_index([['location', Mongo::GEO2D]], :min => 500, :max => 500)
541
515
  #
542
516
  # @return [String] the name of the index created.
543
- #
544
- # @core indexes create_index-instance_method
545
517
  def create_index(spec, opts={})
546
518
  opts[:dropDups] = opts[:drop_dups] if opts[:drop_dups]
547
519
  opts[:bucketSize] = opts[:bucket_size] if opts[:bucket_size]
@@ -589,8 +561,6 @@ module Mongo
589
561
  # Drop a specified index.
590
562
  #
591
563
  # @param [String] name
592
- #
593
- # @core indexes
594
564
  def drop_index(name)
595
565
  if name.is_a?(Array)
596
566
  return drop_index(index_name(name))
@@ -600,8 +570,6 @@ module Mongo
600
570
  end
601
571
 
602
572
  # Drop all indexes.
603
- #
604
- # @core indexes
605
573
  def drop_indexes
606
574
  @cache = {}
607
575
 
@@ -625,16 +593,19 @@ module Mongo
625
593
  # of the sort options available for Cursor#sort. Sort order is important
626
594
  # if the query will be matching multiple documents since only the first
627
595
  # matching document will be updated and returned.
628
- # @option opts [Boolean] :remove (false) If true, removes the the returned
596
+ # @option opts [Boolean] :remove (false) If true, removes the returned
629
597
  # document from the collection.
630
598
  # @option opts [Boolean] :new (false) If true, returns the updated
631
599
  # document; otherwise, returns the document prior to update.
600
+ # @option opts [Boolean] :upsert (false) If true, creates a new document
601
+ # if the query returns no document.
602
+ # @option opts [Hash] :fields (nil) A subset of fields to return.
603
+ # Specify an inclusion of a field with 1. _id is included by default and must
604
+ # be explicitly excluded.
632
605
  # @option opts [Boolean] :full_response (false) If true, returns the entire
633
606
  # response object from the server including 'ok' and 'lastErrorObject'.
634
607
  #
635
608
  # @return [Hash] the matched document.
636
- #
637
- # @core findandmodify find_and_modify-instance_method
638
609
  def find_and_modify(opts={})
639
610
  full_response = opts.delete(:full_response)
640
611
 
@@ -672,9 +643,13 @@ module Mongo
672
643
  #
673
644
  # '$sort' Sorts all input documents and returns them to the pipeline in sorted order.
674
645
  #
675
- # @option opts [:primary, :secondary] :read Read preference indicating which server to perform this query
676
- # on. See Collection#find for more details.
646
+ # '$out' The name of a collection to which the result set will be saved.
647
+ #
648
+ # @option opts [:primary, :secondary] :read Read preference indicating which server to perform this operation
649
+ # on. If $out is specified and :read is not :primary, the aggregation will be rerouted to the primary with
650
+ # a warning. See Collection#find for more details.
677
651
  # @option opts [String] :comment (nil) a comment to include in profiling logs
652
+ # @option opts [Hash] :cursor cursor options for aggregation
678
653
  #
679
654
  # @return [Array] An Array with the aggregate command's results.
680
655
  #
@@ -685,16 +660,31 @@ module Mongo
685
660
  raise MongoArgumentError, "pipeline must be an array of operators" unless pipeline.class == Array
686
661
  raise MongoArgumentError, "pipeline operators must be hashes" unless pipeline.all? { |op| op.class == Hash }
687
662
 
688
- hash = BSON::OrderedHash.new
689
- hash['aggregate'] = self.name
690
- hash['pipeline'] = pipeline
663
+ selector = BSON::OrderedHash.new
664
+ selector['aggregate'] = self.name
665
+ selector['pipeline'] = pipeline
691
666
 
692
- result = @db.command(hash, command_options(opts))
667
+ result = @db.command(selector, command_options(opts))
693
668
  unless Mongo::Support.ok?(result)
694
669
  raise Mongo::OperationFailure, "aggregate failed: #{result['errmsg']}"
695
670
  end
696
671
 
697
- return result["result"]
672
+ if result.key?('cursor')
673
+ cursor_info = result['cursor']
674
+
675
+ seed = {
676
+ :cursor_id => cursor_info['id'],
677
+ :first_batch => cursor_info['firstBatch'],
678
+ :pool => @connection.pinned_pool
679
+ }
680
+
681
+ return Cursor.new(self, seed.merge!(opts))
682
+
683
+ elsif selector['pipeline'].any? { |op| op.key?('$out') || op.key?(:$out) }
684
+ return result
685
+ end
686
+
687
+ result['result'] || result
698
688
  end
699
689
 
700
690
  # Perform a map-reduce operation on the current collection.
@@ -709,9 +699,10 @@ module Mongo
709
699
  # @option opts [Integer] :limit (nil) if passing a query, number of objects to return from the collection.
710
700
  # @option opts [String, BSON::Code] :finalize (nil) a javascript function to apply to the result set after the
711
701
  # map/reduce operation has finished.
712
- # @option opts [String] :out (nil) a valid output type. In versions of MongoDB prior to v1.7.6,
713
- # this option takes the name of a collection for the output results. In versions 1.7.6 and later,
714
- # this option specifies the output type. See the core docs for available output types.
702
+ # @option opts [String, Hash] :out Location of the result of the map-reduce operation. You can output to a
703
+ # collection, output to a collection with an action, or output inline. You may output to a collection
704
+ # when performing map reduce operations on the primary members of the set; on secondary members you
705
+ # may only use the inline output. See the server mapReduce documentation for available options.
715
706
  # @option opts [Boolean] :keeptemp (false) if true, the generated collection will be persisted. The default
716
707
  # is false. Note that this option has no effect is versions of MongoDB > v1.7.6.
717
708
  # @option opts [Boolean ] :verbose (false) if true, provides statistics on job execution time.
@@ -728,9 +719,8 @@ module Mongo
728
719
  # @raise ArgumentError if you specify { :out => { :inline => true }} but don't specify :raw => true.
729
720
  #
730
721
  # @see http://www.mongodb.org/display/DOCS/MapReduce Offical MongoDB map/reduce documentation.
731
- #
732
- # @core mapreduce map_reduce-instance_method
733
722
  def map_reduce(map, reduce, opts={})
723
+ opts = opts.dup
734
724
  map = BSON::Code.new(map) unless map.is_a?(BSON::Code)
735
725
  reduce = BSON::Code.new(reduce) unless reduce.is_a?(BSON::Code)
736
726
  raw = opts.delete(:raw)
@@ -739,10 +729,8 @@ module Mongo
739
729
  hash['mapreduce'] = self.name
740
730
  hash['map'] = map
741
731
  hash['reduce'] = reduce
742
- hash.merge! opts
743
- if hash[:sort]
744
- hash[:sort] = Mongo::Support.format_order_clause(hash[:sort])
745
- end
732
+ hash['out'] = opts.delete(:out)
733
+ hash['sort'] = Mongo::Support.format_order_clause(opts.delete(:sort)) if opts.key?(:sort)
746
734
 
747
735
  result = @db.command(hash, command_options(opts))
748
736
  unless Mongo::Support.ok?(result)
@@ -751,8 +739,10 @@ module Mongo
751
739
 
752
740
  if raw
753
741
  result
754
- elsif result["result"]
755
- if result['result'].is_a? BSON::OrderedHash and result['result'].has_key? 'db' and result['result'].has_key? 'collection'
742
+ elsif result['result']
743
+ if result['result'].is_a?(BSON::OrderedHash) &&
744
+ result['result'].key?('db') &&
745
+ result['result'].key?('collection')
756
746
  otherdb = @db.connection[result['result']['db']]
757
747
  otherdb[result['result']['collection']]
758
748
  else
@@ -785,13 +775,17 @@ module Mongo
785
775
  #
786
776
  # @return [Array] the command response consisting of grouped items.
787
777
  def group(opts, condition={}, initial={}, reduce=nil, finalize=nil)
778
+ opts = opts.dup
788
779
  if opts.is_a?(Hash)
789
780
  return new_group(opts)
790
- else
791
- warn "Collection#group no longer take a list of parameters. This usage is deprecated and will be remove in v2.0." +
792
- "Check out the new API at http://api.mongodb.org/ruby/current/Mongo/Collection.html#group-instance_method"
781
+ elsif opts.is_a?(Symbol)
782
+ raise MongoArgumentError, "Group takes either an array of fields to group by or a JavaScript function" +
783
+ "in the form of a String or BSON::Code."
793
784
  end
794
785
 
786
+ warn "Collection#group no longer takes a list of parameters. This usage is deprecated and will be removed in v2.0." +
787
+ "Check out the new API at http://api.mongodb.org/ruby/current/Mongo/Collection.html#group-instance_method"
788
+
795
789
  reduce = BSON::Code.new(reduce) unless reduce.is_a?(BSON::Code)
796
790
 
797
791
  group_command = {
@@ -803,11 +797,6 @@ module Mongo
803
797
  }
804
798
  }
805
799
 
806
- if opts.is_a?(Symbol)
807
- raise MongoArgumentError, "Group takes either an array of fields to group by or a JavaScript function" +
808
- "in the form of a String or BSON::Code."
809
- end
810
-
811
800
  unless opts.nil?
812
801
  if opts.is_a? Array
813
802
  key_type = "key"
@@ -835,13 +824,31 @@ module Mongo
835
824
  end
836
825
  end
837
826
 
827
+
828
+ def parallel_scan(num_cursors, opts={})
829
+ cmd = BSON::OrderedHash.new
830
+ cmd[:parallelCollectionScan] = self.name
831
+ cmd[:numCursors] = num_cursors
832
+ result = @db.command(cmd, command_options(opts))
833
+
834
+ result['cursors'].collect do |cursor_info|
835
+ seed = {
836
+ :cursor_id => cursor_info['cursor']['id'],
837
+ :first_batch => cursor_info['cursor']['firstBatch'],
838
+ :pool => @connection.pinned_pool
839
+ }
840
+ Cursor.new(self, seed.merge!(opts))
841
+ end
842
+
843
+ end
844
+
838
845
  private
839
846
 
840
847
  def new_group(opts={})
841
- reduce = opts[:reduce]
842
- finalize = opts[:finalize]
843
- cond = opts.fetch(:cond, {})
844
- initial = opts[:initial]
848
+ reduce = opts.delete(:reduce)
849
+ finalize = opts.delete(:finalize)
850
+ cond = opts.delete(:cond) || {}
851
+ initial = opts.delete(:initial)
845
852
 
846
853
  if !(reduce && initial)
847
854
  raise MongoArgumentError, "Group requires at minimum values for initial and reduce."
@@ -860,14 +867,14 @@ module Mongo
860
867
  cmd['group']['finalize'] = finalize.to_bson_code
861
868
  end
862
869
 
863
- if key = opts[:key]
870
+ if key = opts.delete(:key)
864
871
  if key.is_a?(String) || key.is_a?(Symbol)
865
872
  key = [key]
866
873
  end
867
874
  key_value = {}
868
875
  key.each { |k| key_value[k] = 1 }
869
876
  cmd["group"]["key"] = key_value
870
- elsif keyf = opts[:keyf]
877
+ elsif keyf = opts.delete(:keyf)
871
878
  cmd["group"]["$keyf"] = keyf.to_bson_code
872
879
  end
873
880
 
@@ -953,8 +960,6 @@ module Mongo
953
960
  # Get information on the indexes for this collection.
954
961
  #
955
962
  # @return [Hash] a hash where the keys are index names.
956
- #
957
- # @core indexes
958
963
  def index_information
959
964
  @db.index_information(@name)
960
965
  end
@@ -996,19 +1001,11 @@ module Mongo
996
1001
 
997
1002
  protected
998
1003
 
999
- # Parse common options for read-only commands from an input @opts
1000
- # hash and return a hash suitable for passing to DB#command.
1004
+ # Provide required command options if they are missing in the command options hash.
1005
+ #
1006
+ # @return [Hash] The command options hash
1001
1007
  def command_options(opts)
1002
- out = {}
1003
-
1004
- if read = opts[:read]
1005
- Mongo::ReadPreference::validate(read)
1006
- else
1007
- read = @read
1008
- end
1009
- out[:read] = read
1010
- out[:comment] = opts[:comment] if opts[:comment]
1011
- out
1008
+ opts[:read] ? opts : opts.merge(:read => @read)
1012
1009
  end
1013
1010
 
1014
1011
  def normalize_hint_fields(hint)
@@ -1028,6 +1025,15 @@ module Mongo
1028
1025
 
1029
1026
  private
1030
1027
 
1028
+ def send_write(op_type, selector, doc_or_docs, check_keys, opts, collection_name=@name)
1029
+ write_concern = get_write_concern(opts, self)
1030
+ if @db.connection.use_write_command?(write_concern)
1031
+ @command_writer.send_write_command(op_type, selector, doc_or_docs, check_keys, opts, write_concern, collection_name)
1032
+ else
1033
+ @operation_writer.send_write_operation(op_type, selector, doc_or_docs, check_keys, opts, write_concern, collection_name)
1034
+ end
1035
+ end
1036
+
1031
1037
  def index_name(spec)
1032
1038
  field_spec = parse_index_spec(spec)
1033
1039
  index_information.each do |index|
@@ -1071,17 +1077,17 @@ module Mongo
1071
1077
  def generate_indexes(field_spec, name, opts)
1072
1078
  selector = {
1073
1079
  :name => name,
1074
- :ns => "#{@db.name}.#{@name}",
1075
1080
  :key => field_spec
1076
1081
  }
1077
1082
  selector.merge!(opts)
1078
1083
 
1079
1084
  begin
1080
- insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, {:w => 1})
1081
-
1082
- rescue Mongo::OperationFailure => e
1083
- if selector[:dropDups] && e.message =~ /^11000/
1084
- # NOP. If the user is intentionally dropping dups, we can ignore duplicate key errors.
1085
+ cmd = BSON::OrderedHash[:createIndexes, @name].merge!(selector)
1086
+ @db.command(cmd)
1087
+ rescue Mongo::OperationFailure => ex
1088
+ if ex.error_code == Mongo::ErrorCode::COMMAND_NOT_FOUND || ex.error_code.nil?
1089
+ selector[:ns] = "#{@db.name}.#{@name}"
1090
+ send_write(:insert, nil, selector, false, {:w => 1}, Mongo::DB::SYSTEM_INDEX_COLLECTION)
1085
1091
  else
1086
1092
  raise Mongo::OperationFailure, "Failed to create index #{selector.inspect} with the following error: " +
1087
1093
  "#{e.message}"
@@ -1099,81 +1105,15 @@ module Mongo
1099
1105
  indexes.join("_")
1100
1106
  end
1101
1107
 
1102
- def insert_buffer(collection_name, continue_on_error)
1103
- message = BSON::ByteBuffer.new("", @connection.max_message_size)
1104
- message.put_int(continue_on_error ? 1 : 0)
1105
- BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{collection_name}")
1106
- message
1107
- end
1108
-
1109
- def insert_batch(message, documents, write_concern, continue_on_error, errors, collection_name=@name)
1110
- begin
1111
- send_insert_message(message, documents, collection_name, write_concern)
1112
- rescue OperationFailure => ex
1113
- raise ex unless continue_on_error
1114
- errors << ex
1115
- end
1116
- end
1117
-
1118
- def send_insert_message(message, documents, collection_name, write_concern)
1119
- instrument(:insert, :database => @db.name, :collection => collection_name, :documents => documents) do
1120
- if Mongo::WriteConcern.gle?(write_concern)
1121
- @connection.send_message_with_gle(Mongo::Constants::OP_INSERT, message, @db.name, nil, write_concern)
1122
- else
1123
- @connection.send_message(Mongo::Constants::OP_INSERT, message)
1124
- end
1108
+ def batch_write(op_type, documents, check_keys=true, opts={})
1109
+ write_concern = get_write_concern(opts, self)
1110
+ if @db.connection.use_write_command?(write_concern)
1111
+ return @command_writer.batch_write(op_type, documents, check_keys, opts)
1112
+ else
1113
+ return @operation_writer.batch_write(op_type, documents, check_keys, opts)
1125
1114
  end
1126
1115
  end
1127
1116
 
1128
- # Sends a Mongo::Constants::OP_INSERT message to the database.
1129
- # Takes an array of +documents+, an optional +collection_name+, and a
1130
- # +check_keys+ setting.
1131
- def insert_documents(documents, collection_name=@name, check_keys=true, write_concern={}, flags={})
1132
- continue_on_error = !!flags[:continue_on_error]
1133
- collect_on_error = !!flags[:collect_on_error]
1134
- error_docs = [] # docs with errors on serialization
1135
- errors = [] # for all errors on insertion
1136
- batch_start = 0
1137
-
1138
- message = insert_buffer(collection_name, continue_on_error)
1139
-
1140
- documents.each_with_index do |doc, index|
1141
- begin
1142
- serialized_doc = BSON::BSON_CODER.serialize(doc, check_keys, true, @connection.max_bson_size)
1143
- rescue BSON::InvalidDocument, BSON::InvalidKeyName, BSON::InvalidStringEncoding => ex
1144
- raise ex unless collect_on_error
1145
- error_docs << doc
1146
- next
1147
- end
1148
-
1149
- # Check if the current msg has room for this doc. If not, send current msg and create a new one.
1150
- # GLE is a sep msg with its own header so shouldn't be included in padding with header size.
1151
- total_message_size = Networking::STANDARD_HEADER_SIZE + message.size + serialized_doc.size
1152
- if total_message_size > @connection.max_message_size
1153
- docs_to_insert = documents[batch_start..index] - error_docs
1154
- insert_batch(message, docs_to_insert, write_concern, continue_on_error, errors, collection_name)
1155
- batch_start = index
1156
- message = insert_buffer(collection_name, continue_on_error)
1157
- redo
1158
- else
1159
- message.put_binary(serialized_doc.to_s)
1160
- end
1161
- end
1162
-
1163
- docs_to_insert = documents[batch_start..-1] - error_docs
1164
- inserted_docs = documents - error_docs
1165
- inserted_ids = inserted_docs.collect {|o| o[:_id] || o['_id']}
1166
-
1167
- # Avoid insertion if all docs failed serialization and collect_on_error
1168
- if error_docs.empty? || !docs_to_insert.empty?
1169
- insert_batch(message, docs_to_insert, write_concern, continue_on_error, errors, collection_name)
1170
- # insert_batch collects errors if w > 0 and continue_on_error is true,
1171
- # so raise the error here, as this is the last or only msg sent
1172
- raise errors.last unless errors.empty?
1173
- end
1174
-
1175
- collect_on_error ? [inserted_ids, error_docs] : inserted_ids
1176
- end
1177
1117
  end
1178
1118
 
1179
1119
  end