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