mongo 0.1.0 → 0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. data/README.rdoc +268 -71
  2. data/Rakefile +27 -62
  3. data/bin/bson_benchmark.rb +59 -0
  4. data/bin/mongo_console +3 -3
  5. data/bin/run_test_script +19 -0
  6. data/bin/standard_benchmark +109 -0
  7. data/examples/admin.rb +41 -0
  8. data/examples/benchmarks.rb +42 -0
  9. data/examples/blog.rb +76 -0
  10. data/examples/capped.rb +23 -0
  11. data/examples/cursor.rb +47 -0
  12. data/examples/gridfs.rb +87 -0
  13. data/examples/index_test.rb +125 -0
  14. data/examples/info.rb +30 -0
  15. data/examples/queries.rb +69 -0
  16. data/examples/simple.rb +23 -0
  17. data/examples/strict.rb +34 -0
  18. data/examples/types.rb +35 -0
  19. data/lib/mongo.rb +9 -2
  20. data/lib/mongo/admin.rb +65 -68
  21. data/lib/mongo/collection.rb +379 -117
  22. data/lib/mongo/connection.rb +151 -0
  23. data/lib/mongo/cursor.rb +271 -216
  24. data/lib/mongo/db.rb +500 -315
  25. data/lib/mongo/errors.rb +26 -0
  26. data/lib/mongo/gridfs.rb +16 -0
  27. data/lib/mongo/gridfs/chunk.rb +92 -0
  28. data/lib/mongo/gridfs/grid_store.rb +464 -0
  29. data/lib/mongo/message.rb +16 -0
  30. data/lib/mongo/message/get_more_message.rb +24 -13
  31. data/lib/mongo/message/insert_message.rb +29 -11
  32. data/lib/mongo/message/kill_cursors_message.rb +23 -12
  33. data/lib/mongo/message/message.rb +74 -62
  34. data/lib/mongo/message/message_header.rb +35 -24
  35. data/lib/mongo/message/msg_message.rb +21 -9
  36. data/lib/mongo/message/opcodes.rb +26 -15
  37. data/lib/mongo/message/query_message.rb +63 -43
  38. data/lib/mongo/message/remove_message.rb +29 -12
  39. data/lib/mongo/message/update_message.rb +30 -13
  40. data/lib/mongo/query.rb +97 -89
  41. data/lib/mongo/types/binary.rb +25 -21
  42. data/lib/mongo/types/code.rb +30 -0
  43. data/lib/mongo/types/dbref.rb +19 -23
  44. data/lib/mongo/types/objectid.rb +130 -116
  45. data/lib/mongo/types/regexp_of_holding.rb +27 -31
  46. data/lib/mongo/util/bson.rb +273 -160
  47. data/lib/mongo/util/byte_buffer.rb +32 -28
  48. data/lib/mongo/util/ordered_hash.rb +88 -42
  49. data/lib/mongo/util/xml_to_ruby.rb +18 -15
  50. data/mongo-ruby-driver.gemspec +103 -0
  51. data/test/mongo-qa/_common.rb +8 -0
  52. data/test/mongo-qa/admin +26 -0
  53. data/test/mongo-qa/capped +22 -0
  54. data/test/mongo-qa/count1 +18 -0
  55. data/test/mongo-qa/dbs +22 -0
  56. data/test/mongo-qa/find +10 -0
  57. data/test/mongo-qa/find1 +15 -0
  58. data/test/mongo-qa/gridfs_in +16 -0
  59. data/test/mongo-qa/gridfs_out +17 -0
  60. data/test/mongo-qa/indices +49 -0
  61. data/test/mongo-qa/remove +25 -0
  62. data/test/mongo-qa/stress1 +35 -0
  63. data/test/mongo-qa/test1 +11 -0
  64. data/test/mongo-qa/update +18 -0
  65. data/{tests → test}/test_admin.rb +25 -16
  66. data/test/test_bson.rb +268 -0
  67. data/{tests → test}/test_byte_buffer.rb +0 -0
  68. data/test/test_chunk.rb +84 -0
  69. data/test/test_collection.rb +282 -0
  70. data/test/test_connection.rb +101 -0
  71. data/test/test_cursor.rb +321 -0
  72. data/test/test_db.rb +196 -0
  73. data/test/test_db_api.rb +798 -0
  74. data/{tests → test}/test_db_connection.rb +4 -3
  75. data/test/test_grid_store.rb +284 -0
  76. data/{tests → test}/test_message.rb +1 -1
  77. data/test/test_objectid.rb +105 -0
  78. data/{tests → test}/test_ordered_hash.rb +55 -0
  79. data/{tests → test}/test_round_trip.rb +13 -9
  80. data/test/test_threading.rb +37 -0
  81. metadata +74 -32
  82. data/bin/validate +0 -51
  83. data/lib/mongo/mongo.rb +0 -74
  84. data/lib/mongo/types/undefined.rb +0 -31
  85. data/tests/test_bson.rb +0 -135
  86. data/tests/test_cursor.rb +0 -66
  87. data/tests/test_db.rb +0 -51
  88. data/tests/test_db_api.rb +0 -349
  89. data/tests/test_objectid.rb +0 -88
