mongodb-mongo 0.12 → 0.13

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 (81) hide show
  1. data/README.rdoc +12 -12
  2. data/Rakefile +1 -1
  3. data/bin/bson_benchmark.rb +1 -1
  4. data/bin/mongo_console +3 -3
  5. data/bin/run_test_script +2 -2
  6. data/bin/standard_benchmark +3 -3
  7. data/examples/admin.rb +3 -3
  8. data/examples/benchmarks.rb +2 -2
  9. data/examples/blog.rb +4 -4
  10. data/examples/capped.rb +3 -3
  11. data/examples/cursor.rb +3 -3
  12. data/examples/gridfs.rb +4 -4
  13. data/examples/index_test.rb +11 -11
  14. data/examples/info.rb +3 -3
  15. data/examples/queries.rb +3 -3
  16. data/examples/simple.rb +3 -3
  17. data/examples/strict.rb +3 -3
  18. data/examples/types.rb +4 -9
  19. data/lib/mongo.rb +35 -3
  20. data/lib/mongo/admin.rb +56 -60
  21. data/lib/mongo/collection.rb +368 -320
  22. data/lib/mongo/connection.rb +166 -0
  23. data/lib/mongo/cursor.rb +206 -209
  24. data/lib/mongo/db.rb +478 -489
  25. data/lib/mongo/errors.rb +8 -9
  26. data/lib/mongo/gridfs/chunk.rb +66 -70
  27. data/lib/mongo/gridfs/grid_store.rb +406 -410
  28. data/lib/mongo/message/get_more_message.rb +8 -13
  29. data/lib/mongo/message/insert_message.rb +7 -11
  30. data/lib/mongo/message/kill_cursors_message.rb +7 -12
  31. data/lib/mongo/message/message.rb +58 -62
  32. data/lib/mongo/message/message_header.rb +19 -24
  33. data/lib/mongo/message/msg_message.rb +5 -9
  34. data/lib/mongo/message/opcodes.rb +10 -15
  35. data/lib/mongo/message/query_message.rb +42 -46
  36. data/lib/mongo/message/remove_message.rb +8 -12
  37. data/lib/mongo/message/update_message.rb +9 -13
  38. data/lib/mongo/query.rb +84 -88
  39. data/lib/mongo/types/binary.rb +13 -17
  40. data/lib/mongo/types/code.rb +9 -13
  41. data/lib/mongo/types/dbref.rb +10 -14
  42. data/lib/mongo/types/objectid.rb +103 -107
  43. data/lib/mongo/types/regexp_of_holding.rb +18 -22
  44. data/lib/mongo/types/undefined.rb +7 -10
  45. data/lib/mongo/util/bson.rb +4 -9
  46. data/lib/mongo/util/xml_to_ruby.rb +1 -3
  47. data/mongo-ruby-driver.gemspec +33 -32
  48. data/{tests → test}/mongo-qa/_common.rb +1 -1
  49. data/{tests → test}/mongo-qa/admin +1 -1
  50. data/{tests → test}/mongo-qa/capped +1 -1
  51. data/{tests → test}/mongo-qa/count1 +4 -4
  52. data/{tests → test}/mongo-qa/dbs +1 -1
  53. data/{tests → test}/mongo-qa/find +1 -1
  54. data/{tests → test}/mongo-qa/find1 +1 -1
  55. data/{tests → test}/mongo-qa/gridfs_in +2 -2
  56. data/{tests → test}/mongo-qa/gridfs_out +2 -2
  57. data/{tests → test}/mongo-qa/indices +2 -2
  58. data/{tests → test}/mongo-qa/remove +1 -1
  59. data/{tests → test}/mongo-qa/stress1 +1 -1
  60. data/{tests → test}/mongo-qa/test1 +1 -1
  61. data/{tests → test}/mongo-qa/update +1 -1
  62. data/{tests → test}/test_admin.rb +3 -3
  63. data/{tests → test}/test_bson.rb +4 -4
  64. data/{tests → test}/test_byte_buffer.rb +0 -0
  65. data/{tests → test}/test_chunk.rb +4 -4
  66. data/{tests → test}/test_collection.rb +42 -4
  67. data/{tests/test_mongo.rb → test/test_connection.rb} +35 -11
  68. data/test/test_cursor.rb +223 -0
  69. data/{tests → test}/test_db.rb +12 -12
  70. data/{tests → test}/test_db_api.rb +28 -33
  71. data/{tests → test}/test_db_connection.rb +3 -3
  72. data/{tests → test}/test_grid_store.rb +4 -4
  73. data/{tests → test}/test_message.rb +1 -1
  74. data/{tests → test}/test_objectid.rb +3 -3
  75. data/{tests → test}/test_ordered_hash.rb +0 -0
  76. data/{tests → test}/test_round_trip.rb +6 -2
  77. data/{tests → test}/test_threading.rb +3 -3
  78. data/test/test_xgen.rb +73 -0
  79. metadata +33 -32
  80. data/lib/mongo/mongo.rb +0 -164
  81. data/tests/test_cursor.rb +0 -121
