mongo 2.1.0.beta → 2.1.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (253) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Rakefile +2 -2
  5. data/lib/mongo.rb +2 -3
  6. data/lib/mongo/address.rb +7 -5
  7. data/lib/mongo/address/unix.rb +2 -2
  8. data/lib/mongo/auth/ldap/conversation.rb +6 -2
  9. data/lib/mongo/auth/scram/conversation.rb +8 -2
  10. data/lib/mongo/auth/user/view.rb +21 -0
  11. data/lib/mongo/bulk_write.rb +155 -23
  12. data/lib/mongo/bulk_write/combineable.rb +51 -0
  13. data/lib/mongo/bulk_write/ordered_combiner.rb +55 -0
  14. data/lib/mongo/bulk_write/result.rb +61 -8
  15. data/lib/mongo/bulk_write/result_combiner.rb +117 -0
  16. data/lib/mongo/bulk_write/transformable.rb +117 -0
  17. data/lib/mongo/bulk_write/unordered_combiner.rb +52 -0
  18. data/lib/mongo/bulk_write/validatable.rb +62 -0
  19. data/lib/mongo/client.rb +7 -3
  20. data/lib/mongo/cluster.rb +3 -3
  21. data/lib/mongo/cluster/topology/replica_set.rb +8 -6
  22. data/lib/mongo/cluster/topology/unknown.rb +5 -2
  23. data/lib/mongo/collection.rb +75 -4
  24. data/lib/mongo/collection/view.rb +1 -2
  25. data/lib/mongo/collection/view/aggregation.rb +13 -8
  26. data/lib/mongo/collection/view/immutable.rb +6 -6
  27. data/lib/mongo/collection/view/iterable.rb +13 -4
  28. data/lib/mongo/collection/view/map_reduce.rb +22 -17
  29. data/lib/mongo/collection/view/readable.rb +121 -70
  30. data/lib/mongo/cursor.rb +5 -1
  31. data/lib/mongo/database.rb +3 -3
  32. data/lib/mongo/database/view.rb +1 -1
  33. data/lib/mongo/error.rb +7 -0
  34. data/lib/mongo/{bulk_write/unordered_bulk_write.rb → error/closed_stream.rb} +12 -21
  35. data/lib/mongo/{bulk_write/ordered_bulk_write.rb → error/extra_file_chunk.rb} +13 -27
  36. data/lib/mongo/error/file_not_found.rb +37 -0
  37. data/lib/mongo/error/invalid_file.rb +2 -2
  38. data/lib/mongo/error/invalid_file_revision.rb +37 -0
  39. data/lib/mongo/error/invalid_uri.rb +5 -4
  40. data/lib/mongo/error/missing_file_chunk.rb +38 -0
  41. data/lib/mongo/error/operation_failure.rb +1 -1
  42. data/lib/mongo/error/unchangeable_collection_option.rb +38 -0
  43. data/lib/mongo/error/unexpected_chunk_length.rb +39 -0
  44. data/lib/mongo/grid.rb +2 -1
  45. data/lib/mongo/grid/file.rb +12 -9
  46. data/lib/mongo/grid/file/chunk.rb +6 -6
  47. data/lib/mongo/grid/file/{metadata.rb → info.rb} +41 -39
  48. data/lib/mongo/grid/fs_bucket.rb +441 -0
  49. data/lib/mongo/grid/stream.rb +64 -0
  50. data/lib/mongo/grid/stream/read.rb +208 -0
  51. data/lib/mongo/grid/stream/write.rb +187 -0
  52. data/lib/mongo/index/view.rb +1 -1
  53. data/lib/mongo/loggable.rb +34 -57
  54. data/lib/mongo/logger.rb +16 -78
  55. data/lib/mongo/monitoring.rb +1 -5
  56. data/lib/mongo/monitoring/command_log_subscriber.rb +35 -17
  57. data/lib/mongo/monitoring/event/command_succeeded.rb +20 -1
  58. data/lib/mongo/monitoring/publishable.rb +22 -12
  59. data/lib/mongo/operation.rb +3 -6
  60. data/lib/mongo/operation/commands.rb +24 -0
  61. data/lib/mongo/operation/{aggregate.rb → commands/aggregate.rb} +3 -41
  62. data/lib/mongo/operation/{aggregate → commands/aggregate}/result.rb +0 -0
  63. data/lib/mongo/operation/commands/collections_info.rb +66 -0
  64. data/lib/mongo/operation/{command.rb → commands/command.rb} +2 -18
  65. data/lib/mongo/operation/commands/indexes.rb +70 -0
  66. data/lib/mongo/operation/commands/list_collections.rb +54 -0
  67. data/lib/mongo/operation/commands/list_collections/result.rb +112 -0
  68. data/lib/mongo/operation/commands/list_indexes.rb +56 -0
  69. data/lib/mongo/operation/commands/list_indexes/result.rb +115 -0
  70. data/lib/mongo/operation/{map_reduce.rb → commands/map_reduce.rb} +3 -41
  71. data/lib/mongo/operation/{map_reduce → commands/map_reduce}/result.rb +0 -0
  72. data/lib/mongo/operation/{parallel_scan.rb → commands/parallel_scan.rb} +3 -23
  73. data/lib/mongo/operation/{parallel_scan → commands/parallel_scan}/result.rb +0 -0
  74. data/lib/mongo/operation/commands/user_query.rb +69 -0
  75. data/lib/mongo/operation/commands/users_info.rb +53 -0
  76. data/lib/mongo/operation/commands/users_info/result.rb +36 -0
  77. data/lib/mongo/operation/executable.rb +4 -68
  78. data/lib/mongo/operation/kill_cursors.rb +3 -3
  79. data/lib/mongo/operation/read.rb +0 -4
  80. data/lib/mongo/operation/read/get_more.rb +2 -22
  81. data/lib/mongo/operation/read/query.rb +2 -21
  82. data/lib/mongo/operation/{read_preferrable.rb → read_preference.rb} +3 -2
  83. data/lib/mongo/operation/specifiable.rb +24 -0
  84. data/lib/mongo/operation/write.rb +2 -0
  85. data/lib/mongo/operation/write/bulk.rb +6 -3
  86. data/lib/mongo/operation/write/bulk/bulkable.rb +82 -0
  87. data/lib/mongo/operation/write/bulk/delete.rb +71 -0
  88. data/lib/mongo/operation/write/bulk/delete/result.rb +74 -0
  89. data/lib/mongo/operation/write/bulk/insert.rb +96 -0
  90. data/lib/mongo/operation/write/bulk/insert/result.rb +129 -0
  91. data/lib/mongo/operation/write/bulk/legacy_mergable.rb +87 -0
  92. data/lib/mongo/operation/write/bulk/mergable.rb +71 -0
  93. data/lib/mongo/operation/write/bulk/update.rb +81 -0
  94. data/lib/mongo/operation/write/bulk/update/result.rb +174 -0
  95. data/lib/mongo/operation/write/command/create_index.rb +0 -1
  96. data/lib/mongo/operation/write/command/create_user.rb +0 -1
  97. data/lib/mongo/operation/write/command/delete.rb +0 -1
  98. data/lib/mongo/operation/write/command/drop_index.rb +0 -1
  99. data/lib/mongo/operation/write/command/insert.rb +0 -1
  100. data/lib/mongo/operation/write/command/remove_user.rb +0 -1
  101. data/lib/mongo/operation/write/command/update.rb +0 -1
  102. data/lib/mongo/operation/write/command/update_user.rb +0 -1
  103. data/lib/mongo/operation/write/command/writable.rb +13 -18
  104. data/lib/mongo/operation/write/create_index.rb +4 -27
  105. data/lib/mongo/operation/write/create_user.rb +4 -30
  106. data/lib/mongo/operation/write/delete.rb +5 -28
  107. data/lib/mongo/operation/write/drop_index.rb +3 -3
  108. data/lib/mongo/operation/write/gle.rb +48 -0
  109. data/lib/mongo/operation/write/idable.rb +5 -0
  110. data/lib/mongo/operation/write/insert.rb +2 -24
  111. data/lib/mongo/operation/write/remove_user.rb +4 -27
  112. data/lib/mongo/operation/write/update.rb +4 -32
  113. data/lib/mongo/operation/write/update_user.rb +4 -30
  114. data/lib/mongo/operation/write/write_command_enabled.rb +53 -0
  115. data/lib/mongo/options/mapper.rb +4 -2
  116. data/lib/mongo/protocol/delete.rb +68 -3
  117. data/lib/mongo/protocol/get_more.rb +54 -2
  118. data/lib/mongo/protocol/insert.rb +59 -1
  119. data/lib/mongo/protocol/kill_cursors.rb +53 -4
  120. data/lib/mongo/protocol/message.rb +12 -12
  121. data/lib/mongo/protocol/query.rb +139 -65
  122. data/lib/mongo/protocol/reply.rb +69 -1
  123. data/lib/mongo/protocol/update.rb +70 -1
  124. data/lib/mongo/server/connection.rb +11 -3
  125. data/lib/mongo/server/description.rb +29 -0
  126. data/lib/mongo/server/description/features.rb +2 -1
  127. data/lib/mongo/server/monitor.rb +2 -2
  128. data/lib/mongo/server_selector.rb +14 -10
  129. data/lib/mongo/server_selector/selectable.rb +24 -22
  130. data/lib/mongo/socket.rb +6 -3
  131. data/lib/mongo/socket/tcp.rb +2 -2
  132. data/lib/mongo/socket/unix.rb +5 -8
  133. data/lib/mongo/uri.rb +243 -139
  134. data/lib/mongo/version.rb +1 -1
  135. data/spec/mongo/address/unix_spec.rb +1 -1
  136. data/spec/mongo/address_spec.rb +25 -0
  137. data/spec/mongo/auth/ldap/conversation_spec.rb +43 -0
  138. data/spec/mongo/auth/user/view_spec.rb +26 -1
  139. data/spec/mongo/bulk_write/ordered_combiner_spec.rb +271 -0
  140. data/spec/mongo/bulk_write/unordered_combiner_spec.rb +239 -0
  141. data/spec/mongo/bulk_write_spec.rb +332 -166
  142. data/spec/mongo/client_spec.rb +25 -0
  143. data/spec/mongo/cluster/topology/replica_set_spec.rb +2 -0
  144. data/spec/mongo/collection/view/aggregation_spec.rb +65 -0
  145. data/spec/mongo/collection/view/immutable_spec.rb +103 -0
  146. data/spec/mongo/collection/view/map_reduce_spec.rb +98 -3
  147. data/spec/mongo/collection/view/readable_spec.rb +17 -30
  148. data/spec/mongo/collection/view_spec.rb +233 -7
  149. data/spec/mongo/collection_spec.rb +360 -18
  150. data/spec/mongo/command_monitoring_spec.rb +51 -0
  151. data/spec/mongo/connection_string_spec.rb +137 -0
  152. data/spec/mongo/database_spec.rb +27 -11
  153. data/spec/mongo/grid/file/chunk_spec.rb +5 -5
  154. data/spec/mongo/grid/file/{metadata_spec.rb → info_spec.rb} +29 -17
  155. data/spec/mongo/grid/file_spec.rb +8 -8
  156. data/spec/mongo/grid/fs_bucket_spec.rb +1020 -0
  157. data/spec/mongo/grid/stream/read_spec.rb +275 -0
  158. data/spec/mongo/grid/stream/write_spec.rb +440 -0
  159. data/spec/mongo/grid/stream_spec.rb +48 -0
  160. data/spec/mongo/gridfs_spec.rb +50 -0
  161. data/spec/mongo/logger_spec.rb +0 -40
  162. data/spec/mongo/monitoring/command_log_subscriber_spec.rb +76 -0
  163. data/spec/mongo/operation/{aggregate_spec.rb → commands/aggregate_spec.rb} +0 -42
  164. data/spec/mongo/operation/{read → commands}/collections_info_spec.rb +1 -1
  165. data/spec/mongo/operation/{command_spec.rb → commands/command_spec.rb} +0 -0
  166. data/spec/mongo/operation/{read → commands}/indexes_spec.rb +1 -1
  167. data/spec/mongo/operation/{map_reduce_spec.rb → commands/map_reduce_spec.rb} +0 -18
  168. data/spec/mongo/operation/kill_cursors_spec.rb +1 -1
  169. data/spec/mongo/operation/{read_preferrable_spec.rb → read_preference_spec.rb} +11 -11
  170. data/spec/mongo/operation/write/bulk/{bulk_delete_spec.rb → delete_spec.rb} +1 -12
  171. data/spec/mongo/operation/write/bulk/{bulk_insert_spec.rb → insert_spec.rb} +1 -12
  172. data/spec/mongo/operation/write/bulk/{bulk_update_spec.rb → update_spec.rb} +1 -12
  173. data/spec/mongo/operation/write/insert_spec.rb +0 -11
  174. data/spec/mongo/protocol/kill_cursors_spec.rb +5 -3
  175. data/spec/mongo/server/description_spec.rb +42 -0
  176. data/spec/mongo/server/monitor_spec.rb +21 -0
  177. data/spec/mongo/server_discovery_and_monitoring_spec.rb +1 -0
  178. data/spec/mongo/server_selection_spec.rb +3 -3
  179. data/spec/mongo/server_selector/nearest_spec.rb +34 -27
  180. data/spec/mongo/server_selector/primary_preferred_spec.rb +31 -30
  181. data/spec/mongo/server_selector/primary_spec.rb +14 -13
  182. data/spec/mongo/server_selector/secondary_preferred_spec.rb +27 -26
  183. data/spec/mongo/server_selector/secondary_spec.rb +23 -22
  184. data/spec/mongo/server_selector_spec.rb +87 -24
  185. data/spec/mongo/socket/unix_spec.rb +52 -0
  186. data/spec/mongo/uri_spec.rb +251 -39
  187. data/spec/spec_helper.rb +11 -4
  188. data/spec/support/authorization.rb +4 -5
  189. data/spec/support/command_monitoring.rb +365 -0
  190. data/spec/support/command_monitoring/bulkWrite.yml +73 -0
  191. data/spec/support/command_monitoring/command.yml +42 -0
  192. data/spec/support/command_monitoring/deleteMany.yml +55 -0
  193. data/spec/support/command_monitoring/deleteOne.yml +55 -0
  194. data/spec/support/command_monitoring/find.yml +219 -0
  195. data/spec/support/command_monitoring/insertMany.yml +81 -0
  196. data/spec/support/command_monitoring/insertOne.yml +51 -0
  197. data/spec/support/command_monitoring/updateMany.yml +67 -0
  198. data/spec/support/command_monitoring/updateOne.yml +95 -0
  199. data/spec/support/connection_string.rb +228 -0
  200. data/spec/support/connection_string_tests/invalid-uris.yml +193 -0
  201. data/spec/support/connection_string_tests/valid-auth.yml +256 -0
  202. data/spec/support/connection_string_tests/valid-host_identifiers.yml +121 -0
  203. data/spec/support/connection_string_tests/valid-options.yml +30 -0
  204. data/spec/support/connection_string_tests/valid-unix_socket-absolute.yml +197 -0
  205. data/spec/support/connection_string_tests/valid-unix_socket-relative.yml +213 -0
  206. data/spec/support/connection_string_tests/valid-warnings.yml +55 -0
  207. data/spec/support/crud.rb +3 -1
  208. data/spec/support/crud/read.rb +14 -10
  209. data/spec/support/crud/write.rb +36 -9
  210. data/spec/support/gridfs.rb +637 -0
  211. data/spec/support/gridfs_tests/delete.yml +157 -0
  212. data/spec/support/gridfs_tests/download.yml +210 -0
  213. data/spec/support/gridfs_tests/download_by_name.yml +113 -0
  214. data/spec/support/gridfs_tests/upload.yml +158 -0
  215. data/spec/support/sdam/rs/equal_electionids.yml +1 -2
  216. data/spec/support/sdam/rs/new_primary_new_electionid.yml +0 -3
  217. data/spec/support/sdam/rs/primary_mismatched_me.yml +37 -0
  218. data/spec/support/sdam/rs/primary_to_no_primary_mismatched_me.yml +75 -0
  219. data/spec/support/sdam/rs/secondary_mismatched_me.yml +37 -0
  220. data/spec/support/sdam/single/direct_connection_rsarbiter.yml +1 -1
  221. data/spec/support/sdam/single/direct_connection_rsprimary.yml +1 -1
  222. data/spec/support/sdam/single/direct_connection_rssecondary.yml +1 -1
  223. data/spec/support/sdam/single/direct_connection_slave.yml +1 -1
  224. data/spec/support/sdam/single/direct_connection_standalone.yml +1 -1
  225. data/spec/support/sdam/single/not_ok_response.yml +0 -1
  226. data/spec/support/server_discovery_and_monitoring.rb +3 -1
  227. data/spec/support/server_selection.rb +3 -1
  228. data/spec/support/shared/bulk_write.rb +192 -0
  229. data/spec/support/shared/server_selector.rb +21 -12
  230. metadata +147 -57
  231. metadata.gz.sig +0 -0
  232. data/lib/mongo/bulk_write/bulk_writable.rb +0 -252
  233. data/lib/mongo/bulk_write/deletable.rb +0 -57
  234. data/lib/mongo/bulk_write/insertable.rb +0 -49
  235. data/lib/mongo/bulk_write/replacable.rb +0 -58
  236. data/lib/mongo/bulk_write/updatable.rb +0 -69
  237. data/lib/mongo/grid/fs.rb +0 -146
  238. data/lib/mongo/operation/list_collections/result.rb +0 -114
  239. data/lib/mongo/operation/list_indexes/result.rb +0 -118
  240. data/lib/mongo/operation/read/collections_info.rb +0 -68
  241. data/lib/mongo/operation/read/indexes.rb +0 -69
  242. data/lib/mongo/operation/read/list_collections.rb +0 -76
  243. data/lib/mongo/operation/read/list_indexes.rb +0 -78
  244. data/lib/mongo/operation/write/bulk/bulk_delete.rb +0 -145
  245. data/lib/mongo/operation/write/bulk/bulk_delete/result.rb +0 -75
  246. data/lib/mongo/operation/write/bulk/bulk_insert.rb +0 -132
  247. data/lib/mongo/operation/write/bulk/bulk_insert/result.rb +0 -130
  248. data/lib/mongo/operation/write/bulk/bulk_mergable.rb +0 -67
  249. data/lib/mongo/operation/write/bulk/bulk_update.rb +0 -154
  250. data/lib/mongo/operation/write/bulk/bulk_update/result.rb +0 -174
  251. data/lib/mongo/operation/write/bulk/legacy_bulk_mergable.rb +0 -83
  252. data/spec/mongo/grid/fs_spec.rb +0 -160
  253. data/spec/mongo/loggable_spec.rb +0 -63