@@ -0,0 +1,151 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/db'
18
+
19
+ module Mongo
20
+
21
+ # A connection to MongoDB.
22
+ class Connection
23
+
24
+ DEFAULT_PORT = 27017
25
+
26
+ # Create a Mongo database server instance. You specify either one or a
27
+ # pair of servers. If one, you also say if connecting to a slave is
28
+ # OK. In either case, the host default is "localhost" and port default
29
+ # is DEFAULT_PORT.
30
+ #
31
+ # If you specify a pair, pair_or_host is a hash with two keys :left
32
+ # and :right. Each key maps to either
33
+ # * a server name, in which case port is DEFAULT_PORT
34
+ # * a port number, in which case server is "localhost"
35
+ # * an array containing a server name and a port number in that order
36
+ #
37
+ # +options+ are passed on to each DB instance:
38
+ #
39
+ # :slave_ok :: Only used if one host is specified. If false, when
40
+ # connecting to that host/port a DB object will check to
41
+ # see if the server is the master. If it is not, an error
42
+ # is thrown.
43
+ #
44
+ # :auto_reconnect :: If a DB connection gets closed (for example, we
45
+ # have a server pair and saw the "not master"
46
+ # error, which closes the connection), then
47
+ # automatically try to reconnect to the master or
48
+ # to the single server we have been given. Defaults
49
+ # to +false+.
50
+ # :logger :: Optional Logger instance to which driver usage information
51
+ # will be logged.
52
+ #
53
+ # Since that's so confusing, here are a few examples:
54
+ #
55
+ # Connection.new # localhost, DEFAULT_PORT, !slave
56
+ # Connection.new("localhost") # localhost, DEFAULT_PORT, !slave
57
+ # Connection.new("localhost", 3000) # localhost, 3000, slave not ok
58
+ # # localhost, 3000, slave ok
59
+ # Connection.new("localhost", 3000, :slave_ok => true)
60
+ # # localhost, DEFAULT_PORT, auto reconnect
61
+ # Connection.new(nil, nil, :auto_reconnect => true)
62
+ #
63
+ # # A pair of servers. DB will always talk to the master. On socket
64
+ # # error or "not master" error, we will auto-reconnect to the
65
+ # # current master.
66
+ # Connection.new({:left => ["db1.example.com", 3000],
67
+ # :right => "db2.example.com"}, # DEFAULT_PORT
68
+ # nil, :auto_reconnect => true)
69
+ #
70
+ # # Here, :right is localhost/DEFAULT_PORT. No auto-reconnect.
71
+ # Connection.new({:left => ["db1.example.com", 3000]})
72
+ #
73
+ # When a DB object first connects to a pair, it will find the master
74
+ # instance and connect to that one.
75
+ def initialize(pair_or_host=nil, port=nil, options={})
76
+ @pair = case pair_or_host
77
+ when String
78
+ [[pair_or_host, port ? port.to_i : DEFAULT_PORT]]
79
+ when Hash
80
+ connections = []
81
+ connections << pair_val_to_connection(pair_or_host[:left])
82
+ connections << pair_val_to_connection(pair_or_host[:right])
83
+ connections
84
+ when nil
85
+ [['localhost', DEFAULT_PORT]]
86
+ end
87
+
88
+ @options = options
89
+ end
90
+
91
+ # Return the Mongo::DB named +db_name+. The slave_ok and
92
+ # auto_reconnect options passed in via #new may be overridden here.
93
+ # See DB#new for other options you can pass in.
94
+ def db(db_name, options={})
95
+ DB.new(db_name, @pair, @options.merge(options))
96
+ end
97
+
98
+ # Returns a hash containing database names as keys and disk space for
99
+ # each as values.
100
+ def database_info
101
+ doc = single_db_command('admin', :listDatabases => 1)
102
+ h = {}
103
+ doc['databases'].each { |db|
104
+ h[db['name']] = db['sizeOnDisk'].to_i
105
+ }
106
+ h
107
+ end
108
+
109
+ # Returns an array of database names.
110
+ def database_names
111
+ database_info.keys
112
+ end
113
+
114
+ # Drops the database +name+.
115
+ def drop_database(name)
116
+ single_db_command(name, :dropDatabase => 1)
117
+ end
118
+
119
+ protected
120
+
121
+ # Turns an array containing a host name string and a
122
+ # port number integer into a [host, port] pair array.
123
+ def pair_val_to_connection(a)
124
+ case a
125
+ when nil
126
+ ['localhost', DEFAULT_PORT]
127
+ when String
128
+ [a, DEFAULT_PORT]
129
+ when Integer
130
+ ['localhost', a]
131
+ when Array
132
+ a
133
+ end
134
+ end
135
+
136
+ # Send cmd (a hash, possibly ordered) to the admin database and return
137
+ # the answer. Raises an error unless the return is "ok" (DB#ok?
138
+ # returns +true+).
139
+ def single_db_command(db_name, cmd)
140
+ db = nil
141
+ begin
142
+ db = db(db_name)
143
+ doc = db.db_command(cmd)
144
+ raise "error retrieving database info: #{doc.inspect}" unless db.ok?(doc)
145
+ doc
146
+ ensure
147
+ db.close if db
148
+ end
149
+ end
150
+ end
151
+ end
@@ -1,229 +1,284 @@
1
- # --
2
1
  # Copyright (C) 2008-2009 10gen Inc.