data/lib/mongo/admin.rb CHANGED
@@ -16,72 +16,68 @@
16
16
 
17
17
  require 'mongo/util/ordered_hash'
18
18
 
19
- module XGen
20
- module Mongo
21
- module Driver
19
+ module Mongo
22
20
 
23
- # Provide administrative database methods: those having to do with
24
- # profiling and validation.
25
- class Admin
21
+ # Provide administrative database methods: those having to do with
22
+ # profiling and validation.
23
+ class Admin
26
24
 
27
- def initialize(db)
28
- @db = db
29
- end
30
-
31
- # Return the current database profiling level.
32
- def profiling_level
33
- oh = OrderedHash.new
34
- oh[:profile] = -1
35
- doc = @db.db_command(oh)
36
- raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc) && doc['was'].kind_of?(Numeric)
37
- case doc['was'].to_i
38
- when 0
39
- :off
40
- when 1
41
- :slow_only
42
- when 2
43
- :all
44
- else
45
- raise "Error: illegal profiling level value #{doc['was']}"
46
- end
47
- end
25
+ def initialize(db)
26
+ @db = db
27
+ end
48
28
 
49
- # Set database profiling level to :off, :slow_only, or :all.
50
- def profiling_level=(level)
51
- oh = OrderedHash.new
52
- oh[:profile] = case level
53
- when :off
54
- 0
55
- when :slow_only
56
- 1
57
- when :all
58
- 2
59
- else
60
- raise "Error: illegal profiling level value #{level}"
61
- end
62
- doc = @db.db_command(oh)
63
- raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc)
64
- end
29
+ # Return the current database profiling level.
30
+ def profiling_level
31
+ oh = OrderedHash.new
32
+ oh[:profile] = -1
33
+ doc = @db.db_command(oh)
34
+ raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc) && doc['was'].kind_of?(Numeric)
35
+ case doc['was'].to_i
36
+ when 0
37
+ :off
38
+ when 1
39
+ :slow_only
40
+ when 2
41
+ :all
42
+ else
43
+ raise "Error: illegal profiling level value #{doc['was']}"
44
+ end
45
+ end
65
46
 
66
- # Return an array contining current profiling information from the
67
- # database.
68
- def profiling_info
69
- @db.query(Collection.new(@db, DB::SYSTEM_PROFILE_COLLECTION), Query.new({})).to_a
70
- end
47
+ # Set database profiling level to :off, :slow_only, or :all.
48
+ def profiling_level=(level)
49
+ oh = OrderedHash.new
50
+ oh[:profile] = case level
51
+ when :off
52
+ 0
53
+ when :slow_only
54
+ 1
55
+ when :all
56
+ 2
57
+ else
58
+ raise "Error: illegal profiling level value #{level}"
59
+ end
60
+ doc = @db.db_command(oh)
61
+ raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc)
62
+ end
71
63
 
72
- # Validate a named collection by raising an exception if there is a
73
- # problem or returning an interesting hash (see especially the
74
- # 'result' string value) if all is well.
75
- def validate_collection(name)
76
- doc = @db.db_command(:validate => name)
77
- raise "Error with validate command: #{doc.inspect}" unless @db.ok?(doc)
78
- result = doc['result']
79
- raise "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
80
- raise "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
81
- doc
82
- end
64
+ # Return an array contining current profiling information from the
65
+ # database.
66
+ def profiling_info
67
+ @db.query(Collection.new(@db, DB::SYSTEM_PROFILE_COLLECTION), Query.new({})).to_a
68
+ end
83
69
 
84
- end
70
+ # Validate a named collection by raising an exception if there is a
71
+ # problem or returning an interesting hash (see especially the
72
+ # 'result' string value) if all is well.
73
+ def validate_collection(name)
74
+ doc = @db.db_command(:validate => name)
75
+ raise "Error with validate command: #{doc.inspect}" unless @db.ok?(doc)
76
+ result = doc['result']
77
+ raise "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
78
+ raise "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
79
+ doc
85
80
  end
81
+
86
82
  end
87
83
  end
@@ -16,273 +16,318 @@
16
16
 
