mongodb-mongo 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/README.rdoc +216 -0
  2. data/Rakefile +54 -0
  3. data/bin/mongo_console +21 -0
  4. data/bin/validate +51 -0
  5. data/examples/benchmarks.rb +38 -0
  6. data/examples/blog.rb +76 -0
  7. data/examples/index_test.rb +128 -0
  8. data/examples/simple.rb +17 -0
  9. data/lib/mongo/admin.rb +86 -0
  10. data/lib/mongo/collection.rb +161 -0
  11. data/lib/mongo/cursor.rb +230 -0
  12. data/lib/mongo/db.rb +399 -0
  13. data/lib/mongo/message/get_more_message.rb +21 -0
  14. data/lib/mongo/message/insert_message.rb +19 -0
  15. data/lib/mongo/message/kill_cursors_message.rb +20 -0
  16. data/lib/mongo/message/message.rb +68 -0
  17. data/lib/mongo/message/message_header.rb +34 -0
  18. data/lib/mongo/message/msg_message.rb +17 -0
  19. data/lib/mongo/message/opcodes.rb +16 -0
  20. data/lib/mongo/message/query_message.rb +67 -0
  21. data/lib/mongo/message/remove_message.rb +20 -0
  22. data/lib/mongo/message/update_message.rb +21 -0
  23. data/lib/mongo/message.rb +4 -0
  24. data/lib/mongo/mongo.rb +98 -0
  25. data/lib/mongo/query.rb +110 -0
  26. data/lib/mongo/types/binary.rb +34 -0
  27. data/lib/mongo/types/dbref.rb +37 -0
  28. data/lib/mongo/types/objectid.rb +137 -0
  29. data/lib/mongo/types/regexp_of_holding.rb +44 -0
  30. data/lib/mongo/types/undefined.rb +31 -0
  31. data/lib/mongo/util/bson.rb +431 -0
  32. data/lib/mongo/util/byte_buffer.rb +163 -0
  33. data/lib/mongo/util/ordered_hash.rb +68 -0
  34. data/lib/mongo/util/xml_to_ruby.rb +102 -0
  35. data/lib/mongo.rb +12 -0
  36. data/mongo-ruby-driver.gemspec +62 -0
  37. data/tests/test_admin.rb +60 -0
  38. data/tests/test_bson.rb +135 -0
  39. data/tests/test_byte_buffer.rb +69 -0
  40. data/tests/test_cursor.rb +66 -0
  41. data/tests/test_db.rb +85 -0
  42. data/tests/test_db_api.rb +354 -0
  43. data/tests/test_db_connection.rb +17 -0
  44. data/tests/test_message.rb +35 -0
  45. data/tests/test_objectid.rb +98 -0
  46. data/tests/test_ordered_hash.rb +85 -0
  47. data/tests/test_round_trip.rb +116 -0
  48. metadata +100 -0
