em-mongo 0.1.1 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,7 +15,7 @@ module EM::Mongo
15
15
  skip = opts.delete(:skip) || 0
16
16
  limit = opts.delete(:limit) || 0
17
17
 
18
- @connection.find(@name, skip, limit, selector, &blk)
18
+ @connection.find(@name, skip, limit, selector, nil, &blk)
19
19
  end
20
20
 
21
21
  def first(selector={}, opts={}, &blk)
@@ -26,7 +26,7 @@ module EM::Mongo
26
26
  end
27
27
 
28
28
  def insert(obj)
29
- obj[:_id] ||= EM::Mongo::Util.unique_id
29
+ obj['_id'] ||= EM::Mongo::Util.unique_id
30
30
  @connection.insert(@name, obj)
31
31
  obj
32
32
  end
@@ -36,11 +36,5 @@ module EM::Mongo
36
36
  true
37
37
  end
38
38
 
39
- #def method_missing meth
40
- # puts meth
41
- # raise ArgumentError, 'collection cannot take block' if block_given?
42
- # (@subns ||= {})[meth] ||= self.class.new("#{@ns}.#{meth}", @client)
43
- #end
44
-
45
39
  end
46
40
  end
@@ -19,6 +19,9 @@ module EM::Mongo
19
19
  OP_INSERT = 2002
20
20
  OP_QUERY = 2004
21
21
  OP_DELETE = 2006
22
+
23
+ STANDARD_HEADER_SIZE = 16
24
+ RESPONSE_HEADER_SIZE = 20
22
25
 
23
26
  attr_reader :connection
24
27
 
@@ -30,56 +33,73 @@ module EM::Mongo
30
33
  @is_connected
31
34
  end
32
35
 
33
- # RMongo interface
36
+ # XXX RMongo interface
34
37
  def collection(db = DEFAULT_DB, ns = DEFAULT_NS)
35
38
  raise "Not connected" if not connected?
36
39
  EM::Mongo::Collection.new(db, ns, self)
37
40
  end
38
41
 
42
+ def new_request_id
43
+ @request_id += 1
44
+ end
45
+
39
46
  # MongoDB Commands
40
47
 
41
- def send_command(id, *args, &blk)
42
- request_id = @request_id += 1
48
+ def message_headers(operation, request_id, message)
49
+ headers = BSON::ByteBuffer.new
50
+ headers.put_int(16 + message.size)
51
+ headers.put_int(request_id)
52
+ headers.put_int(0)
53
+ headers.put_int(operation)
54
+ headers
55
+ end
43
56
 
44
- callback {
45
- buf = Buffer.new
46
- buf.write :int, request_id,
47
- :int, response = 0,
48
- :int, operation = id
57
+ def send_command(buffer, request_id, &blk)
49
58
 
50
- buf.write *args
51
- send_data [ buf.size + 4 ].pack('i') # header length first
52
- send_data buf.data
53
- }
59
+ callback do
60
+ send_data buffer
61
+ end
54
62
 
55
63
  @responses[request_id] = blk if blk
56
64
  request_id
57
65
  end
58
66
 
67
+
59
68
  def insert(collection_name, documents)
60
- # XXX multiple documents?
61
- send_command(OP_INSERT, :int, RESERVED,
62
- :cstring, collection_name,
63
- :bson, documents)
69
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
70
+ BSON::BSON_RUBY.serialize_cstr(message, collection_name)
71
+
72
+ documents = [documents] if not documents.is_a?(Array)
73
+ documents.each { |doc| message.put_array(BSON::BSON_CODER.serialize(doc, true, true).to_a) }
74
+
75
+ req_id = new_request_id
76
+ message.prepend!(message_headers(OP_INSERT, req_id, message))
77
+ send_command(message.to_s, req_id)
64
78
  end
65
79
 
66
80
  def delete(collection_name, selector)
67
- send_command(OP_DELETE, :int, RESERVED,
68
- :cstring, collection_name,
69
- :int, RESERVED,
70
- :bson, selector)
81
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
82
+ BSON::BSON_RUBY.serialize_cstr(message, collection_name)
83
+ message.put_int(0)
84
+ message.put_array(BSON::BSON_CODER.serialize(selector, false, true).to_a)
85
+ req_id = new_request_id
86
+ message.prepend!(message_headers(OP_DELETE, req_id, message))
87
+ send_command(message.to_s, req_id)
71
88
  end
72
89
 