17
17
  require 'mongo/query'
18
18
 
19
- module XGen
20
- module Mongo
21
- module Driver
22
-
23
- # A named collection of records in a database.
24
- class Collection
25
-
26
- attr_reader :db, :name, :hint
27
-
28
- def initialize(db, name)
29
- case name
30
- when Symbol, String
31
- else
32
- raise TypeError, "new_name must be a string or symbol"
33
- end
34
-
35
- name = name.to_s
36
-
37
- if name.empty? or name.include? ".."
38
- raise InvalidName, "collection names cannot be empty"
39
- end
40
- if name.include? "$" and not name.match(/^\$cmd/)
41
- raise InvalidName, "collection names must not contain '$'"
42
- end
43
- if name.match(/^\./) or name.match(/\.$/)
44
- raise InvalidName, "collection names must not start or end with '.'"
45
- end
46
-
47
- @db, @name = db, name
48
- @hint = nil
49
- end
19
+ module Mongo
50
20
 
51
- # Get a sub-collection of this collection by name.
52
- #
53
- # Raises InvalidName if an invalid collection name is used.
54
- #
55
- # :name :: the name of the collection to get
56
- def [](name)
57
- name = "#{self.name}.#{name}"
58
- return Collection.new(self, name) if !db.strict? || db.collection_names.include?(name)
59
- raise "Collection #{name} doesn't exist. Currently in strict mode."
60
- end
21
+ # A named collection of records in a database.
22
+ class Collection
61
23
 
62
- # Set hint fields to use and return +self+. hint may be a single field
63
- # name, array of field names, or a hash (preferably an OrderedHash).
64
- # May be +nil+.
65
- def hint=(hint)
66
- @hint = normalize_hint_fields(hint)
67
- self
68
- end
24
+ attr_reader :db, :name, :hint
69
25
 
70
- # Return records that match a +selector+ hash. See Mongo docs for
71
- # details.
72
- #
73
- # Options:
74
- # :fields :: Array of collection field names; only those will be returned (plus _id if defined)
75
- # :offset :: Start at this record when returning records
76
- # :limit :: Maximum number of records to return
77
- # :sort :: Either hash of field names as keys and 1/-1 as values; 1 ==
78
- # ascending, -1 == descending, or array of field names (all
79
- # assumed to be sorted in ascending order).
80
- # :hint :: See #hint. This option overrides the collection-wide value.
81
- # :snapshot :: If true, snapshot mode will be used for this query.
82
- # Snapshot mode assures no duplicates are returned, or
83
- # objects missed, which were preset at both the start and
84
- # end of the query's execution. For details see
85
- # http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
86
- def find(selector={}, options={})
87
- fields = options.delete(:fields)
88
- fields = nil if fields && fields.empty?
89
- offset = options.delete(:offset) || 0
90
- limit = options.delete(:limit) || 0
91
- sort = options.delete(:sort)
92
- hint = options.delete(:hint)
93
- snapshot = options.delete(:snapshot)
94
- if hint
95
- hint = normalize_hint_fields(hint)
96
- else
97
- hint = @hint # assumed to be normalized already
98
- end
99
- raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty?
100
- @db.query(self, Query.new(selector, fields, offset, limit, sort, hint, snapshot))
101
- end
26
+ def initialize(db, name)
27
+ case name
28
+ when Symbol, String
29
+ else
30
+ raise TypeError, "new_name must be a string or symbol"
31
+ end
102
32
 
103
- # Get a single object from the database.
104
- #
105
- # Raises TypeError if the argument is of an improper type. Returns a
106
- # single document (hash), or nil if no result is found.
107
- #
108
- # :spec_or_object_id :: a hash specifying elements which must be
109
- # present for a document to be included in the result set OR an
110
- # instance of ObjectID to be used as the value for an _id query.
111
- # if nil an empty spec, {}, will be used.
112
- # :options :: options, as passed to Collection#find
113
- def find_one(spec_or_object_id=nil, options={})
114
- spec = case spec_or_object_id
115
- when nil
116
- {}
117
- when ObjectID
118
- {:_id => spec_or_object_id}
119
- when Hash
120
- spec_or_object_id
121
- else
122
- raise TypeError, "spec_or_object_id must be an instance of ObjectID or Hash, or nil"
123
- end
124
- find(spec, options.merge(:limit => -1)).next_object
125
- end
33
+ name = name.to_s
126
34
 
