mongo 0.0.1

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