mongo 1.3.0 → 1.12.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +122 -271
  5. data/Rakefile +25 -209
  6. data/VERSION +1 -0
  7. data/bin/mongo_console +31 -9
  8. data/lib/mongo/bulk_write_collection_view.rb +387 -0
  9. data/lib/mongo/collection.rb +576 -269
  10. data/lib/mongo/collection_writer.rb +364 -0
  11. data/lib/mongo/connection/node.rb +249 -0
  12. data/lib/mongo/connection/pool.rb +340 -0
  13. data/lib/mongo/connection/pool_manager.rb +320 -0
  14. data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
  15. data/lib/mongo/connection/socket/socket_util.rb +37 -0
  16. data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
  17. data/lib/mongo/connection/socket/tcp_socket.rb +87 -0
  18. data/lib/mongo/connection/socket/unix_socket.rb +39 -0
  19. data/lib/mongo/connection/socket.rb +18 -0
  20. data/lib/mongo/connection.rb +7 -875
  21. data/lib/mongo/cursor.rb +403 -117
  22. data/lib/mongo/db.rb +444 -243
  23. data/lib/mongo/exception.rb +145 -0
  24. data/lib/mongo/functional/authentication.rb +455 -0
  25. data/lib/mongo/functional/logging.rb +85 -0
  26. data/lib/mongo/functional/read_preference.rb +183 -0
  27. data/lib/mongo/functional/scram.rb +556 -0
  28. data/lib/mongo/functional/uri_parser.rb +409 -0
  29. data/lib/mongo/functional/write_concern.rb +66 -0
  30. data/lib/mongo/functional.rb +20 -0
  31. data/lib/mongo/gridfs/grid.rb +30 -24
  32. data/lib/mongo/gridfs/grid_ext.rb +6 -10
  33. data/lib/mongo/gridfs/grid_file_system.rb +38 -20
  34. data/lib/mongo/gridfs/grid_io.rb +84 -75
  35. data/lib/mongo/gridfs.rb +18 -0
  36. data/lib/mongo/legacy.rb +140 -0
  37. data/lib/mongo/mongo_client.rb +697 -0
  38. data/lib/mongo/mongo_replica_set_client.rb +535 -0
  39. data/lib/mongo/mongo_sharded_client.rb +159 -0
  40. data/lib/mongo/networking.rb +372 -0
  41. data/lib/mongo/{util → utils}/conversions.rb +29 -8
  42. data/lib/mongo/{util → utils}/core_ext.rb +28 -18
  43. data/lib/mongo/{util → utils}/server_version.rb +4 -6
  44. data/lib/mongo/{util → utils}/support.rb +29 -31
  45. data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
  46. data/lib/mongo/utils.rb +19 -0
  47. data/lib/mongo.rb +51 -50
  48. data/mongo.gemspec +29 -32
  49. data/test/functional/authentication_test.rb +39 -0
  50. data/test/functional/bulk_api_stress_test.rb +133 -0
  51. data/test/functional/bulk_write_collection_view_test.rb +1198 -0
  52. data/test/functional/client_test.rb +627 -0
  53. data/test/functional/collection_test.rb +2175 -0
  54. data/test/functional/collection_writer_test.rb +83 -0
  55. data/test/{conversions_test.rb → functional/conversions_test.rb} +47 -3
  56. data/test/functional/cursor_fail_test.rb +57 -0
  57. data/test/functional/cursor_message_test.rb +56 -0
  58. data/test/functional/cursor_test.rb +683 -0
  59. data/test/functional/db_api_test.rb +835 -0
  60. data/test/functional/db_connection_test.rb +25 -0
  61. data/test/functional/db_test.rb +348 -0
  62. data/test/functional/grid_file_system_test.rb +285 -0
  63. data/test/{grid_io_test.rb → functional/grid_io_test.rb} +72 -11
  64. data/test/{grid_test.rb → functional/grid_test.rb} +88 -15
  65. data/test/functional/pool_test.rb +136 -0
  66. data/test/functional/safe_test.rb +98 -0
  67. data/test/functional/ssl_test.rb +29 -0
  68. data/test/functional/support_test.rb +62 -0
  69. data/test/functional/timeout_test.rb +60 -0
  70. data/test/functional/uri_test.rb +446 -0
  71. data/test/functional/write_concern_test.rb +118 -0
  72. data/test/helpers/general.rb +50 -0
  73. data/test/helpers/test_unit.rb +476 -0
  74. data/test/replica_set/authentication_test.rb +37 -0
  75. data/test/replica_set/basic_test.rb +189 -0
  76. data/test/replica_set/client_test.rb +393 -0
  77. data/test/replica_set/connection_test.rb +138 -0
  78. data/test/replica_set/count_test.rb +66 -0
  79. data/test/replica_set/cursor_test.rb +220 -0
  80. data/test/replica_set/insert_test.rb +157 -0
  81. data/test/replica_set/max_values_test.rb +151 -0
  82. data/test/replica_set/pinning_test.rb +105 -0
  83. data/test/replica_set/query_test.rb +73 -0
  84. data/test/replica_set/read_preference_test.rb +219 -0
  85. data/test/replica_set/refresh_test.rb +211 -0
  86. data/test/replica_set/replication_ack_test.rb +95 -0
  87. data/test/replica_set/ssl_test.rb +32 -0
  88. data/test/sharded_cluster/basic_test.rb +203 -0
  89. data/test/shared/authentication/basic_auth_shared.rb +260 -0
  90. data/test/shared/authentication/bulk_api_auth_shared.rb +249 -0
  91. data/test/shared/authentication/gssapi_shared.rb +176 -0
  92. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  93. data/test/shared/authentication/scram_shared.rb +92 -0
  94. data/test/shared/ssl_shared.rb +235 -0
  95. data/test/test_helper.rb +53 -94
  96. data/test/threading/basic_test.rb +120 -0
  97. data/test/tools/mongo_config.rb +708 -0
  98. data/test/tools/mongo_config_test.rb +160 -0
  99. data/test/unit/client_test.rb +381 -0
  100. data/test/unit/collection_test.rb +89 -53
  101. data/test/unit/connection_test.rb +282 -32
  102. data/test/unit/cursor_test.rb +206 -8
  103. data/test/unit/db_test.rb +55 -13
  104. data/test/unit/grid_test.rb +43 -16
  105. data/test/unit/mongo_sharded_client_test.rb +48 -0
  106. data/test/unit/node_test.rb +93 -0
  107. data/test/unit/pool_manager_test.rb +111 -0
  108. data/test/unit/read_pref_test.rb +406 -0
  109. data/test/unit/read_test.rb +159 -0
  110. data/test/unit/safe_test.rb +69 -36
  111. data/test/unit/sharding_pool_manager_test.rb +84 -0
  112. data/test/unit/write_concern_test.rb +175 -0
  113. data.tar.gz.sig +3 -0
  114. metadata +227 -216
  115. metadata.gz.sig +0 -0
  116. data/docs/CREDITS.md +0 -123
  117. data/docs/FAQ.md +0 -116
  118. data/docs/GridFS.md +0 -158
  119. data/docs/HISTORY.md +0 -244
  120. data/docs/RELEASES.md +0 -33
  121. data/docs/REPLICA_SETS.md +0 -72
  122. data/docs/TUTORIAL.md +0 -247
  123. data/docs/WRITE_CONCERN.md +0 -28
  124. data/lib/mongo/exceptions.rb +0 -71
  125. data/lib/mongo/gridfs/grid_io_fix.rb +0 -38
  126. data/lib/mongo/repl_set_connection.rb +0 -342
  127. data/lib/mongo/test.rb +0 -20
  128. data/lib/mongo/util/pool.rb +0 -177
  129. data/lib/mongo/util/uri_parser.rb +0 -185
  130. data/test/async/collection_test.rb +0 -224
  131. data/test/async/connection_test.rb +0 -24
  132. data/test/async/cursor_test.rb +0 -162
  133. data/test/async/worker_pool_test.rb +0 -99
  134. data/test/auxillary/1.4_features.rb +0 -166
  135. data/test/auxillary/authentication_test.rb +0 -68
  136. data/test/auxillary/autoreconnect_test.rb +0 -41
  137. data/test/auxillary/fork_test.rb +0 -30
  138. data/test/auxillary/repl_set_auth_test.rb +0 -58
  139. data/test/auxillary/slave_connection_test.rb +0 -36
  140. data/test/auxillary/threaded_authentication_test.rb +0 -101
  141. data/test/bson/binary_test.rb +0 -15
  142. data/test/bson/bson_test.rb +0 -649
  143. data/test/bson/byte_buffer_test.rb +0 -208
  144. data/test/bson/hash_with_indifferent_access_test.rb +0 -38
  145. data/test/bson/json_test.rb +0 -17
  146. data/test/bson/object_id_test.rb +0 -154
  147. data/test/bson/ordered_hash_test.rb +0 -204
  148. data/test/bson/timestamp_test.rb +0 -24
  149. data/test/collection_test.rb +0 -910
  150. data/test/connection_test.rb +0 -309
  151. data/test/cursor_fail_test.rb +0 -75
  152. data/test/cursor_message_test.rb +0 -43
  153. data/test/cursor_test.rb +0 -483
  154. data/test/db_api_test.rb +0 -726
  155. data/test/db_connection_test.rb +0 -15
  156. data/test/db_test.rb +0 -287
  157. data/test/grid_file_system_test.rb +0 -243
  158. data/test/load/resque/load.rb +0 -21
  159. data/test/load/resque/processor.rb +0 -26
  160. data/test/load/thin/load.rb +0 -24
  161. data/test/load/unicorn/load.rb +0 -23
  162. data/test/load/unicorn/unicorn.rb +0 -29
  163. data/test/replica_sets/connect_test.rb +0 -94
  164. data/test/replica_sets/connection_string_test.rb +0 -32
  165. data/test/replica_sets/count_test.rb +0 -35
  166. data/test/replica_sets/insert_test.rb +0 -53
  167. data/test/replica_sets/pooled_insert_test.rb +0 -55
  168. data/test/replica_sets/query_secondaries.rb +0 -96
  169. data/test/replica_sets/query_test.rb +0 -51
  170. data/test/replica_sets/replication_ack_test.rb +0 -66
  171. data/test/replica_sets/rs_test_helper.rb +0 -27
  172. data/test/safe_test.rb +0 -68
  173. data/test/support/hash_with_indifferent_access.rb +0 -186
  174. data/test/support/keys.rb +0 -45
  175. data/test/support_test.rb +0 -18
  176. data/test/threading/threading_with_large_pool_test.rb +0 -90
  177. data/test/threading_test.rb +0 -87
  178. data/test/tools/auth_repl_set_manager.rb +0 -14
  179. data/test/tools/load.rb +0 -58
  180. data/test/tools/repl_set_manager.rb +0 -266
  181. data/test/tools/sharding_manager.rb +0 -202
  182. data/test/tools/test.rb +0 -4
  183. data/test/unit/pool_test.rb +0 -9
  184. data/test/unit/repl_set_connection_test.rb +0 -59
  185. data/test/uri_test.rb +0 -91