127
- # DEPRECATED - use find_one instead
128
- #
129
- # Find the first record that matches +selector+. See #find.
130
- def find_first(selector={}, options={})
131
- warn "Collection#find_first is deprecated and will be removed. Please use Collection#find_one instead."
132
- find_one(selector, options)
133
- end
35
+ if name.empty? or name.include? ".."
36
+ raise InvalidName, "collection names cannot be empty"
37
+ end
38
+ if name.include? "$" and not name.match(/^\$cmd/)
39
+ raise InvalidName, "collection names must not contain '$'"
40
+ end
41
+ if name.match(/^\./) or name.match(/\.$/)
42
+ raise InvalidName, "collection names must not start or end with '.'"
43
+ end
134
44
 
135
- # Save a document in this collection.
136
- #
137
- # If +to_save+ already has an '_id' then an update (upsert) operation
138
- # is performed and any existing document with that _id is overwritten.
139
- # Otherwise an insert operation is performed. Returns the _id of the
140
- # saved document.
141
- #
142
- # :to_save :: the document (a hash) to be saved
143
- #
144
- # Options:
145
- # :safe :: if true, check that the save succeeded. OperationFailure
146
- # will be raised on an error. Checking for safety requires an extra
147
- # round-trip to the database
148
- def save(to_save, options={})
149
- if id = to_save[:_id] || to_save['_id']
150
- update({:_id => id}, to_save, :upsert => true, :safe => options.delete(:safe))
151
- id
152
- else
153
- insert(to_save, :safe => options.delete(:safe))
154
- end
155
- end
45
+ @db, @name = db, name
46
+ @hint = nil
47
+ end
156
48
 
157
- # Insert a document(s) into this collection.
158
- #
159
- # "<<" is aliased to this method. Returns the _id of the inserted
160
- # document or a list of _ids of the inserted documents. The object(s)
161
- # may have been modified by the database's PK factory, if it has one.
162
- #
163
- # :doc_or_docs :: a document (as a hash) or Array of documents to be
164
- # inserted
165
- #
166
- # Options:
167
- # :safe :: if true, check that the insert succeeded. OperationFailure
168
- # will be raised on an error. Checking for safety requires an extra
169
- # round-trip to the database
170
- def insert(doc_or_docs, options={})
171
- doc_or_docs = [doc_or_docs] if !doc_or_docs.is_a?(Array)
172
- res = @db.insert_into_db(@name, doc_or_docs)
173
- if options.delete(:safe)
174
- error = @db.error
175
- if error
176
- raise OperationFailure, error
177
- end
178
- end
179
- res.size > 1 ? res : res.first
180
- end
181
- alias_method :<<, :insert
49
+ # Get a sub-collection of this collection by name.
50
+ #
51
+ # Raises InvalidName if an invalid collection name is used.
52
+ #
53
+ # :name :: the name of the collection to get
54
+ def [](name)
55
+ name = "#{self.name}.#{name}"
56
+ return Collection.new(self, name) if !db.strict? || db.collection_names.include?(name)
57
+ raise "Collection #{name} doesn't exist. Currently in strict mode."
58
+ end
182
59
 
183
- # Remove the records that match +selector+.
184
- def remove(selector={})
185
- @db.remove_from_db(@name, selector)
186
- end
60
+ # Set hint fields to use and return +self+. hint may be a single field
61
+ # name, array of field names, or a hash (preferably an OrderedHash).
62
+ # May be +nil+.
63
+ def hint=(hint)
64
+ @hint = normalize_hint_fields(hint)
65
+ self
66
+ end
187
67
 
