mongo 0.0.1

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.
@@ -0,0 +1,103 @@
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 'socket'
18
+ require 'mongo/collection'
19
+ require 'mongo/message'
20
+
21
+ module XGen
22
+ module Mongo
23
+ module Driver
24
+
25
+ # A query against a collection. A query's selector is a hash. See the
26
+ # Mongo documentation for query details.
27
+ class Query
28
+
29
+ attr_accessor :number_to_skip, :number_to_return, :order_by
30
+ attr_reader :selector # writer defined below
31
+
32
+ # sel :: A hash describing the query. See the Mongo docs for details.
33
+ #
34
+ # return_fields :: If not +nil+, a single field name or an array of
35
+ # field names. Only those fields will be returned.
36
+ # (Called :fields in calls to Collection#find.)
37
+ #
38
+ # number_to_skip :: Number of records to skip before returning
39
+ # records. (Called :offset in calls to
40
+ # Collection#find.) Default is 0.
41
+ #
42
+ # number_to_return :: Max number of records to return. (Called :limit
43
+ # in calls to Collection#find.) Default is 0 (all
44
+ # records).
45
+ #
46
+ # order_by :: If not +nil+, specifies record sort order. May be a
47
+ # String, Hash, OrderedHash, or Array. If a string, the
48
+ # results will be ordered by that field in ascending
49
+ # order. If an array, it should be an array of field names
50
+ # which will all be sorted in ascending order. If a hash,
51
+ # it may be either a regular Hash or an OrderedHash. The
52
+ # keys should be field names, and the values should be 1
53
+ # (ascending) or -1 (descending). Note that if it is a
54
+ # regular Hash then sorting by more than one field
55
+ # probably will not be what you intend because key order
56
+ # is not preserved. (order_by is called :sort in calls to
57
+ # Collection#find.)
58
+ def initialize(sel={}, return_fields=nil, number_to_skip=0, number_to_return=0, order_by=nil)
59
+ @number_to_skip, @number_to_return, @order_by = number_to_skip, number_to_return, order_by
60
+ self.selector = sel
61
+ self.fields = return_fields
62
+ end
63
+
64
+ # Set query selector hash. If sel is a string, it will be used as a
65
+ # $where clause. (See Mongo docs for details.)
66
+ def selector=(sel)
67
+ @selector = case sel
68
+ when nil
69
+ {}
70
+ when String
71
+ {"$where" => sel}
72
+ when Hash
73
+ sel
74
+ end
75
+ end
76
+
77
+ # Set fields to return. If +val+ is +nil+ or empty, all fields will be
78
+ # returned.
79
+ def fields=(val)
80
+ @fields = val
81
+ @fields = nil if @fields && @fields.empty?
82
+ end
83
+
84
+ def fields
85
+ case @fields
86
+ when String
87
+ {@fields => 1}
88
+ when Array
89
+ if @fields.length == 0
90
+ nil
91
+ else
92
+ h = {}
93
+ @fields.each { |field| h[field] = 1 }
94
+ h
95
+ end
96
+ else # nil, anything else
97
+ nil
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,339 @@
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/util/byte_buffer'
18
+ require 'mongo/util/ordered_hash'
19
+ require 'mongo/objectid'
20
+
21
+ # A BSON seralizer/deserializer.
22
+ class BSON
23
+
24
+ MINKEY = -1
25
+ EOO = 0
26
+ NUMBER = 1
27
+ STRING = 2
28
+ OBJECT = 3
29
+ ARRAY = 4
30
+ BINARY = 5
31
+ UNDEFINED = 6
32
+ OID = 7
33
+ BOOLEAN = 8
34
+ DATE = 9
35
+ NULL = 10
36
+ REGEX = 11
37
+ REF = 12
38
+ CODE = 13
39
+ SYMBOL = 14
40
+ CODE_W_SCOPE = 15
41
+ NUMBER_INT = 16
42
+ MAXKEY = 127
43
+
44
+ def self.serialize_cstr(buf, val)
45
+ buf.put_array(val.to_s.unpack("C*") + [0])
46
+ end
47
+
48
+ def initialize
49
+ @buf = ByteBuffer.new
50
+ end
51
+
52
+ def to_a
53
+ @buf.to_a
54
+ end
55
+
56
+ def serialize(obj)
57
+ raise "Document is null" unless obj
58
+
59
+ @buf.rewind
60
+ # put in a placeholder for the total size
61
+ @buf.put_int(0)
62
+
63
+ obj.each {|k, v|
64
+ type = bson_type(v, k)
65
+ case type
66
+ when STRING, CODE
67
+ serialize_string_element(@buf, k, v, type)
68
+ when NUMBER, NUMBER_INT
69
+ serialize_number_element(@buf, k, v, type)
70
+ when OBJECT
71
+ serialize_object_element(@buf, k, v)
72
+ when OID
73
+ serialize_oid_element(@buf, k, v)
74
+ when ARRAY
75
+ serialize_array_element(@buf, k, v)
76
+ when REGEX
77
+ serialize_regex_element(@buf, k, v)
78
+ when BOOLEAN
79
+ serialize_boolean_element(@buf, k, v)
80
+ when DATE
81
+ serialize_date_element(@buf, k, v)
82
+ when NULL
83
+ serialize_null_element(@buf, k)
84
+ when BINARY, UNDEFINED, REF, SYMBOL, CODE_W_SCOPE
85
+ # TODO
86
+ raise "unimplemented type #{type}"
87
+ else
88
+ raise "unhandled type #{type}"
89
+ end
90
+ }
91
+ serialize_eoo_element(@buf)
92
+ @buf.put_int(@buf.size, 0)
93
+ self
94
+ end
95
+
96
+ def deserialize(buf=nil)
97
+ # If buf is nil, use @buf, assumed to contain already-serialized BSON.
98
+ # This is only true during testing.
99
+ @buf = ByteBuffer.new(buf.to_a) if buf
100
+ @buf.rewind
101
+ @buf.get_int # eat message size
102
+ doc = {}
103
+ while @buf.more?
104
+ type = @buf.get
105
+ case type
106
+ when STRING, CODE
107
+ key = deserialize_element_name(@buf)
108
+ doc[key] = deserialize_string_data(@buf)
109
+ when NUMBER
110
+ key = deserialize_element_name(@buf)
111
+ doc[key] = deserialize_number_data(@buf)
112
+ when NUMBER_INT
113
+ key = deserialize_element_name(@buf)
114
+ doc[key] = deserialize_number_int_data(@buf)
115
+ when OID
116
+ key = deserialize_element_name(@buf)
117
+ doc[key] = deserialize_oid_data(@buf)
118
+ when ARRAY
119
+ key = deserialize_element_name(@buf)
120
+ doc[key] = deserialize_array_data(@buf)
121
+ when REGEX
122
+ key = deserialize_element_name(@buf)
123
+ doc[key] = deserialize_regex_data(@buf)
124
+ when OBJECT
125
+ key = deserialize_element_name(@buf)
126
+ doc[key] = deserialize_object_data(@buf)
127
+ when BOOLEAN
128
+ key = deserialize_element_name(@buf)
129
+ doc[key] = deserialize_boolean_data(@buf)
130
+ when DATE
131
+ key = deserialize_element_name(@buf)
132
+ doc[key] = deserialize_date_data(@buf)
133
+ when NULL
134
+ key = deserialize_element_name(@buf)
135
+ doc[key] = nil
136
+ when BINARY, UNDEFINED, REF, SYMBOL, CODE_W_SCOPE
137
+ # TODO
138
+ raise "unimplemented type #{type}"
139
+ when EOO
140
+ break
141
+ else
142
+ raise "Unknown type #{type}, key = #{key}"
143
+ end
144
+ end
145
+ @buf.rewind
146
+ doc
147
+ end
148
+
149
+ def hex_dump
150
+ str = ''
151
+ @buf.to_a.each_with_index { |b,i|
152
+ if (i % 8) == 0
153
+ str << "\n" if i > 0
154
+ str << '%4d: ' % i
155
+ else
156
+ str << ' '
157
+ end
158
+ str << '%02X' % b
159
+ }
160
+ str
161
+ end
162
+
163
+ def deserialize_date_data(buf)
164
+ millisecs = buf.get_long()
165
+ Time.at(millisecs.to_f / 1000.0) # at() takes fractional seconds
166
+ end
167
+
168
+ def deserialize_boolean_data(buf)
169
+ buf.get == 1
170
+ end
171
+
172
+ def deserialize_number_data(buf)
173
+ buf.get_double
174
+ end
175
+
176
+ def deserialize_number_int_data(buf)
177
+ buf.get_int
178
+ end
179
+
180
+ def deserialize_object_data(buf)
181
+ size = buf.get_int
182
+ buf.position -= 4
183
+ BSON.new.deserialize(buf.get(size))
184
+ end
185
+
186
+ def deserialize_array_data(buf)
187
+ h = deserialize_object_data(buf)
188
+ a = []
189
+ h.each { |k, v| a[k.to_i] = v }
190
+ a
191
+ end
192
+
193
+ def deserialize_regex_data(buf)
194
+ str = deserialize_element_name(buf)
195
+ options_str = deserialize_element_name(buf)
196
+ options = 0
197
+ options |= Regexp::IGNORECASE if options_str.include?('i')
198
+ options |= Regexp::MULTILINE if options_str.include?('m')
199
+ options |= Regexp::EXTENDED if options_str.include?('x')
200
+ Regexp.new(str, options)
201
+ end
202
+
203
+ def deserialize_string_data(buf)
204
+ len = buf.get_int
205
+ bytes = buf.get(len)
206
+ bytes[0..-2].pack("C*")
207
+ end
208
+
209
+ def deserialize_oid_data(buf)
210
+ XGen::Mongo::Driver::ObjectID.new(buf.get(12))
211
+ end
212
+
213
+ def serialize_eoo_element(buf)
214
+ buf.put(EOO)
215
+ end
216
+
217
+ def serialize_null_element(buf, key)
218
+ buf.put(NULL)
219
+ self.class.serialize_cstr(buf, key)
220
+ end
221
+
222
+ def serialize_boolean_element(buf, key, val)
223
+ buf.put(BOOLEAN)
224
+ self.class.serialize_cstr(buf, key)
225
+ buf.put(val ? 1 : 0)
226
+ end
227
+
228
+ def serialize_date_element(buf, key, val)
229
+ buf.put(DATE)
230
+ self.class.serialize_cstr(buf, key)
231
+ millisecs = (val.to_f * 1000).to_i
232
+ buf.put_long(millisecs)
233
+ end
234
+
235
+ def serialize_number_element(buf, key, val, type)
236
+ buf.put(type)
237
+ self.class.serialize_cstr(buf, key)
238
+ if type == NUMBER
239
+ buf.put_double(val)
240
+ else
241
+ buf.put_int(val)
242
+ end
243
+ end
244
+
245
+ def serialize_object_element(buf, key, val, opcode=OBJECT)
246
+ buf.put(opcode)
247
+ self.class.serialize_cstr(buf, key)
248
+ buf.put_array(BSON.new.serialize(val).to_a)
249
+ end
250
+
251
+ def serialize_array_element(buf, key, val)
252
+ # Turn array into hash with integer indices as keys
253
+ h = OrderedHash.new
254
+ i = 0
255
+ val.each { |v| h[i] = v; i += 1 }
256
+ serialize_object_element(buf, key, h, ARRAY)
257
+ end
258
+
259
+ def serialize_regex_element(buf, key, val)
260
+ buf.put(REGEX)
261
+ self.class.serialize_cstr(buf, key)
262
+
263
+ str = val.to_s.sub(/.*?:/, '')[0..-2] # Turn "(?xxx:yyy)" into "yyy"
264
+ self.class.serialize_cstr(buf, str)
265
+
266
+ options = val.options
267
+ options_str = ''
268
+ options_str << 'i' if ((options & Regexp::IGNORECASE) != 0)
269
+ options_str << 'm' if ((options & Regexp::MULTILINE) != 0)
270
+ options_str << 'x' if ((options & Regexp::EXTENDED) != 0)
271
+ self.class.serialize_cstr(buf, options_str)
272
+ end
273
+
274
+ def serialize_oid_element(buf, key, val)
275
+ buf.put(OID)
276
+ self.class.serialize_cstr(buf, key)
277
+
278
+ buf.put_array(val.to_a)
279
+ end
280
+
281
+ def serialize_string_element(buf, key, val, type)
282
+ buf.put(type)
283
+ self.class.serialize_cstr(buf, key)
284
+
285
+ # Make a hole for the length
286
+ len_pos = buf.position
287
+ buf.put_int(0)
288
+
289
+ # Save the string
290
+ start_pos = buf.position
291
+ self.class.serialize_cstr(buf, val)
292
+ end_pos = buf.position
293
+
294
+ # Put the string size in front
295
+ buf.put_int(end_pos - start_pos, len_pos)
296
+
297
+ # Go back to where we were
298
+ buf.position = end_pos
299
+ end
300
+
301
+ def deserialize_element_name(buf)
302
+ chars = ""
303
+ while 1
304
+ b = buf.get
305
+ break if b == 0
306
+ chars << b.chr
307
+ end
308
+ chars
309
+ end
310
+
311
+ def bson_type(o, key)
312
+ case o
313
+ when nil
314
+ NULL
315
+ when Integer
316
+ NUMBER_INT
317
+ when Numeric
318
+ NUMBER
319
+ when String
320
+ # magic awful stuff - the DB requires that a where clause is sent as CODE
321
+ key == "$where" ? CODE : STRING
322
+ when Array
323
+ ARRAY
324
+ when Regexp
325
+ REGEX
326
+ when XGen::Mongo::Driver::ObjectID
327
+ OID
328
+ when true, false
329
+ BOOLEAN
330
+ when Time
331
+ DATE
332
+ when Hash
333
+ OBJECT
334
+ else
335
+ raise "Unknown type of object: #{o.class.name}"
336
+ end
337
+ end
338
+
339
+ end
@@ -0,0 +1,163 @@
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
+ # A byte buffer.
18
+ class ByteBuffer
19
+
20
+ attr_reader :order
21
+
22
+ def initialize(initial_data=[])
23
+ @buf = initial_data
24
+ @cursor = 0
25
+ self.order = :little_endian
26
+ end
27
+
28
+ # +endianness+ should be :little_endian or :big_endian. Default is :little_endian
29
+ def order=(endianness)
30
+ @order = endianness
31
+ @int_pack_order = endianness == :little_endian ? 'V' : 'N'
32
+ @double_pack_order = endianness == :little_endian ? 'E' : 'G'
33
+ end
34
+
35
+ def rewind
36
+ @cursor = 0
37
+ end
38
+
39
+ def position
40
+ @cursor
41
+ end
42
+
43
+ def position=(val)
44
+ @cursor = val
45
+ end
46
+
47
+ def clear
48
+ @buf = []
49
+ rewind
50
+ end
51
+
52
+ def size
53
+ @buf.size
54
+ end
55
+ alias_method :length, :size
56
+
57
+ def put(byte, offset=nil)
58
+ @cursor = offset if offset
59
+ @buf[@cursor] = byte
60
+ @cursor += 1
61
+ end
62
+
63
+ def put_array(array, offset=nil)
64
+ @cursor = offset if offset
65
+ @buf[@cursor, array.length] = array
66
+ @cursor += array.length
67
+ end
68
+
69
+ if RUBY_VERSION >= '1.9'
70
+ def put_int(i, offset=nil)
71
+ put_array([i].pack(@int_pack_order).split(//).collect{|c| c.bytes.first}, offset)
72
+ end
73
+ else
74
+ def put_int(i, offset=nil)
75
+ put_array([i].pack(@int_pack_order).split(//).collect{|c| c[0]}, offset)
76
+ end
77
+ end
78
+
79
+ def put_long(i, offset=nil)
80
+ offset = @cursor unless offset
81
+ if @int_pack_order == 'N'
82
+ put_int(i >> 32, offset)
83
+ put_int(i & 0xffffffff, offset + 4)
84
+ else
85
+ put_int(i & 0xffffffff, offset)
86
+ put_int(i >> 32, offset + 4)
87
+ end
88
+ end
89
+
90
+ if RUBY_VERSION >= '1.9'
91
+ def put_double(d, offset=nil)
92
+ put_array([d].pack(@double_pack_order).split(//).collect{|c| c.bytes.first}, offset)
93
+ end
94
+ else
95
+ def put_double(d, offset=nil)
96
+ put_array([d].pack(@double_pack_order).split(//).collect{|c| c[0]}, offset)
97
+ end
98
+ end
99
+
100
+ # If +size+ == nil, returns one byte. Else returns array of bytes of length
101
+ # # +size+.
102
+ def get(len=nil)
103
+ one_byte = len.nil?
104
+ len ||= 1
105
+ check_read_length(len)
106
+ start = @cursor
107
+ @cursor += len
108
+ if one_byte
109
+ @buf[start]
110
+ else
111
+ @buf[start, len]
112
+ end
113
+ end
114
+
115
+ def get_int
116
+ check_read_length(4)
117
+ vals = ""
118
+ (@cursor..@cursor+3).each { |i| vals << @buf[i].chr }
119
+ @cursor += 4
120
+ vals.unpack(@int_pack_order)[0]
121
+ end
122
+
123
+ def get_long
124
+ i1 = get_int
125
+ i2 = get_int
126
+ if @int_pack_order == 'N'
127
+ (i1 << 32) + i2
128
+ else
129
+ (i2 << 32) + i1
130
+ end
131
+ end
132
+
133
+ def get_double
134
+ check_read_length(8)
135
+ vals = ""
136
+ (@cursor..@cursor+7).each { |i| vals << @buf[i].chr }
137
+ @cursor += 8
138
+ vals.unpack(@double_pack_order)[0]
139
+ end
140
+
141
+ def more?
142
+ @cursor < @buf.size
143
+ end
144
+
145
+ def to_a
146
+ @buf
147
+ end
148
+
149
+ def to_s
150
+ @buf.pack("C*")
151
+ end
152
+
153
+ def dump
154
+ @buf.each_with_index { |c, i| $stderr.puts "#{'%04d' % i}: #{'%02x' % c} #{'%03o' % c} #{'%s' % c.chr} #{'%3d' % c}" }
155
+ end
156
+
157
+ private
158
+
159
+ def check_read_length(len)
160
+ raise "attempt to read past end of buffer" if @cursor + len > @buf.length
161
+ end
162
+
163
+ end