mongo 0.1.0 → 0.15

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