188
- # Remove all records.
189
- def clear
190
- remove({})
191
- end
68
+ # Query the database.
69
+ #
70
+ # The +selector+ argument is a prototype document that all results must
71
+ # match. For example:
72
+ #
73
+ # collection.find({"hello" => "world"})
74
+ #
75
+ # only matches documents that have a key "hello" with value "world".
76
+ # Matches can have other keys *in addition* to "hello".
77
+ #
78
+ # If given an optional block +find+ will yield a Cursor to that block,
79
+ # close the cursor, and then return nil. This guarantees that partially
80
+ # evaluated cursors will be closed. If given no block +find+ returns a
81
+ # cursor.
82
+ #
83
+ # :selector :: A document (hash) specifying elements which must be
84
+ # present for a document to be included in the result set.
85
+ #
86
+ # Options:
87
+ # :fields :: Array of field names that should be returned in the result
88
+ # set ("_id" will always be included). By limiting results
89
+ # to a certain subset of fields you can cut down on network
90
+ # traffic and decoding time.
91
+ # :offset :: Start at this record when returning records
92
+ # :limit :: Maximum number of records to return
93
+ # :sort :: Either hash of field names as keys and 1/-1 as values; 1 ==
94
+ # ascending, -1 == descending, or array of field names (all
95
+ # assumed to be sorted in ascending order).
96
+ # :hint :: See #hint. This option overrides the collection-wide value.
97
+ # :snapshot :: If true, snapshot mode will be used for this query.
98
+ # Snapshot mode assures no duplicates are returned, or
99
+ # objects missed, which were preset at both the start and
100
+ # end of the query's execution. For details see
101
+ # http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
102
+ def find(selector={}, options={})
103
+ fields = options.delete(:fields)
104
+ fields = ["_id"] if fields && fields.empty?
105
+ offset = options.delete(:offset) || 0
106
+ limit = options.delete(:limit) || 0
107
+ sort = options.delete(:sort)
108
+ hint = options.delete(:hint)
109
+ snapshot = options.delete(:snapshot)
110
+ if hint
111
+ hint = normalize_hint_fields(hint)
112
+ else
113
+ hint = @hint # assumed to be normalized already
114
+ end
115
+ raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty?
116
+
117
+ cursor = @db.query(self, Query.new(selector, fields, offset, limit, sort, hint, snapshot))
118
+ if block_given?
119
+ yield cursor
120
+ cursor.close()
121
+ nil
122
+ else
123
+ cursor
124
+ end
125
+ end
192
126
 
193
- # DEPRECATED - use update(... :upsert => true) instead
194
- #
195
- # Update records that match +selector+ by applying +obj+ as an update.
196
- # If no match, inserts (???).
197
- def repsert(selector, obj)
198
- warn "Collection#repsert is deprecated and will be removed. Please use Collection#update instead."
199
- update(selector, obj, :upsert => true)
200
- end
127
+ # Get a single object from the database.
128
+ #
129
+ # Raises TypeError if the argument is of an improper type. Returns a
130
+ # single document (hash), or nil if no result is found.
131
+ #
132
+ # :spec_or_object_id :: a hash specifying elements which must be
133
+ # present for a document to be included in the result set OR an
134
+ # instance of ObjectID to be used as the value for an _id query.
135
+ # if nil an empty spec, {}, will be used.
136
+ # :options :: options, as passed to Collection#find
137
+ def find_one(spec_or_object_id=nil, options={})
138
+ spec = case spec_or_object_id
139
+ when nil
140
+ {}
141
+ when ObjectID
142
+ {:_id => spec_or_object_id}
143
+ when Hash
144
+ spec_or_object_id
145
+ else
146
+ raise TypeError, "spec_or_object_id must be an instance of ObjectID or Hash, or nil"
147
+ end
148
+ find(spec, options.merge(:limit => -1)).next_object
149
+ end
201
150
 
202
- # DEPRECATED - use update(... :upsert => false) instead
203
- #
204
- # Update records that match +selector+ by applying +obj+ as an update.
205
- def replace(selector, obj)
206
- warn "Collection#replace is deprecated and will be removed. Please use Collection#update instead."
207
- update(selector, obj)
208
- end
151
+ # DEPRECATED - use find_one instead
152
+ #
153
+ # Find the first record that matches +selector+. See #find.
154
+ def find_first(selector={}, options={})
155
+ warn "Collection#find_first is deprecated and will be removed. Please use Collection#find_one instead."
156
+ find_one(selector, options)
157
+ end
209
158
 
210
- # DEPRECATED - use update(... :upsert => false) instead
211
- #
212
- # Update records that match +selector+ by applying +obj+ as an update.
213
- # Both +selector+ and +modifier_obj+ are required.
214
- def modify(selector, modifier_obj)
215
- warn "Collection#modify is deprecated and will be removed. Please use Collection#update instead."
216
- update(selector, modifier_obj)
217
- end
159
+ # Save a document in this collection.
160
+ #
161
+ # If +to_save+ already has an '_id' then an update (upsert) operation
162
+ # is performed and any existing document with that _id is overwritten.
163
+ # Otherwise an insert operation is performed. Returns the _id of the
164
+ # saved document.
165
+ #
166
+ # :to_save :: the document (a hash) to be saved
167
+ #
168
+ # Options:
169
+ # :safe :: if true, check that the save succeeded. OperationFailure
170
+ # will be raised on an error. Checking for safety requires an extra
171
+ # round-trip to the database
172
+ def save(to_save, options={})
173
+ if id = to_save[:_id] || to_save['_id']
174
+ update({:_id => id}, to_save, :upsert => true, :safe => options.delete(:safe))
175
+ id
176
+ else
177
+ insert(to_save, :safe => options.delete(:safe))
178
+ end
179
+ end
218
180
 