data/Rakefile CHANGED
@@ -1,215 +1,31 @@
1
- # -*- mode: ruby; -*-
2
- require 'rubygems'
3
- require 'rubygems/specification'
4
- require 'fileutils'
5
- require 'rake'
6
- require 'rake/testtask'
7
- require 'rake/gempackagetask'
8
- require 'rbconfig'
9
- include Config
10
- ENV['TEST_MODE'] = 'TRUE'
11
-
12
- task :java do
13
- Rake::Task['build:java'].invoke
14
- Rake::Task['test:ruby'].invoke
15
- end
16
-
17
- namespace :build do
18
- desc "Build the java extensions."
19
- task :java do
20
- puts "Building Java extensions..."
21
- java_dir = File.join(File.dirname(__FILE__), 'ext', 'java')
22
- jar_dir = File.join(java_dir, 'jar')
23
-
24
- jruby_jar = File.join(jar_dir, 'jruby.jar')
25
- mongo_jar = File.join(jar_dir, 'mongo-2.4.jar')
26
- bson_jar = File.join(jar_dir, 'bson-2.2.jar')
27
-
28
- src_base = File.join(java_dir, 'src')
29
-
30
- system("javac -Xlint:unchecked -classpath #{jruby_jar}:#{mongo_jar}:#{bson_jar} #{File.join(src_base, 'org', 'jbson', '*.java')}")
31
- system("cd #{src_base} && jar cf #{File.join(jar_dir, 'jbson.jar')} #{File.join('.', 'org', 'jbson', '*.class')}")
32
- end
33
- end
34
-
35
- desc "Test the MongoDB Ruby driver."
36
- task :test do
37
- puts "\nTo test the driver with the C-extensions:\nrake test:c\n\n"
38
- puts "To test the pure ruby driver: \nrake test:ruby\n\n"
39
- end
40
-
41
- namespace :test do
42
-
43
- desc "Test the driver with the C extension enabled."
44
- task :c do
45
- ENV['C_EXT'] = 'TRUE'
46
- if ENV['TEST']
47
- Rake::Task['test:functional'].invoke
48
- else
49
- Rake::Task['test:unit'].invoke
50
- Rake::Task['test:functional'].invoke
51
- Rake::Task['test:bson'].invoke
52
- Rake::Task['test:pooled_threading'].invoke
53
- Rake::Task['test:drop_databases'].invoke
54
- end
55
- ENV['C_EXT'] = nil
56
- end
57
-
58
- desc "Test the driver using pure ruby (no C extension)"
59
- task :ruby do
60
- ENV['C_EXT'] = nil
61
- if ENV['TEST']
62
- Rake::Task['test:functional'].invoke
63
- else
64
- Rake::Task['test:unit'].invoke
65
- Rake::Task['test:functional'].invoke
66
- Rake::Task['test:bson'].invoke
67
- Rake::Task['test:pooled_threading'].invoke
68
- Rake::Task['test:drop_databases'].invoke
69
- end
70
- end
71
-
72
- desc "Run the replica set test suite"
73
- Rake::TestTask.new(:rs) do |t|
74
- t.test_files = FileList['test/replica_sets/*_test.rb']
75
- t.verbose = true
76
- t.ruby_opts << '-w'
77
- end
78
-
79
- Rake::TestTask.new(:unit) do |t|
80
- t.test_files = FileList['test/unit/*_test.rb']
81
- t.verbose = true
82
- t.ruby_opts << '-w'
83
- end
84
-
85
- Rake::TestTask.new(:functional) do |t|
86
- t.test_files = FileList['test/*_test.rb']
87
- t.verbose = true
88
- t.ruby_opts << '-w'
89
- end
90
-
91
- Rake::TestTask.new(:pooled_threading) do |t|
92
- t.test_files = FileList['test/threading/*_test.rb']
93
- t.verbose = true
94
- t.ruby_opts << '-w'
95
- end
96
-
97
- Rake::TestTask.new(:auto_reconnect) do |t|
98
- t.test_files = FileList['test/auxillary/autoreconnect_test.rb']
99
- t.verbose = true
100
- t.ruby_opts << '-w'
101
- end
102
-
103
- Rake::TestTask.new(:authentication) do |t|
104
- t.test_files = FileList['test/auxillary/authentication_test.rb']
105
- t.verbose = true
106
- t.ruby_opts << '-w'
107
- end
108
-
109
- Rake::TestTask.new(:new_features) do |t|
110
- t.test_files = FileList['test/auxillary/1.4_features.rb']
111
- t.verbose = true
112
- t.ruby_opts << '-w'
113
- end
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
114
14
 
