jonbell-mongo 1.3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +190 -0
- data/README.md +333 -0
- data/Rakefile +215 -0
- data/bin/mongo_console +21 -0
- data/docs/CREDITS.md +123 -0
- data/docs/FAQ.md +116 -0
- data/docs/GridFS.md +158 -0
- data/docs/HISTORY.md +263 -0
- data/docs/RELEASES.md +33 -0
- data/docs/REPLICA_SETS.md +72 -0
- data/docs/TUTORIAL.md +247 -0
- data/docs/WRITE_CONCERN.md +28 -0
- data/lib/mongo.rb +97 -0
- data/lib/mongo/collection.rb +895 -0
- data/lib/mongo/connection.rb +926 -0
- data/lib/mongo/cursor.rb +474 -0
- data/lib/mongo/db.rb +617 -0
- data/lib/mongo/exceptions.rb +71 -0
- data/lib/mongo/gridfs/grid.rb +107 -0
- data/lib/mongo/gridfs/grid_ext.rb +57 -0
- data/lib/mongo/gridfs/grid_file_system.rb +146 -0
- data/lib/mongo/gridfs/grid_io.rb +485 -0
- data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
- data/lib/mongo/repl_set_connection.rb +356 -0
- data/lib/mongo/util/conversions.rb +89 -0
- data/lib/mongo/util/core_ext.rb +60 -0
- data/lib/mongo/util/pool.rb +177 -0
- data/lib/mongo/util/server_version.rb +71 -0
- data/lib/mongo/util/support.rb +82 -0
- data/lib/mongo/util/uri_parser.rb +185 -0
- data/mongo.gemspec +34 -0
- data/test/auxillary/1.4_features.rb +166 -0
- data/test/auxillary/authentication_test.rb +68 -0
- data/test/auxillary/autoreconnect_test.rb +41 -0
- data/test/auxillary/fork_test.rb +30 -0
- data/test/auxillary/repl_set_auth_test.rb +58 -0
- data/test/auxillary/slave_connection_test.rb +36 -0
- data/test/auxillary/threaded_authentication_test.rb +101 -0
- data/test/bson/binary_test.rb +15 -0
- data/test/bson/bson_test.rb +654 -0
- data/test/bson/byte_buffer_test.rb +208 -0
- data/test/bson/hash_with_indifferent_access_test.rb +38 -0
- data/test/bson/json_test.rb +17 -0
- data/test/bson/object_id_test.rb +154 -0
- data/test/bson/ordered_hash_test.rb +210 -0
- data/test/bson/timestamp_test.rb +24 -0
- data/test/collection_test.rb +910 -0
- data/test/connection_test.rb +324 -0
- data/test/conversions_test.rb +119 -0
- data/test/cursor_fail_test.rb +75 -0
- data/test/cursor_message_test.rb +43 -0
- data/test/cursor_test.rb +483 -0
- data/test/db_api_test.rb +738 -0
- data/test/db_connection_test.rb +15 -0
- data/test/db_test.rb +315 -0
- data/test/grid_file_system_test.rb +259 -0
- data/test/grid_io_test.rb +209 -0
- data/test/grid_test.rb +258 -0
- data/test/load/thin/load.rb +24 -0
- data/test/load/unicorn/load.rb +23 -0
- data/test/replica_sets/connect_test.rb +112 -0
- data/test/replica_sets/connection_string_test.rb +32 -0
- data/test/replica_sets/count_test.rb +35 -0
- data/test/replica_sets/insert_test.rb +53 -0
- data/test/replica_sets/pooled_insert_test.rb +55 -0
- data/test/replica_sets/query_secondaries.rb +108 -0
- data/test/replica_sets/query_test.rb +51 -0
- data/test/replica_sets/replication_ack_test.rb +66 -0
- data/test/replica_sets/rs_test_helper.rb +27 -0
- data/test/safe_test.rb +68 -0
- data/test/support/hash_with_indifferent_access.rb +186 -0
- data/test/support/keys.rb +45 -0
- data/test/support_test.rb +18 -0
- data/test/test_helper.rb +102 -0
- data/test/threading/threading_with_large_pool_test.rb +90 -0
- data/test/threading_test.rb +87 -0
- data/test/tools/auth_repl_set_manager.rb +14 -0
- data/test/tools/repl_set_manager.rb +266 -0
- data/test/unit/collection_test.rb +130 -0
- data/test/unit/connection_test.rb +85 -0
- data/test/unit/cursor_test.rb +109 -0
- data/test/unit/db_test.rb +94 -0
- data/test/unit/grid_test.rb +49 -0
- data/test/unit/pool_test.rb +9 -0
- data/test/unit/repl_set_connection_test.rb +59 -0
- data/test/unit/safe_test.rb +125 -0
- data/test/uri_test.rb +91 -0
- metadata +224 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
# Replica Sets in Ruby
|
2
|
+
|
3
|
+
Here follow a few considerations for those using the MongoDB Ruby driver with [replica sets](http://www.mongodb.org/display/DOCS/Replica+Sets).
|
4
|
+
|
5
|
+
### Setup
|
6
|
+
|
7
|
+
First, make sure that you've configured and initialized a replica set.
|
8
|
+
|
9
|
+
Use `ReplSetConnection.new` to connect to a replica set. This method, which accepts a variable number of arugments,
|
10
|
+
takes a list of seed nodes followed by any connection options. You'll want to specify at least two seed nodes. This gives
|
11
|
+
the driver more chances to connect in the event that any one seed node is offline. Once the driver connects, it will
|
12
|
+
cache the replica set topology as reported by the given seed node and use that information if a failover is later required.
|
13
|
+
|
14
|
+
@connection = ReplSetConnection.new(['n1.mydb.net', 27017], ['n2.mydb.net', 27017], ['n3.mydb.net', 27017])
|
15
|
+
|
16
|
+
### Read slaves
|
17
|
+
|
18
|
+
If you want to read from a seconday node, you can pass :read_secondary => true to ReplSetConnection#new.
|
19
|
+
|
20
|
+
@connection = ReplSetConnection.new(['n1.mydb.net', 27017], ['n2.mydb.net', 27017], ['n3.mydb.net', 27017],
|
21
|
+
:read_secondary => true)
|
22
|
+
|
23
|
+
A random secondary will be chosen to be read from. In a typical multi-process Ruby application, you'll have a good distribution of reads across secondary nodes.
|
24
|
+
|
25
|
+
### Connection Failures
|
26
|
+
|
27
|
+
Imagine that either the master node or one of the read nodes goes offline. How will the driver respond?
|
28
|
+
|
29
|
+
If any read operation fails, the driver will raise a *ConnectionFailure* exception. It then becomes the client's responsibility to decide how to handle this.
|
30
|
+
|
31
|
+
If the client decides to retry, it's not guaranteed that another member of the replica set will have been promoted to master right away, so it's still possible that the driver will raise another *ConnectionFailure*. However, once a member has been promoted to master, typically within a few seconds, subsequent operations will succeed.
|
32
|
+
|
33
|
+
The driver will essentially cycle through all known seed addresses until a node identifies itself as master.
|
34
|
+
|
35
|
+
### Recovery
|
36
|
+
|
37
|
+
Driver users may wish to wrap their database calls with failure recovery code. Here's one possibility, which will attempt to connection
|
38
|
+
every half second and time out after thirty seconds.
|
39
|
+
|
40
|
+
# Ensure retry upon failure
|
41
|
+
def rescue_connection_failure(max_retries=60)
|
42
|
+
retries = 0
|
43
|
+
begin
|
44
|
+
yield
|
45
|
+
rescue Mongo::ConnectionFailure => ex
|
46
|
+
retries += 1
|
47
|
+
raise ex if retries > max_retries
|
48
|
+
sleep(0.5)
|
49
|
+
retry
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Wrapping a call to #count()
|
54
|
+
rescue_connection_failure do
|
55
|
+
@db.collection('users').count()
|
56
|
+
end
|
57
|
+
|
58
|
+
Of course, the proper way to handle connection failures will always depend on the individual application. We encourage object-mapper and application developers to publish any promising results.
|
59
|
+
|
60
|
+
### Testing
|
61
|
+
|
62
|
+
The Ruby driver (>= 1.1.5) includes unit tests for verifying replica set behavior. They reside in *tests/replica_sets*. You can run them as a group with the following rake task:
|
63
|
+
|
64
|
+
rake test:rs
|
65
|
+
|
66
|
+
The suite will set up a five-node replica set by itself and ensure that driver behaves correctly even in the face
|
67
|
+
of individual node failures. Note that the `mongod` executable must be in the search path for this to work.
|
68
|
+
|
69
|
+
### Further Reading
|
70
|
+
|
71
|
+
* [Replica Sets](http://www.mongodb.org/display/DOCS/Replica+Set+Configuration)
|
72
|
+
* [Replics Set Configuration](http://www.mongodb.org/display/DOCS/Replica+Set+Configuration)
|
data/docs/TUTORIAL.md
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
# MongoDB Ruby Driver Tutorial
|
2
|
+
|
3
|
+
This tutorial gives many common examples of using MongoDB with the Ruby driver. If you're looking for information on data modeling, see [MongoDB Data Modeling and Rails](http://www.mongodb.org/display/DOCS/MongoDB+Data+Modeling+and+Rails). Links to the various object mappers are listed on our [object mappers page](http://www.mongodb.org/display/DOCS/Object+Mappers+for+Ruby+and+MongoDB).
|
4
|
+
|
5
|
+
Interested in GridFS? See [GridFS in Ruby](file.GridFS.html).
|
6
|
+
|
7
|
+
As always, the [latest source for the Ruby driver](http://github.com/mongodb/mongo-ruby-driver) can be found on [github](http://github.com/mongodb/mongo-ruby-driver/).
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
The mongo-ruby-driver gem is served through Rubygems.org. To install, make sure you have the latest version of rubygems.
|
12
|
+
gem update --system
|
13
|
+
Next, install the mongo rubygem:
|
14
|
+
gem install mongo
|
15
|
+
|
16
|
+
The required `bson` gem will be installed automatically.
|
17
|
+
|
18
|
+
For optimum performance, install the bson_ext gem:
|
19
|
+
|
20
|
+
gem install bson_ext
|
21
|
+
|
22
|
+
After installing, you may want to look at the [examples](http://github.com/mongodb/mongo-ruby-driver/tree/master/examples) directory included in the source distribution. These examples walk through some of the basics of using the Ruby driver.
|
23
|
+
|
24
|
+
## Getting started
|
25
|
+
|
26
|
+
#### Using the gem
|
27
|
+
|
28
|
+
All of the code here assumes that you have already executed the following Ruby code:
|
29
|
+
|
30
|
+
require 'rubygems' # not necessary for Ruby 1.9
|
31
|
+
require 'mongo'
|
32
|
+
|
33
|
+
#### Making a Connection
|
34
|
+
|
35
|
+
An `Mongo::Connection` instance represents a connection to MongoDB. You use a Connection instance to obtain an Mongo:DB instance, which represents a named database. The database doesn't have to exist - if it doesn't, MongoDB will create it for you.
|
36
|
+
|
37
|
+
You can optionally specify the MongoDB server address and port when connecting. The following example shows three ways to connect to the database "mydb" on the local machine:
|
38
|
+
|
39
|
+
db = Mongo::Connection.new.db("mydb")
|
40
|
+
db = Mongo::Connection.new("localhost").db("mydb")
|
41
|
+
db = Mongo::Connection.new("localhost", 27017).db("mydb")
|
42
|
+
|
43
|
+
At this point, the `db` object will be a connection to a MongoDB server for the specified database. Each DB instance uses a separate socket connection to the server.
|
44
|
+
|
45
|
+
If you're trying to connect to a replica set, see [Replica Sets in Ruby](http://www.mongodb.org/display/DOCS/Replica+Sets+in+Ruby).
|
46
|
+
|
47
|
+
#### Listing All Databases
|
48
|
+
|
49
|
+
connection = Mongo::Connection.new # (optional host/port args)
|
50
|
+
connection.database_names.each { |name| puts name }
|
51
|
+
connection.database_info.each { |info| puts info.inspect}
|
52
|
+
|
53
|
+
#### Dropping a Database
|
54
|
+
connection.drop_database('database_name')
|
55
|
+
|
56
|
+
MongoDB can be run in a secure mode where access to databases is controlled through name and password authentication. When run in this mode, any client application must provide a name and password before doing any operations. In the Ruby driver, you simply do the following with the connected mongo object:
|
57
|
+
|
58
|
+
auth = db.authenticate(my_user_name, my_password)
|
59
|
+
|
60
|
+
If the name and password are valid for the database, `auth` will be `true`. Otherwise, it will be `false`. You should look at the MongoDB log for further information if available.
|
61
|
+
|
62
|
+
#### Getting a List Of Collections
|
63
|
+
|
64
|
+
Each database has zero or more collections. You can retrieve a list of them from the db (and print out any that are there):
|
65
|
+
|
66
|
+
db.collection_names.each { |name| puts name }
|
67
|
+
|
68
|
+
and assuming that there are two collections, name and address, in the database, you would see
|
69
|
+
|
70
|
+
name
|
71
|
+
address
|
72
|
+
|
73
|
+
as the output.
|
74
|
+
|
75
|
+
#### Getting a Collection
|
76
|
+
|
77
|
+
You can get a collection to use using the `collection` method:
|
78
|
+
coll = db.collection("testCollection")
|
79
|
+
This is aliased to the \[\] method:
|
80
|
+
coll = db["testCollection"]
|
81
|
+
|
82
|
+
Once you have this collection object, you can now do things like insert data, query for data, etc.
|
83
|
+
|
84
|
+
#### Inserting a Document
|
85
|
+
|
86
|
+
Once you have the collection object, you can insert documents into the collection. For example, lets make a little document that in JSON would be represented as
|
87
|
+
|
88
|
+
{
|
89
|
+
"name" : "MongoDB",
|
90
|
+
"type" : "database",
|
91
|
+
"count" : 1,
|
92
|
+
"info" : {
|
93
|
+
x : 203,
|
94
|
+
y : 102
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
Notice that the above has an "inner" document embedded within it. To do this, we can use a Hash or the driver's OrderedHash (which preserves key order) to create the document (including the inner document), and then just simply insert it into the collection using the `insert()` method.
|
99
|
+
|
100
|
+
doc = {"name" => "MongoDB", "type" => "database", "count" => 1,
|
101
|
+
"info" => {"x" => 203, "y" => '102'`
|
102
|
+
coll.insert(doc)
|
103
|
+
|
104
|
+
#### Updating a Document
|
105
|
+
|
106
|
+
We can update the previous document using the `update` method. There are a couple ways to update a document. We can rewrite it:
|
107
|
+
|
108
|
+
doc["name"] = "MongoDB Ruby"
|
109
|
+
coll.update({"_id" => doc["_id"]}, doc)
|
110
|
+
|
111
|
+
Or we can use an atomic operator to change a single value:
|
112
|
+
|
113
|
+
coll.update({"_id" => doc["_id"]}, {"$set" => {"name" => "MongoDB Ruby"`)
|
114
|
+
|
115
|
+
Read [more about updating documents|Updating].
|
116
|
+
|
117
|
+
#### Finding the First Document In a Collection using `find_one()`
|
118
|
+
|
119
|
+
To show that the document we inserted in the previous step is there, we can do a simple `find_one()` operation to get the first document in the collection. This method returns a single document (rather than the `Cursor` that the `find()` operation returns).
|
120
|
+
|
121
|
+
my_doc = coll.find_one()
|
122
|
+
puts my_doc.inspect
|
123
|
+
|
124
|
+
and you should see:
|
125
|
+
|
126
|
+
{"_id"=>#<BSON::ObjectID:0x118576c ...>, "name"=>"MongoDB",
|
127
|
+
"info"=>{"x"=>203, "y"=>102}, "type"=>"database", "count"=>1}
|
128
|
+
|
129
|
+
Note the `\_id` element has been added automatically by MongoDB to your document.
|
130
|
+
|
131
|
+
#### Adding Multiple Documents
|
132
|
+
|
133
|
+
To demonstrate some more interesting queries, let's add multiple simple documents to the collection. These documents will have the following form:
|
134
|
+
{
|
135
|
+
"i" : value
|
136
|
+
}
|
137
|
+
|
138
|
+
Here's how to insert them:
|
139
|
+
|
140
|
+
100.times { |i| coll.insert("i" => i) }
|
141
|
+
|
142
|
+
Notice that we can insert documents of different "shapes" into the same collection. These records are in the same collection as the complex record we inserted above. This aspect is what we mean when we say that MongoDB is "schema-free".
|
143
|
+
|
144
|
+
#### Counting Documents in a Collection
|
145
|
+
|
146
|
+
Now that we've inserted 101 documents (the 100 we did in the loop, plus the first one), we can check to see if we have them all using the `count()` method.
|
147
|
+
|
148
|
+
puts coll.count()
|
149
|
+
|
150
|
+
and it should print `101`.
|
151
|
+
|
152
|
+
#### Using a Cursor to get all of the Documents
|
153
|
+
|
154
|
+
To get all the documents from the collection, we use the `find()` method. `find()` returns a `Cursor` object, which allows us to iterate over the set of documents that matches our query. The Ruby driver's Cursor implemented Enumerable, which allows us to use `Enumerable#each`, `Enumerable#map}, etc. For instance:
|
155
|
+
|
156
|
+
coll.find().each { |row| puts row.inspect }
|
157
|
+
|
158
|
+
and that should print all 101 documents in the collection.
|
159
|
+
|
160
|
+
#### Getting a Single Document with a Query
|
161
|
+
|
162
|
+
We can create a _query_ hash to pass to the `find()` method to get a subset of the documents in our collection. For example, if we wanted to find the document for which the value of the "i" field is 71, we would do the following ;
|
163
|
+
|
164
|
+
coll.find("i" => 71).each { |row| puts row.inspect }
|
165
|
+
|
166
|
+
and it should just print just one document:
|
167
|
+
|
168
|
+
{"_id"=>#<BSON::ObjectID:0x117de90 ...>, "i"=>71}
|
169
|
+
|
170
|
+
#### Getting a Set of Documents With a Query
|
171
|
+
|
172
|
+
We can use the query to get a set of documents from our collection. For example, if we wanted to get all documents where "i" > 50, we could write:
|
173
|
+
|
174
|
+
coll.find("i" => {"$gt" => 50}).each { |row| puts row }
|
175
|
+
|
176
|
+
which should print the documents where i > 50. We could also get a range, say 20 < i <= 30:
|
177
|
+
|
178
|
+
coll.find("i" => {"$gt" => 20, "$lte" => 30}).each { |row| puts row }
|
179
|
+
|
180
|
+
#### Selecting a subset of fields for a query
|
181
|
+
|
182
|
+
Use the `:fields` option. If you just want fields "a" and "b":
|
183
|
+
|
184
|
+
coll.find("i" => {"$gt" => 50}, :fields => ["a", "b"]).each { |row| puts row }
|
185
|
+
|
186
|
+
#### Querying with Regular Expressions
|
187
|
+
|
188
|
+
Regular expressions can be used to query MongoDB. To find all names that begin with 'a':
|
189
|
+
|
190
|
+
coll.find({"name" => /^a/})
|
191
|
+
|
192
|
+
You can also construct a regular expression dynamically. To match a given search string:
|
193
|
+
|
194
|
+
search_string = params['search']
|
195
|
+
|
196
|
+
# Constructor syntax
|
197
|
+
coll.find({"name" => Regexp.new(search_string)})
|
198
|
+
|
199
|
+
# Literal syntax
|
200
|
+
coll.find({"name" => /#{search_string}/})
|
201
|
+
|
202
|
+
Although MongoDB isn't vulnerable to anything like SQL-injection, it may be worth checking the search string for anything malicious.
|
203
|
+
|
204
|
+
## Indexing
|
205
|
+
|
206
|
+
#### Creating An Index
|
207
|
+
|
208
|
+
MongoDB supports indexes, and they are very easy to add on a collection. To create an index, you specify an index name and an array of field names to be indexed, or a single field name. The following creates an ascending index on the "i" field:
|
209
|
+
|
210
|
+
# create_index assumes ascending order; see method docs
|
211
|
+
# for details
|
212
|
+
coll.create_index("i")
|
213
|
+
To specify complex indexes or a descending index you need to use a slightly more complex syntax - the index specifier must be an Array of [field name, direction] pairs. Directions should be specified as Mongo::ASCENDING or Mongo::DESCENDING:
|
214
|
+
|
215
|
+
# Explicit "ascending"
|
216
|
+
coll.create_index([["i", Mongo::ASCENDING]])
|
217
|
+
|
218
|
+
#### Creating and querying on a geospatial index
|
219
|
+
|
220
|
+
First, create the index on a field containing long-lat values:
|
221
|
+
|
222
|
+
people.create_index([["loc", Mongo::GEO2D]])
|
223
|
+
|
224
|
+
Then get a list of the twenty locations nearest to the point 50, 50:
|
225
|
+
|
226
|
+
people.find({"loc" => {"$near" => [50, 50]}}, {:limit => 20}).each do |p|
|
227
|
+
puts p.inspect
|
228
|
+
end
|
229
|
+
|
230
|
+
#### Getting a List of Indexes on a Collection
|
231
|
+
|
232
|
+
You can get a list of the indexes on a collection using `coll.index_information()`.
|
233
|
+
|
234
|
+
## Database Administration
|
235
|
+
|
236
|
+
A database can have one of three profiling levels: off (:off), slow queries only (:slow_only), or all (:all). To see the database level:
|
237
|
+
|
238
|
+
puts db.profiling_level # => off (the symbol :off printed as a string)
|
239
|
+
db.profiling_level = :slow_only
|
240
|
+
|
241
|
+
Validating a collection will return an interesting hash if all is well or raise an exception if there is a problem.
|
242
|
+
p db.validate_collection('coll_name')
|
243
|
+
|
244
|
+
## See Also
|
245
|
+
|
246
|
+
* [MongoDB Koans](http://github.com/chicagoruby/MongoDB_Koans) A path to MongoDB enlightenment via the Ruby driver.
|
247
|
+
* [MongoDB Manual](http://www.mongodb.org/display/DOCS/Developer+Zone)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Write Concern in Ruby
|
2
|
+
|
3
|
+
## Setting the write concern
|
4
|
+
|
5
|
+
Write concern is set using the `:safe` option. There are several possible options:
|
6
|
+
|
7
|
+
@collection.save({:doc => 'foo'}, :safe => true)
|
8
|
+
@collection.save({:doc => 'foo'}, :safe => {:w => 2})
|
9
|
+
@collection.save({:doc => 'foo'}, :safe => {:w => 2, :wtimeout => 200})
|
10
|
+
@collection.save({:doc => 'foo'}, :safe => {:w => 2, :wtimeout => 200, :fsync => true})
|
11
|
+
|
12
|
+
The first, `true`, simply indicates that we should request a response from the server to ensure that to errors have occurred. The second, `{:w => 2}`forces the server to wait until at least two servers have recorded the write. The third does the same but will time out if the replication can't be completed in 200 milliseconds. The fourth forces an fsync on each server being written to (note: this option is rarely necessary and will have a dramaticly negative effect on performance).
|
13
|
+
|
14
|
+
## Write concern inheritance
|
15
|
+
|
16
|
+
The Ruby driver allows you to set write concern on each of four levels: the connection, database, collection, and write operation.
|
17
|
+
Objects will inherit the default write concern from their parents. Thus, if you set a write concern of `{:w => 1}` when creating
|
18
|
+
a new connection, then all databases and collections created from that connection will inherit the same setting. See this code example:
|
19
|
+
|
20
|
+
@con = Mongo::Connection.new('localhost', 27017, :safe => {:w => 2})
|
21
|
+
@db = @con['test']
|
22
|
+
@collection = @db['foo']
|
23
|
+
@collection.save({:name => 'foo'})
|
24
|
+
|
25
|
+
@collection.save({:name => 'bar'}, :safe => false)
|
26
|
+
|
27
|
+
Here, the first call to Collection#save will use the inherited write concern, `{:w => 2}`. But notice that the second call
|
28
|
+
to Collection#save overrides this setting.
|
data/lib/mongo.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# --
|
4
|
+
# Copyright (C) 2008-2011 10gen Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ++
|
18
|
+
|
19
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
20
|
+
|
21
|
+
module Mongo
|
22
|
+
VERSION = "1.3.1"
|
23
|
+
end
|
24
|
+
|
25
|
+
module Mongo
|
26
|
+
ASCENDING = 1
|
27
|
+
DESCENDING = -1
|
28
|
+
GEO2D = '2d'
|
29
|
+
|
30
|
+
DEFAULT_MAX_BSON_SIZE = 4 * 1024 * 1024
|
31
|
+
|
32
|
+
module Constants
|
33
|
+
OP_REPLY = 1
|
34
|
+
OP_MSG = 1000
|
35
|
+
OP_UPDATE = 2001
|
36
|
+
OP_INSERT = 2002
|
37
|
+
OP_QUERY = 2004
|
38
|
+
OP_GET_MORE = 2005
|
39
|
+
OP_DELETE = 2006
|
40
|
+
OP_KILL_CURSORS = 2007
|
41
|
+
|
42
|
+
OP_QUERY_TAILABLE = 2 ** 1
|
43
|
+
OP_QUERY_SLAVE_OK = 2 ** 2
|
44
|
+
OP_QUERY_OPLOG_REPLAY = 2 ** 3
|
45
|
+
OP_QUERY_NO_CURSOR_TIMEOUT = 2 ** 4
|
46
|
+
OP_QUERY_AWAIT_DATA = 2 ** 5
|
47
|
+
OP_QUERY_EXHAUST = 2 ** 6
|
48
|
+
|
49
|
+
REPLY_CURSOR_NOT_FOUND = 2 ** 0
|
50
|
+
REPLY_QUERY_FAILURE = 2 ** 1
|
51
|
+
REPLY_SHARD_CONFIG_STALE = 2 ** 2
|
52
|
+
REPLY_AWAIT_CAPABLE = 2 ** 3
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
require 'bson'
|
57
|
+
|
58
|
+
require 'mongo/util/conversions'
|
59
|
+
require 'mongo/util/support'
|
60
|
+
require 'mongo/util/core_ext'
|
61
|
+
require 'mongo/util/pool'
|
62
|
+
require 'mongo/util/server_version'
|
63
|
+
require 'mongo/util/uri_parser'
|
64
|
+
|
65
|
+
require 'mongo/collection'
|
66
|
+
require 'mongo/connection'
|
67
|
+
require 'mongo/repl_set_connection'
|
68
|
+
require 'mongo/cursor'
|
69
|
+
require 'mongo/db'
|
70
|
+
require 'mongo/exceptions'
|
71
|
+
require 'mongo/gridfs/grid_ext'
|
72
|
+
require 'mongo/gridfs/grid'
|
73
|
+
require 'mongo/gridfs/grid_io'
|
74
|
+
if RUBY_PLATFORM =~ /java/
|
75
|
+
require 'mongo/gridfs/grid_io_fix'
|
76
|
+
end
|
77
|
+
require 'mongo/gridfs/grid_file_system'
|
78
|
+
|
79
|
+
# Use SystemTimer on Ruby 1.8
|
80
|
+
if !defined?(RUBY_ENGINE) || (RUBY_ENGINE == 'ruby' && RUBY_VERSION < '1.9.0')
|
81
|
+
begin
|
82
|
+
require 'system_timer'
|
83
|
+
if SystemTimer.method(:timeout).arity.abs != 2
|
84
|
+
raise LoadError
|
85
|
+
end
|
86
|
+
Mongo::TimeoutHandler = SystemTimer
|
87
|
+
rescue LoadError
|
88
|
+
warn "Could not load SystemTimer >= v1.2.0. Falling back to timeout.rb. " +
|
89
|
+
"SystemTimer is STRONGLY recommended for timeouts in Ruby 1.8.7. " +
|
90
|
+
"See http://ph7spot.com/blog/system-timer-1-2-release for details."
|
91
|
+
require 'timeout'
|
92
|
+
Mongo::TimeoutHandler = Timeout
|
93
|
+
end
|
94
|
+
else
|
95
|
+
require 'timeout'
|
96
|
+
Mongo::TimeoutHandler = Timeout
|
97
|
+
end
|
@@ -0,0 +1,895 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# --
|
4
|
+
# Copyright (C) 2008-2011 10gen Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
module Mongo
|
19
|
+
|
20
|
+
# A named collection of documents in a database.
|
21
|
+
class Collection
|
22
|
+
|
23
|
+
attr_reader :db, :name, :pk_factory, :hint, :safe
|
24
|
+
|
25
|
+
# Initialize a collection object.
|
26
|
+
#
|
27
|
+
# @param [String, Symbol] name the name of the collection.
|
28
|
+
# @param [DB] db a MongoDB database instance.
|
29
|
+
#
|
30
|
+
# @option opts [:create_pk] :pk (BSON::ObjectId) A primary key factory to use
|
31
|
+
# other than the default BSON::ObjectId.
|
32
|
+
#
|
33
|
+
# @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
|
34
|
+
# for insert, update, and remove method called on this Collection instance. If no
|
35
|
+
# value is provided, the default value set on this instance's DB will be used. This
|
36
|
+
# default can be overridden for any invocation of insert, update, or remove.
|
37
|
+
#
|
38
|
+
# @raise [InvalidNSName]
|
39
|
+
# if collection name is empty, contains '$', or starts or ends with '.'
|
40
|
+
#
|
41
|
+
# @raise [TypeError]
|
42
|
+
# if collection name is not a string or symbol
|
43
|
+
#
|
44
|
+
# @return [Collection]
|
45
|
+
#
|
46
|
+
# @core collections constructor_details
|
47
|
+
def initialize(name, db, opts={})
|
48
|
+
if db.is_a?(String) && name.is_a?(Mongo::DB)
|
49
|
+
warn "Warning: the order of parameters to initialize a collection have changed. " +
|
50
|
+
"Please specify the collection name first, followed by the db."
|
51
|
+
db, name = name, db
|
52
|
+
end
|
53
|
+
|
54
|
+
case name
|
55
|
+
when Symbol, String
|
56
|
+
else
|
57
|
+
raise TypeError, "new_name must be a string or symbol"
|
58
|
+
end
|
59
|
+
|
60
|
+
name = name.to_s
|
61
|
+
|
62
|
+
if name.empty? or name.include? ".."
|
63
|
+
raise Mongo::InvalidNSName, "collection names cannot be empty"
|
64
|
+
end
|
65
|
+
if name.include? "$"
|
66
|
+
raise Mongo::InvalidNSName, "collection names must not contain '$'" unless name =~ /((^\$cmd)|(oplog\.\$main))/
|
67
|
+
end
|
68
|
+
if name.match(/^\./) or name.match(/\.$/)
|
69
|
+
raise Mongo::InvalidNSName, "collection names must not start or end with '.'"
|
70
|
+
end
|
71
|
+
|
72
|
+
if opts.respond_to?(:create_pk) || !opts.is_a?(Hash)
|
73
|
+
warn "The method for specifying a primary key factory on a Collection has changed.\n" +
|
74
|
+
"Please specify it as an option (e.g., :pk => PkFactory)."
|
75
|
+
pk_factory = opts
|
76
|
+
else
|
77
|
+
pk_factory = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
@db, @name = db, name
|
81
|
+
@connection = @db.connection
|
82
|
+
@cache_time = @db.cache_time
|
83
|
+
@cache = Hash.new(0)
|
84
|
+
unless pk_factory
|
85
|
+
@safe = opts.fetch(:safe, @db.safe)
|
86
|
+
end
|
87
|
+
@pk_factory = pk_factory || opts[:pk] || BSON::ObjectId
|
88
|
+
@hint = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return a sub-collection of this collection by name. If 'users' is a collection, then
|
92
|
+
# 'users.comments' is a sub-collection of users.
|
93
|
+
#
|
94
|
+
# @param [String, Symbol] name
|
95
|
+
# the collection to return
|
96
|
+
#
|
97
|
+
# @raise [Mongo::InvalidNSName]
|
98
|
+
# if passed an invalid collection name
|
99
|
+
#
|
100
|
+
# @return [Collection]
|
101
|
+
# the specified sub-collection
|
102
|
+
def [](name)
|
103
|
+
name = "#{self.name}.#{name}"
|
104
|
+
return Collection.new(name, db) if !db.strict? ||
|
105
|
+
db.collection_names.include?(name.to_s)
|
106
|
+
raise "Collection #{name} doesn't exist. Currently in strict mode."
|
107
|
+
end
|
108
|
+
|
109
|
+
# Set a hint field for query optimizer. Hint may be a single field
|
110
|
+
# name, array of field names, or a hash (preferably an [OrderedHash]).
|
111
|
+
# If using MongoDB > 1.1, you probably don't ever need to set a hint.
|
112
|
+
#
|
113
|
+
# @param [String, Array, OrderedHash] hint a single field, an array of
|
114
|
+
# fields, or a hash specifying fields
|
115
|
+
def hint=(hint=nil)
|
116
|
+
@hint = normalize_hint_fields(hint)
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
# Query the database.
|
121
|
+
#
|
122
|
+
# The +selector+ argument is a prototype document that all results must
|
123
|
+
# match. For example:
|
124
|
+
#
|
125
|
+
# collection.find({"hello" => "world"})
|
126
|
+
#
|
127
|
+
# only matches documents that have a key "hello" with value "world".
|
128
|
+
# Matches can have other keys *in addition* to "hello".
|
129
|
+
#
|
130
|
+
# If given an optional block +find+ will yield a Cursor to that block,
|
131
|
+
# close the cursor, and then return nil. This guarantees that partially
|
132
|
+
# evaluated cursors will be closed. If given no block +find+ returns a
|
133
|
+
# cursor.
|
134
|
+
#
|
135
|
+
# @param [Hash] selector
|
136
|
+
# a document specifying elements which must be present for a
|
137
|
+
# document to be included in the result set. Note that in rare cases,
|
138
|
+
# (e.g., with $near queries), the order of keys will matter. To preserve
|
139
|
+
# key order on a selector, use an instance of BSON::OrderedHash (only applies
|
140
|
+
# to Ruby 1.8).
|
141
|
+
#
|
142
|
+
# @option opts [Array, Hash] :fields field names that should be returned in the result
|
143
|
+
# set ("_id" will be included unless explicity excluded). By limiting results to a certain subset of fields,
|
144
|
+
# you can cut down on network traffic and decoding time. If using a Hash, keys should be field
|
145
|
+
# names and values should be either 1 or 0, depending on whether you want to include or exclude
|
146
|
+
# the given field.
|
147
|
+
# @option opts [Integer] :skip number of documents to skip from the beginning of the result set
|
148
|
+
# @option opts [Integer] :limit maximum number of documents to return
|
149
|
+
# @option opts [Array] :sort an array of [key, direction] pairs to sort by. Direction should
|
150
|
+
# be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
|
151
|
+
# @option opts [String, Array, OrderedHash] :hint hint for query optimizer, usually not necessary if
|
152
|
+
# using MongoDB > 1.1
|
153
|
+
# @option opts [Boolean] :snapshot (false) if true, snapshot mode will be used for this query.
|
154
|
+
# Snapshot mode assures no duplicates are returned, or objects missed, which were preset at both the start and
|
155
|
+
# end of the query's execution.
|
156
|
+
# For details see http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
|
157
|
+
# @option opts [Boolean] :batch_size (100) the number of documents to returned by the database per
|
158
|
+
# GETMORE operation. A value of 0 will let the database server decide how many results to returns.
|
159
|
+
# This option can be ignored for most use cases.
|
160
|
+
# @option opts [Boolean] :timeout (true) when +true+, the returned cursor will be subject to
|
161
|
+
# the normal cursor timeout behavior of the mongod process. When +false+, the returned cursor will
|
162
|
+
# never timeout. Note that disabling timeout will only work when #find is invoked with a block.
|
163
|
+
# This is to prevent any inadvertant failure to close the cursor, as the cursor is explicitly
|
164
|
+
# closed when block code finishes.
|
165
|
+
# @option opts [Integer] :max_scan (nil) Limit the number of items to scan on both collection scans and indexed queries..
|
166
|
+
# @option opts [Boolean] :show_disk_loc (false) Return the disk location of each query result (for debugging).
|
167
|
+
# @option opts [Boolean] :return_key (false) Return the index key used to obtain the result (for debugging).
|
168
|
+
# @option opts [Block] :transformer (nil) a block for tranforming returned documents.
|
169
|
+
# This is normally used by object mappers to convert each returned document to an instance of a class.
|
170
|
+
#
|
171
|
+
# @raise [ArgumentError]
|
172
|
+
# if timeout is set to false and find is not invoked in a block
|
173
|
+
#
|
174
|
+
# @raise [RuntimeError]
|
175
|
+
# if given unknown options
|
176
|
+
#
|
177
|
+
# @core find find-instance_method
|
178
|
+
def find(selector={}, opts={})
|
179
|
+
opts = opts.dup
|
180
|
+
fields = opts.delete(:fields)
|
181
|
+
fields = ["_id"] if fields && fields.empty?
|
182
|
+
skip = opts.delete(:skip) || skip || 0
|
183
|
+
limit = opts.delete(:limit) || 0
|
184
|
+
sort = opts.delete(:sort)
|
185
|
+
hint = opts.delete(:hint)
|
186
|
+
snapshot = opts.delete(:snapshot)
|
187
|
+
batch_size = opts.delete(:batch_size)
|
188
|
+
timeout = (opts.delete(:timeout) == false) ? false : true
|
189
|
+
transformer = opts.delete(:transformer)
|
190
|
+
|
191
|
+
if timeout == false && !block_given?
|
192
|
+
raise ArgumentError, "Collection#find must be invoked with a block when timeout is disabled."
|
193
|
+
end
|
194
|
+
|
195
|
+
if hint
|
196
|
+
hint = normalize_hint_fields(hint)
|
197
|
+
else
|
198
|
+
hint = @hint # assumed to be normalized already
|
199
|
+
end
|
200
|
+
|
201
|
+
raise RuntimeError, "Unknown options [#{opts.inspect}]" unless opts.empty?
|
202
|
+
|
203
|
+
cursor = Cursor.new(self, {
|
204
|
+
:selector => selector,
|
205
|
+
:fields => fields,
|
206
|
+
:skip => skip,
|
207
|
+
:limit => limit,
|
208
|
+
:order => sort,
|
209
|
+
:hint => hint,
|
210
|
+
:snapshot => snapshot,
|
211
|
+
:timeout => timeout,
|
212
|
+
:batch_size => batch_size,
|
213
|
+
:transformer => transformer,
|
214
|
+
})
|
215
|
+
|
216
|
+
if block_given?
|
217
|
+
yield cursor
|
218
|
+
cursor.close
|
219
|
+
nil
|
220
|
+
else
|
221
|
+
cursor
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Return a single object from the database.
|
226
|
+
#
|
227
|
+
# @return [OrderedHash, Nil]
|
228
|
+
# a single document or nil if no result is found.
|
229
|
+
#
|
230
|
+
# @param [Hash, ObjectId, Nil] spec_or_object_id a hash specifying elements
|
231
|
+
# which must be present for a document to be included in the result set or an
|
232
|
+
# instance of ObjectId to be used as the value for an _id query.
|
233
|
+
# If nil, an empty selector, {}, will be used.
|
234
|
+
#
|
235
|
+
# @option opts [Hash]
|
236
|
+
# any valid options that can be send to Collection#find
|
237
|
+
#
|
238
|
+
# @raise [TypeError]
|
239
|
+
# if the argument is of an improper type.
|
240
|
+
def find_one(spec_or_object_id=nil, opts={})
|
241
|
+
spec = case spec_or_object_id
|
242
|
+
when nil
|
243
|
+
{}
|
244
|
+
when BSON::ObjectId
|
245
|
+
{:_id => spec_or_object_id}
|
246
|
+
when Hash
|
247
|
+
spec_or_object_id
|
248
|
+
else
|
249
|
+
raise TypeError, "spec_or_object_id must be an instance of ObjectId or Hash, or nil"
|
250
|
+
end
|
251
|
+
find(spec, opts.merge(:limit => -1)).next_document
|
252
|
+
end
|
253
|
+
|
254
|
+
# Save a document to this collection.
|
255
|
+
#
|
256
|
+
# @param [Hash] doc
|
257
|
+
# the document to be saved. If the document already has an '_id' key,
|
258
|
+
# then an update (upsert) operation will be performed, and any existing
|
259
|
+
# document with that _id is overwritten. Otherwise an insert operation is performed.
|
260
|
+
#
|
261
|
+
# @return [ObjectId] the _id of the saved document.
|
262
|
+
#
|
263
|
+
# @option opts [Boolean, Hash] :safe (+false+)
|
264
|
+
# run the operation in safe mode, which run a getlasterror command on the
|
265
|
+
# database to report any assertion. In addition, a hash can be provided to
|
266
|
+
# run an fsync and/or wait for replication of the save (>= 1.5.1). See the options
|
267
|
+
# for DB#error.
|
268
|
+
#
|
269
|
+
# @raise [OperationFailure] when :safe mode fails.
|
270
|
+
#
|
271
|
+
# @see DB#remove for options that can be passed to :safe.
|
272
|
+
def save(doc, opts={})
|
273
|
+
if doc.has_key?(:_id) || doc.has_key?('_id')
|
274
|
+
id = doc[:_id] || doc['_id']
|
275
|
+
update({:_id => id}, doc, :upsert => true, :safe => opts.fetch(:safe, @safe))
|
276
|
+
id
|
277
|
+
else
|
278
|
+
insert(doc, :safe => opts.fetch(:safe, @safe))
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Insert one or more documents into the collection.
|
283
|
+
#
|
284
|
+
# @param [Hash, Array] doc_or_docs
|
285
|
+
# a document (as a hash) or array of documents to be inserted.
|
286
|
+
#
|
287
|
+
# @return [ObjectId, Array]
|
288
|
+
# The _id of the inserted document or a list of _ids of all inserted documents.
|
289
|
+
#
|
290
|
+
# @option opts [Boolean, Hash] :safe (+false+)
|
291
|
+
# run the operation in safe mode, which run a getlasterror command on the
|
292
|
+
# database to report any assertion. In addition, a hash can be provided to
|
293
|
+
# run an fsync and/or wait for replication of the insert (>= 1.5.1). Safe
|
294
|
+
# options provided here will override any safe options set on this collection,
|
295
|
+
# its database object, or the current connection. See the options on
|
296
|
+
# for DB#get_last_error.
|
297
|
+
#
|
298
|
+
# @see DB#remove for options that can be passed to :safe.
|
299
|
+
#
|
300
|
+
# @core insert insert-instance_method
|
301
|
+
def insert(doc_or_docs, opts={})
|
302
|
+
doc_or_docs = [doc_or_docs] unless doc_or_docs.is_a?(Array)
|
303
|
+
doc_or_docs.collect! { |doc| @pk_factory.create_pk(doc) }
|
304
|
+
safe = opts.fetch(:safe, @safe)
|
305
|
+
result = insert_documents(doc_or_docs, @name, true, safe)
|
306
|
+
result.size > 1 ? result : result.first
|
307
|
+
end
|
308
|
+
alias_method :<<, :insert
|
309
|
+
|
310
|
+
# Remove all documents from this collection.
|
311
|
+
#
|
312
|
+
# @param [Hash] selector
|
313
|
+
# If specified, only matching documents will be removed.
|
314
|
+
#
|
315
|
+
# @option opts [Boolean, Hash] :safe (+false+)
|
316
|
+
# run the operation in safe mode, which will run a getlasterror command on the
|
317
|
+
# database to report any assertion. In addition, a hash can be provided to
|
318
|
+
# run an fsync and/or wait for replication of the remove (>= 1.5.1). Safe
|
319
|
+
# options provided here will override any safe options set on this collection,
|
320
|
+
# its database, or the current connection. See the options for DB#get_last_error for more details.
|
321
|
+
#
|
322
|
+
# @example remove all documents from the 'users' collection:
|
323
|
+
# users.remove
|
324
|
+
# users.remove({})
|
325
|
+
#
|
326
|
+
# @example remove only documents that have expired:
|
327
|
+
# users.remove({:expire => {"$lte" => Time.now}})
|
328
|
+
#
|
329
|
+
# @return [Hash, true] Returns a Hash containing the last error object if running in safe mode.
|
330
|
+
# Otherwise, returns true.
|
331
|
+
#
|
332
|
+
# @raise [Mongo::OperationFailure] an exception will be raised iff safe mode is enabled
|
333
|
+
# and the operation fails.
|
334
|
+
#
|
335
|
+
# @see DB#remove for options that can be passed to :safe.
|
336
|
+
#
|
337
|
+
# @core remove remove-instance_method
|
338
|
+
def remove(selector={}, opts={})
|
339
|
+
# Initial byte is 0.
|
340
|
+
safe = opts.fetch(:safe, @safe)
|
341
|
+
message = BSON::ByteBuffer.new("\0\0\0\0")
|
342
|
+
BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
|
343
|
+
message.put_int(0)
|
344
|
+
message.put_binary(BSON::BSON_CODER.serialize(selector, false, true).to_s)
|
345
|
+
|
346
|
+
@connection.instrument(:remove, :database => @db.name, :collection => @name, :selector => selector) do
|
347
|
+
if safe
|
348
|
+
@connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name, nil, safe)
|
349
|
+
else
|
350
|
+
@connection.send_message(Mongo::Constants::OP_DELETE, message)
|
351
|
+
true
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Update one or more documents in this collection.
|
357
|
+
#
|
358
|
+
# @param [Hash] selector
|
359
|
+
# a hash specifying elements which must be present for a document to be updated. Note:
|
360
|
+
# the update command currently updates only the first document matching the
|
361
|
+
# given selector. If you want all matching documents to be updated, be sure
|
362
|
+
# to specify :multi => true.
|
363
|
+
# @param [Hash] document
|
364
|
+
# a hash specifying the fields to be changed in the selected document,
|
365
|
+
# or (in the case of an upsert) the document to be inserted
|
366
|
+
#
|
367
|
+
# @option opts [Boolean] :upsert (+false+) if true, performs an upsert (update or insert)
|
368
|
+
# @option opts [Boolean] :multi (+false+) update all documents matching the selector, as opposed to
|
369
|
+
# just the first matching document. Note: only works in MongoDB 1.1.3 or later.
|
370
|
+
# @option opts [Boolean] :safe (+false+)
|
371
|
+
# If true, check that the save succeeded. OperationFailure
|
372
|
+
# will be raised on an error. Note that a safe check requires an extra
|
373
|
+
# round-trip to the database. Safe options provided here will override any safe
|
374
|
+
# options set on this collection, its database object, or the current collection.
|
375
|
+
# See the options for DB#get_last_error for details.
|
376
|
+
#
|
377
|
+
# @return [Hash, true] Returns a Hash containing the last error object if running in safe mode.
|
378
|
+
# Otherwise, returns true.
|
379
|
+
#
|
380
|
+
# @core update update-instance_method
|
381
|
+
def update(selector, document, opts={})
|
382
|
+
# Initial byte is 0.
|
383
|
+
safe = opts.fetch(:safe, @safe)
|
384
|
+
message = BSON::ByteBuffer.new("\0\0\0\0")
|
385
|
+
BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
|
386
|
+
update_options = 0
|
387
|
+
update_options += 1 if opts[:upsert]
|
388
|
+
update_options += 2 if opts[:multi]
|
389
|
+
message.put_int(update_options)
|
390
|
+
message.put_binary(BSON::BSON_CODER.serialize(selector, false, true).to_s)
|
391
|
+
message.put_binary(BSON::BSON_CODER.serialize(document, false, true).to_s)
|
392
|
+
|
393
|
+
@connection.instrument(:update, :database => @db.name, :collection => @name, :selector => selector, :document => document) do
|
394
|
+
if safe
|
395
|
+
@connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name, nil, safe)
|
396
|
+
else
|
397
|
+
@connection.send_message(Mongo::Constants::OP_UPDATE, message)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# Create a new index.
|
403
|
+
#
|
404
|
+
# @param [String, Array] spec
|
405
|
+
# should be either a single field name or an array of
|
406
|
+
# [field name, direction] pairs. Directions should be specified
|
407
|
+
# as Mongo::ASCENDING, Mongo::DESCENDING, or Mongo::GEO2D.
|
408
|
+
#
|
409
|
+
# Note that geospatial indexing only works with versions of MongoDB >= 1.3.3+. Keep in mind, too,
|
410
|
+
# that in order to geo-index a given field, that field must reference either an array or a sub-object
|
411
|
+
# where the first two values represent x- and y-coordinates. Examples can be seen below.
|
412
|
+
#
|
413
|
+
# Also note that it is permissible to create compound indexes that include a geospatial index as
|
414
|
+
# long as the geospatial index comes first.
|
415
|
+
#
|
416
|
+
# If your code calls create_index frequently, you can use Collection#ensure_index to cache these calls
|
417
|
+
# and thereby prevent excessive round trips to the database.
|
418
|
+
#
|
419
|
+
# @option opts [Boolean] :unique (false) if true, this index will enforce a uniqueness constraint.
|
420
|
+
# @option opts [Boolean] :background (false) indicate that the index should be built in the background. This
|
421
|
+
# feature is only available in MongoDB >= 1.3.2.
|
422
|
+
# @option opts [Boolean] :drop_dups (nil) If creating a unique index on a collection with pre-existing records,
|
423
|
+
# this option will keep the first document the database indexes and drop all subsequent with duplicate values.
|
424
|
+
# @option opts [Integer] :min (nil) specify the minimum longitude and latitude for a geo index.
|
425
|
+
# @option opts [Integer] :max (nil) specify the maximum longitude and latitude for a geo index.
|
426
|
+
#
|
427
|
+
# @example Creating a compound index:
|
428
|
+
# @posts.create_index([['subject', Mongo::ASCENDING], ['created_at', Mongo::DESCENDING]])
|
429
|
+
#
|
430
|
+
# @example Creating a geospatial index:
|
431
|
+
# @restaurants.create_index([['location', Mongo::GEO2D]])
|
432
|
+
#
|
433
|
+
# # Note that this will work only if 'location' represents x,y coordinates:
|
434
|
+
# {'location': [0, 50]}
|
435
|
+
# {'location': {'x' => 0, 'y' => 50}}
|
436
|
+
# {'location': {'latitude' => 0, 'longitude' => 50}}
|
437
|
+
#
|
438
|
+
# @example A geospatial index with alternate longitude and latitude:
|
439
|
+
# @restaurants.create_index([['location', Mongo::GEO2D]], :min => 500, :max => 500)
|
440
|
+
#
|
441
|
+
# @return [String] the name of the index created.
|
442
|
+
#
|
443
|
+
# @core indexes create_index-instance_method
|
444
|
+
def create_index(spec, opts={})
|
445
|
+
opts[:dropDups] = opts[:drop_dups] if opts[:drop_dups]
|
446
|
+
field_spec = parse_index_spec(spec)
|
447
|
+
opts = opts.dup
|
448
|
+
name = opts.delete(:name) || generate_index_name(field_spec)
|
449
|
+
name = name.to_s if name
|
450
|
+
|
451
|
+
generate_indexes(field_spec, name, opts)
|
452
|
+
name
|
453
|
+
end
|
454
|
+
|
455
|
+
# Calls create_index and sets a flag to not do so again for another X minutes.
|
456
|
+
# this time can be specified as an option when initializing a Mongo::DB object as options[:cache_time]
|
457
|
+
# Any changes to an index will be propogated through regardless of cache time (e.g., a change of index direction)
|
458
|
+
#
|
459
|
+
# The parameters and options for this methods are the same as those for Collection#create_index.
|
460
|
+
#
|
461
|
+
# @example Call sequence:
|
462
|
+
# Time t: @posts.ensure_index([['subject', Mongo::ASCENDING]) -- calls create_index and
|
463
|
+
# sets the 5 minute cache
|
464
|
+
# Time t+2min : @posts.ensure_index([['subject', Mongo::ASCENDING]) -- doesn't do anything
|
465
|
+
# Time t+3min : @posts.ensure_index([['something_else', Mongo::ASCENDING]) -- calls create_index
|
466
|
+
# and sets 5 minute cache
|
467
|
+
# Time t+10min : @posts.ensure_index([['subject', Mongo::ASCENDING]) -- calls create_index and
|
468
|
+
# resets the 5 minute counter
|
469
|
+
#
|
470
|
+
# @return [String] the name of the index.
|
471
|
+
def ensure_index(spec, opts={})
|
472
|
+
now = Time.now.utc.to_i
|
473
|
+
field_spec = parse_index_spec(spec)
|
474
|
+
|
475
|
+
name = opts[:name] || generate_index_name(field_spec)
|
476
|
+
name = name.to_s if name
|
477
|
+
|
478
|
+
if !@cache[name] || @cache[name] <= now
|
479
|
+
generate_indexes(field_spec, name, opts)
|
480
|
+
end
|
481
|
+
|
482
|
+
# Reset the cache here in case there are any errors inserting. Best to be safe.
|
483
|
+
@cache[name] = now + @cache_time
|
484
|
+
name
|
485
|
+
end
|
486
|
+
|
487
|
+
# Drop a specified index.
|
488
|
+
#
|
489
|
+
# @param [String] name
|
490
|
+
#
|
491
|
+
# @core indexes
|
492
|
+
def drop_index(name)
|
493
|
+
@cache[name.to_s] = nil
|
494
|
+
@db.drop_index(@name, name)
|
495
|
+
end
|
496
|
+
|
497
|
+
# Drop all indexes.
|
498
|
+
#
|
499
|
+
# @core indexes
|
500
|
+
def drop_indexes
|
501
|
+
@cache = {}
|
502
|
+
|
503
|
+
# Note: calling drop_indexes with no args will drop them all.
|
504
|
+
@db.drop_index(@name, '*')
|
505
|
+
end
|
506
|
+
|
507
|
+
# Drop the entire collection. USE WITH CAUTION.
|
508
|
+
def drop
|
509
|
+
@db.drop_collection(@name)
|
510
|
+
end
|
511
|
+
|
512
|
+
# Atomically update and return a document using MongoDB's findAndModify command. (MongoDB > 1.3.0)
|
513
|
+
#
|
514
|
+
# @option opts [Hash] :query ({}) a query selector document for matching the desired document.
|
515
|
+
# @option opts [Hash] :update (nil) the update operation to perform on the matched document.
|
516
|
+
# @option opts [Array, String, OrderedHash] :sort ({}) specify a sort option for the query using any
|
517
|
+
# of the sort options available for Cursor#sort. Sort order is important if the query will be matching
|
518
|
+
# multiple documents since only the first matching document will be updated and returned.
|
519
|
+
# @option opts [Boolean] :remove (false) If true, removes the the returned document from the collection.
|
520
|
+
# @option opts [Boolean] :new (false) If true, returns the updated document; otherwise, returns the document
|
521
|
+
# prior to update.
|
522
|
+
#
|
523
|
+
# @return [Hash] the matched document.
|
524
|
+
#
|
525
|
+
# @core findandmodify find_and_modify-instance_method
|
526
|
+
def find_and_modify(opts={})
|
527
|
+
cmd = BSON::OrderedHash.new
|
528
|
+
cmd[:findandmodify] = @name
|
529
|
+
cmd.merge!(opts)
|
530
|
+
cmd[:sort] = Mongo::Support.format_order_clause(opts[:sort]) if opts[:sort]
|
531
|
+
|
532
|
+
@db.command(cmd)['value']
|
533
|
+
end
|
534
|
+
|
535
|
+
# Perform a map-reduce operation on the current collection.
|
536
|
+
#
|
537
|
+
# @param [String, BSON::Code] map a map function, written in JavaScript.
|
538
|
+
# @param [String, BSON::Code] reduce a reduce function, written in JavaScript.
|
539
|
+
#
|
540
|
+
# @option opts [Hash] :query ({}) a query selector document, like what's passed to #find, to limit
|
541
|
+
# the operation to a subset of the collection.
|
542
|
+
# @option opts [Array] :sort ([]) an array of [key, direction] pairs to sort by. Direction should
|
543
|
+
# be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
|
544
|
+
# @option opts [Integer] :limit (nil) if passing a query, number of objects to return from the collection.
|
545
|
+
# @option opts [String, BSON::Code] :finalize (nil) a javascript function to apply to the result set after the
|
546
|
+
# map/reduce operation has finished.
|
547
|
+
# @option opts [String] :out (nil) a valid output type. In versions of MongoDB prior to v1.7.6,
|
548
|
+
# this option takes the name of a collection for the output results. In versions 1.7.6 and later,
|
549
|
+
# this option specifies the output type. See the core docs for available output types.
|
550
|
+
# @option opts [Boolean] :keeptemp (false) if true, the generated collection will be persisted. The defualt
|
551
|
+
# is false. Note that this option has no effect is versions of MongoDB > v1.7.6.
|
552
|
+
# @option opts [Boolean ] :verbose (false) if true, provides statistics on job execution time.
|
553
|
+
# @option opts [Boolean] :raw (false) if true, return the raw result object from the map_reduce command, and not
|
554
|
+
# the instantiated collection that's returned by default. Note if a collection name isn't returned in the
|
555
|
+
# map-reduce output (as, for example, when using :out => {:inline => 1}), then you must specify this option
|
556
|
+
# or an ArgumentError will be raised.
|
557
|
+
#
|
558
|
+
# @return [Collection, Hash] a Mongo::Collection object or a Hash with the map-reduce command's results.
|
559
|
+
#
|
560
|
+
# @raise ArgumentError if you specify {:out => {:inline => true}} but don't specify :raw => true.
|
561
|
+
#
|
562
|
+
# @see http://www.mongodb.org/display/DOCS/MapReduce Offical MongoDB map/reduce documentation.
|
563
|
+
#
|
564
|
+
# @core mapreduce map_reduce-instance_method
|
565
|
+
def map_reduce(map, reduce, opts={})
|
566
|
+
map = BSON::Code.new(map) unless map.is_a?(BSON::Code)
|
567
|
+
reduce = BSON::Code.new(reduce) unless reduce.is_a?(BSON::Code)
|
568
|
+
raw = opts[:raw]
|
569
|
+
|
570
|
+
hash = BSON::OrderedHash.new
|
571
|
+
hash['mapreduce'] = self.name
|
572
|
+
hash['map'] = map
|
573
|
+
hash['reduce'] = reduce
|
574
|
+
hash.merge! opts
|
575
|
+
|
576
|
+
result = @db.command(hash)
|
577
|
+
unless Mongo::Support.ok?(result)
|
578
|
+
raise Mongo::OperationFailure, "map-reduce failed: #{result['errmsg']}"
|
579
|
+
end
|
580
|
+
|
581
|
+
if raw
|
582
|
+
result
|
583
|
+
elsif result["result"]
|
584
|
+
@db[result["result"]]
|
585
|
+
else
|
586
|
+
raise ArgumentError, "Could not instantiate collection from result. If you specified " +
|
587
|
+
"{:out => {:inline => true}}, then you must also specify :raw => true to get the results."
|
588
|
+
end
|
589
|
+
end
|
590
|
+
alias :mapreduce :map_reduce
|
591
|
+
|
592
|
+
# Perform a group aggregation.
|
593
|
+
#
|
594
|
+
# @param [Hash] opts the options for this group operation. The minimum required are :initial
|
595
|
+
# and :reduce.
|
596
|
+
#
|
597
|
+
# @option opts [Array, String, Symbol] :key (nil) Either the name of a field or a list of fields to group by (optional).
|
598
|
+
# @option opts [String, BSON::Code] :keyf (nil) A JavaScript function to be used to generate the grouping keys (optional).
|
599
|
+
# @option opts [String, BSON::Code] :cond ({}) A document specifying a query for filtering the documents over
|
600
|
+
# which the aggregation is run (optional).
|
601
|
+
# @option opts [Hash] :initial the initial value of the aggregation counter object (required).
|
602
|
+
# @option opts [String, BSON::Code] :reduce (nil) a JavaScript aggregation function (required).
|
603
|
+
# @option opts [String, BSON::Code] :finalize (nil) a JavaScript function that receives and modifies
|
604
|
+
# each of the resultant grouped objects. Available only when group is run with command
|
605
|
+
# set to true.
|
606
|
+
#
|
607
|
+
# @return [Array] the command response consisting of grouped items.
|
608
|
+
def group(opts, condition={}, initial={}, reduce=nil, finalize=nil)
|
609
|
+
if opts.is_a?(Hash)
|
610
|
+
return new_group(opts)
|
611
|
+
else
|
612
|
+
warn "Collection#group no longer take a list of parameters. This usage is deprecated." +
|
613
|
+
"Check out the new API at http://api.mongodb.org/ruby/current/Mongo/Collection.html#group-instance_method"
|
614
|
+
end
|
615
|
+
|
616
|
+
reduce = BSON::Code.new(reduce) unless reduce.is_a?(BSON::Code)
|
617
|
+
|
618
|
+
group_command = {
|
619
|
+
"group" => {
|
620
|
+
"ns" => @name,
|
621
|
+
"$reduce" => reduce,
|
622
|
+
"cond" => condition,
|
623
|
+
"initial" => initial
|
624
|
+
}
|
625
|
+
}
|
626
|
+
|
627
|
+
if opts.is_a?(Symbol)
|
628
|
+
raise MongoArgumentError, "Group takes either an array of fields to group by or a JavaScript function" +
|
629
|
+
"in the form of a String or BSON::Code."
|
630
|
+
end
|
631
|
+
|
632
|
+
unless opts.nil?
|
633
|
+
if opts.is_a? Array
|
634
|
+
key_type = "key"
|
635
|
+
key_value = {}
|
636
|
+
opts.each { |k| key_value[k] = 1 }
|
637
|
+
else
|
638
|
+
key_type = "$keyf"
|
639
|
+
key_value = opts.is_a?(BSON::Code) ? opts : BSON::Code.new(opts)
|
640
|
+
end
|
641
|
+
|
642
|
+
group_command["group"][key_type] = key_value
|
643
|
+
end
|
644
|
+
|
645
|
+
finalize = BSON::Code.new(finalize) if finalize.is_a?(String)
|
646
|
+
if finalize.is_a?(BSON::Code)
|
647
|
+
group_command['group']['finalize'] = finalize
|
648
|
+
end
|
649
|
+
|
650
|
+
result = @db.command(group_command)
|
651
|
+
|
652
|
+
if Mongo::Support.ok?(result)
|
653
|
+
result["retval"]
|
654
|
+
else
|
655
|
+
raise OperationFailure, "group command failed: #{result['errmsg']}"
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
private
|
660
|
+
|
661
|
+
def new_group(opts={})
|
662
|
+
reduce = opts[:reduce]
|
663
|
+
finalize = opts[:finalize]
|
664
|
+
cond = opts.fetch(:cond, {})
|
665
|
+
initial = opts[:initial]
|
666
|
+
|
667
|
+
if !(reduce && initial)
|
668
|
+
raise MongoArgumentError, "Group requires at minimum values for initial and reduce."
|
669
|
+
end
|
670
|
+
|
671
|
+
cmd = {
|
672
|
+
"group" => {
|
673
|
+
"ns" => @name,
|
674
|
+
"$reduce" => reduce.to_bson_code,
|
675
|
+
"cond" => cond,
|
676
|
+
"initial" => initial
|
677
|
+
}
|
678
|
+
}
|
679
|
+
|
680
|
+
if finalize
|
681
|
+
cmd['group']['finalize'] = finalize.to_bson_code
|
682
|
+
end
|
683
|
+
|
684
|
+
if key = opts[:key]
|
685
|
+
if key.is_a?(String) || key.is_a?(Symbol)
|
686
|
+
key = [key]
|
687
|
+
end
|
688
|
+
key_value = {}
|
689
|
+
key.each { |k| key_value[k] = 1 }
|
690
|
+
cmd["group"]["key"] = key_value
|
691
|
+
elsif keyf = opts[:keyf]
|
692
|
+
cmd["group"]["$keyf"] = keyf.to_bson_code
|
693
|
+
end
|
694
|
+
|
695
|
+
result = @db.command(cmd)
|
696
|
+
result["retval"]
|
697
|
+
end
|
698
|
+
|
699
|
+
public
|
700
|
+
|
701
|
+
# Return a list of distinct values for +key+ across all
|
702
|
+
# documents in the collection. The key may use dot notation
|
703
|
+
# to reach into an embedded object.
|
704
|
+
#
|
705
|
+
# @param [String, Symbol, OrderedHash] key or hash to group by.
|
706
|
+
# @param [Hash] query a selector for limiting the result set over which to group.
|
707
|
+
#
|
708
|
+
# @example Saving zip codes and ages and returning distinct results.
|
709
|
+
# @collection.save({:zip => 10010, :name => {:age => 27}})
|
710
|
+
# @collection.save({:zip => 94108, :name => {:age => 24}})
|
711
|
+
# @collection.save({:zip => 10010, :name => {:age => 27}})
|
712
|
+
# @collection.save({:zip => 99701, :name => {:age => 24}})
|
713
|
+
# @collection.save({:zip => 94108, :name => {:age => 27}})
|
714
|
+
#
|
715
|
+
# @collection.distinct(:zip)
|
716
|
+
# [10010, 94108, 99701]
|
717
|
+
# @collection.distinct("name.age")
|
718
|
+
# [27, 24]
|
719
|
+
#
|
720
|
+
# # You may also pass a document selector as the second parameter
|
721
|
+
# # to limit the documents over which distinct is run:
|
722
|
+
# @collection.distinct("name.age", {"name.age" => {"$gt" => 24}})
|
723
|
+
# [27]
|
724
|
+
#
|
725
|
+
# @return [Array] an array of distinct values.
|
726
|
+
def distinct(key, query=nil)
|
727
|
+
raise MongoArgumentError unless [String, Symbol].include?(key.class)
|
728
|
+
command = BSON::OrderedHash.new
|
729
|
+
command[:distinct] = @name
|
730
|
+
command[:key] = key.to_s
|
731
|
+
command[:query] = query
|
732
|
+
|
733
|
+
@db.command(command)["values"]
|
734
|
+
end
|
735
|
+
|
736
|
+
# Rename this collection.
|
737
|
+
#
|
738
|
+
# Note: If operating in auth mode, the client must be authorized as an admin to
|
739
|
+
# perform this operation.
|
740
|
+
#
|
741
|
+
# @param [String] new_name the new name for this collection
|
742
|
+
#
|
743
|
+
# @return [String] the name of the new collection.
|
744
|
+
#
|
745
|
+
# @raise [Mongo::InvalidNSName] if +new_name+ is an invalid collection name.
|
746
|
+
def rename(new_name)
|
747
|
+
case new_name
|
748
|
+
when Symbol, String
|
749
|
+
else
|
750
|
+
raise TypeError, "new_name must be a string or symbol"
|
751
|
+
end
|
752
|
+
|
753
|
+
new_name = new_name.to_s
|
754
|
+
|
755
|
+
if new_name.empty? or new_name.include? ".."
|
756
|
+
raise Mongo::InvalidNSName, "collection names cannot be empty"
|
757
|
+
end
|
758
|
+
if new_name.include? "$"
|
759
|
+
raise Mongo::InvalidNSName, "collection names must not contain '$'"
|
760
|
+
end
|
761
|
+
if new_name.match(/^\./) or new_name.match(/\.$/)
|
762
|
+
raise Mongo::InvalidNSName, "collection names must not start or end with '.'"
|
763
|
+
end
|
764
|
+
|
765
|
+
@db.rename_collection(@name, new_name)
|
766
|
+
@name = new_name
|
767
|
+
end
|
768
|
+
|
769
|
+
# Get information on the indexes for this collection.
|
770
|
+
#
|
771
|
+
# @return [Hash] a hash where the keys are index names.
|
772
|
+
#
|
773
|
+
# @core indexes
|
774
|
+
def index_information
|
775
|
+
@db.index_information(@name)
|
776
|
+
end
|
777
|
+
|
778
|
+
# Return a hash containing options that apply to this collection.
|
779
|
+
# For all possible keys and values, see DB#create_collection.
|
780
|
+
#
|
781
|
+
# @return [Hash] options that apply to this collection.
|
782
|
+
def options
|
783
|
+
@db.collections_info(@name).next_document['options']
|
784
|
+
end
|
785
|
+
|
786
|
+
# Return stats on the collection. Uses MongoDB's collstats command.
|
787
|
+
#
|
788
|
+
# @return [Hash]
|
789
|
+
def stats
|
790
|
+
@db.command({:collstats => @name})
|
791
|
+
end
|
792
|
+
|
793
|
+
# Get the number of documents in this collection.
|
794
|
+
#
|
795
|
+
# @return [Integer]
|
796
|
+
def count
|
797
|
+
find().count()
|
798
|
+
end
|
799
|
+
|
800
|
+
alias :size :count
|
801
|
+
|
802
|
+
protected
|
803
|
+
|
804
|
+
def normalize_hint_fields(hint)
|
805
|
+
case hint
|
806
|
+
when String
|
807
|
+
{hint => 1}
|
808
|
+
when Hash
|
809
|
+
hint
|
810
|
+
when nil
|
811
|
+
nil
|
812
|
+
else
|
813
|
+
h = BSON::OrderedHash.new
|
814
|
+
hint.to_a.each { |k| h[k] = 1 }
|
815
|
+
h
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
private
|
820
|
+
|
821
|
+
def parse_index_spec(spec)
|
822
|
+
field_spec = BSON::OrderedHash.new
|
823
|
+
if spec.is_a?(String) || spec.is_a?(Symbol)
|
824
|
+
field_spec[spec.to_s] = 1
|
825
|
+
elsif spec.is_a?(Array) && spec.all? {|field| field.is_a?(Array) }
|
826
|
+
spec.each do |f|
|
827
|
+
if [Mongo::ASCENDING, Mongo::DESCENDING, Mongo::GEO2D].include?(f[1])
|
828
|
+
field_spec[f[0].to_s] = f[1]
|
829
|
+
else
|
830
|
+
raise MongoArgumentError, "Invalid index field #{f[1].inspect}; " +
|
831
|
+
"should be one of Mongo::ASCENDING (1), Mongo::DESCENDING (-1) or Mongo::GEO2D ('2d')."
|
832
|
+
end
|
833
|
+
end
|
834
|
+
else
|
835
|
+
raise MongoArgumentError, "Invalid index specification #{spec.inspect}; " +
|
836
|
+
"should be either a string, symbol, or an array of arrays."
|
837
|
+
end
|
838
|
+
field_spec
|
839
|
+
end
|
840
|
+
|
841
|
+
def generate_indexes(field_spec, name, opts)
|
842
|
+
selector = {
|
843
|
+
:name => name,
|
844
|
+
:ns => "#{@db.name}.#{@name}",
|
845
|
+
:key => field_spec
|
846
|
+
}
|
847
|
+
selector.merge!(opts)
|
848
|
+
|
849
|
+
begin
|
850
|
+
insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, true)
|
851
|
+
|
852
|
+
rescue Mongo::OperationFailure => e
|
853
|
+
if selector[:dropDups] && e.message =~ /^11000/
|
854
|
+
# NOP. If the user is intentionally dropping dups, we can ignore duplicate key errors.
|
855
|
+
else
|
856
|
+
raise Mongo::OperationFailure, "Failed to create index #{selector.inspect} with the following error: " +
|
857
|
+
"#{e.message}"
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|
861
|
+
nil
|
862
|
+
end
|
863
|
+
|
864
|
+
# Sends a Mongo::Constants::OP_INSERT message to the database.
|
865
|
+
# Takes an array of +documents+, an optional +collection_name+, and a
|
866
|
+
# +check_keys+ setting.
|
867
|
+
def insert_documents(documents, collection_name=@name, check_keys=true, safe=false)
|
868
|
+
# Initial byte is 0.
|
869
|
+
message = BSON::ByteBuffer.new("\0\0\0\0")
|
870
|
+
BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{collection_name}")
|
871
|
+
documents.each do |doc|
|
872
|
+
message.put_binary(BSON::BSON_CODER.serialize(doc, check_keys, true).to_s)
|
873
|
+
end
|
874
|
+
raise InvalidOperation, "Exceded maximum insert size of 16,000,000 bytes" if message.size > 16_000_000
|
875
|
+
|
876
|
+
@connection.instrument(:insert, :database => @db.name, :collection => collection_name, :documents => documents) do
|
877
|
+
if safe
|
878
|
+
@connection.send_message_with_safe_check(Mongo::Constants::OP_INSERT, message, @db.name, nil, safe)
|
879
|
+
else
|
880
|
+
@connection.send_message(Mongo::Constants::OP_INSERT, message)
|
881
|
+
end
|
882
|
+
end
|
883
|
+
documents.collect { |o| o[:_id] || o['_id'] }
|
884
|
+
end
|
885
|
+
|
886
|
+
def generate_index_name(spec)
|
887
|
+
indexes = []
|
888
|
+
spec.each_pair do |field, direction|
|
889
|
+
indexes.push("#{field}_#{direction}")
|
890
|
+
end
|
891
|
+
indexes.join("_")
|
892
|
+
end
|
893
|
+
end
|
894
|
+
|
895
|
+
end
|