219
- # Update a document(s) in this collection.
220
- #
221
- # :spec :: a hash specifying elements which must be present for
222
- # a document to be updated
223
- # :document :: a hash specifying the fields to be changed in the
224
- # selected document(s), or (in the case of an upsert) the document to
225
- # be inserted
226
- #
227
- # Options:
228
- # :upsert :: if true, perform an upsert operation
229
- # :safe :: if true, check that the update succeeded. OperationFailure
230
- # will be raised on an error. Checking for safety requires an extra
231
- # round-trip to the database
232
- def update(spec, document, options={})
233
- upsert = options.delete(:upsert)
234
- safe = options.delete(:safe)
235
-
236
- if upsert
237
- @db.repsert_in_db(@name, spec, document)
238
- else
239
- @db.replace_in_db(@name, spec, document)
240
- end
241
- if safe
242
- error = @db.error
243
- if error
244
- raise OperationFailure, error
245
- end
246
- end
181
+ # Insert a document(s) into this collection.
182
+ #
183
+ # "<<" is aliased to this method. Returns the _id of the inserted
184
+ # document or a list of _ids of the inserted documents. The object(s)
185
+ # may have been modified by the database's PK factory, if it has one.
186
+ #
187
+ # :doc_or_docs :: a document (as a hash) or Array of documents to be
188
+ # inserted
189
+ #
190
+ # Options:
191
+ # :safe :: if true, check that the insert succeeded. OperationFailure
192
+ # will be raised on an error. Checking for safety requires an extra
193
+ # round-trip to the database
194
+ def insert(doc_or_docs, options={})
195
+ doc_or_docs = [doc_or_docs] if !doc_or_docs.is_a?(Array)
196
+ res = @db.insert_into_db(@name, doc_or_docs)
197
+ if options.delete(:safe)
198
+ error = @db.error
199
+ if error
200
+ raise OperationFailure, error
247
201
  end
202
+ end
203
+ res.size > 1 ? res : res.first
204
+ end
205
+ alias_method :<<, :insert
248
206
 
249
- # Create a new index. +field_or_spec+
250
- # should be either a single field name or a Array of [field name,
251
- # direction] pairs. Directions should be specified as
252
- # XGen::Mongo::ASCENDING or XGen::Mongo::DESCENDING.
253
- # +unique+ is an optional boolean indicating whether this index
254
- # should enforce a uniqueness constraint.
255
- def create_index(field_or_spec, unique=false)
256
- @db.create_index(@name, field_or_spec, unique)
257
- end
207
+ # Remove the records that match +selector+.
208
+ def remove(selector={})
209
+ @db.remove_from_db(@name, selector)
210
+ end
258
211
 
259
- # Drop index +name+.
260
- def drop_index(name)
261
- @db.drop_index(@name, name)
262
- end
212
+ # Remove all records.
213
+ def clear
214
+ remove({})
215
+ end
263
216
 
264
- # Drop all indexes.
265
- def drop_indexes
266
- # just need to call drop indexes with no args; will drop them all
267
- @db.drop_index(@name, '*')
268
- end
217
+ # DEPRECATED - use update(... :upsert => true) instead
218
+ #
219
+ # Update records that match +selector+ by applying +obj+ as an update.
220
+ # If no match, inserts (???).
221
+ def repsert(selector, obj)
222
+ warn "Collection#repsert is deprecated and will be removed. Please use Collection#update instead."
223
+ update(selector, obj, :upsert => true)
224
+ end
225
+
226
+ # DEPRECATED - use update(... :upsert => false) instead
227
+ #
228
+ # Update records that match +selector+ by applying +obj+ as an update.
229
+ def replace(selector, obj)
230
+ warn "Collection#replace is deprecated and will be removed. Please use Collection#update instead."
231
+ update(selector, obj)
232
+ end
269
233
 