3
2
  #
4
- # This program is free software: you can redistribute it and/or modify it
5
- # under the terms of the GNU Affero General Public License, version 3, as
6
- # published by the Free Software Foundation.
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
7
6
  #
8
- # This program is distributed in the hope that it will be useful, but WITHOUT
9
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
- # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
11
- # for more details.
7
+ # http://www.apache.org/licenses/LICENSE-2.0
12
8
  #
13
- # You should have received a copy of the GNU Affero General Public License
14
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
- # ++
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
16
14
 
17
15
  require 'mongo/message'
18
16
  require 'mongo/util/byte_buffer'
19
17
  require 'mongo/util/bson'
20
18
 
21
- module XGen
22
- module Mongo
23
- module Driver
24
-
25
- # A cursor over query results. Returned objects are hashes.
26
- class Cursor
27
-
28
- include Enumerable
29
-
30
- RESPONSE_HEADER_SIZE = 20
31
-
32
- attr_reader :db, :collection, :query
33
-
34
- def initialize(db, collection, query)
35
- @db, @collection, @query = db, collection, query
36
- @num_to_return = @query.number_to_return || 0
37
- @cache = []
38
- @closed = false
39
- @can_call_to_a = true
40
- @query_run = false
41
- end
42
-
43
- def closed?; @closed; end
44
-
45
- # Set hint fields to use and return +self+. hint_fields may be a
46
- # single field name, array of field names, or a hash whose keys will
47
- # become the hint field names. May be +nil+. If no hint fields are
48
- # specified, the ones in the collection are used if they exist.
49
- def hint(hint_fields)
50
- @hint_fields = case hint_fields
51
- when String
52
- [hint_fields]
53
- when Hash
54
- hint_fields.keys
55
- when nil
56
- nil
57
- else
58
- hint_fields.to_a
59
- end
60
- self
61
- end
62
-
63
- # Return +true+ if there are more records to retrieve. We do not check
64
- # @num_to_return; #each is responsible for doing that.
65
- def more?
66
- num_remaining > 0
67
- end
68
-
69
- # Return the next object. Raises an error if necessary.
70
- def next_object
71
- refill_via_get_more if num_remaining == 0
72
- o = @cache.shift
73
- raise o['$err'] if o && o['$err']
74
- o
75
- end
76
-
77
- # Iterate over each object, yielding it to the given block. At most
78
- # @num_to_return records are returned (or all of them, if
79
- # @num_to_return is 0).
80
- #
81
- # If #to_a has already been called then this method uses the array
82
- # that we store internally. In that case, #each can be called multiple
83
- # times because it re-uses that array.
84
- #
85
- # You can call #each after calling #to_a (multiple times even, because
86
- # it will use the internally-stored array), but you can't call #to_a
87
- # after calling #each unless you also called it before calling #each.
88
- # If you try to do that, an error will be raised.
89
- def each
90
- if @rows # Already turned into an array
91
- @rows.each { |row| yield row }
92
- else
93
- num_returned = 0
94
- while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
95
- yield next_object()
96
- num_returned += 1
97
- end
98
- @can_call_to_a = false
99
- end
100
- end
101
-
102
- # Return all of the rows (up to the +num_to_return+ value specified in
103
- # #new) as an array. Calling this multiple times will work fine; it
104
- # always returns the same array.
105
- #
106
- # Don't use this if you're expecting large amounts of data, of course.
107
- # All of the returned rows are kept in an array stored in this object
108
- # so it can be reused.
109
- #
110
- # You can call #each after calling #to_a (multiple times even, because
111
- # it will use the internally-stored array), but you can't call #to_a
112
- # after calling #each unless you also called it before calling #each.
113
- # If you try to do that, an error will be raised.
114
- def to_a
115
- return @rows if @rows
116
- raise "can't call Cursor#to_a after calling Cursor#each" unless @can_call_to_a
117
- @rows = []
118
- num_returned = 0
119
- while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
120
- @rows << next_object()
121
- num_returned += 1
122
- end
123
- @rows
124
- end
125
-
126
- # Returns an explain plan record.
127
- def explain
128
- old_val = @query.explain
129
- @query.explain = true
130
-
131
- c = Cursor.new(@db, @collection, @query)
132
- explanation = c.next_object
133
- c.close
134
-
135
- @query.explain = old_val
136
- explanation
137
- end
138
-
139
- # Close the cursor.
140
- def close
141
- @db.send_to_db(KillCursorsMessage.new(@cursor_id)) if @cursor_id
142
- @cache = []
143
- @cursor_id = 0
144
- @closed = true
145
- end
146
-
147
- protected
148
-
149
- def read_all
150
- read_message_header
151
- read_response_header
152
- read_objects_off_wire
153
- end
154
-
155
- def read_objects_off_wire
156
- while doc = next_object_on_wire
157
- @cache << doc
158
- end
159
- end
160
-
161
- def read_message_header
162
- MessageHeader.new.read_header(@db.socket)
163
- end
164
-
165
- def read_response_header
166
- header_buf = ByteBuffer.new
167
- header_buf.put_array(@db.socket.recv(RESPONSE_HEADER_SIZE).unpack("C*"))
168
- raise "Short read for DB response header; expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}" unless header_buf.length == RESPONSE_HEADER_SIZE
169
- header_buf.rewind
170
- @result_flags = header_buf.get_int
171
- @cursor_id = header_buf.get_long
172
- @starting_from = header_buf.get_int
173
- @n_returned = header_buf.get_int
174
- @n_remaining = @n_returned
175
- end
176
-
177
- def num_remaining
178
- refill_via_get_more if @cache.length == 0
179
- @cache.length
180
- end
181
-
182
- private
183
-
184
- def next_object_on_wire
185
- send_query_if_needed
186
- # if @n_remaining is 0 but we have a non-zero cursor, there are more
187
- # to fetch, so do a GetMore operation, but don't do it here - do it
188
- # when someone pulls an object out of the cache and it's empty
189
- return nil if @n_remaining == 0
190
- object_from_stream
191
- end
192
-
193
- def refill_via_get_more
194
- send_query_if_needed
195
- return if @cursor_id == 0
196
- @db.send_to_db(GetMoreMessage.new(@db.name, @collection.name, @cursor_id))
19
+ module Mongo
20
+
21
+ # A cursor over query results. Returned objects are hashes.
22
+ class Cursor
23
+
24
+ include Enumerable
25
+
26
+ RESPONSE_HEADER_SIZE = 20
27
+
28
+ attr_reader :db, :collection, :query
29
+
30
+ # Create a new cursor.
31
+ #
32
+ # Should not be called directly by application developers.
33
+ def initialize(db, collection, query, admin=false)
34
+ @db, @collection, @query, @admin = db, collection, query, admin
35
+ @cache = []
36
+ @closed = false
37
+ @query_run = false
38
+ end
39
+
40
+ # Return the next object or nil if there are no more. Raises an error
41
+ # if necessary.
42
+ def next_object
43
+ refill_via_get_more if num_remaining == 0
44
+ o = @cache.shift
45
+
46
+ if o && o['$err']
47
+ err = o['$err']
48
+
49
+ # If the server has stopped being the master (e.g., it's one of a
50
+ # pair but it has died or something like that) then we close that
51
+ # connection. If the db has auto connect option and a pair of
52
+ # servers, next request will re-open on master server.
53
+ @db.close if err == "not master"
54
+
55
+ raise err
56
+ end
57
+
58
+ o
59
+ end
60
+
61
+ # Get the size of the results set for this query.
62
+ #
63
+ # Returns the number of objects in the results set for this query. Does
64
+ # not take limit and skip into account. Raises OperationFailure on a
65
+ # database error.
66
+ def count
67
+ command = OrderedHash["count", @collection.name,
68
+ "query", @query.selector,
69
+ "fields", @query.fields()]
70
+ response = @db.db_command(command)
71
+ return response['n'].to_i if response['ok'] == 1
72
+ return 0 if response['errmsg'] == "ns missing"
73
+ raise OperationFailure, "Count failed: #{response['errmsg']}"
74
+ end
75
+
76
+ # Sort this cursor's result
77
+ #
78
+ # Takes either a hash of field names as keys and 1/-1 as values; 1 ==
79
+ # ascending, -1 == descending, or array of field names (all assumed to be
80
+ # sorted in ascending order).
81
+ #
82
+ # Raises InvalidOperation if this cursor has already been used.
83
+ #
84
+ # This method overrides any sort order specified in the Collection#find
85
+ # method, and only the last sort applied has an effect
86
+ def sort(order)
87
+ check_modifiable
88
+ @query.order_by = order
89
+ self
90
+ end
91
+
92
+ # Limits the number of results to be returned by this cursor.
93
+ #
94
+ # Raises InvalidOperation if this cursor has already been used.
95
+ #
96
+ # This method overrides any limit specified in the Collection#find method,
97
+ # and only the last limit applied has an effect.
98
+ def limit(number_to_return)
99
+ check_modifiable
100
+ raise ArgumentError, "limit requires an integer" unless number_to_return.is_a? Integer
101
+
102
+ @query.number_to_return = number_to_return
103
+ self
104
+ end
105
+
106
+ # Skips the first +number_to_skip+ results of this cursor.
107
+ #
108
+ # Raises InvalidOperation if this cursor has already been used.
109
+ #
110
+ # This method overrides any skip specified in the Collection#find method,
111
+ # and only the last skip applied has an effect.
112
+ def skip(number_to_skip)
113
+ check_modifiable
114
+ raise ArgumentError, "skip requires an integer" unless number_to_skip.is_a? Integer
115
+
116
+ @query.number_to_skip = number_to_skip
117
+ self
118
+ end
119
+
120
+ # Iterate over each document in this cursor, yielding it to the given
121
+ # block.
122
+ #
123
+ # Iterating over an entire cursor will close it.
124
+ def each
125
+ num_returned = 0
126
+ while more? && (@query.number_to_return <= 0 || num_returned < @query.number_to_return)
127
+ yield next_object()
128
+ num_returned += 1
129
+ end
130
+ end
131
+
132
+ # Return all of the documents in this cursor as an array of hashes.
133
+ #
134
+ # Raises InvalidOperation if this cursor has already been used (including
135
+ # any previous calls to this method).
136
+ #
137
+ # Use of this method is discouraged - iterating over a cursor is much
138
+ # more efficient in most cases.
139
+ def to_a
140
+ raise InvalidOperation, "can't call Cursor#to_a on a used cursor" if @query_run
141
+ rows = []
142
+ num_returned = 0
143
+ while more? && (@query.number_to_return <= 0 || num_returned < @query.number_to_return)
144
+ rows << next_object()
145
+ num_returned += 1
146
+ end
147
+ rows
148
+ end
149
+
150
+ # Returns an explain plan record for this cursor.
151
+ def explain
152
+ limit = @query.number_to_return
153
+ @query.explain = true
154
+ @query.number_to_return = -limit.abs
155
+
156
+ c = Cursor.new(@db, @collection, @query)
157
+ explanation = c.next_object
158
+ c.close
159
+
160
+ @query.explain = false
161
+ @query.number_to_return = limit
162
+ explanation
163
+ end
164
+
165
+ # Close the cursor.
166
+ #
167
+ # Note: if a cursor is read until exhausted (read until OP_QUERY or
168
+ # OP_GETMORE returns zero for the cursor id), there is no need to
169
+ # close it by calling this method.
170
+ #
171
+ # Collection#find takes an optional block argument which can be used to
172
+ # ensure that your cursors get closed. See the documentation for
173
+ # Collection#find for details.
174
+ def close
175
+ @db.send_to_db(KillCursorsMessage.new(@cursor_id)) if @cursor_id
176
+ @cursor_id = 0
177
+ @closed = true
178
+ end
179
+
180
+ # Returns true if this cursor is closed, false otherwise.
181
+ def closed?; @closed; end
182
+
183
+ private
184
+
185
+ def read_all
186
+ read_message_header
187
+ read_response_header
188
+ read_objects_off_wire
189
+ end
190
+
191
+ def read_objects_off_wire
192
+ while doc = next_object_on_wire
193
+ @cache << doc
194
+ end
195
+ end
196
+
197
+ def read_message_header
198
+ MessageHeader.new.read_header(@db)
199
+ end
200
+
201
+ def read_response_header
202
+ header_buf = ByteBuffer.new
203
+ header_buf.put_array(@db.receive_full(RESPONSE_HEADER_SIZE).unpack("C*"))
204
+ raise "Short read for DB response header; expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}" unless header_buf.length == RESPONSE_HEADER_SIZE
205
+ header_buf.rewind
206
+ @result_flags = header_buf.get_int
207
+ @cursor_id = header_buf.get_long
208
+ @starting_from = header_buf.get_int
209
+ @n_remaining = header_buf.get_int
210
+ if @n_received
211
+ @n_received += @n_remaining
212
+ else
213
+ @n_received = @n_remaining
214
+ end
215
+ if @query.number_to_return > 0 and @n_received >= @query.number_to_return
216
+ close()
217
+ end
218
+ end
219
+
220
+ def num_remaining
221
+ refill_via_get_more if @cache.length == 0
222
+ @cache.length
223
+ end
224
+
225
+ # Internal method, not for general use. Return +true+ if there are
226
+ # more records to retrieve. We do not check @query.number_to_return;
227
+ # #each is responsible for doing that.
228
+ def more?
229
+ num_remaining > 0
230
+ end
231
+
232
+ def next_object_on_wire
233
+ # if @n_remaining is 0 but we have a non-zero cursor, there are more
234
+ # to fetch, so do a GetMore operation, but don't do it here - do it
235
+ # when someone pulls an object out of the cache and it's empty
236
+ return nil if @n_remaining == 0
237
+ object_from_stream
238
+ end
239
+
240
+ def refill_via_get_more
241
+ if send_query_if_needed or @cursor_id == 0
242
+ return
243
+ end
244
+ @db._synchronize {
245
+ @db.send_to_db(GetMoreMessage.new(@admin ? 'admin' : @db.name, @collection.name, @cursor_id))
246
+ read_all
247
+ }
248
+ end
249
+
250
+ def object_from_stream
251
+ buf = ByteBuffer.new
252
+ buf.put_array(@db.receive_full(4).unpack("C*"))
253
+ buf.rewind
254
+ size = buf.get_int
255
+ buf.put_array(@db.receive_full(size - 4).unpack("C*"), 4)
256
+ @n_remaining -= 1
257
+ buf.rewind
258
+ BSON.new.deserialize(buf)
259
+ end
260
+
261
+ def send_query_if_needed
262
+ # Run query first time we request an object from the wire
263
+ if @query_run
264
+ false
265
+ else
266
+ @db._synchronize {
267
+ @db.send_query_message(QueryMessage.new(@admin ? 'admin' : @db.name, @collection.name, @query))
268
+ @query_run = true
197
269
  read_all
198
- end
199
-
200
- def object_from_stream
201
- buf = ByteBuffer.new
202
- buf.put_array(@db.socket.recv(4).unpack("C*"))
203
- buf.rewind
204
- size = buf.get_int
205
- buf.put_array(@db.socket.recv(size-4).unpack("C*"), 4)
206
- @n_remaining -= 1
207
- buf.rewind
208
- BSON.new(@db).deserialize(buf)
209
- end
210
-
211
- def send_query_if_needed
212
- # Run query first time we request an object from the wire
213
- unless @query_run
214
- hints = @hint_fields || @collection.hint_fields
215
- old_hints = @query.hint_fields
216
- @query.hint_fields = hints
217
- @db.send_query_message(QueryMessage.new(@db.name, @collection.name, @query))
218
- @query_run = true
219
- @query.hint_fields = old_hints
220
- read_all
221
- end
222
- end
223
-
224
- def to_s
225
- "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
226
- end
270
+ }
271
+ true
272
+ end
273
+ end
274
+
275
+ def to_s
276
+ "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
277
+ end
278
+
279
+ def check_modifiable
280
+ if @query_run || @closed
281
+ raise InvalidOperation, "Cannot modify the query once it has been run or closed."
227
282
  end
228
283
  end
229
284
  end