@@ -0,0 +1,1020 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongo::Grid::FSBucket do
4
+
5
+ let(:fs) do
6
+ described_class.new(authorized_client.database, options)
7
+ end
8
+
9
+ let(:options) do
10
+ { }
11
+ end
12
+
13
+ let(:filename) do
14
+ 'specs.rb'
15
+ end
16
+
17
+ let(:file) do
18
+ File.open(__FILE__)
19
+ end
20
+
21
+ describe '#initialize' do
22
+
23
+ it 'sets the files collection' do
24
+ expect(fs.files_collection.name).to eq('fs.files')
25
+ end
26
+
27
+ it 'sets the chunks collection' do
28
+ expect(fs.chunks_collection.name).to eq('fs.chunks')
29
+ end
30
+
31
+ context 'when options are provided' do
32
+
33
+ let(:fs) do
34
+ described_class.new(authorized_client.database, options)
35
+ end
36
+
37
+ context 'when a write concern is set' do
38
+
39
+ context 'when the option :write is provided' do
40
+
41
+ let(:options) do
42
+ { write: { w: 2 } }
43
+ end
44
+
45
+ it 'sets the write concern' do
46
+ expect(fs.send(:write_concern).options).to eq(Mongo::WriteConcern.get(w: 2).options)
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'when a read preference is set' do
52
+
53
+ let(:options) do
54
+ { read: { mode: :secondary, server_selection_timeout: 0.1 } }
55
+ end
56
+
57
+ let(:read_pref) do
58
+ Mongo::ServerSelector.get(options[:read].merge(authorized_client.options))
59
+ end
60
+
61
+ it 'sets the read preference' do
62
+ expect(fs.send(:read_preference)).to eq(read_pref)
63
+ end
64
+ end
65
+
66
+ context 'when a write stream is opened' do
67
+
68
+ let(:stream) do
69
+ fs.open_upload_stream('test.txt')
70
+ end
71
+
72
+ let(:fs) do
73
+ described_class.new(authorized_client.database, options)
74
+ end
75
+
76
+ context 'when a write option is specified' do
77
+
78
+ let(:options) do
79
+ { write: { w: 2 } }
80
+ end
81
+
82
+ it 'passes the write concern to the write stream' do
83
+ expect(stream.write_concern.options).to eq(Mongo::WriteConcern.get(options[:write]).options)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ describe '#find' do
91
+
92
+ let(:fs) do
93
+ described_class.new(authorized_client.database)
94
+ end
95
+
96
+ context 'when there is no selector provided' do
97
+
98
+ let(:files) do
99
+ [
100
+ Mongo::Grid::File.new('hello world!', :filename => 'test.txt'),
101
+ Mongo::Grid::File.new('goodbye world!', :filename => 'test1.txt')
102
+ ]
103
+ end
104
+
105
+ before do
106
+ files.each do |file|
107
+ fs.insert_one(file)
108
+ end
109
+ end
110
+
111
+ after do
112
+ fs.files_collection.delete_many
113
+ fs.chunks_collection.delete_many
114
+ end
115
+
116
+ it 'returns a collection view' do
117
+ expect(fs.find).to be_a(Mongo::Collection::View)
118
+ end
119
+
120
+ it 'iterates over the documents in the result' do
121
+ fs.find.each do |document|
122
+ expect(document).to_not be_nil
123
+ end
124
+ end
125
+ end
126
+
127
+ context 'when provided a selector' do
128
+
129
+ let(:view) do
130
+ fs.find(filename: 'test.txt')
131
+ end
132
+
133
+ it 'returns a collection view for the selector' do
134
+ expect(view.selector).to eq(filename: 'test.txt')
135
+ end
136
+ end
137
+
138
+ context 'when options are provided' do
139
+
140
+ let(:view) do
141
+ fs.find({filename: 'test.txt'}, options)
142
+ end
143
+
144
+ context 'when provided batch_size' do
145
+
146
+ let(:options) do
147
+ { batch_size: 5 }
148
+ end
149
+
150
+ it 'sets the batch_size on the view' do
151
+ expect(view.batch_size).to eq(options[:batch_size])
152
+ end
153
+ end
154
+
155
+ context 'when provided limit' do
156
+
157
+ let(:options) do
158
+ { limit: 5 }
159
+ end
160
+
161
+ it 'sets the limit on the view' do
162
+ expect(view.limit).to eq(options[:limit])
163
+ end
164
+ end
165
+
166
+ context 'when provided no_cursor_timeout' do
167
+
168
+ let(:options) do
169
+ { no_cursor_timeout: true }
170
+ end
171
+
172
+ it 'sets the no_cursor_timeout on the view' do
173
+ expect(view.options[:no_cursor_timeout]).to eq(options[:no_cursor_timeout])
174
+ end
175
+ end
176
+
177
+ context 'when provided skip' do
178
+
179
+ let(:options) do
180
+ { skip: 5 }
181
+ end
182
+
183
+ it 'sets the skip on the view' do
184
+ expect(view.skip).to eq(options[:skip])
185
+ end
186
+ end
187
+
188
+ context 'when provided sort' do
189
+
190
+ let(:options) do
191
+ { sort: { 'x' => Mongo::Index::ASCENDING } }
192
+ end
193
+
194
+ it 'sets the sort on the view' do
195
+ expect(view.sort).to eq(options[:sort])
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#find_one' do
202
+
203
+ let(:fs) do
204
+ described_class.new(authorized_client.database)
205
+ end
206
+
207
+ let(:file) do
208
+ Mongo::Grid::File.new('hello world!', :filename => 'test.txt')
209
+ end
210
+
211
+ before do
212
+ fs.insert_one(file)
213
+ end
214
+
215
+ after do
216
+ fs.files_collection.delete_many
217
+ fs.chunks_collection.delete_many
218
+ end
219
+
220
+ let(:from_db) do
221
+ fs.find_one(:filename => 'test.txt')
222
+ end
223
+
224
+ it 'returns the assembled file from the db' do
225
+ expect(from_db.filename).to eq(file.info.filename)
226
+ end
227
+ end
228
+
229
+ describe '#insert_one' do
230
+
231
+ let(:fs) do
232
+ described_class.new(authorized_client.database)
233
+ end
234
+
235
+ let(:file) do
236
+ Mongo::Grid::File.new('Hello!', :filename => 'test.txt')
237
+ end
238
+
239
+ context 'when inserting the file once' do
240
+
241
+ let!(:result) do
242
+ fs.insert_one(file)
243
+ end
244
+
245
+ after do
246
+ fs.files_collection.delete_many
247
+ fs.chunks_collection.delete_many
248
+ end
249
+
250
+ let(:from_db) do
251
+ fs.find_one(:filename => 'test.txt')
252
+ end
253
+
254
+ it 'inserts the file into the database' do
255
+ expect(from_db.filename).to eq(file.info.filename)
256
+ end
257
+
258
+ it 'includes the chunks and data with the file' do
259
+ expect(from_db.data).to eq('Hello!')
260
+ end
261
+
262
+ it 'returns the file id' do
263
+ expect(result).to eq(file.id)
264
+ end
265
+ end
266
+
267
+ context 'when the files collection is empty' do
268
+
269
+ before do
270
+ fs.files_collection.delete_many
271
+ fs.chunks_collection.delete_many
272
+ expect(fs.files_collection).to receive(:indexes).and_call_original
273
+ expect(fs.chunks_collection).to receive(:indexes).and_call_original
274
+ fs.insert_one(file)
275
+ end
276
+
277
+ after do
278
+ fs.files_collection.delete_many
279
+ fs.chunks_collection.delete_many
280
+ end
281
+
282
+ let(:chunks_index) do
283
+ fs.database[fs.chunks_collection.name].indexes.get(:files_id => 1, :n => 1)
284
+ end
285
+
286
+ let(:files_index) do
287
+ fs.database[fs.files_collection.name].indexes.get(:filename => 1, :uploadDate => 1)
288
+ end
289
+
290
+ it 'creates an index on the files collection' do
291
+ expect(files_index[:name]).to eq('filename_1_uploadDate_1')
292
+ end
293
+
294
+ it 'creates an index on the chunks collection' do
295
+ expect(chunks_index[:name]).to eq('files_id_1_n_1')
296
+ end
297
+
298
+ context 'when a write operation is called more than once' do
299
+
300
+ before do
301
+ expect(fs).not_to receive(:ensure_indexes!)
302
+ end
303
+
304
+ let(:file2) do
305
+ Mongo::Grid::File.new('Goodbye!', :filename => 'test2.txt')
306
+ end
307
+
308
+ it 'only creates the indexes the first time' do
309
+ expect(fs.insert_one(file2)).to be_a(BSON::ObjectId)
310
+ end
311
+ end
312
+ end
313
+
314
+ context 'when the index creation encounters an error', if: write_command_enabled? do
315
+
316
+ before do
317
+ fs.chunks_collection.drop
318
+ fs.chunks_collection.indexes.create_one(Mongo::Grid::FSBucket::CHUNKS_INDEX, :unique => false)
319
+ expect(fs.chunks_collection).to receive(:indexes).and_call_original
320
+ expect(fs.files_collection).not_to receive(:indexes)
321
+ end
322
+
323
+ after do
324
+ fs.database[fs.chunks_collection.name].indexes.drop_one('files_id_1_n_1')
325
+ end
326
+
327
+ it 'raises the error to the user' do
328
+ expect {
329
+ fs.insert_one(file)
330
+ }.to raise_error(Mongo::Error::OperationFailure)
331
+ end
332
+ end
333
+
334
+ context 'when the files collection is not empty' do
335
+
336
+ before do
337
+ fs.files_collection.insert_one(a: 1)
338
+ expect(fs.files_collection).not_to receive(:indexes)
339
+ expect(fs.chunks_collection).not_to receive(:indexes)
340
+ fs.insert_one(file)
341
+ end
342
+
343
+ after do
344
+ fs.files_collection.delete_many
345
+ fs.chunks_collection.delete_many
346
+ end
347
+
348
+ let(:files_index) do
349
+ fs.database[fs.files_collection.name].indexes.get(:filename => 1, :uploadDate => 1)
350
+ end
351
+
352
+ it 'assumes indexes already exist' do
353
+ expect(files_index[:name]).to eq('filename_1_uploadDate_1')
354
+ end
355
+ end
356
+
357
+ context 'when inserting the file more than once' do
358
+
359
+ after do
360
+ fs.files_collection.delete_many
361
+ fs.chunks_collection.delete_many
362
+ end
363
+
364
+ it 'raises an error' do
365
+ expect {
366
+ fs.insert_one(file)
367
+ fs.insert_one(file)
368
+ }.to raise_error(Mongo::Error::BulkWriteError)
369
+ end
370
+ end
371
+
372
+ context 'when the file exceeds the max bson size' do
373
+
374
+ let(:fs) do
375
+ described_class.new(authorized_client.database)
376
+ end
377
+
378
+ let(:file) do
379
+ str = 'y' * 16777216
380
+ Mongo::Grid::File.new(str, :filename => 'large-file.txt')
381
+ end
382
+
383
+ before do
384
+ fs.insert_one(file)
385
+ end
386
+
387
+ after do
388
+ fs.files_collection.delete_many
389
+ fs.chunks_collection.delete_many
390
+ end
391
+
392
+ it 'successfully inserts the file' do
393
+ expect(
394
+ fs.find_one(:filename => 'large-file.txt').chunks
395
+ ).to eq(file.chunks)
396
+ end
397
+ end
398
+ end
399
+
400
+ describe '#delete_one' do
401
+
402
+ let(:file) do
403
+ Mongo::Grid::File.new('Hello!', :filename => 'test.txt')
404
+ end
405
+
406
+ before do
407
+ fs.insert_one(file)
408
+ fs.delete_one(file)
409
+ end
410
+
411
+ let(:from_db) do
412
+ fs.find_one(:filename => 'test.txt')
413
+ end
414
+
415
+ it 'removes the file from the db' do
416
+ expect(from_db).to be_nil
417
+ end
418
+ end
419
+
420
+ describe '#delete' do
421
+
422
+ let(:file_id) do
423
+ fs.upload_from_stream(filename, file)
424
+ end
425
+
426
+ before do
427
+ fs.delete(file_id)
428
+ end
429
+
430
+ let(:from_db) do
431
+ fs.find_one(:filename => filename)
432
+ end
433
+
434
+ it 'removes the file from the db' do
435
+ expect(from_db).to be_nil
436
+ end
437
+ end
438
+
439
+ context 'when a read stream is opened' do
440
+
441
+ let(:fs) do
442
+ described_class.new(authorized_client.database)
443
+ end
444
+
445
+ let(:io) do
446
+ StringIO.new
447
+ end
448
+
449
+ after do
450
+ fs.files_collection.delete_many
451
+ fs.chunks_collection.delete_many
452
+ end
453
+
454
+ describe '#open_download_stream' do
455
+
456
+ let!(:file_id) do
457
+ fs.open_upload_stream(filename) do |stream|
458
+ stream.write(file)
459
+ end.file_id
460
+ end
461
+
462
+ context 'when a block is provided' do
463
+
464
+ let!(:stream) do
465
+ fs.open_download_stream(file_id) do |stream|
466
+ io.write(stream.read)
467
+ end
468
+ end
469
+
470
+ it 'returns a Stream::Read object' do
471
+ expect(stream).to be_a(Mongo::Grid::FSBucket::Stream::Read)
472
+ end
473
+
474
+ it 'closes the stream after the block completes' do
475
+ expect(stream.closed?).to be(true)
476
+ end
477
+
478
+ it 'yields the stream to the block' do
479
+ expect(io.size).to eq(file.size)
480
+ end
481
+ end
482
+
483
+ context 'when a block is not provided' do
484
+
485
+ let!(:stream) do
486
+ fs.open_download_stream(file_id)
487
+ end
488
+
489
+ it 'returns a Stream::Read object' do
490
+ expect(stream).to be_a(Mongo::Grid::FSBucket::Stream::Read)
491
+ end
492
+
493
+ it 'does not close the stream' do
494
+ expect(stream.closed?).to be(false)
495
+ end
496
+
497
+ it 'does not yield the stream to the block' do
498
+ expect(io.size).to eq(0)
499
+ end
500
+ end
501
+ end
502
+
503
+ describe '#download_to_stream' do
504
+
505
+ context 'when the file is found' do
506
+
507
+ let!(:file_id) do
508
+ fs.open_upload_stream(filename) do |stream|
509
+ stream.write(file)
510
+ end.file_id
511
+ end
512
+
513
+ before do
514
+ fs.download_to_stream(file_id, io)
515
+ end
516
+
517
+ it 'writes to the provided stream' do
518
+ expect(io.size).to eq(file.size)
519
+ end
520
+
521
+ it 'does not close the stream' do
522
+ expect(io.closed?).to be(false)
523
+ end
524
+
525
+ context 'when the file has length 0' do
526
+
527
+ let(:file) do
528
+ StringIO.new('')
529
+ end
530
+
531
+ let(:from_db) do
532
+ fs.open_upload_stream(filename) { |s| s.write(file) }
533
+ fs.find_one(:filename => filename)
534
+ end
535
+
536
+ it 'can read the file back' do
537
+ expect(from_db.data.size).to eq(file.size)
538
+ end
539
+ end
540
+ end
541
+
542
+ context 'when there is no files collection document found' do
543
+
544
+ it 'raises an exception' do
545
+ expect{
546
+ fs.download_to_stream(BSON::ObjectId.new, io)
547
+ }.to raise_exception(Mongo::Error::FileNotFound)
548
+ end
549
+ end
550
+
551
+ context 'when a file has an id that is not an ObjectId' do
552
+
553
+ before do
554
+ fs.insert_one(file)
555
+ fs.download_to_stream(file_id, io)
556
+ end
557
+
558
+ let(:file_id) do
559
+ 'non-object-id'
560
+ end
561
+
562
+ let(:file) do
563
+ Mongo::Grid::File.new(File.open(__FILE__).read,
564
+ :filename => filename,
565
+ :_id => file_id)
566
+ end
567
+
568
+ it 'reads the file successfully' do
569
+ expect(io.size).to eq(file.data.size)
570
+ end
571
+ end
572
+ end
573
+
574
+ context 'when a read preference is specified' do
575
+
576
+ let(:fs) do
577
+ described_class.new(authorized_client.database, options)
578
+ end
579
+
580
+ let(:options) do
581
+ { read: { mode: :secondary } }
582
+ end
583
+
584
+ let(:stream) do
585
+ fs.open_download_stream(BSON::ObjectId)
586
+ end
587
+
588
+ it 'sets the read preference on the Stream::Read object' do
589
+ expect(stream.read_preference).to eq(Mongo::ServerSelector.get(options[:read]))
590
+ end
591
+ end
592
+
593
+ describe '#download_to_stream_by_name' do
594
+
595
+ let(:files) do
596
+ [
597
+ StringIO.new('hello 1'),
598
+ StringIO.new('hello 2'),
599
+ StringIO.new('hello 3'),
600
+ StringIO.new('hello 4')
601
+ ]
602
+ end
603
+
604
+ before do
605
+ files.each do |file|
606
+ fs.upload_from_stream('test.txt', file)
607
+ end
608
+ end
609
+
610
+ let(:io) do
611
+ StringIO.new
612
+ end
613
+
614
+ context 'when revision is not specified' do
615
+
616
+ let!(:result) do
617
+ fs.download_to_stream_by_name('test.txt', io)
618
+ end
619
+
620
+ it 'returns the most recent version' do
621
+ expect(io.string).to eq('hello 4')
622
+ end
623
+ end
624
+
625
+ context 'when revision is 0' do
626
+
627
+ let!(:result) do
628
+ fs.download_to_stream_by_name('test.txt', io, revision: 0)
629
+ end
630
+
631
+ it 'returns the original stored file' do
632
+ expect(io.string).to eq('hello 1')
633
+ end
634
+ end
635
+
636
+ context 'when revision is negative' do
637
+
638
+ let!(:result) do
639
+ fs.download_to_stream_by_name('test.txt', io, revision: -2)
640
+ end
641
+
642
+ it 'returns that number of versions from the most recent' do
643
+ expect(io.string).to eq('hello 3')
644
+ end
645
+ end
646
+
647
+ context 'when revision is positive' do
648
+
649
+ let!(:result) do
650
+ fs.download_to_stream_by_name('test.txt', io, revision: 1)
651
+ end
652
+
653
+ it 'returns that number revision' do
654
+ expect(io.string).to eq('hello 2')
655
+ end
656
+ end
657
+
658
+ context 'when the file revision is not found' do
659
+
660
+ it 'raises a FileNotFound error' do
661
+ expect {
662
+ fs.download_to_stream_by_name('test.txt', io, revision: 100)
663
+ }.to raise_exception(Mongo::Error::InvalidFileRevision)
664
+ end
665
+ end
666
+
667
+ context 'when the file is not found' do
668
+
669
+ it 'raises a FileNotFound error' do
670
+ expect {
671
+ fs.download_to_stream_by_name('non-existent.txt', io)
672
+ }.to raise_exception(Mongo::Error::FileNotFound)
673
+ end
674
+ end
675
+ end
676
+
677
+ describe '#open_download_stream_by_name' do
678
+
679
+ let(:files) do
680
+ [
681
+ StringIO.new('hello 1'),
682
+ StringIO.new('hello 2'),
683
+ StringIO.new('hello 3'),
684
+ StringIO.new('hello 4')
685
+ ]
686
+ end
687
+
688
+ before do
689
+ files.each do |file|
690
+ fs.upload_from_stream('test.txt', file)
691
+ end
692
+ end
693
+
694
+ let(:io) do
695
+ StringIO.new
696
+ end
697
+
698
+ context 'when a block is provided' do
699
+
700
+ let(:stream) do
701
+ fs.open_download_stream_by_name('test.txt') do |stream|
702
+ io.write(stream.read)
703
+ end
704
+ end
705
+
706
+ it 'returns a Stream::Read object' do
707
+ expect(stream).to be_a(Mongo::Grid::FSBucket::Stream::Read)
708
+ end
709
+
710
+ it 'closes the stream after the block completes' do
711
+ expect(stream.closed?).to be(true)
712
+ end
713
+
714
+ it 'yields the stream to the block' do
715
+ stream
716
+ expect(io.size).to eq(files[0].size)
717
+ end
718
+
719
+ context 'when revision is not specified' do
720
+
721
+ let!(:result) do
722
+ fs.open_download_stream_by_name('test.txt') do |stream|
723
+ io.write(stream.read)
724
+ end
725
+ end
726
+
727
+ it 'returns the most recent version' do
728
+ expect(io.string).to eq('hello 4')
729
+ end
730
+ end
731
+
732
+ context 'when revision is 0' do
733
+
734
+ let!(:result) do
735
+ fs.open_download_stream_by_name('test.txt', revision: 0) do |stream|
736
+ io.write(stream.read)
737
+ end
738
+ end
739
+
740
+ it 'returns the original stored file' do
741
+ expect(io.string).to eq('hello 1')
742
+ end
743
+ end
744
+
745
+ context 'when revision is negative' do
746
+
747
+ let!(:result) do
748
+ fs.open_download_stream_by_name('test.txt', revision: -2) do |stream|
749
+ io.write(stream.read)
750
+ end
751
+ end
752
+
753
+ it 'returns that number of versions from the most recent' do
754
+ expect(io.string).to eq('hello 3')
755
+ end
756
+ end
757
+
758
+ context 'when revision is positive' do
759
+
760
+ let!(:result) do
761
+ fs.open_download_stream_by_name('test.txt', revision: 1) do |stream|
762
+ io.write(stream.read)
763
+ end
764
+ end
765
+
766
+ it 'returns that number revision' do
767
+ expect(io.string).to eq('hello 2')
768
+ end
769
+ end
770
+
771
+ context 'when the file revision is not found' do
772
+
773
+ it 'raises a FileNotFound error' do
774
+ expect {
775
+ fs.open_download_stream_by_name('test.txt', revision: 100)
776
+ }.to raise_exception(Mongo::Error::InvalidFileRevision)
777
+ end
778
+ end
779
+
780
+ context 'when the file is not found' do
781
+
782
+ it 'raises a FileNotFound error' do
783
+ expect {
784
+ fs.open_download_stream_by_name('non-existent.txt')
785
+ }.to raise_exception(Mongo::Error::FileNotFound)
786
+ end
787
+ end
788
+ end
789
+
790
+ context 'when a block is not provided' do
791
+
792
+ let!(:stream) do
793
+ fs.open_download_stream_by_name('test.txt')
794
+ end
795
+
796
+ it 'returns a Stream::Read object' do
797
+ expect(stream).to be_a(Mongo::Grid::FSBucket::Stream::Read)
798
+ end
799
+
800
+ it 'does not close the stream' do
801
+ expect(stream.closed?).to be(false)
802
+ end
803
+
804
+ it 'does not yield the stream to the block' do
805
+ expect(io.size).to eq(0)
806
+ end
807
+ end
808
+ end
809
+ end
810
+
811
+ context 'when a write stream is opened' do
812
+
813
+ let(:stream) do
814
+ fs.open_upload_stream(filename)
815
+ end
816
+
817
+ after do
818
+ fs.files_collection.delete_many
819
+ fs.chunks_collection.delete_many
820
+ end
821
+
822
+ describe '#open_upload_stream' do
823
+
824
+ context 'when a block is not provided' do
825
+
826
+ it 'returns a Stream::Write object' do
827
+ expect(stream).to be_a(Mongo::Grid::FSBucket::Stream::Write)
828
+ end
829
+
830
+ it 'creates an ObjectId for the file' do
831
+ expect(stream.file_id).to be_a(BSON::ObjectId)
832
+ end
833
+ end
834
+
835
+ context 'when a block is provided' do
836
+
837
+ let!(:stream) do
838
+ fs.open_upload_stream(filename) do |stream|
839
+ stream.write(file)
840
+ end
841
+ end
842
+
843
+ let(:result) do
844
+ fs.find_one(filename: filename)
845
+ end
846
+
847
+ it 'returns the stream' do
848
+ expect(stream).to be_a(Mongo::Grid::FSBucket::Stream::Write)
849
+ end
850
+
851
+ it 'creates an ObjectId for the file' do
852
+ expect(stream.file_id).to be_a(BSON::ObjectId)
853
+ end
854
+
855
+ it 'yields the stream to the block' do
856
+ expect(result.data.size).to eq(file.size)
857
+ end
858
+
859
+ it 'closes the stream when the block completes' do
860
+ expect(stream.closed?).to be(true)
861
+ end
862
+ end
863
+ end
864
+
865
+ describe '#upload_from_stream' do
866
+
867
+ let!(:result) do
868
+ fs.upload_from_stream(filename, file)
869
+ end
870
+
871
+ let(:file_from_db) do
872
+ fs.find_one(:filename => filename)
873
+ end
874
+
875
+ it 'writes to the provided stream' do
876
+ expect(file_from_db.data.length).to eq(file.size)
877
+ end
878
+
879
+ it 'does not close the stream' do
880
+ expect(file.closed?).to be(false)
881
+ end
882
+
883
+ it 'returns the id of the file' do
884
+ expect(result).to be_a(BSON::ObjectId)
885
+ end
886
+
887
+ context 'when the io stream raises an error' do
888
+
889
+ let(:stream) do
890
+ fs.open_upload_stream(filename)
891
+ end
892
+
893
+ before do
894
+ allow(fs).to receive(:open_upload_stream).and_yield(stream)
895
+ end
896
+
897
+ context 'when stream#abort does not raise an OperationFailure' do
898
+
899
+ before do
900
+ expect(stream).to receive(:abort).and_call_original
901
+ file.close
902
+ end
903
+
904
+ it 'raises the original IOError' do
905
+ expect {
906
+ fs.upload_from_stream(filename, file)
907
+ }.to raise_exception(IOError)
908
+ end
909
+ end
910
+
911
+ context 'when stream#abort raises an OperationFailure' do
912
+
913
+ before do
914
+ allow(stream).to receive(:abort).and_raise(Mongo::Error::OperationFailure)
915
+ file.close
916
+ end
917
+
918
+ it 'raises the original IOError' do
919
+ expect {
920
+ fs.upload_from_stream(filename, file)
921
+ }.to raise_exception(IOError)
922
+ end
923
+ end
924
+ end
925
+ end
926
+
927
+ context 'when options are provided when opening the write stream' do
928
+
929
+ let(:stream) do
930
+ fs.open_upload_stream(filename, stream_options)
931
+ end
932
+
933
+ context 'when a write option is specified' do
934
+
935
+ let(:stream_options) do
936
+ { write: { w: 2 } }
937
+ end
938
+
939
+ it 'sets the write concern on the write stream' do
940
+ expect(stream.write_concern.options).to eq(Mongo::WriteConcern.get(stream_options[:write]).options)
941
+ end
942
+ end
943
+
944
+ context 'when there is a chunk size set on the FSBucket' do
945
+
946
+ let(:stream_options) do
947
+ { }
948
+ end
949
+
950
+ let(:options) do
951
+ { chunk_size: 100 }
952
+ end
953
+
954
+ it 'sets the chunk size as the default on the write stream' do
955
+ expect(stream.options[:chunk_size]).to eq(options[:chunk_size])
956
+ end
957
+ end
958
+
959
+ context 'when a chunk size option is specified' do
960
+
961
+ let(:stream_options) do
962
+ { chunk_size: 50 }
963
+ end
964
+
965
+ it 'sets the chunk size on the write stream' do
966
+ expect(stream.options[:chunk_size]).to eq(stream_options[:chunk_size])
967
+ end
968
+
969
+ context 'when there is a chunk size set on the FSBucket' do
970
+
971
+ let(:options) do
972
+ { chunk_size: 100 }
973
+ end
974
+
975
+ let(:fs) do
976
+ described_class.new(authorized_client.database, options)
977
+ end
978
+
979
+ it 'uses the chunk size set on the write stream' do
980
+ expect(stream.options[:chunk_size]).to eq(stream_options[:chunk_size])
981
+ end
982
+
983
+ end
984
+ end
985
+
986
+ context 'when a file metadata option is specified' do
987
+
988
+ let(:stream_options) do
989
+ { metadata: { some_field: 1 } }
990
+ end
991
+
992
+ it 'sets the file metadata option on the write stream' do
993
+ expect(stream.options[:metadata]).to eq(stream_options[:metadata])
994
+ end
995
+ end
996
+
997
+ context 'when a content type option is specified' do
998
+
999
+ let(:stream_options) do
1000
+ { content_type: 'text/plain' }
1001
+ end
1002
+
1003
+ it 'sets the content type on the write stream' do
1004
+ expect(stream.options[:content_type]).to eq(stream_options[:content_type])
1005
+ end
1006
+ end
1007
+
1008
+ context 'when a aliases option is specified' do
1009
+
1010
+ let(:stream_options) do
1011
+ { aliases: [ 'another-name.txt' ] }
1012
+ end
1013
+
1014
+ it 'sets the alias option on the write stream' do
1015
+ expect(stream.options[:aliases]).to eq(stream_options[:aliases])
1016
+ end
1017
+ end
1018
+ end
1019
+ end
1020
+ end