270
- # Drop the entire collection. USE WITH CAUTION.
271
- def drop
272
- @db.drop_collection(@name)
234
+ # DEPRECATED - use update(... :upsert => false) instead
235
+ #
236
+ # Update records that match +selector+ by applying +obj+ as an update.
237
+ # Both +selector+ and +modifier_obj+ are required.
238
+ def modify(selector, modifier_obj)
239
+ warn "Collection#modify is deprecated and will be removed. Please use Collection#update instead."
240
+ update(selector, modifier_obj)
241
+ end
242
+
243
+ # Update a document(s) in this collection.
244
+ #
245
+ # :spec :: a hash specifying elements which must be present for
246
+ # a document to be updated
247
+ # :document :: a hash specifying the fields to be changed in the
248
+ # selected document(s), or (in the case of an upsert) the document to
249
+ # be inserted
250
+ #
251
+ # Options:
252
+ # :upsert :: if true, perform an upsert operation
253
+ # :safe :: if true, check that the update succeeded. OperationFailure
254
+ # will be raised on an error. Checking for safety requires an extra
255
+ # round-trip to the database
256
+ def update(spec, document, options={})
257
+ upsert = options.delete(:upsert)
258
+ safe = options.delete(:safe)
259
+
260
+ if upsert
261
+ @db.repsert_in_db(@name, spec, document)
262
+ else
263
+ @db.replace_in_db(@name, spec, document)
264
+ end
265
+ if safe
266
+ error = @db.error
267
+ if error
268
+ raise OperationFailure, error
273
269
  end
270
+ end
271
+ end
272
+
273
+ # Create a new index. +field_or_spec+
274
+ # should be either a single field name or a Array of [field name,
275
+ # direction] pairs. Directions should be specified as
276
+ # Mongo::ASCENDING or Mongo::DESCENDING.
277
+ # +unique+ is an optional boolean indicating whether this index
278
+ # should enforce a uniqueness constraint.
279
+ def create_index(field_or_spec, unique=false)
280
+ @db.create_index(@name, field_or_spec, unique)
281
+ end
282
+
283
+ # Drop index +name+.
284
+ def drop_index(name)
285
+ @db.drop_index(@name, name)
286
+ end
274
287
 
