animehunter-mongo 0.9

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 (79) hide show
  1. data/README.rdoc +311 -0
  2. data/Rakefile +62 -0
  3. data/bin/bson_benchmark.rb +59 -0
  4. data/bin/mongo_console +21 -0
  5. data/bin/run_test_script +19 -0
  6. data/bin/standard_benchmark +109 -0
  7. data/examples/admin.rb +41 -0
  8. data/examples/benchmarks.rb +42 -0
  9. data/examples/blog.rb +76 -0
  10. data/examples/capped.rb +23 -0
  11. data/examples/cursor.rb +47 -0
  12. data/examples/gridfs.rb +87 -0
  13. data/examples/index_test.rb +125 -0
  14. data/examples/info.rb +30 -0
  15. data/examples/queries.rb +69 -0
  16. data/examples/simple.rb +23 -0
  17. data/examples/strict.rb +34 -0
  18. data/examples/types.rb +40 -0
  19. data/lib/mongo.rb +19 -0
  20. data/lib/mongo/admin.rb +87 -0
  21. data/lib/mongo/collection.rb +235 -0
  22. data/lib/mongo/cursor.rb +227 -0
  23. data/lib/mongo/db.rb +538 -0
  24. data/lib/mongo/gridfs.rb +16 -0
  25. data/lib/mongo/gridfs/chunk.rb +96 -0
  26. data/lib/mongo/gridfs/grid_store.rb +468 -0
  27. data/lib/mongo/message.rb +20 -0
  28. data/lib/mongo/message/get_more_message.rb +37 -0
  29. data/lib/mongo/message/insert_message.rb +35 -0
  30. data/lib/mongo/message/kill_cursors_message.rb +36 -0
  31. data/lib/mongo/message/message.rb +84 -0
  32. data/lib/mongo/message/message_header.rb +50 -0
  33. data/lib/mongo/message/msg_message.rb +33 -0
  34. data/lib/mongo/message/opcodes.rb +32 -0
  35. data/lib/mongo/message/query_message.rb +77 -0
  36. data/lib/mongo/message/remove_message.rb +36 -0
  37. data/lib/mongo/message/update_message.rb +37 -0
  38. data/lib/mongo/mongo.rb +164 -0
  39. data/lib/mongo/query.rb +119 -0
  40. data/lib/mongo/types/binary.rb +42 -0
  41. data/lib/mongo/types/code.rb +34 -0
  42. data/lib/mongo/types/dbref.rb +37 -0
  43. data/lib/mongo/types/objectid.rb +137 -0
  44. data/lib/mongo/types/regexp_of_holding.rb +44 -0
  45. data/lib/mongo/types/undefined.rb +31 -0
  46. data/lib/mongo/util/bson.rb +534 -0
  47. data/lib/mongo/util/byte_buffer.rb +167 -0
  48. data/lib/mongo/util/ordered_hash.rb +96 -0
  49. data/lib/mongo/util/xml_to_ruby.rb +107 -0
  50. data/mongo-ruby-driver.gemspec +99 -0
  51. data/tests/mongo-qa/_common.rb +8 -0
  52. data/tests/mongo-qa/admin +26 -0
  53. data/tests/mongo-qa/capped +22 -0
  54. data/tests/mongo-qa/count1 +18 -0
  55. data/tests/mongo-qa/dbs +22 -0
  56. data/tests/mongo-qa/find +10 -0
  57. data/tests/mongo-qa/find1 +15 -0
  58. data/tests/mongo-qa/gridfs_in +16 -0
  59. data/tests/mongo-qa/gridfs_out +17 -0
  60. data/tests/mongo-qa/indices +49 -0
  61. data/tests/mongo-qa/remove +25 -0
  62. data/tests/mongo-qa/stress1 +35 -0
  63. data/tests/mongo-qa/test1 +11 -0
  64. data/tests/mongo-qa/update +18 -0
  65. data/tests/test_admin.rb +69 -0
  66. data/tests/test_bson.rb +246 -0
  67. data/tests/test_byte_buffer.rb +69 -0
  68. data/tests/test_chunk.rb +84 -0
  69. data/tests/test_cursor.rb +121 -0
  70. data/tests/test_db.rb +160 -0
  71. data/tests/test_db_api.rb +701 -0
  72. data/tests/test_db_connection.rb +18 -0
  73. data/tests/test_grid_store.rb +284 -0
  74. data/tests/test_message.rb +35 -0
  75. data/tests/test_mongo.rb +78 -0
  76. data/tests/test_objectid.rb +98 -0
  77. data/tests/test_ordered_hash.rb +129 -0
  78. data/tests/test_round_trip.rb +116 -0
  79. metadata +133 -0