73
- def find(collection_name, skip, limit, query, &blk)
74
- send_command(OP_QUERY, :int, RESERVED,
75
- :cstring, collection_name,
76
- :int, skip,
77
- :int, limit,
78
- :bson, query,
79
- &blk)
90
+ def find(collection_name, skip, limit, query, fields, &blk)
91
+ message = BSON::ByteBuffer.new
92
+ message.put_int(RESERVED) # query options
93
+ BSON::BSON_RUBY.serialize_cstr(message, collection_name)
94
+ message.put_int(skip)
95
+ message.put_int(limit)
96
+ message.put_array(BSON::BSON_CODER.serialize(query, false).to_a)
97
+ message.put_array(BSON::BSON_CODER.serialize(fields, false).to_a) if fields
98
+ req_id = new_request_id
99
+ message.prepend!(message_headers(OP_QUERY, req_id, message))
100
+ send_command(message.to_s, req_id, &blk)
80
101
  end
81
102
 
82
-
83
103
  # EM hooks
84
104
  def initialize(options={})
85
105
  @request_id = 0
@@ -88,7 +108,7 @@ module EM::Mongo
88
108
  @host = options[:host] || DEFAULT_IP
89
109
  @port = options[:port] || DEFAULT_PORT
90
110
 
91
- @on_close = proc{
111
+ @on_close = proc {
92
112
  raise Error, "could not connect to server #{@host}:#{@port}"
93
113
  }
94
114
  timeout options[:timeout] if options[:timeout]
@@ -101,36 +121,82 @@ module EM::Mongo
101
121
  end
102
122
 
103
123
  def connection_completed
104
- log 'connected'
105
- @buf = Buffer.new
124
+ @buffer = BSON::ByteBuffer.new
106
125
  @is_connected = true
107
126
  @on_close = proc{
108
127
  }
109
128
  succeed
110
129
  end
111
130
 
112
- def receive_data data
113
- log "receive_data: #{data.size}"#, data
114
131
 
115
- @buf << data
132
+ def message_received?
133
+ size = @buffer.get_int
134
+ @buffer.rewind
135
+ @buffer.size >= size-4 ? true : false
136
+ end
137
+
138
+ def receive_data(data)
139
+
140
+ @buffer.put_array(data.unpack('C*'))
141
+ @buffer.rewind
142
+ return if @buffer.size < STANDARD_HEADER_SIZE
143
+
144
+ if message_received?
145
+
146
+ # Header
147
+ header = BSON::ByteBuffer.new
148
+ header.put_array(@buffer.get(STANDARD_HEADER_SIZE))
149
+ unless header.size == STANDARD_HEADER_SIZE
150
+ raise "Short read for DB header: " +
151
+ "expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
152
+ end
153
+ header.rewind
154
+ size = header.get_int
155
+ request_id = header.get_int
156
+ response_to = header.get_int
157
+ op = header.get_int
158
+
159
+ # Response Header
160
+ response_header = BSON::ByteBuffer.new
161
+ response_header.put_array(@buffer.get(RESPONSE_HEADER_SIZE))
162
+ if response_header.length != RESPONSE_HEADER_SIZE
163
+ raise "Short read for DB response header; " +
164
+ "expected #{RESPONSE_HEADER_SIZE} bytes, saw #{response_header.length}"
165
+ end
166
+
167
+ response_header.rewind
168
+ result_flags = response_header.get_int
169
+ cursor_id = response_header.get_long
170
+ starting_from = response_header.get_int
171
+ number_remaining = response_header.get_int
116
172
 
117
- until @buf.empty?
118
- size = @buf._peek(0, 4, 'I')
173
+ # Documents
174
+ docs = (1..number_remaining).map do |n|
119
175
 
120
- break unless @buf.size >= size-4
176
+ buf = BSON::ByteBuffer.new
177
+ buf.put_int(@buffer.get_int)
121
178
 
122
- size, id, response, operation = @buf.read(:int, :int, :int, :int)
123
- reserved, cursor, start, num = @buf.read(:int, :longlong, :int, :int)
179
+ buf.rewind
180
+ size = buf.get_int
124
181
 
125
- results = (1..num).map do
126
- @buf.read(:bson)
182
+ if size > @buffer.size
183
+ @buffer = ''
184
+ raise "Buffer Overflow: Failed to parse buffer"
185
+ end
186
+ buf.put_array(@buffer.get(size-4), 4)
187
+
188
+ buf.rewind
189
+ BSON::BSON_CODER.deserialize(buf)
127
190
  end
128
191
 
129
- if cb = @responses.delete(response)
130
- cb.call(results)
192
+ @buffer.clear
193
+
194
+ if cb = @responses.delete(response_to)
195
+ cb.call(docs)
131
196
  end
132
- close_connection if @close_pending and @responses.size == 0
197
+ close_connection if @close_pending and @responses.size == 0
133
198
  end
199
+
134
200
  end
135
201
 
136
202
  def send_data data
data/lib/em-mongo.rb CHANGED
@@ -1,30 +1,29 @@
1
- require 'eventmachine'
2
- require 'pp'
3
1
 
4
- begin
5
- require 'securerandom'
6
- rescue LoadError
7
- require 'uuid'
8
- end
2
+ require "eventmachine"
3
+ begin; require "bson_ext"; rescue LoadError; require "bson"; end
4
+
9
5
 
10
6
  module EM::Mongo
11
- VERSION = '0.0.1'
7
+
8
+ module Version
9
+ MAJOR = 0
10
+ MINOR = 2
11
+ TINY = 4
12
+ STRING = [MAJOR, MINOR, TINY].join('.')
13
+ end
14
+
15
+ NAME = 'em-mongo'
12
16
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
13
- PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
17
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
14
18
 
15
19
  class Util
16
20
  def self.unique_id
17
- if defined? SecureRandom
18
- SecureRandom.hex(12)
19
- else
20
- UUID.new.generate(:compact).gsub(/^(.{20})(.{8})(.{4})$/){ $1+$3 }
21
- end
21
+ BSON::ObjectID.new.to_s
22
22
  end
23
23
  end
24
24
  end
25
25
 
26
26
  require File.join(EM::Mongo::LIBPATH, "em-mongo/connection")
27
- require File.join(EM::Mongo::LIBPATH, "em-mongo/buffer")
28
27
  require File.join(EM::Mongo::LIBPATH, "em-mongo/collection")
29
28
 
30
29
  EMMongo = EM::Mongo
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
2
 
3
3
  describe EMMongo::Collection do
4
4
  include EM::SpecHelper
@@ -22,20 +22,31 @@ describe EMMongo::Collection do
22
22
 
23
23
  it 'should insert an object' do
24
24
  EM::Spec::Mongo.collection do |collection|
25
- obj = collection.insert(:hello => 'world')
26
- obj.keys.should include :_id
27
- obj[:_id].should be_a_kind_of String
28
- obj[:_id].length.should == 24
25
+ obj = collection.insert('hello' => 'world')
26
+ obj.keys.should include '_id'
27
+ obj['_id'].should be_a_kind_of String
28
+ obj['_id'].length.should == 24
29
29
  EM::Spec::Mongo.close
30
30
  end
31
31
  end
32
32
 
33
- it 'should find an object' do
33
+ it 'should find an object by attribute' do
34
34
  EM::Spec::Mongo.collection do |collection|
35
- collection.insert(:hello => 'world')
36
- r = collection.find({:hello => "world"},{}) do |res|
35
+ collection.insert("hello" => 'world')
36
+ r = collection.find({"hello" => "world"},{}) do |res|
37
37
  res.size.should >= 1
38
- res[0][:hello].should == "world"
38
+ res[0]["hello"].should == "world"
39
+ EM::Spec::Mongo.close
40
+ end
41
+ end
42
+ end
43
+
44
+ it 'should find an object by id' do
45
+ EM::Spec::Mongo.collection do |collection|
46
+ obj = collection.insert('hello' => 'world')
47
+ collection.find({'_id' => obj['_id']},{}) do |res|
48
+ res.size.should >= 1
49
+ res[0]['hello'].should == "world"
39
50
  EM::Spec::Mongo.close
40
51
  end
41
52
  end
@@ -43,8 +54,8 @@ describe EMMongo::Collection do
43
54
 
44
55
  it 'should find all objects' do
45
56
  EM::Spec::Mongo.collection do |collection|
46
- collection.insert(:one => 'one')
47
- collection.insert(:two => 'two')
57
+ collection.insert('one' => 'one')
58
+ o = collection.insert('two' => 'two')
48
59
  collection.find do |res|
49
60
  res.size.should >= 2
50
61
  EM::Spec::Mongo.close
@@ -54,9 +65,9 @@ describe EMMongo::Collection do
54
65
 
55
66
  it 'should remove an object' do
56
67
  EM::Spec::Mongo.collection do |collection|
57
- obj = collection.insert(:hello => 'world')
58
- collection.remove(obj)
59
- collection.find({:hello => "world"}) do |res|
68
+ obj = collection.insert('hello' => 'world')
69
+ collection.remove('_id' => obj['_id'])
70
+ collection.find({'hello' => "world"}) do |res|
60
71
  res.size.should == 0
61
72
  EM::Spec::Mongo.close
62
73
  end
@@ -65,8 +76,8 @@ describe EMMongo::Collection do
65
76
 
66
77
  it 'should remove all objects' do
67
78
  EM::Spec::Mongo.collection do |collection|
68
- collection.insert(:one => 'one')
69
- collection.insert(:two => 'two')
79
+ collection.insert('one' => 'one')
80
+ collection.insert('two' => 'two')
70
81
  collection.remove
71
82
  collection.find do |res|
72
83
  res.size.should == 0
@@ -75,37 +86,52 @@ describe EMMongo::Collection do
75
86
  end
76
87
  end
77
88
 
89
+ it 'should insert a Time' do
90
+ EM::Spec::Mongo.collection do |collection|
91
+ t = Time.now.utc.freeze
92
+ collection.insert('date' => t)
93
+ collection.find do |res|
94
+ res[0]['date'].to_s.should == t.to_s
95
+ EM::Spec::Mongo.close
96
+ end
97
+ end
98
+ end
99
+
78
100
  it 'should insert a complex object' do
79
101
  EM::Spec::Mongo.collection do |collection|
80
102
  obj = {
81
- :array => [1,2,3],
82
- :float => 123.456,
83
- :hash => {:boolean => true},
84
- :nil => nil,
85
- :symbol => :name,
86
- :string => 'hello world',
87
- :time => Time.at(Time.now.to_i),
88
- :regex => /abc$/ix
103
+ 'array' => [1,2,3],
104
+ 'float' => 123.456,
105
+ 'hash' => {'boolean' => true},
106
+ 'nil' => nil,
107
+ 'symbol' => :name,
108
+ 'string' => 'hello world',
109
+ 'time' => Time.now.to_f,
110
+ 'regex' => /abc$/ix
89
111
  }
90
- obj = collection.insert(obj)
91
- collection.find(:_id => obj[:_id]) do |ret|
92
- ret.should == [ obj ]
112
+ retobj = collection.insert(obj)
113
+ collection.find({'_id' => obj['_id']}) do |ret|
114
+ ret.size.should == 1
115
+ ret[0].each_key do |key|
116
+ ret[0][key].should == obj[key]
117
+ end
93
118
  EM::Spec::Mongo.close
94
119
  end
120
+
95
121
  end
96
122
  end
97
123
 
98
124
  it 'should find an object using nested properties' do
99
125
  EM::Spec::Mongo.collection do |collection|
100
126
  collection.insert({
101
- :name => 'Google',
102
- :address => {
103
- :city => 'Mountain View',
104
- :state => 'California'}
127
+ 'name' => 'Google',
128
+ 'address' => {
129
+ 'city' => 'Mountain View',
130
+ 'state' => 'California'}
105
131
  })
106
132
 
107
133
  collection.first('address.city' => 'Mountain View') do |res|
108
- res[:name].should == 'Google'
134
+ res['name'].should == 'Google'
109
135
  EM::Spec::Mongo.close
110
136
  end
111
137
  end
@@ -114,12 +140,12 @@ describe EMMongo::Collection do
114
140
  it 'should find objects with specific values' do
115
141
  EM::Spec::Mongo.collection do |collection|
116
142
  @numbers.each do |num, word|
117
- collection.insert(:num => num, :word => word)
143
+ collection.insert({'num' => num, 'word' => word})
118
144
  end
119
145
 
120
- collection.find({:num => {'$in' => [1,3,5]}}) do |res|
146
+ collection.find({'num' => {'$in' => [1,3,5]}}) do |res|
121
147
  res.size.should == 3
122
- res.map{|r| r[:num] }.sort.should == [1,3,5]
148
+ res.map{|r| r['num'] }.sort.should == [1,3,5]
123
149
  EM::Spec::Mongo.close
124
150
  end
125
151
  end
@@ -128,12 +154,12 @@ describe EMMongo::Collection do
128
154
  it 'should find objects greater than something' do
129
155
  EM::Spec::Mongo.collection do |collection|
130
156
  @numbers.each do |num, word|
131
- collection.insert(:num => num, :word => word)
157
+ collection.insert('num' => num, 'word' => word)
132
158
  end
133
159
 
134
- collection.find({:num => {'$gt' => 3}}) do |res|
160
+ collection.find({'num' => {'$gt' => 3}}) do |res|
135
161
  res.size.should == 6
136
- res.map{|r| r[:num] }.sort.should == [4,5,6,7,8,9]
162
+ res.map{|r| r['num'] }.sort.should == [4,5,6,7,8,9]
137
163
  EM::Spec::Mongo.close
138
164
  end
139
165
  end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
2
 
3
3
  describe EMMongo::Connection do
4
4
  include EM::SpecHelper
@@ -32,7 +32,7 @@ describe EMMongo::Connection do
32
32
  # Support the old RMongo interface for now
33
33
  it 'should instantiate a Collection' do
34
34
  EM::Spec::Mongo.connection do |connection|
35
- connection.collection.is_a?(Collection).should == true
35
+ connection.collection.is_a?(EM::Mongo::Collection).should == true
36
36
  EM::Spec::Mongo.close
37
37
  end
38
38
  end
@@ -40,9 +40,9 @@ describe EMMongo::Connection do
40
40
  it 'should instantiate a Databse' do
41
41
  EM::Spec::Mongo.connection do |connection|
42
42
  db1 = connection.db
43
- db1.is_a?(Database).should == true
43
+ db1.is_a?(EM::Mongo::Database).should == true
44
44
  db2 = connection.db('db2')
45
- db2.is_a?(Database).should == true
45
+ db2.is_a?(EM::Mongo::Database).should == true
46
46
  db2.should_not == db1
47
47
  EM::Spec::Mongo.close
48
48
  end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,9 @@
1
- require File.dirname(__FILE__) + '/../lib/em-mongo'
1
+ require "rubygems"
2
+ require "bundler"
2
3
 
3
- #$LOAD_PATH << File.dirname(__FILE__)+'/../../em-spec/lib'
4
- #require File.dirname(__FILE__)+'/../../em-spec/lib/em/spec'
5
- #require 'spec'
6
- #require File.dirname(__FILE__)+'/../../em-spec/lib/em/spec/rspec'
7
- #EM.spec_backend = EventMachine::Spec::Rspec
4
+ Bundler.setup(:test)
5
+
6
+ require File.expand_path('../lib/em-mongo', File.dirname(__FILE__))
8
7
 
9
8
  require "em-spec/rspec"
10
9
 
metadata CHANGED
@@ -4,17 +4,17 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
- - 1
9
- version: 0.1.1
7
+ - 2
8
+ - 4
9
+ version: 0.2.4
10
10
  platform: ruby
11
11
  authors:
12
- - tmm1, bcg
12
+ - bcg
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-03 00:00:00 -05:00
17
+ date: 2010-04-19 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -31,8 +31,22 @@ dependencies:
31
31
  version: 0.12.10
32
32
  type: :runtime
33
33
  version_requirements: *id001
34
- description: em-mongo
35
- email:
34
+ - !ruby/object:Gem::Dependency
35
+ name: bson
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ - 20
44
+ - 1
45
+ version: 0.20.1
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: EventMachine drive for MongoDB.
49
+ email: brenden.grace@gmail.com
36
50
  executables: []
37
51
 
38
52
  extensions: []
@@ -40,7 +54,6 @@ extensions: []
40
54
  extra_rdoc_files: []
41
55
 
42
56
  files:
43
- - lib/em-mongo/buffer.rb
44
57
  - lib/em-mongo/collection.rb
45
58
  - lib/em-mongo/connection.rb
46
59
  - lib/em-mongo.rb
@@ -73,9 +86,8 @@ rubyforge_project:
73
86
  rubygems_version: 1.3.6
74
87
  signing_key:
75
88
  specification_version: 3
76
- summary: em-mongo based on rmongo
89
+ summary: EventMachine drive for MongoDB.
77
90
  test_files:
78
- - spec/buffer_spec.rb
79
91
  - spec/collection_spec.rb
80
92
  - spec/connection_spec.rb
81
93
  - spec/spec_helper.rb
@@ -1,414 +0,0 @@
1
- if [].map.respond_to? :with_index
2
- class Array
3
- def enum_with_index
4
- each.with_index
5
- end
6
- end
7
- else
8
- require 'enumerator'
9
- end
10
-
11
- module EM::Mongo
12
- class Buffer
13
- class Overflow < Exception; end
14
- class InvalidType < Exception; end
15
-
16
- def initialize data = ''
17
- @data = data
18
- @pos = 0
19
- end
20
- attr_reader :pos
21
-
22
- def data
23
- @data.clone
24
- end
25
- alias :contents :data
26
- alias :to_s :data
27
-
28
- def << data
29
- @data << data.to_s
30
- self
31
- end
32
-
33
- def length
34
- @data.length
35
- end
36
- alias :size :length
37
-
38
- def empty?
39
- pos == length
40
- end
41
-
42
- def rewind
43
- @pos = 0
44
- end
45
-
46
- def read *types
47
- values = types.map do |type|
48
- case type
49
- when :byte
50
- _read(1, 'C')
51
- when :short
52
- _read(2, 'n')
53
- when :int
54
- _read(4, 'I')
55
- when :double
56
- _read(8, 'd')
57
- when :long
58
- _read(4, 'N')
59
- when :longlong
60
- upper, lower = _read(8, 'NN')
61
- upper << 32 | lower
62
- when :cstring
63
- str = @data.unpack("@#{@pos}Z*").first
64
- @data.slice!(@pos, str.size+1)
65
- str
66
- when :oid
67
- # _read(12, 'i*').enum_with_index.inject([]) do |a, (num, i)|
68
- # a[ i == 0 ? 1 : i == 1 ? 0 : i ] = num # swap first two
69
- # a
70
- # end.map do |num|
71
- # num.to_s(16).rjust(8,'0')
72
- # end.join('')
73
-
74
- _read(12, 'I*').map do |num|
75
- num.to_s(16).rjust(8,'0')
76
- end.join('')
77
- when :bson
78
- bson = {}
79
- data = Buffer.new _read(read(:int)-4)
80
-
81
- until data.empty?
82
- type = data.read(:byte)
83
- next if type == 0 # end of object
84
-
85
- key = data.read(:cstring).intern
86
-
87
- bson[key] = case type
88
- when 1 # number
89
- data.read(:double)
90
- when 2 # string
91
- data.read(:int)
92
- data.read(:cstring)
93
- when 3 # object
94
- data.read(:bson)
95
- when 4 # array
96
- data.read(:bson).inject([]){ |a, (k,v)| a[k.to_s.to_i] = v; a }
97
- when 5 # binary
98
- data._read data.read(:int)
99
- when 6 # undefined
100
- when 7 # oid
101
- data.read(:oid)
102
- when 8 # bool
103
- data.read(:byte) == 1 ? true : false
104
- when 9 # time
105
- Time.at data.read(:longlong)/1000.0
106
- when 10 # nil
107
- nil
108
- when 11 # regex
109
- source = data.read(:cstring)
110
- options = data.read(:cstring).split('')
111
-
112
- options = { 'i' => 1, 'm' => 2, 'x' => 4 }.inject(0) do |s, (o, n)|
113
- s |= n if options.include?(o)
114
- s
115
- end
116
-
117
- Regexp.new(source, options)
118
- when 12 # ref
119
- ref = {}
120
- ref[:_ns] = data.read(:cstring)
121
- ref[:_id] = data.read(:oid)
122
- ref
123
- when 13 # code
124
- data.read(:int)
125
- data.read(:cstring)
126
- when 14 # symbol
127
- data.read(:int)
128
- data.read(:cstring).intern
129
- end
130
- end
131
-
132
- bson
133
- else
134
- raise InvalidType, "Cannot read data of type #{type}"
135
- end
136
- end
137
-
138
- types.size == 1 ? values.first : values
139
- end
140
-
141
- def write *args
142
- args.each_slice(2) do |type, data|
143
- case type
144
- when :byte
145
- _write(data, 'C')
146
- when :short
147
- _write(data, 'n')
148
- when :int
149
- _write(data, 'I')
150
- when :double
151
- _write(data, 'd')
152
- when :long
153
- _write(data, 'N')
154
- when :longlong
155
- lower = data & 0xffffffff
156
- upper = (data & ~0xffffffff) >> 32
157
- _write([upper, lower], 'NN')
158
- when :cstring
159
- _write(data.to_s + "\0")
160
- when :oid
161
- # data.scan(/.{8}/).enum_with_index.inject([]) do |a, (num, i)|
162
- # a[ i == 0 ? 1 : i == 1 ? 0 : i ] = num # swap first two
163
- # a
164
- # end.each do |num|
165
- # write(:int, num.to_i(16))
166
- # end
167
- data.scan(/.{8}/).each do |num|
168
- write(:int, num.to_i(16))
169
- end
170
- when :bson
171
- buf = Buffer.new
172
- data.each do |key,value|
173
- case value
174
- when Numeric
175
- id = 1
176
- type = :double
177
- when String
178
- if key == :_id
179
- id = 7
180
- type = :oid
181
- else
182
- id = 2
183
- type = proc{ |out|
184
- out.write(:int, value.length+1)
185
- out.write(:cstring, value)
186
- }
187
- end
188
- when Hash
189
- if data.keys.map{|k| k.to_s}.sort == %w[ _id _ns ]
190
- id = 12 # ref
191
- type = proc{ |out|
192
- out.write(:cstring, data[:_ns])
193
- out.write(:oid, data[:_id])
194
- }
195
- else
196
- id = 3
197
- type = :bson
198
- end
199
- when Array
200
- id = 4
201
- type = :bson
202
- value = value.enum_with_index.inject({}){ |h, (v, i)| h.update i => v }
203
- when TrueClass, FalseClass
204
- id = 8
205
- type = :byte
206
- value = value ? 1 : 0
207
- when Time
208
- id = 9
209
- type = :longlong
210
- value = value.to_i * 1000 + (value.tv_usec/1000)
211
- when NilClass
212
- id = 10
213
- type = nil
214
- when Regexp
215
- id = 11
216
- type = proc{ |out|
217
- out.write(:cstring, value.source)
218
- out.write(:cstring, { 'i' => 1, 'm' => 2, 'x' => 4 }.inject('') do |s, (o, n)|
219
- s += o if value.options & n > 0
220
- s
221
- end)
222
- }
223
- when Symbol
224
- id = 14
225
- type = proc{ |out|
226
- out.write(:int, value.to_s.length+1)
227
- out.write(:cstring, value.to_s)
228
- }
229
- end
230
-
231
- buf.write(:byte, id)
232
- buf.write(:cstring, key)
233
-
234
- if type.respond_to? :call
235
- type.call(buf)
236
- elsif type
237
- buf.write(type, value)
238
- end
239
- end
240
- buf.write(:byte, 0) # eoo
241
-
242
- write(:int, buf.size+4)
243
- _write(buf.to_s)
244
- else
245
- raise InvalidType, "Cannot write data of type #{type}"
246
- end
247
- end
248
- self
249
- end
250
-
251
- def _peek(pos, size, pack)
252
- data = @data[pos,size]
253
- data = data.unpack(pack)
254
- data = data.pop if data.size == 1
255
- data
256
- end
257
-
258
- def _read size, pack = nil
259
- if @pos + size > length
260
- raise Overflow
261
- else
262
- data = @data[@pos,size]
263
- @data[@pos,size] = ''
264
- if pack
265
- data = data.unpack(pack)
266
- data = data.pop if data.size == 1
267
- end
268
- data
269
- end
270
- end
271
-
272
- def _write data, pack = nil
273
- data = [*data].pack(pack) if pack
274
- @data[@pos,0] = data
275
- @pos += data.length
276
- end
277
-
278
- def extract
279
- begin
280
- cur_data, cur_pos = @data.clone, @pos
281
- yield self
282
- rescue Overflow
283
- @data, @pos = cur_data, cur_pos
284
- nil
285
- end
286
- end
287
- end
288
- end
289
-
290
- if $0 =~ /bacon/ or $0 == __FILE__
291
- require 'bacon'
292
- Bacon.summary_on_exit
293
- include Mongo
294
-
295
- describe Buffer do
296
- before do
297
- @buf = Buffer.new
298
- end
299
-
300
- should 'have contents' do
301
- @buf.contents.should == ''
302
- end
303
-
304
- should 'initialize with data' do
305
- @buf = Buffer.new('abc')
306
- @buf.contents.should == 'abc'
307
- end
308
-
309
- should 'append raw data' do
310
- @buf << 'abc'
311
- @buf << 'def'
312
- @buf.contents.should == 'abcdef'
313
- end
314
-
315
- should 'append other buffers' do
316
- @buf << Buffer.new('abc')
317
- @buf.data.should == 'abc'
318
- end
319
-
320
- should 'have a position' do
321
- @buf.pos.should == 0
322
- end
323
-
324
- should 'have a length' do
325
- @buf.length.should == 0
326
- @buf << 'abc'
327
- @buf.length.should == 3
328
- end
329
-
330
- should 'know the end' do
331
- @buf.empty?.should == true
332
- end
333
-
334
- should 'read and write data' do
335
- @buf._write('abc')
336
- @buf.rewind
337
- @buf._read(2).should == 'ab'
338
- @buf._read(1).should == 'c'
339
- end
340
-
341
- should 'raise on overflow' do
342
- lambda{ @buf._read(1) }.should.raise Buffer::Overflow
343
- end
344
-
345
- should 'raise on invalid types' do
346
- lambda{ @buf.read(:junk) }.should.raise Buffer::InvalidType
347
- lambda{ @buf.write(:junk, 1) }.should.raise Buffer::InvalidType
348
- end
349
-
350
- { :byte => 0b10101010,
351
- :short => 100,
352
- :int => 65536,
353
- :double => 123.456,
354
- :long => 100_000_000,
355
- :longlong => 666_555_444_333_222_111,
356
- :cstring => 'hello',
357
- }.each do |type, value|
358
-
359
- should "read and write a #{type}" do
360
- @buf.write(type, value)
361
- @buf.rewind
362
- @buf.read(type).should == value
363
- @buf.should.be.empty
364
- end
365
-
366
- end
367
-
368
- should "read and write multiple times" do
369
- arr = [ :byte, 0b10101010, :short, 100 ]
370
- @buf.write(*arr)
371
- @buf.rewind
372
- @buf.read(arr.shift).should == arr.shift
373
- @buf.read(arr.shift).should == arr.shift
374
- end
375
-
376
- [
377
- { :num => 1 },
378
- { :symbol => :abc },
379
- { :object => {} },
380
- { :array => [1, 2, 3] },
381
- { :string => 'abcdefg' },
382
- { :oid => { :_id => '51d9ca7053a2012be4ecd660' } },
383
- { :ref => { :_ns => 'namespace',
384
- :_id => '51d9ca7053a2012be4ecd660' } },
385
- { :boolean => true },
386
- { :time => Time.at(Time.now.to_i) },
387
- { :null => nil },
388
- { :regex => /^.*?def/im }
389
- ]. each do |bson|
390
-
391
- should "read and write bson with #{bson.keys.first}s" do
392
- @buf.write(:bson, bson)
393
- @buf.rewind
394
- @buf.read(:bson).should == bson
395
- @buf.should.be.empty
396
- end
397
-
398
- end
399
-
400
- should 'do transactional reads with #extract' do
401
- @buf.write :byte, 8
402
- orig = @buf.to_s
403
-
404
- @buf.rewind
405
- @buf.extract do |b|
406
- b.read :byte
407
- b.read :short
408
- end
409
-
410
- @buf.pos.should == 0
411
- @buf.data.should == orig
412
- end
413
- end
414
- end
data/spec/buffer_spec.rb DELETED
@@ -1,123 +0,0 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
2
-
3
- include EMMongo
4
-
5
- describe Buffer do
6
- before do
7
- @buf = Buffer.new
8
- end
9
-
10
- it 'should have contents' do
11
- @buf.contents.should == ''
12
- end
13
-
14
- it 'should initialize with data' do
15
- @buf = Buffer.new('abc')
16
- @buf.contents.should == 'abc'
17
- end
18
-
19
- it 'should append raw data' do
20
- @buf << 'abc'
21
- @buf << 'def'
22
- @buf.contents.should == 'abcdef'
23
- end
24
-
25
- it 'should append other buffers' do
26
- @buf << Buffer.new('abc')
27
- @buf.data.should == 'abc'
28
- end
29
-
30
- it 'should have a position' do
31
- @buf.pos.should == 0
32
- end
33
-
34
- it 'should have a length' do
35
- @buf.length.should == 0
36
- @buf << 'abc'
37
- @buf.length.should == 3
38
- end
39
-
40
- it 'should know the end' do
41
- @buf.empty?.should == true
42
- end
43
-
44
- it 'should read and write data' do
45
- @buf._write('abc')
46
- @buf.rewind
47
- @buf._read(2).should == 'ab'
48
- @buf._read(1).should == 'c'
49
- end
50
-
51
- it 'should raise on overflow' do
52
- lambda{ @buf._read(1) }.should { raise Buffer::Overflow }
53
- end
54
-
55
- it 'should raise on invalid types' do
56
- lambda{ @buf.read(:junk) }.should { raise Buffer::InvalidType }
57
- lambda{ @buf.write(:junk, 1) }.should { raise Buffer::InvalidType }
58
- end
59
-
60
- { :byte => 0b10101010,
61
- :short => 100,
62
- :int => 65536,
63
- :double => 123.456,
64
- :long => 100_000_000,
65
- :longlong => 666_555_444_333_222_111,
66
- :cstring => 'hello',
67
- }.each do |type, value|
68
-
69
- it "should read and write a #{type}" do
70
- @buf.write(type, value)
71
- @buf.rewind
72
- @buf.read(type).should == value
73
- #@buf.should.be.empty
74
- end
75
-
76
- end
77
-
78
- it "should read and write multiple times" do
79
- arr = [ :byte, 0b10101010, :short, 100 ]
80
- @buf.write(*arr)
81
- @buf.rewind
82
- @buf.read(arr.shift).should == arr.shift
83
- @buf.read(arr.shift).should == arr.shift
84
- end
85
-
86
- [
87
- { :num => 1 },
88
- { :symbol => :abc },
89
- { :object => {} },
90
- { :array => [1, 2, 3] },
91
- { :string => 'abcdefg' },
92
- { :oid => { :_id => '51d9ca7053a2012be4ecd660' } },
93
- { :ref => { :_ns => 'namespace',
94
- :_id => '51d9ca7053a2012be4ecd660' } },
95
- { :boolean => true },
96
- { :time => Time.at(Time.now.to_i) },
97
- { :null => nil },
98
- { :regex => /^.*?def/im }
99
- ]. each do |bson|
100
-
101
- it "should read and write bson with #{bson.keys.first}s" do
102
- @buf.write(:bson, bson)
103
- @buf.rewind
104
- @buf.read(:bson).should == bson
105
- #@buf.should.be.empty
106
- end
107
-
108
- end
109
-
110
- it 'should do transactional reads with #extract' do
111
- @buf.write :byte, 8
112
- orig = @buf.to_s
113
-
114
- @buf.rewind
115
- @buf.extract do |b|
116
- b.read :byte
117
- b.read :short
118
- end
119
-
120
- @buf.pos.should == 0
121
- @buf.data.should == orig
122
- end
123
- end