115
- Rake::TestTask.new(:bson) do |t|
116
- t.test_files = FileList['test/bson/*_test.rb']
117
- t.verbose = true
118
- t.ruby_opts << '-w'
119
- end
120
-
121
- task :drop_databases do |t|
122
- puts "Dropping test databases..."
123
- require './lib/mongo'
124
- con = Mongo::Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
125
- ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Connection::DEFAULT_PORT)
126
- con.database_names.each do |name|
127
- con.drop_database(name) if name =~ /^ruby-test/
128
- end
129
- end
130
- end
131
-
132
- desc "Generate RDOC documentation"
133
- task :rdoc do
134
- version = eval(File.read("mongo.gemspec")).version
135
- out = File.join('html', version.to_s)
136
- FileUtils.rm_rf('html')
137
- system "rdoc --main README.md --op #{out} --inline-source --quiet README.md `find lib -name '*.rb'`"
138
- end
139
-
140
- desc "Generate YARD documentation"
141
- task :ydoc do
142
- require File.join(File.dirname(__FILE__), 'lib', 'mongo')
143
- out = File.join('ydoc', Mongo::VERSION)
144
- FileUtils.rm_rf('ydoc')
145
- system "yardoc lib/**/*.rb lib/mongo/**/*.rb lib/bson/**/*.rb -e yard/yard_ext.rb -p yard/templates -o #{out} --title MongoRuby-#{Mongo::VERSION} --files docs/TUTORIAL.md,docs/GridFS.md,docs/FAQ.md,docs/REPLICA_SETS.md,docs/WRITE_CONCERN.md,docs/HISTORY.md,docs/CREDITS.md,docs/RELEASES.md"
146
- end
147
-
148
- namespace :bamboo do
149
- task :ci_reporter do
150
- begin
151
- require 'ci/reporter/rake/test_unit'
152
- rescue LoadError
153
- warn "Warning: Unable to load ci_reporter gem."
154
- end
155
- end
156
-
157
- namespace :test do
158
- task :ruby => [:ci_reporter, "ci:setup:testunit"] do
159
- Rake::Task['test:ruby'].invoke
160
- end
161
-
162
- task :c => [:ci_reporter, "ci:setup:testunit"] do
163
- Rake::Task['gem:install_extensions'].invoke
164
- Rake::Task['test:c'].invoke
165
- end
166
- end
167
- end
168
-
169
- namespace :gem do
170
-
171
- desc "Install the gem locally"
172
- task :install do
173
- `gem build bson.gemspec`
174
- `gem install --no-rdoc --no-ri bson-*.gem`
175
-
176
- `gem build mongo.gemspec`
177
- `gem install --no-rdoc --no-ri mongo-*.gem`
178
-
179
- `rm mongo-*.gem`
180
- `rm bson-*.gem`
181
- end
182
-
183
- desc "Install the optional c extensions"
184
- task :install_extensions do
185
- `gem uninstall bson_ext`
186
- `gem build bson_ext.gemspec`
187
- `gem install --no-rdoc --no-ri bson_ext-*.gem`
188
- `rm bson_ext-*.gem`
189
- end
190
-
191
- desc "Build all gems"
192
- task :build_all do
193
- `gem build mongo.gemspec`
194
- `gem build bson.gemspec`
195
- `gem build bson.java.gemspec`
196
- `gem build bson_ext.gemspec`
197
- end
15
+ require 'rubygems'
198
16
 