@@ -0,0 +1,16 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+ require 'mongo/gridfs/grid_store'
@@ -0,0 +1,96 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/types/objectid'
18
+ require 'mongo/util/byte_buffer'
19
+ require 'mongo/util/ordered_hash'
20
+
21
+
22
+ module XGen
23
+ module Mongo
24
+ module GridFS
25
+
26
+ # A chunk stores a portion of GridStore data.
27
+ class Chunk
28
+
29
+ DEFAULT_CHUNK_SIZE = 1024 * 256
30
+
31
+ attr_reader :object_id, :chunk_number
32
+ attr_accessor :data
33
+
34
+ def initialize(file, mongo_object={})
35
+ @file = file
36
+ @object_id = mongo_object['_id'] || XGen::Mongo::Driver::ObjectID.new
37
+ @chunk_number = mongo_object['n'] || 0
38
+
39
+ @data = ByteBuffer.new
40
+ case mongo_object['data']
41
+ when String
42
+ mongo_object['data'].each_byte { |b| @data.put(b) }
43
+ when ByteBuffer
44
+ @data.put_array(mongo_object['data'].to_a)
45
+ when Array
46
+ @data.put_array(mongo_object['data'])
47
+ when nil
48
+ else
49
+ raise "illegal chunk format; data is #{mongo_object['data'] ? (' ' + mongo_object['data'].class.name) : 'nil'}"
50
+ end
51
+ @data.rewind
52
+ end
53
+
54
+ def pos; @data.position; end
55
+ def pos=(pos); @data.position = pos; end
56
+ def eof?; !@data.more?; end
57
+
58
+ def size; @data.size; end
59
+ alias_method :length, :size
60
+
61
+ # Erase all data after current position.
62
+ def truncate
63
+ if @data.position < @data.length
64
+ curr_data = @data
65
+ @data = ByteBuffer.new
66
+ @data.put_array(curr_data.to_a[0...curr_data.position])
67
+ end
68
+ end
69
+
70
+ def getc
71
+ @data.more? ? @data.get : nil
72
+ end
73
+
74
+ def putc(byte)
75
+ @data.put(byte)
76
+ end
77
+
78
+ def save
79
+ coll = @file.chunk_collection
80
+ coll.remove({'_id' => @object_id})
81
+ coll.insert(to_mongo_object)
82
+ end
83
+
84
+ def to_mongo_object
85
+ h = OrderedHash.new
86
+ h['_id'] = @object_id
87
+ h['files_id'] = @file.files_id
88
+ h['n'] = @chunk_number
89
+ h['data'] = data
90
+ h
91
+ end
92
+
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,468 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/types/objectid'
18
+ require 'mongo/util/ordered_hash'
19
+ require 'mongo/gridfs/chunk'
20
+
21
+ module XGen
22
+ module Mongo
23
+ module GridFS
24
+
25
+ # GridStore is an IO-like object that provides input and output for
26
+ # streams of data to Mongo. See Mongo's documentation about GridFS for
27
+ # storage implementation details.
28
+ #
29
+ # Example code:
30
+ #
31
+ # require 'mongo/gridfs'
32
+ # GridStore.open(database, 'filename', 'w') { |f|
33
+ # f.puts "Hello, world!"
34
+ # }
35
+ # GridStore.open(database, 'filename, 'r') { |f|
36
+ # puts f.read # => Hello, world!\n
37
+ # }
38
+ # GridStore.open(database, 'filename', 'w+') { |f|
39
+ # f.puts "But wait, there's more!"
40
+ # }
41
+ # GridStore.open(database, 'filename, 'r') { |f|
42
+ # puts f.read # => Hello, world!\nBut wait, there's more!\n
43
+ # }
44
+ class GridStore
45
+
46
+ DEFAULT_ROOT_COLLECTION = 'fs'
47
+ DEFAULT_CONTENT_TYPE = 'text/plain'
48
+
49
+ include Enumerable
50
+
51
+ attr_accessor :filename
52
+
53
+ # Array of strings; may be +nil+
54
+ attr_accessor :aliases
55
+
56
+ # Default is DEFAULT_CONTENT_TYPE
57
+ attr_accessor :content_type
58
+
59
+ attr_accessor :metadata
60
+
61
+ attr_reader :files_id
62
+
63
+ # Time that the file was first saved.
64
+ attr_reader :upload_date
65
+
66
+ attr_reader :chunk_size
67
+
68
+ attr_accessor :lineno
69
+
70
+ attr_reader :md5
71
+
72
+ class << self
73
+
74
+ def exist?(db, name, root_collection=DEFAULT_ROOT_COLLECTION)
75
+ db.collection("#{root_collection}.files").find({'filename' => name}).next_object != nil
76
+ end
77
+
78
+ def open(db, name, mode, options={})
79
+ gs = self.new(db, name, mode, options)
80
+ result = nil
81
+ begin
82
+ result = yield gs if block_given?
83
+ ensure
84
+ gs.close
85
+ end
86
+ result
87
+ end
88
+
89
+ def read(db, name, length=nil, offset=nil)
90
+ GridStore.open(db, name, 'r') { |gs|
91
+ gs.seek(offset) if offset
92
+ gs.read(length)
93
+ }
94
+ end
95
+
96
+ # List the contains of all GridFS files stored in the given db and
97
+ # root collection.
98
+ #
99
+ # :db :: the database to use
100
+ #
101
+ # :root_collection :: the root collection to use
102
+ def list(db, root_collection=DEFAULT_ROOT_COLLECTION)
103
+ db.collection("#{root_collection}.files").find().map { |f|
104
+ f['filename']
105
+ }
106
+ end
107
+
108
+ def readlines(db, name, separator=$/)
109
+ GridStore.open(db, name, 'r') { |gs|
110
+ gs.readlines(separator)
111
+ }
112
+ end
113
+
114
+ def unlink(db, *names)
115
+ names.each { |name|
116
+ gs = GridStore.new(db, name)
117
+ gs.send(:delete_chunks)
118
+ gs.collection.remove('_id' => gs.files_id)
119
+ }
120
+ end
121
+ alias_method :delete, :unlink
122
+
123
+ end
124
+
125
+ #---
126
+ # ================================================================
127
+ #+++
128
+
129
+ # Mode may only be 'r', 'w', or 'w+'.
130
+ #
131
+ # Options. Descriptions start with a list of the modes for which that
132
+ # option is legitimate.
133
+ #
134
+ # :root :: (r, w, w+) Name of root collection to use, instead of
135
+ # DEFAULT_ROOT_COLLECTION.
136
+ #
137
+ # :metadata:: (w, w+) A hash containing any data you want persisted as
138
+ # this file's metadata. See also metadata=
139
+ #
140
+ # :chunk_size :: (w) Sets chunk size for files opened for writing
141
+ # See also chunk_size= which may only be called before
142
+ # any data is written.
143
+ #
144
+ # :content_type :: (w) Default value is DEFAULT_CONTENT_TYPE. See
145
+ # also #content_type=
146
+ def initialize(db, name, mode='r', options={})
147
+ @db, @filename, @mode = db, name, mode
148
+ @root = options[:root] || DEFAULT_ROOT_COLLECTION
149
+
150
+ doc = collection.find({'filename' => @filename}).next_object
151
+ if doc
152
+ @files_id = doc['_id']
153
+ @content_type = doc['contentType']
154
+ @chunk_size = doc['chunkSize']
155
+ @upload_date = doc['uploadDate']
156
+ @aliases = doc['aliases']
157
+ @length = doc['length']
158
+ @metadata = doc['metadata']
159
+ @md5 = doc['md5']
160
+ else
161
+ @files_id = XGen::Mongo::Driver::ObjectID.new
162
+ @content_type = DEFAULT_CONTENT_TYPE
163
+ @chunk_size = Chunk::DEFAULT_CHUNK_SIZE
164
+ @length = 0
165
+ end
166
+
167
+ case mode
168
+ when 'r'
169
+ @curr_chunk = nth_chunk(0)
170
+ @position = 0
171
+ when 'w'
172
+ chunk_collection.create_index([['files_id', XGen::Mongo::ASCENDING], ['n', XGen::Mongo::ASCENDING]])
173
+ delete_chunks
174
+ @curr_chunk = Chunk.new(self, 'n' => 0)
175
+ @content_type = options[:content_type] if options[:content_type]
176
+ @chunk_size = options[:chunk_size] if options[:chunk_size]
177
+ @metadata = options[:metadata] if options[:metadata]
178
+ @position = 0
179
+ when 'w+'
180
+ chunk_collection.create_index([['files_id', XGen::Mongo::ASCENDING], ['n', XGen::Mongo::ASCENDING]])
181
+ @curr_chunk = nth_chunk(last_chunk_number) || Chunk.new(self, 'n' => 0) # might be empty
182
+ @curr_chunk.pos = @curr_chunk.data.length if @curr_chunk
183
+ @metadata = options[:metadata] if options[:metadata]
184
+ @position = @length
185
+ else
186
+ raise "error: illegal mode #{mode}"
187
+ end
188
+
189
+ @lineno = 0
190
+ @pushback_byte = nil
191
+ end
192
+
193
+ def collection
194
+ @db.collection("#{@root}.files")
195
+ end
196
+
197
+ # Returns collection used for storing chunks. Depends on value of
198
+ # @root.
199
+ def chunk_collection
200
+ @db.collection("#{@root}.chunks")
201
+ end
202
+
203
+ # Change chunk size. Can only change if the file is opened for write
204
+ # and no data has yet been written.
205
+ def chunk_size=(size)
206
+ unless @mode[0] == ?w && @position == 0 && @upload_date == nil
207
+ raise "error: can only change chunk size if open for write and no data written."
208
+ end
209
+ @chunk_size = size
210
+ end
211
+
212
+ #---
213
+ # ================ reading ================
214
+ #+++
215
+
216
+ def getc
217
+ if @pushback_byte
218
+ byte = @pushback_byte
219
+ @pushback_byte = nil
220
+ @position += 1
221
+ byte
222
+ elsif eof?
223
+ nil
224
+ else
225
+ if @curr_chunk.eof?
226
+ @curr_chunk = nth_chunk(@curr_chunk.chunk_number + 1)
227
+ end
228
+ @position += 1
229
+ @curr_chunk.getc
230
+ end
231
+ end
232
+
233
+ def gets(separator=$/)
234
+ str = ''
235
+ byte = self.getc
236
+ return nil if byte == nil # EOF
237
+ while byte != nil
238
+ s = byte.chr
239
+ str << s
240
+ break if s == separator
241
+ byte = self.getc
242
+ end
243
+ @lineno += 1
244
+ str
245
+ end
246
+
247
+ def read(len=nil, buf=nil)
248
+ buf ||= ''
249
+ byte = self.getc
250
+ while byte != nil && (len == nil || len > 0)
251
+ buf << byte.chr
252
+ len -= 1 if len
253
+ byte = self.getc if (len == nil || len > 0)
254
+ end
255
+ buf
256
+ end
257
+
258
+ def readchar
259
+ byte = self.getc
260
+ raise EOFError.new if byte == nil
261
+ byte
262
+ end
263
+
264
+ def readline(separator=$/)
265
+ line = gets
266
+ raise EOFError.new if line == nil
267
+ line
268
+ end
269
+
270
+ def readlines(separator=$/)
271
+ read.split(separator).collect { |line| "#{line}#{separator}" }
272
+ end
273
+
274
+ def each
275
+ line = gets
276
+ while line
277
+ yield line
278
+ line = gets
279
+ end
280
+ end
281
+ alias_method :each_line, :each
282
+
283
+ def each_byte
284
+ byte = self.getc
285
+ while byte
286
+ yield byte
287
+ byte = self.getc
288
+ end
289
+ end
290
+
291
+ def ungetc(byte)
292
+ @pushback_byte = byte
293
+ @position -= 1
294
+ end
295
+
296
+ #---
297
+ # ================ writing ================
298
+ #+++
299
+
300
+ def putc(byte)
301
+ if @curr_chunk.pos == @chunk_size
302
+ prev_chunk_number = @curr_chunk.chunk_number
303
+ @curr_chunk.save
304
+ @curr_chunk = Chunk.new(self, 'n' => prev_chunk_number + 1)
305
+ end
306
+ @position += 1
307
+ @curr_chunk.putc(byte)
308
+ end
309
+
310
+ def print(*objs)
311
+ objs = [$_] if objs == nil || objs.empty?
312
+ objs.each { |obj|
313
+ str = obj.to_s
314
+ str.each_byte { |byte| self.putc(byte) }
315
+ }
316
+ nil
317
+ end
318
+
319
+ def puts(*objs)
320
+ if objs == nil || objs.empty?
321
+ self.putc(10)
322
+ else
323
+ print(*objs.collect{ |obj|
324
+ str = obj.to_s
325
+ str << "\n" unless str =~ /\n$/
326
+ str
327
+ })
328
+ end
329
+ nil
330
+ end
331
+
332
+ def <<(obj)
333
+ write(obj.to_s)
334
+ end
335
+
336
+ # Writes +string+ as bytes and returns the number of bytes written.
337
+ def write(string)
338
+ raise "#@filename not opened for write" unless @mode[0] == ?w
339
+ count = 0
340
+ string.each_byte { |byte|
341
+ self.putc byte
342
+ count += 1
343
+ }
344
+ count
345
+ end
346
+
347
+ # A no-op.
348
+ def flush
349
+ end
350
+
351
+ #---
352
+ # ================ status ================
353
+ #+++
354
+
355
+ def eof
356
+ raise IOError.new("stream not open for reading") unless @mode[0] == ?r
357
+ @position >= @length
358
+ end
359
+ alias_method :eof?, :eof
360
+
361
+ #---
362
+ # ================ positioning ================
363
+ #+++
364
+
365
+ def rewind
366
+ if @curr_chunk.chunk_number != 0
367
+ if @mode[0] == ?w
368
+ delete_chunks
369
+ @curr_chunk = Chunk.new(self, 'n' => 0)
370
+ else
371
+ @curr_chunk == nth_chunk(0)
372
+ end
373
+ end
374
+ @curr_chunk.pos = 0
375
+ @lineno = 0
376
+ @position = 0
377
+ end
378
+
379
+ def seek(pos, whence=IO::SEEK_SET)
380
+ target_pos = case whence
381
+ when IO::SEEK_CUR
382
+ @position + pos
383
+ when IO::SEEK_END
384
+ @length + pos
385
+ when IO::SEEK_SET
386
+ pos
387
+ end
388
+
389
+ new_chunk_number = (target_pos / @chunk_size).to_i
390
+ if new_chunk_number != @curr_chunk.chunk_number
391
+ @curr_chunk.save if @mode[0] == ?w
392
+ @curr_chunk = nth_chunk(new_chunk_number)
393
+ end
394
+ @position = target_pos
395
+ @curr_chunk.pos = @position % @chunk_size
396
+ 0
397
+ end
398
+
399
+ def tell
400
+ @position
401
+ end
402
+
403
+ #---
404
+ # ================ closing ================
405
+ #+++
406
+
407
+ def close
408
+ if @mode[0] == ?w
409
+ if @curr_chunk
410
+ @curr_chunk.truncate
411
+ @curr_chunk.save if @curr_chunk.pos > 0
412
+ end
413
+ files = collection
414
+ if @upload_date
415
+ files.remove('_id' => @files_id)
416
+ else
417
+ @upload_date = Time.now
418
+ end
419
+ files.insert(to_mongo_object)
420
+ end
421
+ @db = nil
422
+ end
423
+
424
+ def closed?
425
+ @db == nil
426
+ end
427
+
428
+ #---
429
+ # ================ protected ================
430
+ #+++
431
+
432
+ protected
433
+
434
+ def to_mongo_object
435
+ h = OrderedHash.new
436
+ h['_id'] = @files_id
437
+ h['filename'] = @filename
438
+ h['contentType'] = @content_type
439
+ h['length'] = @curr_chunk ? @curr_chunk.chunk_number * @chunk_size + @curr_chunk.pos : 0
440
+ h['chunkSize'] = @chunk_size
441
+ h['uploadDate'] = @upload_date
442
+ h['aliases'] = @aliases
443
+ h['metadata'] = @metadata
444
+ md5_command = OrderedHash.new
445
+ md5_command['filemd5'] = @files_id
446
+ md5_command['root'] = @root
447
+ h['md5'] = @db.db_command(md5_command)['md5']
448
+ h
449
+ end
450
+
451
+ def delete_chunks
452
+ chunk_collection.remove({'files_id' => @files_id}) if @files_id
453
+ @curr_chunk = nil
454
+ end
455
+
456
+ def nth_chunk(n)
457
+ mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).next_object
458
+ Chunk.new(self, mongo_chunk || {})
459
+ end
460
+
461
+ def last_chunk_number
462
+ (@length / @chunk_size).to_i
463
+ end
464
+
465
+ end
466
+ end
467
+ end
468
+ end