jonbell-mongo 1.3.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/LICENSE.txt +190 -0
  2. data/README.md +333 -0
  3. data/Rakefile +215 -0
  4. data/bin/mongo_console +21 -0
  5. data/docs/CREDITS.md +123 -0
  6. data/docs/FAQ.md +116 -0
  7. data/docs/GridFS.md +158 -0
  8. data/docs/HISTORY.md +263 -0
  9. data/docs/RELEASES.md +33 -0
  10. data/docs/REPLICA_SETS.md +72 -0
  11. data/docs/TUTORIAL.md +247 -0
  12. data/docs/WRITE_CONCERN.md +28 -0
  13. data/lib/mongo.rb +97 -0
  14. data/lib/mongo/collection.rb +895 -0
  15. data/lib/mongo/connection.rb +926 -0
  16. data/lib/mongo/cursor.rb +474 -0
  17. data/lib/mongo/db.rb +617 -0
  18. data/lib/mongo/exceptions.rb +71 -0
  19. data/lib/mongo/gridfs/grid.rb +107 -0
  20. data/lib/mongo/gridfs/grid_ext.rb +57 -0
  21. data/lib/mongo/gridfs/grid_file_system.rb +146 -0
  22. data/lib/mongo/gridfs/grid_io.rb +485 -0
  23. data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
  24. data/lib/mongo/repl_set_connection.rb +356 -0
  25. data/lib/mongo/util/conversions.rb +89 -0
  26. data/lib/mongo/util/core_ext.rb +60 -0
  27. data/lib/mongo/util/pool.rb +177 -0
  28. data/lib/mongo/util/server_version.rb +71 -0
  29. data/lib/mongo/util/support.rb +82 -0
  30. data/lib/mongo/util/uri_parser.rb +185 -0
  31. data/mongo.gemspec +34 -0
  32. data/test/auxillary/1.4_features.rb +166 -0
  33. data/test/auxillary/authentication_test.rb +68 -0
  34. data/test/auxillary/autoreconnect_test.rb +41 -0
  35. data/test/auxillary/fork_test.rb +30 -0
  36. data/test/auxillary/repl_set_auth_test.rb +58 -0
  37. data/test/auxillary/slave_connection_test.rb +36 -0
  38. data/test/auxillary/threaded_authentication_test.rb +101 -0
  39. data/test/bson/binary_test.rb +15 -0
  40. data/test/bson/bson_test.rb +654 -0
  41. data/test/bson/byte_buffer_test.rb +208 -0
  42. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  43. data/test/bson/json_test.rb +17 -0
  44. data/test/bson/object_id_test.rb +154 -0
  45. data/test/bson/ordered_hash_test.rb +210 -0
  46. data/test/bson/timestamp_test.rb +24 -0
  47. data/test/collection_test.rb +910 -0
  48. data/test/connection_test.rb +324 -0
  49. data/test/conversions_test.rb +119 -0
  50. data/test/cursor_fail_test.rb +75 -0
  51. data/test/cursor_message_test.rb +43 -0
  52. data/test/cursor_test.rb +483 -0
  53. data/test/db_api_test.rb +738 -0
  54. data/test/db_connection_test.rb +15 -0
  55. data/test/db_test.rb +315 -0
  56. data/test/grid_file_system_test.rb +259 -0
  57. data/test/grid_io_test.rb +209 -0
  58. data/test/grid_test.rb +258 -0
  59. data/test/load/thin/load.rb +24 -0
  60. data/test/load/unicorn/load.rb +23 -0
  61. data/test/replica_sets/connect_test.rb +112 -0
  62. data/test/replica_sets/connection_string_test.rb +32 -0
  63. data/test/replica_sets/count_test.rb +35 -0
  64. data/test/replica_sets/insert_test.rb +53 -0
  65. data/test/replica_sets/pooled_insert_test.rb +55 -0
  66. data/test/replica_sets/query_secondaries.rb +108 -0
  67. data/test/replica_sets/query_test.rb +51 -0
  68. data/test/replica_sets/replication_ack_test.rb +66 -0
  69. data/test/replica_sets/rs_test_helper.rb +27 -0
  70. data/test/safe_test.rb +68 -0
  71. data/test/support/hash_with_indifferent_access.rb +186 -0
  72. data/test/support/keys.rb +45 -0
  73. data/test/support_test.rb +18 -0
  74. data/test/test_helper.rb +102 -0
  75. data/test/threading/threading_with_large_pool_test.rb +90 -0
  76. data/test/threading_test.rb +87 -0
  77. data/test/tools/auth_repl_set_manager.rb +14 -0
  78. data/test/tools/repl_set_manager.rb +266 -0
  79. data/test/unit/collection_test.rb +130 -0
  80. data/test/unit/connection_test.rb +85 -0
  81. data/test/unit/cursor_test.rb +109 -0
  82. data/test/unit/db_test.rb +94 -0
  83. data/test/unit/grid_test.rb +49 -0
  84. data/test/unit/pool_test.rb +9 -0
  85. data/test/unit/repl_set_connection_test.rb +59 -0
  86. data/test/unit/safe_test.rb +125 -0
  87. data/test/uri_test.rb +91 -0
  88. 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