first commit
This commit is contained in:
commit
5e3815bf69
1
README
Normal file
1
README
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is a simple pure-Ruby driver for the 10gen MongoDB.
|
8
Rakefile
Normal file
8
Rakefile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
require 'rake/testtask'
|
||||||
|
|
||||||
|
task :default => [:test]
|
||||||
|
|
||||||
|
# NOTE: some of the tests assume Mongo is running
|
||||||
|
Rake::TestTask.new do |t|
|
||||||
|
t.test_files = FileList['tests/test*.rb']
|
||||||
|
end
|
16
examples/demo.rb
Normal file
16
examples/demo.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
||||||
|
require 'mongo'
|
||||||
|
|
||||||
|
include XGen::Mongo::Driver
|
||||||
|
|
||||||
|
db = Mongo.new.db('ruby-mongo-demo')
|
||||||
|
coll = db.collection('test')
|
||||||
|
coll.clear
|
||||||
|
|
||||||
|
doc = {'a' => 1}
|
||||||
|
coll.insert(doc)
|
||||||
|
|
||||||
|
doc = {'a' => 2}
|
||||||
|
coll.insert(doc)
|
||||||
|
|
||||||
|
coll.find().each { |doc| puts doc.inspect }
|
5
lib/mongo.rb
Normal file
5
lib/mongo.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
require 'mongo/mongo'
|
||||||
|
require 'mongo/message'
|
||||||
|
require 'mongo/db'
|
||||||
|
require 'mongo/cursor'
|
||||||
|
require 'mongo/collection'
|
84
lib/mongo/collection.rb
Normal file
84
lib/mongo/collection.rb
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# Copyright (C) 2008 10gen Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License, version 3, as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
# for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
require 'mongo/query'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
class Collection
|
||||||
|
|
||||||
|
attr_reader :db, :name
|
||||||
|
|
||||||
|
def initialize(db, name)
|
||||||
|
@db = db
|
||||||
|
@name = name
|
||||||
|
end
|
||||||
|
|
||||||
|
def find(selector={}, fields=nil, options={})
|
||||||
|
fields = nil if fields && fields.empty?
|
||||||
|
@db.query(@name, Query.new(selector, fields, options[:offset] || 0, options[:limit] || 0, options[:sort]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert(*objects)
|
||||||
|
@db.insert_into_db(@name, objects)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove(selector={})
|
||||||
|
@db.remove_from_db(@name, selector)
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear
|
||||||
|
remove({})
|
||||||
|
end
|
||||||
|
|
||||||
|
def repsert(selector, obj)
|
||||||
|
@db.repsert_in_db(@name, selector, obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace(selector, obj)
|
||||||
|
@db.replace_in_db(@name, selector, obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
def modify(selector, modifierObj)
|
||||||
|
raise "no object" unless modifierObj
|
||||||
|
raise "no selector" unless selector
|
||||||
|
@db.modify_in_db(@name, selector, modifierObj)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_index(name, *fields)
|
||||||
|
@db.create_index(@name, name, fields)
|
||||||
|
end
|
||||||
|
|
||||||
|
def drop_index(name)
|
||||||
|
@db.drop_index(@name, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def drop_indexes
|
||||||
|
index_information.each { |info| @db.drop_index(@name, info.name) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_information
|
||||||
|
@db.index_information(@name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def count(selector={})
|
||||||
|
@db.count(@name, selector || {})
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
127
lib/mongo/cursor.rb
Normal file
127
lib/mongo/cursor.rb
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# Copyright (C) 2008 10gen Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License, version 3, as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
# for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
require 'mongo/message'
|
||||||
|
require 'mongo/util/byte_buffer'
|
||||||
|
require 'mongo/util/bson'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class Cursor
|
||||||
|
|
||||||
|
include Enumerable
|
||||||
|
|
||||||
|
RESPONSE_HEADER_SIZE = 20
|
||||||
|
|
||||||
|
def initialize(db, collection)
|
||||||
|
@db, @collection = db, collection
|
||||||
|
@objects = []
|
||||||
|
@closed = false
|
||||||
|
read_all
|
||||||
|
end
|
||||||
|
|
||||||
|
def more?
|
||||||
|
num_remaining > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_object
|
||||||
|
refill_via_get_more if num_remaining == 0
|
||||||
|
@objects.shift
|
||||||
|
end
|
||||||
|
|
||||||
|
def each
|
||||||
|
while more?
|
||||||
|
yield next_object()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
@db.send_to_db(KillCursorMessage(@cursor_id)) if @cursor_id
|
||||||
|
@objects = []
|
||||||
|
@cursor_id = 0
|
||||||
|
@closed = true
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def read_all
|
||||||
|
read_message_header
|
||||||
|
read_response_header
|
||||||
|
read_objects_off_wire
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_objects_off_wire
|
||||||
|
while doc = next_object_on_wire
|
||||||
|
@objects << doc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_message_header
|
||||||
|
MessageHeader.new.read_header(@db.socket)
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_response_header
|
||||||
|
header_buf = ByteBuffer.new
|
||||||
|
header_buf.put_array(@db.socket.recv(RESPONSE_HEADER_SIZE).unpack("C*"))
|
||||||
|
raise "Short read for DB response header; expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}" unless header_buf.length == RESPONSE_HEADER_SIZE
|
||||||
|
header_buf.rewind
|
||||||
|
@result_flags = header_buf.get_int
|
||||||
|
@cursor_id = header_buf.get_long
|
||||||
|
@starting_from = header_buf.get_int
|
||||||
|
@n_returned = header_buf.get_int
|
||||||
|
@n_remaining = @n_returned
|
||||||
|
end
|
||||||
|
|
||||||
|
def num_remaining
|
||||||
|
refill_via_get_more if @objects.length == 0
|
||||||
|
@objects.length
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def next_object_on_wire
|
||||||
|
# if @n_remaining is 0 but we have a non-zero cursor, there are more
|
||||||
|
# to fetch, so do a GetMore operation, but don't do it here - do it
|
||||||
|
# when someone pulls an object out of the cache and it's empty
|
||||||
|
return nil if @n_remaining == 0
|
||||||
|
object_from_stream
|
||||||
|
end
|
||||||
|
|
||||||
|
def refill_via_get_more
|
||||||
|
return if @cursor_id == 0
|
||||||
|
@db.send_to_db(GetMoreMessage.new(@db.name, @collection, @cursor_id))
|
||||||
|
read_all
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_from_stream
|
||||||
|
buf = ByteBuffer.new
|
||||||
|
buf.put_array(@db.socket.recv(4).unpack("C*"))
|
||||||
|
buf.rewind
|
||||||
|
size = buf.get_int
|
||||||
|
buf.put_array(@db.socket.recv(size-4).unpack("C*"), 4)
|
||||||
|
@n_remaining -= 1
|
||||||
|
buf.rewind
|
||||||
|
BSON.new.deserialize(buf)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
"DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
176
lib/mongo/db.rb
Normal file
176
lib/mongo/db.rb
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
# Copyright (C) 2008 10gen Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License, version 3, as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
# for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
require 'socket'
|
||||||
|
require 'mongo/collection'
|
||||||
|
require 'mongo/message'
|
||||||
|
require 'mongo/query'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class DB
|
||||||
|
SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
|
||||||
|
SYSTEM_INDEX_COLLECTION = "system.indexes"
|
||||||
|
SYSTEM_COMMAND_COLLECTION = "$cmd"
|
||||||
|
|
||||||
|
attr_reader :name, :socket
|
||||||
|
|
||||||
|
def initialize(db_name, host, port)
|
||||||
|
raise "Invalid DB name" if !db_name || (db_name && db_name.length > 0 && db_name.include?("."))
|
||||||
|
@name, @host, @port = db_name, host, port
|
||||||
|
@socket = TCPSocket.new(@host, @port)
|
||||||
|
end
|
||||||
|
|
||||||
|
def collection_names
|
||||||
|
names = collections_info.collect { |doc| doc['name'] || '' }
|
||||||
|
names.delete('')
|
||||||
|
names
|
||||||
|
end
|
||||||
|
|
||||||
|
def collections_info(coll_name=nil)
|
||||||
|
selector = {}
|
||||||
|
selector[:name] = "#{@name}.#{coll_name}" if coll_name
|
||||||
|
query(SYSTEM_NAMESPACE_COLLECTION, Query.new(selector))
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_collection(name, options={})
|
||||||
|
# First check existence
|
||||||
|
return Collection.new(self, name) if collection_names.include?(name)
|
||||||
|
|
||||||
|
# Create new collection
|
||||||
|
sel = {:create => name}.merge(options)
|
||||||
|
doc = db_command(sel)
|
||||||
|
o = doc['ok']
|
||||||
|
return Collection.new(self, name) if o.kind_of?(Numeric) && (o.to_i == 1 || o.to_i == 0)
|
||||||
|
raise "Error creating collection: #{doc.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def admin
|
||||||
|
Admin.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def collection(name)
|
||||||
|
create_collection(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def drop_collection(name)
|
||||||
|
coll = collection(name)
|
||||||
|
return true if coll == nil
|
||||||
|
col.drop_indexes
|
||||||
|
|
||||||
|
doc = db_command(:drop => name)
|
||||||
|
o = md['ok']
|
||||||
|
return o.kind_of?(Numeric) && o.to_i == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
@socket.close
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_message(msg)
|
||||||
|
send_to_db(MsgMessage.new(msg))
|
||||||
|
end
|
||||||
|
|
||||||
|
def query(collection, query)
|
||||||
|
# TODO synchronize
|
||||||
|
send_to_db(QueryMessage.new(@name, collection, query))
|
||||||
|
return Cursor.new(self, collection)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_from_db(collection, selector)
|
||||||
|
# TODO synchronize
|
||||||
|
send_to_db(RemoveMessage.new(@name, collection, selector))
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_in_db(collection, selector, obj)
|
||||||
|
# TODO synchronize
|
||||||
|
send_to_db(UpdateMessage.new(@name, collection, selector, obj, false))
|
||||||
|
end
|
||||||
|
alias_method :modify_in_db, :replace_in_db
|
||||||
|
|
||||||
|
def repsert_in_db(collection, selector, obj)
|
||||||
|
# TODO if PKInjector, inject
|
||||||
|
# TODO synchronize
|
||||||
|
send_to_db(UpdateMessage.new(@name, collection, selector, obj, true))
|
||||||
|
obj
|
||||||
|
end
|
||||||
|
|
||||||
|
def count(collection, selector)
|
||||||
|
doc = db_command(:count => collection, :query => selector)
|
||||||
|
o = doc['ok']
|
||||||
|
return doc['n'].to_i if o.to_i == 1
|
||||||
|
raise "Error with count command: #{doc.to_s}" unless o.kind_of?(Numeric)
|
||||||
|
end
|
||||||
|
|
||||||
|
def drop_index(collection, name)
|
||||||
|
db_command(:deleteIndexes => collection, :index => name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_information(collection)
|
||||||
|
sel = {:ns => full_coll_name(collection)}
|
||||||
|
# TODO synchronize
|
||||||
|
query(SYSTEM_INDEX_COLLECTION, Query.new(sel)).collect { |row|
|
||||||
|
h = {:name => row['name']}
|
||||||
|
raise "Name of index on return from db was nil. Coll = #{full_coll_name(collection)}" unless h[:name]
|
||||||
|
|
||||||
|
h[:keys] = row['keys']
|
||||||
|
raise "Keys for index on return from db was nil. Coll = #{full_coll_name(collection)}" unless h[:keys]
|
||||||
|
|
||||||
|
h[:ns] = row['ns']
|
||||||
|
raise "Namespace for index on return from db was nil. Coll = #{full_coll_name(collection)}" unless h[:ns]
|
||||||
|
h[:ns].sub!(/.*\./, '')
|
||||||
|
raise "Error: ns != collection" unless h[:ns] == collection
|
||||||
|
|
||||||
|
h
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_index(collection, name, fields)
|
||||||
|
sel = {:name => name, :ns => full_coll_name(collection)}
|
||||||
|
field_h = {}
|
||||||
|
fields.each { |f| field_h[f] = 1 }
|
||||||
|
sel['key'] = field_h
|
||||||
|
# TODO synchronize
|
||||||
|
send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, sel))
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_into_db(collection, objects)
|
||||||
|
# TODO synchronize
|
||||||
|
objects.each { |o| send_to_db(InsertMessage.new(@name, collection, o)) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_to_db(message)
|
||||||
|
@socket.print(message.buf.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def full_coll_name(collection)
|
||||||
|
"#{@name}.#{collection}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def db_command(selector)
|
||||||
|
# TODO synchronize
|
||||||
|
q = Query.new(selector)
|
||||||
|
q.number_to_return = 1
|
||||||
|
query(SYSTEM_COMMAND_COLLECTION, q).next_object
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
4
lib/mongo/message.rb
Normal file
4
lib/mongo/message.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
%w(get_more_message insert_message kill_cursors_message message_header
|
||||||
|
msg_message query_message remove_message update_message).each { |f|
|
||||||
|
require "mongo/message/#{f}"
|
||||||
|
}
|
21
lib/mongo/message/get_more_message.rb
Normal file
21
lib/mongo/message/get_more_message.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
require 'mongo/message/message'
|
||||||
|
require 'mongo/message/opcodes'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class GetMoreMessage < Message
|
||||||
|
|
||||||
|
def initialize(name, collection, cursor)
|
||||||
|
super(OP_GET_MORE)
|
||||||
|
write_int(0)
|
||||||
|
write_string("#{name}.#{collection}")
|
||||||
|
write_int(0) # num to return; leave it up to the db for now
|
||||||
|
write_long(cursor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
20
lib/mongo/message/insert_message.rb
Normal file
20
lib/mongo/message/insert_message.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
require 'mongo/message/message'
|
||||||
|
require 'mongo/message/opcodes'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class InsertMessage < Message
|
||||||
|
|
||||||
|
def initialize(name, collection, *objs)
|
||||||
|
super(OP_INSERT)
|
||||||
|
write_int(0)
|
||||||
|
write_string("#{name}.#{collection}")
|
||||||
|
objs.each { |o| write_doc(o) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
20
lib/mongo/message/kill_cursors_message.rb
Normal file
20
lib/mongo/message/kill_cursors_message.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
require 'mongo/message/message'
|
||||||
|
require 'mongo/message/opcodes'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class KillCursorsMessage < Message
|
||||||
|
|
||||||
|
def initialize(*cursors)
|
||||||
|
super(OP_KILL_CURSORS)
|
||||||
|
write_int(0)
|
||||||
|
write_int(cursors.length)
|
||||||
|
cursors.each { |c| write_long c }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
68
lib/mongo/message/message.rb
Normal file
68
lib/mongo/message/message.rb
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
require 'mongo/util/bson'
|
||||||
|
require 'mongo/util/byte_buffer'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class Message
|
||||||
|
|
||||||
|
HEADER_SIZE = 16 # size, id, response_to, opcode
|
||||||
|
|
||||||
|
@@class_req_id = 0
|
||||||
|
|
||||||
|
attr_reader :buf # for testing
|
||||||
|
|
||||||
|
def initialize(op)
|
||||||
|
@op = op
|
||||||
|
@message_length = HEADER_SIZE
|
||||||
|
@data_length = 0
|
||||||
|
@request_id = (@@class_req_id += 1)
|
||||||
|
@response_id = 0
|
||||||
|
@buf = ByteBuffer.new
|
||||||
|
|
||||||
|
@buf.put_int(16) # holder for length
|
||||||
|
@buf.put_int(@@class_req_id += 1)
|
||||||
|
@buf.put_int(0) # response_to
|
||||||
|
@buf.put_int(op)
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_int(i)
|
||||||
|
@buf.put_int(i)
|
||||||
|
update_message_length
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_long(i)
|
||||||
|
@buf.put_long(i)
|
||||||
|
update_message_length
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_string(s)
|
||||||
|
BSON.serialize_cstr(@buf, s)
|
||||||
|
update_message_length
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_doc(hash)
|
||||||
|
@buf.put_array(BSON.new.serialize(hash).to_a)
|
||||||
|
update_message_length
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_a
|
||||||
|
@buf.to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump
|
||||||
|
@buf.dump
|
||||||
|
end
|
||||||
|
|
||||||
|
# Do not call. Private, but kept public for testing.
|
||||||
|
def update_message_length
|
||||||
|
pos = @buf.position
|
||||||
|
@buf.put_int(@buf.size, 0)
|
||||||
|
@buf.position = pos
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
34
lib/mongo/message/message_header.rb
Normal file
34
lib/mongo/message/message_header.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
require 'mongo/util/byte_buffer'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class MessageHeader
|
||||||
|
|
||||||
|
HEADER_SIZE = 16
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
@buf = ByteBuffer.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_header(socket)
|
||||||
|
@buf.rewind
|
||||||
|
@buf.put_array(socket.recv(HEADER_SIZE).unpack("C*"))
|
||||||
|
raise "Short read for DB response header: expected #{HEADER_SIZE} bytes, saw #{@buf.size}" unless @buf.size == HEADER_SIZE
|
||||||
|
@buf.rewind
|
||||||
|
@size = @buf.get_int
|
||||||
|
@request_id = @buf.get_int
|
||||||
|
@response_to = @buf.get_int
|
||||||
|
@op = @buf.get_int
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump
|
||||||
|
@buf.dump
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
17
lib/mongo/message/msg_message.rb
Normal file
17
lib/mongo/message/msg_message.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
require 'mongo/message/message'
|
||||||
|
require 'mongo/message/opcodes'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class MsgMessage < Message
|
||||||
|
|
||||||
|
def initialize(msg)
|
||||||
|
super(OP_MSG)
|
||||||
|
write_string(msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
lib/mongo/message/opcodes.rb
Normal file
16
lib/mongo/message/opcodes.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
OP_REPLY = 1 # reply. responseTo is set.
|
||||||
|
OP_MSG = 1000 # generic msg command followed by a string
|
||||||
|
OP_UPDATE = 2001 # update object
|
||||||
|
OP_INSERT = 2002
|
||||||
|
# GET_BY_OID = 2003
|
||||||
|
OP_QUERY = 2004
|
||||||
|
OP_GET_MORE = 2005
|
||||||
|
OP_DELETE = 2006
|
||||||
|
OP_KILL_CURSORS = 2007
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
22
lib/mongo/message/query_message.rb
Normal file
22
lib/mongo/message/query_message.rb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
require 'mongo/message/message'
|
||||||
|
require 'mongo/message/opcodes'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class QueryMessage < Message
|
||||||
|
|
||||||
|
def initialize(name, collection, query)
|
||||||
|
super(OP_QUERY)
|
||||||
|
write_int(0)
|
||||||
|
write_string("#{name}.#{collection}")
|
||||||
|
write_int(query.number_to_skip)
|
||||||
|
write_int(query.number_to_return)
|
||||||
|
write_doc(query.selector)
|
||||||
|
write_doc(query.fields) if query.fields
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
20
lib/mongo/message/remove_message.rb
Normal file
20
lib/mongo/message/remove_message.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
require 'mongo/message/message'
|
||||||
|
require 'mongo/message/opcodes'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class RemoveMessage < Message
|
||||||
|
|
||||||
|
def initialize(name, collection, sel)
|
||||||
|
super(OP_DELETE)
|
||||||
|
write_int(0)
|
||||||
|
write_string("#{name}.#{collection}")
|
||||||
|
write_int(0) # flags?
|
||||||
|
write_doc(sel)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
21
lib/mongo/message/update_message.rb
Normal file
21
lib/mongo/message/update_message.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
require 'mongo/message/message'
|
||||||
|
require 'mongo/message/opcodes'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class UpdateMessage < Message
|
||||||
|
|
||||||
|
def initialize(name, collection, sel, obj, repsert)
|
||||||
|
super(OP_UPDATE)
|
||||||
|
write_int(0)
|
||||||
|
write_string("#{name}.#{collection}")
|
||||||
|
write_int(repsert ? 1 : 0) # 1 if a repsert operation (upsert)
|
||||||
|
write_doc(sel)
|
||||||
|
write_doc(obj)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
45
lib/mongo/mongo.rb
Normal file
45
lib/mongo/mongo.rb
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Copyright (C) 2008 10gen Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License, version 3, as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
# for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
require 'mongo/db'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class Mongo
|
||||||
|
|
||||||
|
DEFAULT_PORT = 27017
|
||||||
|
|
||||||
|
def initialize(host='localhost', port=DEFAULT_PORT)
|
||||||
|
@host, @port = host, port
|
||||||
|
end
|
||||||
|
|
||||||
|
def db(db_name)
|
||||||
|
XGen::Mongo::Driver::DB.new(db_name, @host, @port)
|
||||||
|
end
|
||||||
|
|
||||||
|
def clone_database(from)
|
||||||
|
raise "not implemented"
|
||||||
|
end
|
||||||
|
|
||||||
|
def copy_database(from_host, from_db, to_db)
|
||||||
|
raise "not implemented"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
52
lib/mongo/query.rb
Normal file
52
lib/mongo/query.rb
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Copyright (C) 2008 10gen Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License, version 3, as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
# for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
require 'socket'
|
||||||
|
require 'mongo/collection'
|
||||||
|
require 'mongo/message'
|
||||||
|
|
||||||
|
module XGen
|
||||||
|
module Mongo
|
||||||
|
module Driver
|
||||||
|
|
||||||
|
class Query
|
||||||
|
|
||||||
|
attr_accessor :number_to_skip, :number_to_return, :order_by
|
||||||
|
attr_reader :selector, :fields # writers defined below
|
||||||
|
|
||||||
|
def initialize(sel={}, return_fields=nil, number_to_skip=0, number_to_return=0, order_by=nil)
|
||||||
|
@number_to_skip, @number_to_return, @order_by = number_to_skip, number_to_return, order_by
|
||||||
|
self.selector = sel
|
||||||
|
self.fields = return_fields
|
||||||
|
end
|
||||||
|
|
||||||
|
def selector=(sel)
|
||||||
|
@selector = case selector
|
||||||
|
when nil
|
||||||
|
{}
|
||||||
|
when String
|
||||||
|
{"$where" => "function() { return #{sel}; }"}
|
||||||
|
when Hash
|
||||||
|
sel
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fields=(val)
|
||||||
|
@fields = val
|
||||||
|
@fields = nil if @fields && @fields.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
257
lib/mongo/util/bson.rb
Normal file
257
lib/mongo/util/bson.rb
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
require 'mongo/util/byte_buffer'
|
||||||
|
|
||||||
|
class BSON
|
||||||
|
|
||||||
|
EOO = 0 # x
|
||||||
|
MAXKEY = -1 # x
|
||||||
|
NUMBER = 1 # x t
|
||||||
|
STRING = 2 # x t
|
||||||
|
OBJECT = 3 # x t
|
||||||
|
ARRAY = 4
|
||||||
|
BINARY = 5
|
||||||
|
UNDEFINED = 6
|
||||||
|
OID = 7 # x
|
||||||
|
BOOLEAN = 8 # x t
|
||||||
|
DATE = 9 # x t
|
||||||
|
NULL = 10 # x t
|
||||||
|
REGEX = 11
|
||||||
|
REF = 12
|
||||||
|
CODE = 13
|
||||||
|
SYMBOL = 14
|
||||||
|
CODE_W_SCOPE = 15
|
||||||
|
NUMBER_INT = 16
|
||||||
|
|
||||||
|
def self.serialize_cstr(buf, val)
|
||||||
|
buf.put_array(val.to_s.unpack("C*") + [0])
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@private_buf = ByteBuffer.new
|
||||||
|
@buf = ByteBuffer.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_a
|
||||||
|
@buf.to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize(obj)
|
||||||
|
raise "Document is null" unless obj
|
||||||
|
|
||||||
|
@buf.rewind
|
||||||
|
# put in a placeholder for the total size
|
||||||
|
@buf.put_int(0)
|
||||||
|
|
||||||
|
obj.each {|k, v|
|
||||||
|
type = bson_type(v, k)
|
||||||
|
case type
|
||||||
|
when STRING, CODE
|
||||||
|
serialize_string_element(@buf, k, v, type)
|
||||||
|
when NUMBER, NUMBER_INT
|
||||||
|
serialize_number_element(@buf, k, v, type)
|
||||||
|
when OBJECT
|
||||||
|
serialize_object_element(@buf, k, v)
|
||||||
|
when OID
|
||||||
|
serialize_oid_element(@buf, k, v)
|
||||||
|
when BOOLEAN
|
||||||
|
serialize_boolean_element(@buf, k, v)
|
||||||
|
when DATE
|
||||||
|
serialize_date_element(@buf, k, v)
|
||||||
|
when NULL
|
||||||
|
serialize_null_element(@buf, k)
|
||||||
|
else
|
||||||
|
raise "Unhandled Type #{type}"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
serialize_eoo_element(@buf)
|
||||||
|
@buf.put_int(@buf.size, 0)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize(buf)
|
||||||
|
@buf = ByteBuffer.new(buf.to_a)
|
||||||
|
@buf.rewind
|
||||||
|
@buf.get_int # eat message size
|
||||||
|
doc = {}
|
||||||
|
while @buf.more?
|
||||||
|
type = @buf.get
|
||||||
|
case type
|
||||||
|
when STRING
|
||||||
|
key = deserialize_element_name(@buf)
|
||||||
|
doc[key] = deserialize_string_data(@buf)
|
||||||
|
when NUMBER
|
||||||
|
key = deserialize_element_name(@buf)
|
||||||
|
doc[key] = deserialize_number_data(@buf)
|
||||||
|
when NUMBER_INT
|
||||||
|
key = deserialize_element_name(@buf)
|
||||||
|
doc[key] = deserialize_number_int_data(@buf)
|
||||||
|
when OID
|
||||||
|
key = deserialize_element_name(@buf)
|
||||||
|
doc[key] = deserialize_oid_data(@buf)
|
||||||
|
when OBJECT
|
||||||
|
key = deserialize_element_name(@buf)
|
||||||
|
doc[key] = deserialize_object_data(@buf)
|
||||||
|
when BOOLEAN
|
||||||
|
key = deserialize_element_name(@buf)
|
||||||
|
doc[key] = deserialize_boolean_data(@buf)
|
||||||
|
when DATE
|
||||||
|
key = deserialize_element_name(@buf)
|
||||||
|
doc[key] = deserialize_date_data(@buf)
|
||||||
|
when NULL
|
||||||
|
key = deserialize_element_name(@buf)
|
||||||
|
doc[key] = nil
|
||||||
|
when EOO
|
||||||
|
break
|
||||||
|
else
|
||||||
|
raise "Unknown type #{type}, key = #{key}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@buf.rewind
|
||||||
|
doc
|
||||||
|
end
|
||||||
|
|
||||||
|
def hex_dump
|
||||||
|
str = ''
|
||||||
|
@buf.to_a.each_with_index { |b,i|
|
||||||
|
if (i % 8) == 0
|
||||||
|
str << "\n" if i > 0
|
||||||
|
str << '%4d: ' % i
|
||||||
|
else
|
||||||
|
str << ' '
|
||||||
|
end
|
||||||
|
str << '%02X' % b
|
||||||
|
}
|
||||||
|
str
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_date_data(buf)
|
||||||
|
Time.at(buf.get_long)
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_boolean_data(buf)
|
||||||
|
buf.get == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_number_data(buf)
|
||||||
|
buf.get_double
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_number_int_data(buf)
|
||||||
|
buf.get_int
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_object_data(buf)
|
||||||
|
size = buf.get_int
|
||||||
|
buf.position -= 4
|
||||||
|
BSON.new.deserialize(buf.get(size))
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_string_data(buf)
|
||||||
|
len = buf.get_int
|
||||||
|
bytes = buf.get(len)
|
||||||
|
bytes[0..-2].pack("C*")
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_oid_data
|
||||||
|
ObjectID.new(buf.get(12).pack("C*"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_eoo_element(buf)
|
||||||
|
buf.put(EOO)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_null_element(buf, key)
|
||||||
|
buf.put(NULL)
|
||||||
|
self.class.serialize_cstr(buf, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_boolean_element(buf, key, val)
|
||||||
|
buf.put(BOOLEAN)
|
||||||
|
self.class.serialize_cstr(buf, key)
|
||||||
|
buf.put(val ? 1 : 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_date_element(buf, key, val)
|
||||||
|
buf.put(DATE)
|
||||||
|
self.class.serialize_cstr(buf, key)
|
||||||
|
buf.put_long(val.to_i)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_number_element(buf, key, val, type)
|
||||||
|
buf.put(type)
|
||||||
|
self.class.serialize_cstr(buf, key)
|
||||||
|
if type == NUMBER
|
||||||
|
buf.put_double(val)
|
||||||
|
else
|
||||||
|
buf.put_int(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_object_element(buf, key, val)
|
||||||
|
buf.put(OBJECT)
|
||||||
|
self.class.serialize_cstr(buf, key)
|
||||||
|
BSON.new(buf).serialize(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_oid_element(buf, key, val)
|
||||||
|
buf.put(OID)
|
||||||
|
self.class.serialize_cstr(buf, key)
|
||||||
|
buf.put_array(val.to_a)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_string_element(buf, key, val, type)
|
||||||
|
buf.put(type)
|
||||||
|
self.class.serialize_cstr(buf, key)
|
||||||
|
|
||||||
|
# Make a hole for the length
|
||||||
|
len_pos = buf.position
|
||||||
|
buf.put_int(0)
|
||||||
|
|
||||||
|
# Save the string
|
||||||
|
start_pos = buf.position
|
||||||
|
self.class.serialize_cstr(buf, val)
|
||||||
|
end_pos = buf.position
|
||||||
|
|
||||||
|
# Put the string size in front
|
||||||
|
buf.put_int(end_pos - start_pos - 1, len_pos)
|
||||||
|
|
||||||
|
# Go back to where we were
|
||||||
|
buf.position = end_pos
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_element_name(buf)
|
||||||
|
chars = ""
|
||||||
|
while 1
|
||||||
|
b = buf.get
|
||||||
|
break if b == 0
|
||||||
|
chars << b.chr
|
||||||
|
end
|
||||||
|
chars
|
||||||
|
end
|
||||||
|
|
||||||
|
def bson_type(o, key)
|
||||||
|
case o
|
||||||
|
when nil
|
||||||
|
NULL
|
||||||
|
when Integer
|
||||||
|
NUMBER_INT
|
||||||
|
when Numeric
|
||||||
|
NUMBER
|
||||||
|
when String
|
||||||
|
# magic awful stuff - the DB requires that a where clause is sent as CODE
|
||||||
|
key == "$where" ? CODE : STRING
|
||||||
|
when Array
|
||||||
|
ARRAY
|
||||||
|
when ObjectID
|
||||||
|
OID
|
||||||
|
when true, false
|
||||||
|
Boolean
|
||||||
|
when Time
|
||||||
|
DATE
|
||||||
|
when Hash
|
||||||
|
OBJECT
|
||||||
|
else
|
||||||
|
raise "Unknown type of object: #{o.class.name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
132
lib/mongo/util/byte_buffer.rb
Normal file
132
lib/mongo/util/byte_buffer.rb
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
class ByteBuffer
|
||||||
|
|
||||||
|
attr_reader :order
|
||||||
|
|
||||||
|
def initialize(initial_data=[])
|
||||||
|
@buf = initial_data
|
||||||
|
@cursor = 0
|
||||||
|
self.order = :little_endian
|
||||||
|
end
|
||||||
|
|
||||||
|
# +endianness+ should be :little_endian or :big_endian. Default is :little_endian
|
||||||
|
def order=(endianness)
|
||||||
|
@order = endianness
|
||||||
|
@int_pack_order = endianness == :little_endian ? 'V' : 'N'
|
||||||
|
@double_pack_order = endianness == :little_endian ? 'E' : 'G'
|
||||||
|
end
|
||||||
|
|
||||||
|
def rewind
|
||||||
|
@cursor = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def position
|
||||||
|
@cursor
|
||||||
|
end
|
||||||
|
|
||||||
|
def position=(val)
|
||||||
|
@cursor = val
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear
|
||||||
|
@buf = []
|
||||||
|
rewind
|
||||||
|
end
|
||||||
|
|
||||||
|
def size
|
||||||
|
@buf.size
|
||||||
|
end
|
||||||
|
alias_method :length, :size
|
||||||
|
|
||||||
|
def put(byte, offset=nil)
|
||||||
|
@cursor = offset if offset
|
||||||
|
@buf[@cursor] = byte
|
||||||
|
@cursor += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def put_array(array, offset=nil)
|
||||||
|
@cursor = offset if offset
|
||||||
|
@buf[@cursor, array.length] = array
|
||||||
|
@cursor += array.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def put_int(i, offset=nil)
|
||||||
|
put_array([i].pack(@int_pack_order).split(//).collect{|c| c[0]}, offset)
|
||||||
|
end
|
||||||
|
|
||||||
|
def put_long(i, offset=nil)
|
||||||
|
offset = @cursor unless offset
|
||||||
|
if @int_pack_order == 'N'
|
||||||
|
put_int(i >> 32, offset)
|
||||||
|
put_int(i & 0xffffffff, offset + 4)
|
||||||
|
else
|
||||||
|
put_int(i & 0xffffffff, offset)
|
||||||
|
put_int(i >> 32, offset + 4)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def put_double(d, offset=nil)
|
||||||
|
put_array([d].pack(@double_pack_order).split(//), offset)
|
||||||
|
end
|
||||||
|
|
||||||
|
# If +size+ == 1, returns one byte. Else returns array of bytes of length
|
||||||
|
# +size+.
|
||||||
|
def get(len=1)
|
||||||
|
check_read_length(len)
|
||||||
|
start = @cursor
|
||||||
|
@cursor += len
|
||||||
|
if len == 1
|
||||||
|
@buf[start]
|
||||||
|
else
|
||||||
|
@buf[start, len]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_int
|
||||||
|
check_read_length(4)
|
||||||
|
vals = ""
|
||||||
|
(@cursor..@cursor+3).each { |i| vals << @buf[i].chr }
|
||||||
|
@cursor += 4
|
||||||
|
vals.unpack(@int_pack_order)[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_long
|
||||||
|
i1 = get_int
|
||||||
|
i2 = get_int
|
||||||
|
if @int_pack_order == 'N'
|
||||||
|
(i1 << 32) + i2
|
||||||
|
else
|
||||||
|
(i2 << 32) + i1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_double
|
||||||
|
check_read_length(8)
|
||||||
|
vals = ""
|
||||||
|
(@cursor..@cursor+7).each { |i| vals << @buf[i].chr }
|
||||||
|
@cursor += 8
|
||||||
|
vals.unpack(@double_pack_order)[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def more?
|
||||||
|
@cursor < @buf.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_a
|
||||||
|
@buf
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
@buf.pack("C*")
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump
|
||||||
|
@buf.each_with_index { |c, i| $stderr.puts "#{'%04d' % i}: #{'%02x' % c} #{'%03o' % c} #{'%s' % c.chr} #{'%3d' % c}" }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def check_read_length(len)
|
||||||
|
raise "attempt to read past end of buffer" if @cursor + len > @buf.length
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
45
tests/test_byte_buffer.rb
Normal file
45
tests/test_byte_buffer.rb
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
||||||
|
require 'mongo'
|
||||||
|
require 'test/unit'
|
||||||
|
|
||||||
|
class ByteBufferTest < Test::Unit::TestCase
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@buf = ByteBuffer.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_empty
|
||||||
|
assert_equal 0, @buf.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_length
|
||||||
|
@buf.put_int 3
|
||||||
|
assert_equal 4, @buf.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_default_order
|
||||||
|
assert_equal :little_endian, @buf.order
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_long_length
|
||||||
|
@buf.put_long 1027
|
||||||
|
assert_equal 8, @buf.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_get_long
|
||||||
|
@buf.put_long 1027
|
||||||
|
@buf.rewind
|
||||||
|
assert_equal 1027, @buf.get_long
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_rewrite
|
||||||
|
@buf.put_int(0)
|
||||||
|
@buf.rewind
|
||||||
|
@buf.put_int(1027)
|
||||||
|
assert_equal 4, @buf.length
|
||||||
|
@buf.rewind
|
||||||
|
assert_equal 1027, @buf.get_int
|
||||||
|
assert_equal 4, @buf.position
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
13
tests/test_db_connection.rb
Normal file
13
tests/test_db_connection.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
||||||
|
require 'mongo'
|
||||||
|
require 'test/unit'
|
||||||
|
|
||||||
|
# NOTE: assumes Mongo is running
|
||||||
|
class DBConnectionTest < Test::Unit::TestCase
|
||||||
|
|
||||||
|
def test_no_exceptions
|
||||||
|
db = XGen::Mongo::Driver::Mongo.new.db('ruby-mongo-demo')
|
||||||
|
coll = db.collection('test')
|
||||||
|
coll.clear
|
||||||
|
end
|
||||||
|
end
|
33
tests/test_message.rb
Normal file
33
tests/test_message.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
||||||
|
require 'mongo'
|
||||||
|
require 'test/unit'
|
||||||
|
|
||||||
|
class MessageTest < Test::Unit::TestCase
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@msg = XGen::Mongo::Driver::Message.new(42)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_initial_info
|
||||||
|
assert_equal XGen::Mongo::Driver::Message::HEADER_SIZE, @msg.buf.length
|
||||||
|
@msg.write_long(1029)
|
||||||
|
@msg.buf.rewind
|
||||||
|
assert_equal XGen::Mongo::Driver::Message::HEADER_SIZE + 8, @msg.buf.get_int
|
||||||
|
@msg.buf.get_int # skip message id
|
||||||
|
assert_equal 0, @msg.buf.get_int
|
||||||
|
assert_equal 42, @msg.buf.get_int
|
||||||
|
assert_equal 1029, @msg.buf.get_long
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_update_length
|
||||||
|
@msg.update_message_length
|
||||||
|
@msg.buf.rewind
|
||||||
|
assert_equal XGen::Mongo::Driver::Message::HEADER_SIZE, @msg.buf.get_int
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_long_length
|
||||||
|
@msg.write_long(1027)
|
||||||
|
assert_equal XGen::Mongo::Driver::Message::HEADER_SIZE + 8, @msg.buf.length
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user