17
+ begin
18
+ require 'bundler'
19
+ rescue LoadError
20
+ raise '[FAIL] Bundler not found! Install it with `gem install bundler && bundle`.'
199
21
  end
200
22
 
201
- namespace :ci do
202
- namespace :test do
203
- task :c do
204
- Rake::Task['gem:install'].invoke
205
- Rake::Task['gem:install_extensions'].invoke
206
- Rake::Task['test:c'].invoke
207
- end
208
- end
23
+ rake_tasks = Dir.glob(File.join('tasks', '**', '*.rake')).sort
24
+ if ENV.keys.any? { |k| k.end_with?('_CI') }
25
+ Bundler.require(:default, :testing)
26
+ rake_tasks.reject! { |r| r =~ /deploy/ }
27
+ else
28
+ Bundler.require(:default, :testing, :deploy, :development)
209
29
  end
210
30
 
211
- task :default => :list
212
-
213
- task :list do
214
- system 'rake -T'
215
- end
31
+ rake_tasks.each { |rake| load File.expand_path(rake) }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.12.5
data/bin/mongo_console CHANGED
@@ -1,21 +1,43 @@
1
1
  #!/usr/bin/env ruby
2
+
3
+ # Copyright (C) 2009-2013 MongoDB, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
2
17
  org_argv = ARGV.dup