275
- # Perform a query similar to an SQL group by operation.
276
- #
277
- # Returns an array of grouped items.
278
- #
279
- # :keys :: list of fields to group by
280
- # :condition :: specification of rows to be considered (as a 'find'
281
- # query specification)
282
- # :initial :: initial value of the aggregation counter object
283
- # :reduce :: aggregation function as a JavaScript string
284
- def group(keys, condition, initial, reduce)
285
- group_function = <<EOS
288
+ # Drop all indexes.
289
+ def drop_indexes
290
+ # just need to call drop indexes with no args; will drop them all
291
+ @db.drop_index(@name, '*')
292
+ end
293
+
294
+ # Drop the entire collection. USE WITH CAUTION.
295
+ def drop
296
+ @db.drop_collection(@name)
297
+ end
298
+
299
+ # Perform a query similar to an SQL group by operation.
300
+ #
301
+ # Returns an array of grouped items.
302
+ #
303
+ # :keys :: Array of fields to group by
304
+ # :condition :: specification of rows to be considered (as a 'find'
305
+ # query specification)
306
+ # :initial :: initial value of the aggregation counter object
307
+ # :reduce :: aggregation function as a JavaScript string
308
+ # :command :: if true, run the group as a command instead of in an
309
+ # eval - it is likely that this option will eventually be
310
+ # deprecated and all groups will be run as commands
311
+ def group(keys, condition, initial, reduce, command=false)
312
+ if command
313
+ hash = {}
314
+ keys.each do |k|
315
+ hash[k] = 1
316
+ end
317
+ result = @db.db_command({"group" =>
318
+ {
319
+ "ns" => @name,
320
+ "$reduce" => Code.new(reduce),
321
+ "key" => hash,
322
+ "cond" => condition,
323
+ "initial" => initial}})
324
+ if result["ok"] == 1
325
+ return result["retval"]
326
+ else
327
+ raise OperationFailure, "group command failed: #{result['errmsg']}"
328
+ end
329
+ end
330
+ group_function = <<EOS
286
331
  function () {
287
332
  var c = db[ns].find(condition);
288
333
  var map = new Map();
@@ -291,8 +336,9 @@ function () {
291
336
  var obj = c.next();
292
337
 
293
338
  var key = {};
294
- for (var i in keys) {
295
- key[keys[i]] = obj[keys[i]];
339
+ for (var i = 0; i < keys.length; i++) {
340
+ var k = keys[i];
341
+ key[k] = obj[k];
296
342
  }
297
343
 
298
344
  var aggObj = map.get(key);
@@ -306,83 +352,85 @@ function () {
306
352
  return {"result": map.values()};
307
353
  }
308
354
  EOS
309
- return @db.eval(Code.new(group_function,
310
- {
311
- "ns" => @name,
312
- "keys" => keys,
313
- "condition" => condition,
314
- "initial" => initial
315
- }))["result"]
316
- end
355
+ return @db.eval(Code.new(group_function,
356
+ {
357
+ "ns" => @name,
358
+ "keys" => keys,
359
+ "condition" => condition,
360
+ "initial" => initial
361
+ }))["result"]
362
+ end
317
363
 
318
- # Rename this collection.
319
- #
320
- # If operating in auth mode, client must be authorized as an admin to
321
- # perform this operation. Raises +InvalidName+ if +new_name+ is an invalid
322
- # collection name.
323
- #
324
- # :new_name :: new name for this collection
325
- def rename(new_name)
326
- case new_name
327
- when Symbol, String
328
- else
329
- raise TypeError, "new_name must be a string or symbol"
330
- end
331
-
332
- new_name = new_name.to_s
333
-
334
- if new_name.empty? or new_name.include? ".."
335
- raise InvalidName, "collection names cannot be empty"
336
- end
337
- if new_name.include? "$"
338
- raise InvalidName, "collection names must not contain '$'"
339
- end
340
- if new_name.match(/^\./) or new_name.match(/\.$/)
341
- raise InvalidName, "collection names must not start or end with '.'"
342
- end
343
-
344
- @db.rename_collection(@name, new_name)
345
- end
364
+ # Rename this collection.
365
+ #
366
+ # If operating in auth mode, client must be authorized as an admin to
367
+ # perform this operation. Raises +InvalidName+ if +new_name+ is an invalid
368
+ # collection name.
369
+ #
370
+ # :new_name :: new name for this collection
371
+ def rename(new_name)
372
+ case new_name
373
+ when Symbol, String
374
+ else
375
+ raise TypeError, "new_name must be a string or symbol"
376
+ end
346
377
 
347
- # Get information on the indexes for the collection +collection_name+.
348
- # Returns a hash where the keys are index names (as returned by
349
- # Collection#create_index and the values are lists of [key, direction]
350
- # pairs specifying the index (as passed to Collection#create_index).
351
- def index_information
352
- @db.index_information(@name)
353
- end
378
+ new_name = new_name.to_s
354
379
 
355
- # Return a hash containing options that apply to this collection.
356
- # 'create' will be the collection name. For the other possible keys
357
- # and values, see DB#create_collection.
358
- def options
359
- @db.collections_info(@name).next_object()['options']
360
- end
380
+ if new_name.empty? or new_name.include? ".."
381
+ raise InvalidName, "collection names cannot be empty"
382
+ end
383
+ if new_name.include? "$"
384
+ raise InvalidName, "collection names must not contain '$'"
385
+ end
386
+ if new_name.match(/^\./) or new_name.match(/\.$/)
387
+ raise InvalidName, "collection names must not start or end with '.'"
388
+ end
361
389
 
362
- # Return the number of records that match +selector+. If +selector+ is
363
- # +nil+ or an empty hash, returns the count of all records.
364
- def count(selector={})
365
- @db.count(@name, selector || {})
366
- end
390
+ @db.rename_collection(@name, new_name)
391
+ end
367
392
 
368
- protected
369
-
370
- def normalize_hint_fields(hint)
371
- case hint
372
- when String
373
- {hint => 1}
374
- when Hash
375
- hint
376
- when nil
377
- nil
378
- else
379
- h = OrderedHash.new
380
- hint.to_a.each { |k| h[k] = 1 }
381
- h
382
- end
383
- end
393
+ # Get information on the indexes for the collection +collection_name+.
394
+ # Returns a hash where the keys are index names (as returned by
395
+ # Collection#create_index and the values are lists of [key, direction]
396
+ # pairs specifying the index (as passed to Collection#create_index).
397
+ def index_information
398
+ @db.index_information(@name)
399
+ end
400
+
401
+ # Return a hash containing options that apply to this collection.
402
+ # 'create' will be the collection name. For the other possible keys
403
+ # and values, see DB#create_collection.
404
+ def options
405
+ @db.collections_info(@name).next_object()['options']
406
+ end
407
+
408
+ # Get the number of documents in this collection.
409
+ #
410
+ # Specifying a +selector+ is DEPRECATED and will be removed. Please use
411
+ # find(selector).count() instead.
412
+ def count(selector=nil)
413
+ if selector
414
+ warn "specifying a selector for Collection#count is deprecated and will be removed. Please use Collection.find(selector).count instead."
415
+ end
416
+ find(selector || {}).count()
417
+ end
418
+
419
+ protected
420
+
421
+ def normalize_hint_fields(hint)
422
+ case hint
423
+ when String
424
+ {hint => 1}
425
+ when Hash
426
+ hint
427
+ when nil
428
+ nil
429
+ else
430
+ h = OrderedHash.new
431
+ hint.to_a.each { |k| h[k] = 1 }
432
+ h
384
433
  end
385
434
  end
386
435
  end
387
436
  end
388
-