@@ -0,0 +1,161 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
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.
7
+ #
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.
12
+ #
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
+ # ++
16
+
17
+ require 'mongo/query'
18
+
19
+ module XGen
20
+ module Mongo
21
+ module Driver
22
+
23
+ # A named collection of records in a database.
24
+ class Collection
25
+
26
+ attr_reader :db, :name, :hint_fields
27
+
28
+ def initialize(db, name)
29
+ @db = db
30
+ @name = name
31
+ end
32
+
33
+ # Set hint fields to use and return +self+. hint_fields may be a
34
+ # single field name, array of field names, or a hash whose keys will
35
+ # become the hint field names. May be +nil+.
36
+ def hint(hint_fields)
37
+ @hint_fields = case hint_fields
38
+ when String
39
+ [hint_fields]
40
+ when Hash
41
+ hint_fields.keys
42
+ when nil
43
+ nil
44
+ else
45
+ hint_fields.to_a
46
+ end
47
+ self
48
+ end
49
+
50
+ # Return records that match a +selector+ hash. See Mongo docs for
51
+ # details.
52
+ #
53
+ # Options:
54
+ # :fields :: Array of collection field names; only those will be returned (plus _id if defined)
55
+ # :offset :: Start at this record when returning records
56
+ # :limit :: Maximum number of records to return
57
+ # :sort :: Either hash of field names as keys and 1/-1 as values; 1 ==
58
+ # ascending, -1 == descending, or array of field names (all
59
+ # assumed to be sorted in ascending order).
60
+ def find(selector={}, options={})
61
+ fields = options.delete(:fields)
62
+ fields = nil if fields && fields.empty?
63
+ offset = options.delete(:offset) || 0
64
+ limit = options.delete(:limit) || 0
65
+ sort = options.delete(:sort)
66
+ raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty?
67
+ @db.query(self, Query.new(selector, fields, offset, limit, sort))
68
+ end
69
+
70
+ # Insert +objects+, which are hashes. "<<" is aliased to this method.
71
+ # Returns either the single inserted object or a new array containing
72
+ # +objects+. The object(s) may have been modified by the database's PK
73
+ # factory, if it has one.
74
+ def insert(*objects)
75
+ objects = objects.first if objects.size == 1 && objects.first.is_a?(Array)
76
+ res = @db.insert_into_db(@name, objects)
77
+ res.size > 1 ? res : res.first
78
+ end
79
+ alias_method :<<, :insert
80
+
81
+ # Remove the records that match +selector+.
82
+ def remove(selector={})
83
+ @db.remove_from_db(@name, selector)
84
+ end
85
+
86
+ # Remove all records.
87
+ def clear
88
+ remove({})
89
+ end
90
+
91
+ # Update records that match +selector+ by applying +obj+ as an update.
92
+ # If no match, inserts (???).
93
+ def repsert(selector, obj)
94
+ @db.repsert_in_db(@name, selector, obj)
95
+ end
96
+
97
+ # Update records that match +selector+ by applying +obj+ as an update.
98
+ def replace(selector, obj)
99
+ @db.replace_in_db(@name, selector, obj)
100
+ end
101
+
102
+ # Update records that match +selector+ by applying +obj+ as an update.
103
+ # Both +selector+ and +modifier_obj+ are required.
104
+ def modify(selector, modifier_obj)
105
+ raise "no object" unless modifier_obj
106
+ raise "no selector" unless selector
107
+ @db.modify_in_db(@name, selector, modifier_obj)
108
+ end
109
+
110
+ # Create a new index named +index_name+. +fields+ should be an array
111
+ # of field names.
112
+ def create_index(name, *fields)
113
+ @db.create_index(@name, name, fields)
114
+ end
115
+
116
+ # Drop index +name+.
117
+ def drop_index(name)
118
+ @db.drop_index(@name, name)
119
+ end
120
+
121
+ # Drop all indexes.
122
+ def drop_indexes
123
+ # just need to call drop indexes with no args; will drop them all
124
+ @db.drop_index(@name, '*')
125
+ end
126
+
127
+ # Drop the entire collection. USE WITH CAUTION.
128
+ def drop
129
+ @db.drop_collection(@name)
130
+ end
131
+
132
+ # Return an array of hashes, one for each index. Each hash contains:
133
+ #
134
+ # :name :: Index name
135
+ #
136
+ # :keys :: Hash whose keys are the names of the fields that make up
137
+ # the key and values are integers.
138
+ #
139
+ # :ns :: Namespace; same as this collection's name.
140
+ def index_information
141
+ @db.index_information(@name)
142
+ end
143
+
144
+ # Return a hash containing options that apply to this collection.
145
+ # 'create' will be the collection name. For the other possible keys
146
+ # and values, see DB#create_collection.
147
+ def options
148
+ @db.collections_info(@name).next_object()['options']
149
+ end
150
+
151
+ # Return the number of records that match +selector+. If +selector+ is
152
+ # +nil+ or an empty hash, returns the count of all records.
153
+ def count(selector={})
154
+ @db.count(@name, selector || {})
155
+ end
156
+
157
+ end
158
+ end
159
+ end
160
+ end
161
+
@@ -0,0 +1,230 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
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.
7
+ #
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.
12
+ #
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
+ # ++
16
+
17
+ require 'mongo/message'
18
+ require 'mongo/util/byte_buffer'
19
+ require 'mongo/util/bson'
20
+
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))
197
+ 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
227
+ end
228
+ end
229
+ end
230
+ end