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.
- data/README.rdoc +12 -12
- data/Rakefile +1 -1
- data/bin/bson_benchmark.rb +1 -1
- data/bin/mongo_console +3 -3
- data/bin/run_test_script +2 -2
- data/bin/standard_benchmark +3 -3
- data/examples/admin.rb +3 -3
- data/examples/benchmarks.rb +2 -2
- data/examples/blog.rb +4 -4
- data/examples/capped.rb +3 -3
- data/examples/cursor.rb +3 -3
- data/examples/gridfs.rb +4 -4
- data/examples/index_test.rb +11 -11
- data/examples/info.rb +3 -3
- data/examples/queries.rb +3 -3
- data/examples/simple.rb +3 -3
- data/examples/strict.rb +3 -3
- data/examples/types.rb +4 -9
- data/lib/mongo.rb +35 -3
- data/lib/mongo/admin.rb +56 -60
- data/lib/mongo/collection.rb +368 -320
- data/lib/mongo/connection.rb +166 -0
- data/lib/mongo/cursor.rb +206 -209
- data/lib/mongo/db.rb +478 -489
- data/lib/mongo/errors.rb +8 -9
- data/lib/mongo/gridfs/chunk.rb +66 -70
- data/lib/mongo/gridfs/grid_store.rb +406 -410
- data/lib/mongo/message/get_more_message.rb +8 -13
- data/lib/mongo/message/insert_message.rb +7 -11
- data/lib/mongo/message/kill_cursors_message.rb +7 -12
- data/lib/mongo/message/message.rb +58 -62
- data/lib/mongo/message/message_header.rb +19 -24
- data/lib/mongo/message/msg_message.rb +5 -9
- data/lib/mongo/message/opcodes.rb +10 -15
- data/lib/mongo/message/query_message.rb +42 -46
- data/lib/mongo/message/remove_message.rb +8 -12
- data/lib/mongo/message/update_message.rb +9 -13
- data/lib/mongo/query.rb +84 -88
- data/lib/mongo/types/binary.rb +13 -17
- data/lib/mongo/types/code.rb +9 -13
- data/lib/mongo/types/dbref.rb +10 -14
- data/lib/mongo/types/objectid.rb +103 -107
- data/lib/mongo/types/regexp_of_holding.rb +18 -22
- data/lib/mongo/types/undefined.rb +7 -10
- data/lib/mongo/util/bson.rb +4 -9
- data/lib/mongo/util/xml_to_ruby.rb +1 -3
- data/mongo-ruby-driver.gemspec +33 -32
- data/{tests → test}/mongo-qa/_common.rb +1 -1
- data/{tests → test}/mongo-qa/admin +1 -1
- data/{tests → test}/mongo-qa/capped +1 -1
- data/{tests → test}/mongo-qa/count1 +4 -4
- data/{tests → test}/mongo-qa/dbs +1 -1
- data/{tests → test}/mongo-qa/find +1 -1
- data/{tests → test}/mongo-qa/find1 +1 -1
- data/{tests → test}/mongo-qa/gridfs_in +2 -2
- data/{tests → test}/mongo-qa/gridfs_out +2 -2
- data/{tests → test}/mongo-qa/indices +2 -2
- data/{tests → test}/mongo-qa/remove +1 -1
- data/{tests → test}/mongo-qa/stress1 +1 -1
- data/{tests → test}/mongo-qa/test1 +1 -1
- data/{tests → test}/mongo-qa/update +1 -1
- data/{tests → test}/test_admin.rb +3 -3
- data/{tests → test}/test_bson.rb +4 -4
- data/{tests → test}/test_byte_buffer.rb +0 -0
- data/{tests → test}/test_chunk.rb +4 -4
- data/{tests → test}/test_collection.rb +42 -4
- data/{tests/test_mongo.rb → test/test_connection.rb} +35 -11
- data/test/test_cursor.rb +223 -0
- data/{tests → test}/test_db.rb +12 -12
- data/{tests → test}/test_db_api.rb +28 -33
- data/{tests → test}/test_db_connection.rb +3 -3
- data/{tests → test}/test_grid_store.rb +4 -4
- data/{tests → test}/test_message.rb +1 -1
- data/{tests → test}/test_objectid.rb +3 -3
- data/{tests → test}/test_ordered_hash.rb +0 -0
- data/{tests → test}/test_round_trip.rb +6 -2
- data/{tests → test}/test_threading.rb +3 -3
- data/test/test_xgen.rb +73 -0
- metadata +33 -32
- data/lib/mongo/mongo.rb +0 -164
- 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
|
20
|
-
module Mongo
|
21
|
-
module Driver
|
19
|
+
module Mongo
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
# Provide administrative database methods: those having to do with
|
22
|
+
# profiling and validation.
|
23
|
+
class Admin
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
data/lib/mongo/collection.rb
CHANGED
@@ -16,273 +16,318 @@
|
|
16
16
|
|
17
17
|
require 'mongo/query'
|
18
18
|
|
19
|
-
module
|
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
|
-
|
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
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
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
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
212
|
+
# Remove all records.
|
213
|
+
def clear
|
214
|
+
remove({})
|
215
|
+
end
|
263
216
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
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
|
295
|
-
|
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
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
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
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
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
|
-
|
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
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
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
|
-
|
363
|
-
|
364
|
-
def count(selector={})
|
365
|
-
@db.count(@name, selector || {})
|
366
|
-
end
|
390
|
+
@db.rename_collection(@name, new_name)
|
391
|
+
end
|
367
392
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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
|
-
|