mongodb-mongo 0.1.3

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 (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