3
18
  ARGV.clear
4
19
 
5
- require 'irb'
6
-
7
20
  $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
8
- require 'mongo'
9
21
 
22
+ require 'mongo'
10
23
  include Mongo
11
24
 
12
25
  host = org_argv[0] || ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
13
- port = org_argv[1] || ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT
26
+ port = org_argv[1] || ENV['MONGO_RUBY_DRIVER_PORT'] || MongoClient::DEFAULT_PORT
14
27
  dbnm = org_argv[2] || ENV['MONGO_RUBY_DRIVER_DB'] || 'ruby-mongo-console'
15
28
 
16
- puts "Connecting to #{host}:#{port} (CONN) on with database #{dbnm} (DB)"
17
- CONN = Connection.new(host, port)
18
- DB = CONN.db(dbnm)
29
+ puts "Connecting to #{host}:#{port} (CLIENT) on with database #{dbnm} (DB)"
30
+ CLIENT = MongoClient.new(host, port)
31
+ DB = CLIENT.db(dbnm)
32
+
33
+ # try pry if available, fall back to irb
34
+ begin
35
+ require 'pry'
36
+ CONSOLE_CLASS = Pry
37
+ rescue LoadError
38
+ require 'irb'
39
+ CONSOLE_CLASS = IRB
40
+ end
19
41
 
