mongodb-mongo 0.12 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
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
-