mongo 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +134 -0
- data/Rakefile +86 -0
- data/bin/mongo_console +21 -0
- data/lib/mongo.rb +7 -0
- data/lib/mongo/admin.rb +86 -0
- data/lib/mongo/collection.rb +136 -0
- data/lib/mongo/cursor.rb +180 -0
- data/lib/mongo/db.rb +307 -0
- data/lib/mongo/message.rb +4 -0
- data/lib/mongo/message/get_more_message.rb +21 -0
- data/lib/mongo/message/insert_message.rb +19 -0
- data/lib/mongo/message/kill_cursors_message.rb +20 -0
- data/lib/mongo/message/message.rb +68 -0
- data/lib/mongo/message/message_header.rb +34 -0
- data/lib/mongo/message/msg_message.rb +17 -0
- data/lib/mongo/message/opcodes.rb +16 -0
- data/lib/mongo/message/query_message.rb +45 -0
- data/lib/mongo/message/remove_message.rb +20 -0
- data/lib/mongo/message/update_message.rb +21 -0
- data/lib/mongo/mongo.rb +52 -0
- data/lib/mongo/objectid.rb +107 -0
- data/lib/mongo/query.rb +103 -0
- data/lib/mongo/util/bson.rb +339 -0
- data/lib/mongo/util/byte_buffer.rb +163 -0
- data/lib/mongo/util/ordered_hash.rb +54 -0
- data/tests/test_admin.rb +59 -0
- data/tests/test_bson.rb +79 -0
- data/tests/test_byte_buffer.rb +69 -0
- data/tests/test_db_api.rb +357 -0
- data/tests/test_db_connection.rb +17 -0
- data/tests/test_message.rb +35 -0
- data/tests/test_objectid.rb +68 -0
- data/tests/test_ordered_hash.rb +82 -0
- metadata +85 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'mongo/message/message'
|
2
|
+
require 'mongo/message/opcodes'
|
3
|
+
|
4
|
+
module XGen
|
5
|
+
module Mongo
|
6
|
+
module Driver
|
7
|
+
|
8
|
+
class GetMoreMessage < Message
|
9
|
+
|
10
|
+
def initialize(db_name, collection_name, cursor)
|
11
|
+
super(OP_GET_MORE)
|
12
|
+
write_int(0)
|
13
|
+
write_string("#{db_name}.#{collection_name}")
|
14
|
+
write_int(0) # num to return; leave it up to the db for now
|
15
|
+
write_long(cursor)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'mongo/message/message'
|
2
|
+
require 'mongo/message/opcodes'
|
3
|
+
|
4
|
+
module XGen
|
5
|
+
module Mongo
|
6
|
+
module Driver
|
7
|
+
|
8
|
+
class InsertMessage < Message
|
9
|
+
|
10
|
+
def initialize(db_name, collection_name, *objs)
|
11
|
+
super(OP_INSERT)
|
12
|
+
write_int(0)
|
13
|
+
write_string("#{db_name}.#{collection_name}")
|
14
|
+
objs.each { |o| write_doc(o) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'mongo/message/message'
|
2
|
+
require 'mongo/message/opcodes'
|
3
|
+
|
4
|
+
module XGen
|
5
|
+
module Mongo
|
6
|
+
module Driver
|
7
|
+
|
8
|
+
class KillCursorsMessage < Message
|
9
|
+
|
10
|
+
def initialize(*cursors)
|
11
|
+
super(OP_KILL_CURSORS)
|
12
|
+
write_int(0)
|
13
|
+
write_int(cursors.length)
|
14
|
+
cursors.each { |c| write_long c }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'mongo/util/bson'
|
2
|
+
require 'mongo/util/byte_buffer'
|
3
|
+
|
4
|
+
module XGen
|
5
|
+
module Mongo
|
6
|
+
module Driver
|
7
|
+
|
8
|
+
class Message
|
9
|
+
|
10
|
+
HEADER_SIZE = 16 # size, id, response_to, opcode
|
11
|
+
|
12
|
+
@@class_req_id = 0
|
13
|
+
|
14
|
+
attr_reader :buf # for testing
|
15
|
+
|
16
|
+
def initialize(op)
|
17
|
+
@op = op
|
18
|
+
@message_length = HEADER_SIZE
|
19
|
+
@data_length = 0
|
20
|
+
@request_id = (@@class_req_id += 1)
|
21
|
+
@response_id = 0
|
22
|
+
@buf = ByteBuffer.new
|
23
|
+
|
24
|
+
@buf.put_int(16) # holder for length
|
25
|
+
@buf.put_int(@request_id)
|
26
|
+
@buf.put_int(0) # response_to
|
27
|
+
@buf.put_int(op)
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_int(i)
|
31
|
+
@buf.put_int(i)
|
32
|
+
update_message_length
|
33
|
+
end
|
34
|
+
|
35
|
+
def write_long(i)
|
36
|
+
@buf.put_long(i)
|
37
|
+
update_message_length
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_string(s)
|
41
|
+
BSON.serialize_cstr(@buf, s)
|
42
|
+
update_message_length
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_doc(hash)
|
46
|
+
@buf.put_array(BSON.new.serialize(hash).to_a)
|
47
|
+
update_message_length
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_a
|
51
|
+
@buf.to_a
|
52
|
+
end
|
53
|
+
|
54
|
+
def dump
|
55
|
+
@buf.dump
|
56
|
+
end
|
57
|
+
|
58
|
+
# Do not call. Private, but kept public for testing.
|
59
|
+
def update_message_length
|
60
|
+
pos = @buf.position
|
61
|
+
@buf.put_int(@buf.size, 0)
|
62
|
+
@buf.position = pos
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'mongo/util/byte_buffer'
|
2
|
+
|
3
|
+
module XGen
|
4
|
+
module Mongo
|
5
|
+
module Driver
|
6
|
+
|
7
|
+
class MessageHeader
|
8
|
+
|
9
|
+
HEADER_SIZE = 16
|
10
|
+
|
11
|
+
def initialize()
|
12
|
+
@buf = ByteBuffer.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def read_header(socket)
|
16
|
+
@buf.rewind
|
17
|
+
@buf.put_array(socket.recv(HEADER_SIZE).unpack("C*"))
|
18
|
+
raise "Short read for DB response header: expected #{HEADER_SIZE} bytes, saw #{@buf.size}" unless @buf.size == HEADER_SIZE
|
19
|
+
@buf.rewind
|
20
|
+
@size = @buf.get_int
|
21
|
+
@request_id = @buf.get_int
|
22
|
+
@response_to = @buf.get_int
|
23
|
+
@op = @buf.get_int
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def dump
|
28
|
+
@buf.dump
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module XGen
|
2
|
+
module Mongo
|
3
|
+
module Driver
|
4
|
+
OP_REPLY = 1 # reply. responseTo is set.
|
5
|
+
OP_MSG = 1000 # generic msg command followed by a string
|
6
|
+
OP_UPDATE = 2001 # update object
|
7
|
+
OP_INSERT = 2002
|
8
|
+
# GET_BY_OID = 2003
|
9
|
+
OP_QUERY = 2004
|
10
|
+
OP_GET_MORE = 2005
|
11
|
+
OP_DELETE = 2006
|
12
|
+
OP_KILL_CURSORS = 2007
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'mongo/message/message'
|
2
|
+
require 'mongo/message/opcodes'
|
3
|
+
require 'mongo/util/ordered_hash'
|
4
|
+
|
5
|
+
module XGen
|
6
|
+
module Mongo
|
7
|
+
module Driver
|
8
|
+
|
9
|
+
class QueryMessage < Message
|
10
|
+
|
11
|
+
def initialize(db_name, collection_name, query)
|
12
|
+
super(OP_QUERY)
|
13
|
+
write_int(0)
|
14
|
+
write_string("#{db_name}.#{collection_name}")
|
15
|
+
write_int(query.number_to_skip)
|
16
|
+
write_int(query.number_to_return)
|
17
|
+
sel = query.selector
|
18
|
+
if query.order_by && query.order_by.length > 0
|
19
|
+
sel = OrderedHash.new
|
20
|
+
sel['query'] = query.selector
|
21
|
+
sel['orderby'] = case query.order_by
|
22
|
+
when String
|
23
|
+
{query.order_by => 1}
|
24
|
+
when Array
|
25
|
+
h = OrderedHash.new
|
26
|
+
query.order_by.each { |ob| h[ob] = 1 }
|
27
|
+
h
|
28
|
+
when Hash # Should be an ordered hash, but this message doesn't care
|
29
|
+
query.order_by
|
30
|
+
else
|
31
|
+
raise "illegal order_by: is a #{query.order_by.class.name}, must be String, Array, Hash, or OrderedHash"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
write_doc(sel)
|
36
|
+
write_doc(query.fields) if query.fields
|
37
|
+
end
|
38
|
+
|
39
|
+
def first_key(key)
|
40
|
+
@first_key = key
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'mongo/message/message'
|
2
|
+
require 'mongo/message/opcodes'
|
3
|
+
|
4
|
+
module XGen
|
5
|
+
module Mongo
|
6
|
+
module Driver
|
7
|
+
|
8
|
+
class RemoveMessage < Message
|
9
|
+
|
10
|
+
def initialize(db_name, collection_name, sel)
|
11
|
+
super(OP_DELETE)
|
12
|
+
write_int(0)
|
13
|
+
write_string("#{db_name}.#{collection_name}")
|
14
|
+
write_int(0) # flags?
|
15
|
+
write_doc(sel)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'mongo/message/message'
|
2
|
+
require 'mongo/message/opcodes'
|
3
|
+
|
4
|
+
module XGen
|
5
|
+
module Mongo
|
6
|
+
module Driver
|
7
|
+
|
8
|
+
class UpdateMessage < Message
|
9
|
+
|
10
|
+
def initialize(db_name, collection_name, sel, obj, repsert)
|
11
|
+
super(OP_UPDATE)
|
12
|
+
write_int(0)
|
13
|
+
write_string("#{db_name}.#{collection_name}")
|
14
|
+
write_int(repsert ? 1 : 0) # 1 if a repsert operation (upsert)
|
15
|
+
write_doc(sel)
|
16
|
+
write_doc(obj)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/mongo/mongo.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 10gen Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it
|
5
|
+
# under the terms of the GNU Affero General Public License, version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
require 'mongo/db'
|
18
|
+
|
19
|
+
module XGen
|
20
|
+
module Mongo
|
21
|
+
module Driver
|
22
|
+
|
23
|
+
# Represents a Mongo database server.
|
24
|
+
class Mongo
|
25
|
+
|
26
|
+
DEFAULT_PORT = 27017
|
27
|
+
|
28
|
+
# Host default is 'localhost', port default is DEFAULT_PORT.
|
29
|
+
def initialize(host='localhost', port=DEFAULT_PORT)
|
30
|
+
@host, @port = host, port
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return the XGen::Mongo::Driver::DB named +db_name+.
|
34
|
+
def db(db_name)
|
35
|
+
XGen::Mongo::Driver::DB.new(db_name, @host, @port)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Not implemented.
|
39
|
+
def clone_database(from)
|
40
|
+
raise "not implemented"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Not implemented.
|
44
|
+
def copy_database(from_host, from_db, to_db)
|
45
|
+
raise "not implemented"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 10gen Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it
|
5
|
+
# under the terms of the GNU Affero General Public License, version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
require 'mutex_m'
|
18
|
+
require 'mongo/util/byte_buffer'
|
19
|
+
|
20
|
+
module XGen
|
21
|
+
module Mongo
|
22
|
+
module Driver
|
23
|
+
|
24
|
+
# Implementation of the Babble OID. Object ids are not required by
|
25
|
+
# Mongo, but they make certain operations more efficient.
|
26
|
+
#
|
27
|
+
# The driver does not automatically assign ids to records that are
|
28
|
+
# inserted. (An upcoming feature will allow you to give an id "factory"
|
29
|
+
# to a database and/or a collection.)
|
30
|
+
#
|
31
|
+
# 12 bytes
|
32
|
+
# ---
|
33
|
+
# 0 time
|
34
|
+
# 1
|
35
|
+
# 2
|
36
|
+
# 3
|
37
|
+
# 4 machine
|
38
|
+
# 5
|
39
|
+
# 6
|
40
|
+
# 7 pid
|
41
|
+
# 8
|
42
|
+
# 9 inc
|
43
|
+
# 10
|
44
|
+
# 11
|
45
|
+
class ObjectID
|
46
|
+
|
47
|
+
MACHINE = ( val = rand(0x1000000); [val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff] )
|
48
|
+
PID = ( val = rand(0x10000); [val & 0xff, (val >> 8) & 0xff]; )
|
49
|
+
|
50
|
+
LOCK = Object.new
|
51
|
+
LOCK.extend Mutex_m
|
52
|
+
|
53
|
+
@@index_time = Time.new.to_i
|
54
|
+
@@index = 0
|
55
|
+
|
56
|
+
# +data+ is an array of bytes. If nil, a new id will be generated.
|
57
|
+
# The time +t+ is only used for testing; leave it nil.
|
58
|
+
def initialize(data=nil, t=nil)
|
59
|
+
@data = data || generate_id(t)
|
60
|
+
end
|
61
|
+
|
62
|
+
def eql?(other)
|
63
|
+
@data == other.to_a
|
64
|
+
end
|
65
|
+
alias_method :==, :eql?
|
66
|
+
|
67
|
+
def to_a
|
68
|
+
@data.dup
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
@data.collect { |b| '%02x' % b }.join
|
73
|
+
end
|
74
|
+
|
75
|
+
# (Would normally be private, but isn't so we can test it.)
|
76
|
+
def generate_id(t=nil)
|
77
|
+
t ||= Time.new.to_i
|
78
|
+
buf = ByteBuffer.new
|
79
|
+
buf.put_int(t & 0xffffffff)
|
80
|
+
buf.put_array(MACHINE)
|
81
|
+
buf.put_array(PID)
|
82
|
+
i = index_for_time(t)
|
83
|
+
buf.put(i & 0xff)
|
84
|
+
buf.put((i >> 8) & 0xff)
|
85
|
+
buf.put((i >> 16) & 0xff)
|
86
|
+
|
87
|
+
buf.rewind
|
88
|
+
buf.to_a.dup
|
89
|
+
end
|
90
|
+
|
91
|
+
# (Would normally be private, but isn't so we can test it.)
|
92
|
+
def index_for_time(t)
|
93
|
+
LOCK.mu_synchronize {
|
94
|
+
if t != @@index_time
|
95
|
+
@@index = 0
|
96
|
+
@@index_time = t
|
97
|
+
end
|
98
|
+
retval = @@index
|
99
|
+
@@index += 1
|
100
|
+
retval
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|