20
- puts "Starting IRB session..."
21
- IRB.start(__FILE__)
42
+ puts "Starting #{CONSOLE_CLASS.name} session..."
43
+ CONSOLE_CLASS.start(__FILE__)
@@ -0,0 +1,387 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+
17
+ # A bulk write view to a collection of documents in a database.
18
+ class BulkWriteCollectionView
19
+ include Mongo::WriteConcern
20
+
21
+ DEFAULT_OP_ARGS = {:q => nil}
22
+ MULTIPLE_ERRORS_MSG = "batch item errors occurred"
23
+ EMPTY_BATCH_MSG = "batch is empty"
24
+
25
+ attr_reader :collection, :options, :ops, :op_args
26
+
27
+ # Initialize a bulk-write-view object to a collection with default query selector {}.
28
+ #
29
+ # A bulk write operation is initialized from a collection object.
30
+ # For example, for an ordered bulk write view:
31
+ #
32
+ # bulk = collection.initialize_ordered_bulk_op
33
+ #
34
+ # or for an unordered bulk write view:
35
+ #
36
+ # bulk = collection.initialize_unordered_bulk_op
37
+ #
38
+ # The bulk write view collects individual write operations together so that they can be
39
+ # executed as a batch for significant performance gains.
40
+ # The ordered bulk operation will execute each operation serially in order.
41
+ # Execution will stop at the first occurrence of an error for an ordered bulk operation.
42
+ # The unordered bulk operation will be executed and may take advantage of parallelism.
43
+ # There are no guarantees for the order of execution of the operations on the server.
44
+ # Execution will continue even if there are errors for an unordered bulk operation.
45
+ #
46
+ # A bulk operation is programmed as a sequence of individual operations.
47
+ # An individual operation is composed of a method chain of modifiers or setters terminated by a write method.
48
+ # A modify method sets a value on the current object.
49
+ # A set methods returns a duplicate of the current object with a value set.
50
+ # A terminator write method appends a write operation to the bulk batch collected in the view.
51
+ #
52
+ # The API supports mixing of write operation types in a bulk operation.
53
+ # However, server support affects the implementation and performance of bulk operations.
54
+ #
55
+ # MongoDB version 2.6 servers currently support only bulk commands of the same type.
56
+ # With an ordered bulk operation,
57
+ # contiguous individual ops of the same type can be batched into the same db request,
58
+ # and the next op of a different type must be sent separately in the next request.
59
+ # Performance will improve if you can arrange your ops to reduce the number of db requests.
60
+ # With an unordered bulk operation,
61
+ # individual ops can be grouped by type and sent in at most three requests,
62
+ # one each per insert, update, or delete.
63
+ #
64
+ # MongoDB pre-version 2.6 servers do not support bulk write commands.
65
+ # The bulk operation must be sent one request per individual op.
66
+ # This also applies to inserts in order to have accurate counts and error reporting.
67
+ #
68
+ # Important note on pre-2.6 performance:
69
+ # Performance is very poor compared to version 2.6.
70
+ # We recommend bulk operation with pre-2.6 only for compatibility or
71
+ # for development in preparation for version 2.6.
72
+ # For better performance with pre-version 2.6, use bulk insertion with Collection#insert.
73
+ #
74
+ # @param [Collection] collection the parent collection object
75
+ #
76
+ # @option opts [Boolean] :ordered (true) Set bulk execution for ordered or unordered
77
+ #
78
+ # @return [BulkWriteCollectionView]
79
+ def initialize(collection, options = {})
80
+ @collection = collection
81
+ @options = options
82
+ @ops = []
83
+ @op_args = DEFAULT_OP_ARGS.dup
84
+ end
85
+
86
+ def inspect
87
+ vars = [:@options, :@ops, :@op_args]
88
+ vars_inspect = vars.collect{|var| "#{var}=#{instance_variable_get(var).inspect}"}
89
+ "#<Mongo::BulkWriteCollectionView:0x#{self.object_id} " <<
90
+ "@collection=#<Mongo::Collection:0x#{@collection.object_id}>, #{vars_inspect.join(', ')}>"
91
+ end
92
+
93
+ # Modify the query selector for subsequent bulk write operations.
94
+ # The default query selector on creation of the bulk write view is {}.
95
+ # For operations that require a query selector, find() must be set
96
+ # per operation, or set once for all operations on the bulk object.
97
+ # For example, these operations:
98
+ #
99
+ # bulk.find({"a" => 2}).update({"$inc" => {"x" => 2}})
100
+ # bulk.find({"a" => 2}).update({"$set" => {"b" => 3}})
101
+ #
102
+ # may be rewritten as:
103
+ #
104
+ # bulk = find({"a" => 2})
105
+ # bulk.update({"$inc" => {"x" => 2}})
106
+ # bulk.update({"$set" => {"b" => 3}})
107
+ #
108
+ # Note that modifying the query selector in this way will not affect
109
+ # operations that do not use a query selector, like insert().
110
+ #
111
+ # @param [Hash] q the query selector
112
+ #
113
+ # @return [BulkWriteCollectionView]
114
+ def find(q)
115
+ op_args_set(:q, q)
116
+ end
117
+
118
+ # Modify the upsert option argument for subsequent bulk write operations.
119
+ #
120
+ # @param [Boolean] value (true) the upsert option value
121
+ #
122
+ # @return [BulkWriteCollectionView]
123
+ def upsert!(value = true)
124
+ op_args_set(:upsert, value)
125
+ end
126
+
127
+ # Set the upsert option argument for subsequent bulk write operations.
128
+ #
129
+ # @param [Boolean] value (true) the upsert option value
130
+ #
131
+ # @return [BulkWriteCollectionView] a duplicated object
132
+ def upsert(value = true)
133
+ dup.upsert!(value)
134
+ end
135
+
136
+ # Update one document matching the selector.
137
+ #
138
+ # bulk.find({"a" => 1}).update_one({"$inc" => {"x" => 1}})
139
+ #
140
+ # Use the upsert! or upsert method to specify an upsert. For example:
141
+ #
142
+ # bulk.find({"a" => 1}).upsert.updateOne({"$inc" => {"x" => 1}})
143
+ #
144
+ # @param [Hash] u the update document
145
+ #
146
+ # @return [BulkWriteCollectionView]
147
+ def update_one(u)
148
+ raise MongoArgumentError, "document must start with an operator" unless update_doc?(u)
149
+ op_push([:update, @op_args.merge(:u => u, :multi => false)])
150
+ end
151
+
152
+ # Update all documents matching the selector. For example:
153
+ #
154
+ # bulk.find({"a" => 2}).update({"$inc" => {"x" => 2}})
155
+ #
156
+ # Use the upsert! or upsert method to specify an upsert. For example:
157
+ #
158
+ # bulk.find({"a" => 2}).upsert.update({"$inc" => {"x" => 2}})
159
+ #
160
+ # @param [Hash] u the update document
161
+ #
162
+ # @return [BulkWriteCollectionView]
163
+ def update(u)
164
+ raise MongoArgumentError, "document must start with an operator" unless update_doc?(u)
165
+ op_push([:update, @op_args.merge(:u => u, :multi => true)])
166
+ end
167
+
168
+ # Replace entire document (update with whole doc replace). For example:
169
+ #
170
+ # bulk.find({"a" => 3}).replace_one({"x" => 3})
171
+ #
172
+ # @param [Hash] u the replacement document
173
+ #
174
+ # @return [BulkWriteCollectionView]
175
+ def replace_one(u)
176
+ raise MongoArgumentError, "document must not contain any operators" unless replace_doc?(u)
177
+ op_push([:update, @op_args.merge(:u => u, :multi => false)])
178
+ end
179
+
180
+ # Remove a single document matching the selector. For example:
181
+ #
182
+ # bulk.find({"a" => 4}).remove_one;
183
+ #
184
+ # @return [BulkWriteCollectionView]
185
+ def remove_one
186
+ op_push([:delete, @op_args.merge(:limit => 1)])
187
+ end
188
+
189
+ # Remove all documents matching the selector. For example:
190
+ #
191
+ # bulk.find({"a" => 5}).remove;
192
+ #
193
+ # @return [BulkWriteCollectionView]
194
+ def remove
195
+ op_push([:delete, @op_args.merge(:limit => 0)])
196
+ end
197
+
198
+ # Insert a document. For example:
199
+ #
200
+ # bulk.insert({"x" => 4})
201
+ #
202
+ # @return [BulkWriteCollectionView]
203
+ def insert(document)
204
+ # TODO - check keys
205
+ op_push([:insert, {:d => document}])
206
+ end
207
+
208
+ # Execute the bulk operation, with an optional write concern overwriting the default w:1.
209
+ # For example:
210
+ #
211
+ # write_concern = {:w => 1, :j => 1}
212
+ # bulk.execute({write_concern})
213
+ #
214
+ # On return from execute, the bulk operation is cleared,
215
+ # but the selector and upsert settings are preserved.
216
+ #
217
+ # @return [BulkWriteCollectionView]
218
+ def execute(opts = {})
219
+ raise MongoArgumentError, EMPTY_BATCH_MSG if @ops.empty?
220
+ write_concern = get_write_concern(opts, @collection)
221
+ @ops.each_with_index{|op, index| op.last.merge!(:ord => index)} # infuse ordinal here to avoid issues with upsert
222
+ if @collection.db.connection.use_write_command?(write_concern)
223
+ errors, write_concern_errors, exchanges = @collection.command_writer.bulk_execute(@ops, @options, opts)
224
+ else
225
+ errors, write_concern_errors, exchanges = @collection.operation_writer.bulk_execute(@ops, @options, opts)
226
+ end
227
+ @ops = []
228
+ return true if errors.empty? && (exchanges.empty? || exchanges.first[:response] == true) # w 0 without GLE
229
+ result = merge_result(errors + write_concern_errors, exchanges)
230
+ raise BulkWriteError.new(MULTIPLE_ERRORS_MSG, Mongo::ErrorCode::MULTIPLE_ERRORS_OCCURRED, result) if !errors.empty? || !write_concern_errors.empty?
231
+ result
232
+ end
233
+
234
+ private
235
+
236
+ def hash_except(h, *keys)
237
+ keys.each { |key| h.delete(key) }
238
+ h
239
+ end
240
+
241
+ def hash_select(h, *keys)
242
+ Hash[*keys.zip(h.values_at(*keys)).flatten]
243
+ end
244
+
245
+ def tally(h, key, n)
246
+ h[key] = h.fetch(key, 0) + n
247
+ end
248
+
249
+ def nil_tally(h, key, n)
250
+ if !h.has_key?(key)
251
+ h[key] = n
252
+ elsif h[key]
253
+ h[key] = n ? h[key] + n : n
254
+ end
255
+ end
256
+
257
+ def append(h, key, obj)
258
+ h[key] = h.fetch(key, []) << obj
259
+ end
260
+
261
+ def concat(h, key, a)
262
+ h[key] = h.fetch(key, []) + a
263
+ end
264
+
265
+ def merge_index(h, exchange)
266
+ h.merge("index" => exchange[:batch][h.fetch("index", 0)][:ord])
267
+ end
268
+
269
+ def merge_indexes(a, exchange)
270
+ a.collect{|h| merge_index(h, exchange)}
271
+ end
272
+
273
+ def merge_result(errors, exchanges)
274
+ ok = 0
275
+ result = {"ok" => 0, "n" => 0}
276
+ unless errors.empty?
277
+ unless (writeErrors = errors.select { |error| error.class != Mongo::OperationFailure && error.class != WriteConcernError }).empty? # assignment
278
+ concat(result, "writeErrors",
279
+ writeErrors.collect { |error|
280
+ {"index" => error.result[:ord], "code" => error.error_code, "errmsg" => error.result[:error].message}
281
+ })
282
+ end
283
+ result.merge!("code" => Mongo::ErrorCode::MULTIPLE_ERRORS_OCCURRED, "errmsg" => MULTIPLE_ERRORS_MSG)
284
+ end
285
+ exchanges.each do |exchange|
286
+ response = exchange[:response]
287
+ next unless response
288
+ ok += response["ok"].to_i
289
+ n = response["n"] || 0
290
+ op_type = exchange[:op_type]
291
+ if op_type == :insert
292
+ n = 1 if response.key?("err") && (response["err"].nil? || response["err"] == "norepl" || response["err"] == "timeout") # OP_INSERT override n = 0 bug, n = exchange[:batch].size always 1
293
+ tally(result, "nInserted", n)
294
+ elsif op_type == :update
295
+ n_upserted = 0
296
+ if (upserted = response.fetch("upserted", nil)) # assignment
297
+ upserted = [{"_id" => upserted}] if upserted.class != Array # OP_UPDATE non-array
298
+ n_upserted = upserted.size
299
+ concat(result, "upserted", merge_indexes(upserted, exchange))
300
+ elsif (response["updatedExisting"] == false && n == 1)
301
+ # workaround for DRIVERS-151 (non-ObjectID _id fields in pre-2.6 servers)
302
+ op = exchange[:batch][0]
303
+ missing_id = op[:u].fetch(:_id, op[:q][:_id]) # _id in update document takes precedence
304
+ upserted = [ { "_id" => missing_id, "index" => 0 } ]
305
+ n_upserted = n
306
+ concat(result, "upserted", merge_indexes(upserted, exchange))
307
+ end
308
+ tally(result, "nUpserted", n_upserted) if n_upserted > 0
309
+ tally(result, "nMatched", n - n_upserted)
310
+ nil_tally(result, "nModified", response["nModified"])
311
+ elsif op_type == :delete
312
+ tally(result, "nRemoved", n)
313
+ end
314
+ result["n"] += n
315
+ write_concern_error = nil
316
+ errmsg = response["errmsg"] || response["err"] # top level
317
+ if (writeErrors = response["writeErrors"] || response["errDetails"]) # assignment
318
+ concat(result, "writeErrors", merge_indexes(writeErrors, exchange))
319
+ elsif response["err"] == "timeout" # errmsg == "timed out waiting for slaves" # OP_*
320
+ write_concern_error = {"errmsg" => errmsg, "code" => Mongo::ErrorCode::WRITE_CONCERN_FAILED,
321
+ "errInfo" => {"wtimeout" => response["wtimeout"]}} # OP_* does not have "code"
322
+ elsif errmsg == "norepl" # OP_*
323
+ write_concern_error = {"errmsg" => errmsg, "code" => Mongo::ErrorCode::WRITE_CONCERN_FAILED} # OP_* does not have "code"
324
+ elsif errmsg # OP_INSERT, OP_UPDATE have "err"
325
+ append(result, "writeErrors", merge_index({"errmsg" => errmsg, "code" => response["code"]}, exchange))
326
+ end
327
+ if response["writeConcernError"]
328
+ write_concern_error = response["writeConcernError"]
329
+ elsif (wnote = response["wnote"]) # assignment - OP_*
330
+ write_concern_error = {"errmsg" => wnote, "code" => Mongo::ErrorCode::WRITE_CONCERN_FAILED} # OP_* does not have "code"
331
+ elsif (jnote = response["jnote"]) # assignment - OP_*
332
+ write_concern_error = {"errmsg" => jnote, "code" => Mongo::ErrorCode::BAD_VALUE} # OP_* does not have "code"
333
+ end
334
+ append(result, "writeConcernError", merge_index(write_concern_error, exchange)) if write_concern_error
335
+ end
336
+ result.delete("nModified") if result.has_key?("nModified") && !result["nModified"]
337
+ result.merge!("ok" => [ok + result["n"], 1].min)
338
+ end
339
+
340
+ def initialize_copy(other)
341
+ other.instance_variable_set(:@options, other.options.dup)
342
+ end
343
+
344
+ def op_args_set(op, value)
345
+ @op_args[op] = value
346
+ self
347
+ end
348
+
349
+ def op_push(op)
350
+ raise MongoArgumentError, "non-nil query must be set via find" if op.first != :insert && !op.last[:q]
351
+ @ops << op
352
+ self
353
+ end
354
+
355
+ def update_doc?(doc)
356
+ !doc.empty? && doc.keys.first.to_s =~ /^\$/
357
+ end
358
+
359
+ def replace_doc?(doc)
360
+ doc.keys.all?{|key| key !~ /^\$/}
361
+ end
362
+
363
+ end
364
+
365
+ class Collection
366
+
367
+ # Initialize an ordered bulk write view for this collection
368
+ # Execution will stop at the first occurrence of an error for an ordered bulk operation.
369
+ #
370
+ # @return [BulkWriteCollectionView]
371
+ def initialize_ordered_bulk_op
372
+ BulkWriteCollectionView.new(self, :ordered => true)
373
+ end
374
+
375
+ # Initialize an unordered bulk write view for this collection
376
+ # The unordered bulk operation will be executed and may take advantage of parallelism.
377
+ # There are no guarantees for the order of execution of the operations on the server.
378
+ # Execution will continue even if there are errors for an unordered bulk operation.
379
+ #
380
+ # @return [BulkWriteCollectionView]
381
+ def initialize_unordered_bulk_op
382
+ BulkWriteCollectionView.new(self, :ordered => false)
383
+ end
384
+
385
+ end
386
+
387
+ end