em-mongo 0.1.1 → 0.2.4

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.
@@ -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