Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

first commit

  • Loading branch information...
commit 5e3815bf69770851db63fda8bb6d02ba2a09aa83 0 parents
authored November 21, 2008
1  README
... ...
@@ -0,0 +1 @@
  1
+This is a simple pure-Ruby driver for the 10gen MongoDB.
8  Rakefile
... ...
@@ -0,0 +1,8 @@
  1
+require 'rake/testtask'
  2
+
  3
+task :default => [:test]
  4
+
  5
+# NOTE: some of the tests assume Mongo is running
  6
+Rake::TestTask.new do |t|
  7
+    t.test_files = FileList['tests/test*.rb']
  8
+end
16  examples/demo.rb
... ...
@@ -0,0 +1,16 @@
  1
+$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
  2
+require 'mongo'
  3
+
  4
+include XGen::Mongo::Driver
  5
+
  6
+db = Mongo.new.db('ruby-mongo-demo')
  7
+coll = db.collection('test')
  8
+coll.clear
  9
+
  10
+doc = {'a' => 1}
  11
+coll.insert(doc)
  12
+
  13
+doc = {'a' => 2}
  14
+coll.insert(doc)
  15
+
  16
+coll.find().each { |doc| puts doc.inspect }
5  lib/mongo.rb
... ...
@@ -0,0 +1,5 @@
  1
+require 'mongo/mongo'
  2
+require 'mongo/message'
  3
+require 'mongo/db'
  4
+require 'mongo/cursor'
  5
+require 'mongo/collection'
84  lib/mongo/collection.rb
... ...
@@ -0,0 +1,84 @@
  1
+# Copyright (C) 2008 10gen Inc.
  2
+#
  3
+# This program is free software: you can redistribute it and/or modify it
  4
+# under the terms of the GNU Affero General Public License, version 3, as
  5
+# published by the Free Software Foundation.
  6
+#
  7
+# This program is distributed in the hope that it will be useful, but WITHOUT
  8
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  9
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
  10
+# for more details.
  11
+#
  12
+# You should have received a copy of the GNU Affero General Public License
  13
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
  14
+
  15
+require 'mongo/query'
  16
+
  17
+module XGen
  18
+  module Mongo
  19
+    module Driver
  20
+      class Collection
  21
+
  22
+        attr_reader :db, :name
  23
+
  24
+        def initialize(db, name)
  25
+          @db = db
  26
+          @name = name
  27
+        end
  28
+
  29
+        def find(selector={}, fields=nil, options={})
  30
+          fields = nil if fields && fields.empty?
  31
+          @db.query(@name, Query.new(selector, fields, options[:offset] || 0, options[:limit] || 0, options[:sort]))
  32
+        end
  33
+
  34
+        def insert(*objects)
  35
+          @db.insert_into_db(@name, objects)
  36
+        end
  37
+
  38
+        def remove(selector={})
  39
+          @db.remove_from_db(@name, selector)
  40
+        end
  41
+
  42
+        def clear
  43
+          remove({})
  44
+        end
  45
+
  46
+        def repsert(selector, obj)
  47
+          @db.repsert_in_db(@name, selector, obj)
  48
+        end
  49
+
  50
+        def replace(selector, obj)
  51
+          @db.replace_in_db(@name, selector, obj)
  52
+        end
  53
+
  54
+        def modify(selector, modifierObj)
  55
+          raise "no object" unless modifierObj
  56
+          raise "no selector" unless selector
  57
+          @db.modify_in_db(@name, selector, modifierObj)
  58
+        end
  59
+
  60
+        def create_index(name, *fields)
  61
+          @db.create_index(@name, name, fields)
  62
+        end
  63
+
  64
+        def drop_index(name)
  65
+          @db.drop_index(@name, name)
  66
+        end
  67
+
  68
+        def drop_indexes
  69
+          index_information.each { |info| @db.drop_index(@name, info.name) }
  70
+        end
  71
+
  72
+        def index_information
  73
+          @db.index_information(@name)
  74
+        end
  75
+
  76
+        def count(selector={})
  77
+          @db.count(@name, selector || {})
  78
+        end
  79
+
  80
+      end
  81
+    end
  82
+  end
  83
+end
  84
+
127  lib/mongo/cursor.rb
... ...
@@ -0,0 +1,127 @@
  1
+# Copyright (C) 2008 10gen Inc.
  2
+#
  3
+# This program is free software: you can redistribute it and/or modify it
  4
+# under the terms of the GNU Affero General Public License, version 3, as
  5
+# published by the Free Software Foundation.
  6
+#
  7
+# This program is distributed in the hope that it will be useful, but WITHOUT
  8
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  9
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
  10
+# for more details.
  11
+#
  12
+# You should have received a copy of the GNU Affero General Public License
  13
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
  14
+
  15
+require 'mongo/message'
  16
+require 'mongo/util/byte_buffer'
  17
+require 'mongo/util/bson'
  18
+
  19
+module XGen
  20
+  module Mongo
  21
+    module Driver
  22
+
  23
+      class Cursor
  24
+
  25
+        include Enumerable
  26
+
  27
+        RESPONSE_HEADER_SIZE = 20
  28
+
  29
+        def initialize(db, collection)
  30
+          @db, @collection = db, collection
  31
+          @objects = []
  32
+          @closed = false
  33
+          read_all
  34
+        end
  35
+
  36
+        def more?
  37
+          num_remaining > 0
  38
+        end
  39
+
  40
+        def next_object
  41
+          refill_via_get_more if num_remaining == 0
  42
+          @objects.shift
  43
+        end
  44
+
  45
+        def each
  46
+          while more?
  47
+            yield next_object()
  48
+          end
  49
+        end
  50
+
  51
+        def close
  52
+          @db.send_to_db(KillCursorMessage(@cursor_id)) if @cursor_id
  53
+          @objects = []
  54
+          @cursor_id = 0
  55
+          @closed = true
  56
+        end
  57
+
  58
+        protected
  59
+
  60
+        def read_all
  61
+          read_message_header
  62
+          read_response_header
  63
+          read_objects_off_wire
  64
+        end
  65
+
  66
+        def read_objects_off_wire
  67
+          while doc = next_object_on_wire
  68
+            @objects << doc
  69
+          end
  70
+        end
  71
+
  72
+        def read_message_header
  73
+          MessageHeader.new.read_header(@db.socket)
  74
+        end
  75
+
  76
+        def read_response_header
  77
+          header_buf = ByteBuffer.new
  78
+          header_buf.put_array(@db.socket.recv(RESPONSE_HEADER_SIZE).unpack("C*"))
  79
+          raise "Short read for DB response header; expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}" unless header_buf.length == RESPONSE_HEADER_SIZE
  80
+          header_buf.rewind
  81
+          @result_flags = header_buf.get_int
  82
+          @cursor_id = header_buf.get_long
  83
+          @starting_from = header_buf.get_int
  84
+          @n_returned = header_buf.get_int
  85
+          @n_remaining = @n_returned
  86
+        end
  87
+
  88
+        def num_remaining
  89
+          refill_via_get_more if @objects.length == 0
  90
+          @objects.length
  91
+        end
  92
+
  93
+        private
  94
+
  95
+        def next_object_on_wire
  96
+          # if @n_remaining is 0 but we have a non-zero cursor, there are more
  97
+          # to fetch, so do a GetMore operation, but don't do it here - do it
  98
+          # when someone pulls an object out of the cache and it's empty
  99
+          return nil if @n_remaining == 0
  100
+          object_from_stream
  101
+        end
  102
+
  103
+        def refill_via_get_more
  104
+          return if @cursor_id == 0
  105
+          @db.send_to_db(GetMoreMessage.new(@db.name, @collection, @cursor_id))
  106
+          read_all
  107
+        end
  108
+
  109
+        def object_from_stream
  110
+          buf = ByteBuffer.new
  111
+          buf.put_array(@db.socket.recv(4).unpack("C*"))
  112
+          buf.rewind
  113
+          size = buf.get_int
  114
+          buf.put_array(@db.socket.recv(size-4).unpack("C*"), 4)
  115
+          @n_remaining -= 1
  116
+          buf.rewind
  117
+          BSON.new.deserialize(buf)
  118
+        end
  119
+
  120
+        def to_s
  121
+          "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
  122
+        end
  123
+      end
  124
+    end
  125
+  end
  126
+end
  127
+
176  lib/mongo/db.rb
... ...
@@ -0,0 +1,176 @@
  1
+# Copyright (C) 2008 10gen Inc.
  2
+#
  3
+# This program is free software: you can redistribute it and/or modify it
  4
+# under the terms of the GNU Affero General Public License, version 3, as
  5
+# published by the Free Software Foundation.
  6
+#
  7
+# This program is distributed in the hope that it will be useful, but WITHOUT
  8
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  9
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
  10
+# for more details.
  11
+#
  12
+# You should have received a copy of the GNU Affero General Public License
  13
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
  14
+
  15
+require 'socket'
  16
+require 'mongo/collection'
  17
+require 'mongo/message'
  18
+require 'mongo/query'
  19
+
  20
+module XGen
  21
+  module Mongo
  22
+    module Driver
  23
+
  24
+      class DB
  25
+        SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
  26
+        SYSTEM_INDEX_COLLECTION = "system.indexes"
  27
+        SYSTEM_COMMAND_COLLECTION = "$cmd"
  28
+
  29
+        attr_reader :name, :socket
  30
+
  31
+        def initialize(db_name, host, port)
  32
+          raise "Invalid DB name" if !db_name || (db_name && db_name.length > 0 && db_name.include?("."))
  33
+          @name, @host, @port = db_name, host, port
  34
+          @socket = TCPSocket.new(@host, @port)
  35
+        end
  36
+
  37
+        def collection_names
  38
+          names = collections_info.collect { |doc| doc['name'] || '' }
  39
+          names.delete('')
  40
+          names
  41
+        end
  42
+
  43
+        def collections_info(coll_name=nil)
  44
+          selector = {}
  45
+          selector[:name] = "#{@name}.#{coll_name}" if coll_name
  46
+          query(SYSTEM_NAMESPACE_COLLECTION, Query.new(selector))
  47
+        end
  48
+
  49
+        def create_collection(name, options={})
  50
+          # First check existence
  51
+          return Collection.new(self, name) if collection_names.include?(name)
  52
+
  53
+          # Create new collection
  54
+          sel = {:create => name}.merge(options)
  55
+          doc = db_command(sel)
  56
+          o = doc['ok']
  57
+          return Collection.new(self, name) if o.kind_of?(Numeric) && (o.to_i == 1 || o.to_i == 0)
  58
+          raise "Error creating collection: #{doc.inspect}"
  59
+        end
  60
+
  61
+        def admin
  62
+          Admin.new(self)
  63
+        end
  64
+
  65
+        def collection(name)
  66
+          create_collection(name)
  67
+        end
  68
+
  69
+        def drop_collection(name)
  70
+          coll = collection(name)
  71
+          return true if coll == nil
  72
+          col.drop_indexes
  73
+
  74
+          doc = db_command(:drop => name)
  75
+          o = md['ok']
  76
+          return o.kind_of?(Numeric) && o.to_i == 1
  77
+        end
  78
+
  79
+        def close
  80
+          @socket.close
  81
+        end
  82
+
  83
+        def send_message(msg)
  84
+          send_to_db(MsgMessage.new(msg))
  85
+        end
  86
+        
  87
+        def query(collection, query)
  88
+          # TODO synchronize
  89
+          send_to_db(QueryMessage.new(@name, collection, query))
  90
+          return Cursor.new(self, collection)
  91
+        end
  92
+
  93
+        def remove_from_db(collection, selector)
  94
+          # TODO synchronize
  95
+          send_to_db(RemoveMessage.new(@name, collection, selector))
  96
+        end
  97
+
  98
+        def replace_in_db(collection, selector, obj)
  99
+          # TODO synchronize
  100
+          send_to_db(UpdateMessage.new(@name, collection, selector, obj, false))
  101
+        end
  102
+        alias_method :modify_in_db, :replace_in_db
  103
+
  104
+        def repsert_in_db(collection, selector, obj)
  105
+          # TODO if PKInjector, inject
  106
+          # TODO synchronize
  107
+          send_to_db(UpdateMessage.new(@name, collection, selector, obj, true))
  108
+          obj
  109
+        end
  110
+
  111
+        def count(collection, selector)
  112
+          doc = db_command(:count => collection, :query => selector)
  113
+          o = doc['ok']
  114
+          return doc['n'].to_i if o.to_i == 1
  115
+          raise "Error with count command: #{doc.to_s}" unless o.kind_of?(Numeric)
  116
+        end
  117
+
  118
+        def drop_index(collection, name)
  119
+          db_command(:deleteIndexes => collection, :index => name)
  120
+        end
  121
+
  122
+        def index_information(collection)
  123
+          sel = {:ns => full_coll_name(collection)}
  124
+          # TODO synchronize
  125
+          query(SYSTEM_INDEX_COLLECTION, Query.new(sel)).collect { |row|
  126
+            h = {:name => row['name']}
  127
+            raise "Name of index on return from db was nil. Coll = #{full_coll_name(collection)}" unless h[:name]
  128
+
  129
+            h[:keys] = row['keys']
  130
+            raise "Keys for index on return from db was nil. Coll = #{full_coll_name(collection)}" unless h[:keys]
  131
+
  132
+            h[:ns] = row['ns']
  133
+            raise "Namespace for index on return from db was nil. Coll = #{full_coll_name(collection)}" unless h[:ns]
  134
+            h[:ns].sub!(/.*\./, '')
  135
+            raise "Error: ns != collection" unless h[:ns] == collection
  136
+
  137
+            h
  138
+          }
  139
+        end
  140
+
  141
+        def create_index(collection, name, fields)
  142
+          sel = {:name => name, :ns => full_coll_name(collection)}
  143
+          field_h = {}
  144
+          fields.each { |f| field_h[f] = 1 }
  145
+          sel['key'] = field_h
  146
+          # TODO synchronize
  147
+          send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, sel))
  148
+        end
  149
+
  150
+        def insert_into_db(collection, objects)
  151
+          # TODO synchronize
  152
+          objects.each { |o| send_to_db(InsertMessage.new(@name, collection, o)) }
  153
+        end
  154
+
  155
+        def send_to_db(message)
  156
+          @socket.print(message.buf.to_s)
  157
+        end
  158
+
  159
+        protected
  160
+
  161
+        def full_coll_name(collection)
  162
+          "#{@name}.#{collection}"
  163
+        end
  164
+
  165
+        def db_command(selector)
  166
+          # TODO synchronize
  167
+          q = Query.new(selector)
  168
+          q.number_to_return = 1
  169
+          query(SYSTEM_COMMAND_COLLECTION, q).next_object
  170
+        end
  171
+
  172
+      end
  173
+    end
  174
+  end
  175
+end
  176
+
4  lib/mongo/message.rb
... ...
@@ -0,0 +1,4 @@
  1
+%w(get_more_message insert_message kill_cursors_message message_header
  2
+   msg_message query_message remove_message update_message).each { |f|
  3
+  require "mongo/message/#{f}"
  4
+}
21  lib/mongo/message/get_more_message.rb
... ...
@@ -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(name, collection, cursor)
  11
+          super(OP_GET_MORE)
  12
+          write_int(0)
  13
+          write_string("#{name}.#{collection}")
  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
+
20  lib/mongo/message/insert_message.rb
... ...
@@ -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 InsertMessage < Message
  9
+
  10
+        def initialize(name, collection, *objs)
  11
+          super(OP_INSERT)
  12
+          write_int(0)
  13
+          write_string("#{name}.#{collection}")
  14
+          objs.each { |o| write_doc(o) }
  15
+        end
  16
+      end
  17
+    end
  18
+  end
  19
+end
  20
+
20  lib/mongo/message/kill_cursors_message.rb
... ...
@@ -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
+
68  lib/mongo/message/message.rb
... ...
@@ -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(@@class_req_id += 1)
  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
34  lib/mongo/message/message_header.rb
... ...
@@ -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
+
17  lib/mongo/message/msg_message.rb
... ...
@@ -0,0 +1,17 @@
  1
+require 'mongo/message/message'
  2
+require 'mongo/message/opcodes'
  3
+
  4
+module XGen
  5
+  module Mongo
  6
+    module Driver
  7
+
  8
+      class MsgMessage < Message
  9
+
  10
+        def initialize(msg)
  11
+          super(OP_MSG)
  12
+          write_string(msg)
  13
+        end
  14
+      end
  15
+    end
  16
+  end
  17
+end
16  lib/mongo/message/opcodes.rb
... ...
@@ -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
+
22  lib/mongo/message/query_message.rb
... ...
@@ -0,0 +1,22 @@
  1
+require 'mongo/message/message'
  2
+require 'mongo/message/opcodes'
  3
+
  4
+module XGen
  5
+  module Mongo
  6
+    module Driver
  7
+
  8
+      class QueryMessage < Message
  9
+
  10
+        def initialize(name, collection, query)
  11
+          super(OP_QUERY)
  12
+          write_int(0)
  13
+          write_string("#{name}.#{collection}")
  14
+          write_int(query.number_to_skip)
  15
+          write_int(query.number_to_return)
  16
+          write_doc(query.selector)
  17
+          write_doc(query.fields) if query.fields
  18
+        end
  19
+      end
  20
+    end
  21
+  end
  22
+end
20  lib/mongo/message/remove_message.rb
... ...
@@ -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(name, collection, sel)
  11
+          super(OP_DELETE)
  12
+          write_int(0)
  13
+          write_string("#{name}.#{collection}")
  14
+          write_int(0)              # flags?
  15
+          write_doc(sel)
  16
+        end
  17
+      end
  18
+    end
  19
+  end
  20
+end
21  lib/mongo/message/update_message.rb
... ...
@@ -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(name, collection, sel, obj, repsert)
  11
+          super(OP_UPDATE)
  12
+          write_int(0)
  13
+          write_string("#{name}.#{collection}")
  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
45  lib/mongo/mongo.rb
... ...
@@ -0,0 +1,45 @@
  1
+# Copyright (C) 2008 10gen Inc.
  2
+#
  3
+# This program is free software: you can redistribute it and/or modify it
  4
+# under the terms of the GNU Affero General Public License, version 3, as
  5
+# published by the Free Software Foundation.
  6
+#
  7
+# This program is distributed in the hope that it will be useful, but WITHOUT
  8
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  9
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
  10
+# for more details.
  11
+#
  12
+# You should have received a copy of the GNU Affero General Public License
  13
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
  14
+
  15
+require 'mongo/db'
  16
+
  17
+module XGen
  18
+  module Mongo
  19
+    module Driver
  20
+
  21
+      class Mongo
  22
+
  23
+        DEFAULT_PORT = 27017
  24
+
  25
+        def initialize(host='localhost', port=DEFAULT_PORT)
  26
+          @host, @port = host, port
  27
+        end
  28
+
  29
+        def db(db_name)
  30
+          XGen::Mongo::Driver::DB.new(db_name, @host, @port)
  31
+        end
  32
+
  33
+        def clone_database(from)
  34
+          raise "not implemented"
  35
+        end
  36
+
  37
+        def copy_database(from_host, from_db, to_db)
  38
+          raise "not implemented"
  39
+        end
  40
+
  41
+      end
  42
+    end
  43
+  end
  44
+end
  45
+
52  lib/mongo/query.rb
... ...
@@ -0,0 +1,52 @@
  1
+# Copyright (C) 2008 10gen Inc.
  2
+#
  3
+# This program is free software: you can redistribute it and/or modify it
  4
+# under the terms of the GNU Affero General Public License, version 3, as
  5
+# published by the Free Software Foundation.
  6
+#
  7
+# This program is distributed in the hope that it will be useful, but WITHOUT
  8
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  9
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
  10
+# for more details.
  11
+#
  12
+# You should have received a copy of the GNU Affero General Public License
  13
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
  14
+
  15
+require 'socket'
  16
+require 'mongo/collection'
  17
+require 'mongo/message'
  18
+
  19
+module XGen
  20
+  module Mongo
  21
+    module Driver
  22
+
  23
+      class Query
  24
+
  25
+        attr_accessor :number_to_skip, :number_to_return, :order_by
  26
+        attr_reader :selector, :fields # writers defined below
  27
+
  28
+        def initialize(sel={}, return_fields=nil, number_to_skip=0, number_to_return=0, order_by=nil)
  29
+          @number_to_skip, @number_to_return, @order_by = number_to_skip, number_to_return, order_by
  30
+          self.selector = sel
  31
+          self.fields = return_fields
  32
+        end
  33
+
  34
+        def selector=(sel)
  35
+          @selector = case selector
  36
+                      when nil
  37
+                        {}
  38
+                      when String
  39
+                        {"$where" => "function() { return #{sel}; }"}
  40
+                      when Hash
  41
+                        sel
  42
+                      end
  43
+        end
  44
+
  45
+        def fields=(val)
  46
+          @fields = val
  47
+          @fields = nil if @fields && @fields.empty?
  48
+        end
  49
+      end
  50
+    end
  51
+  end
  52
+end
257  lib/mongo/util/bson.rb
... ...
@@ -0,0 +1,257 @@
  1
+require 'mongo/util/byte_buffer'
  2
+
  3
+class BSON
  4
+
  5
+  EOO = 0                       # x
  6
+  MAXKEY = -1                   # x
  7
+  NUMBER = 1                    # x t
  8
+  STRING = 2                    # x t
  9
+  OBJECT = 3                    # x t
  10
+  ARRAY = 4
  11
+  BINARY = 5
  12
+  UNDEFINED = 6
  13
+  OID = 7                       # x
  14
+  BOOLEAN = 8                   # x t
  15
+  DATE = 9                      # x t
  16
+  NULL = 10                     # x t
  17
+  REGEX = 11
  18
+  REF = 12
  19
+  CODE = 13
  20
+  SYMBOL = 14
  21
+  CODE_W_SCOPE = 15
  22
+  NUMBER_INT = 16
  23
+
  24
+  def self.serialize_cstr(buf, val)
  25
+    buf.put_array(val.to_s.unpack("C*") + [0])
  26
+  end
  27
+
  28
+  def initialize
  29
+    @private_buf = ByteBuffer.new
  30
+    @buf = ByteBuffer.new
  31
+  end
  32
+
  33
+  def to_a
  34
+    @buf.to_a
  35
+  end
  36
+
  37
+  def serialize(obj)
  38
+    raise "Document is null" unless obj
  39
+
  40
+    @buf.rewind
  41
+    # put in a placeholder for the total size
  42
+    @buf.put_int(0)
  43
+
  44
+    obj.each {|k, v|
  45
+      type = bson_type(v, k)
  46
+      case type
  47
+      when STRING, CODE
  48
+        serialize_string_element(@buf, k, v, type)
  49
+      when NUMBER, NUMBER_INT
  50
+        serialize_number_element(@buf, k, v, type)
  51
+      when OBJECT
  52
+        serialize_object_element(@buf, k, v)
  53
+      when OID
  54
+        serialize_oid_element(@buf, k, v)
  55
+      when BOOLEAN
  56
+        serialize_boolean_element(@buf, k, v)
  57
+      when DATE
  58
+        serialize_date_element(@buf, k, v)
  59
+      when NULL
  60
+        serialize_null_element(@buf, k)
  61
+      else
  62
+        raise "Unhandled Type #{type}"
  63
+      end
  64
+    }
  65
+    serialize_eoo_element(@buf)
  66
+    @buf.put_int(@buf.size, 0)
  67
+    self
  68
+  end
  69
+
  70
+  def deserialize(buf)
  71
+    @buf = ByteBuffer.new(buf.to_a)
  72
+    @buf.rewind
  73
+    @buf.get_int                # eat message size
  74
+    doc = {}
  75
+    while @buf.more?
  76
+      type = @buf.get
  77
+      case type
  78
+      when STRING
  79
+        key = deserialize_element_name(@buf)
  80
+        doc[key] = deserialize_string_data(@buf)
  81
+      when NUMBER
  82
+        key = deserialize_element_name(@buf)
  83
+        doc[key] = deserialize_number_data(@buf)
  84
+      when NUMBER_INT
  85
+        key = deserialize_element_name(@buf)
  86
+        doc[key] = deserialize_number_int_data(@buf)
  87
+      when OID
  88
+        key = deserialize_element_name(@buf)
  89
+        doc[key] = deserialize_oid_data(@buf)
  90
+      when OBJECT
  91
+        key = deserialize_element_name(@buf)
  92
+        doc[key] = deserialize_object_data(@buf)
  93
+      when BOOLEAN
  94
+        key = deserialize_element_name(@buf)
  95
+        doc[key] = deserialize_boolean_data(@buf)
  96
+      when DATE
  97
+        key = deserialize_element_name(@buf)
  98
+        doc[key] = deserialize_date_data(@buf)
  99
+      when NULL
  100
+        key = deserialize_element_name(@buf)
  101
+        doc[key] = nil
  102
+      when EOO
  103
+        break
  104
+      else
  105
+        raise "Unknown type #{type}, key = #{key}"
  106
+      end
  107
+    end
  108
+    @buf.rewind
  109
+    doc
  110
+  end
  111
+
  112
+  def hex_dump
  113
+    str = ''
  114
+    @buf.to_a.each_with_index { |b,i|
  115
+      if (i % 8) == 0
  116
+        str << "\n" if i > 0
  117
+        str << '%4d:  ' % i
  118
+      else
  119
+        str << ' '
  120
+      end
  121
+      str << '%02X' % b
  122
+    }
  123
+    str
  124
+  end
  125
+
  126
+  def deserialize_date_data(buf)
  127
+    Time.at(buf.get_long)
  128
+  end
  129
+
  130
+  def deserialize_boolean_data(buf)
  131
+    buf.get == 1
  132
+  end
  133
+
  134
+  def deserialize_number_data(buf)
  135
+    buf.get_double
  136
+  end
  137
+
  138
+  def deserialize_number_int_data(buf)
  139
+    buf.get_int
  140
+  end
  141
+
  142
+  def deserialize_object_data(buf)
  143
+    size = buf.get_int
  144
+    buf.position -= 4
  145
+    BSON.new.deserialize(buf.get(size))
  146
+  end
  147
+
  148
+  def deserialize_string_data(buf)
  149
+    len = buf.get_int
  150
+    bytes = buf.get(len)
  151
+    bytes[0..-2].pack("C*")
  152
+  end
  153
+
  154
+  def deserialize_oid_data
  155
+    ObjectID.new(buf.get(12).pack("C*"))
  156
+  end
  157
+
  158
+  def serialize_eoo_element(buf)
  159
+    buf.put(EOO)
  160
+  end
  161
+
  162
+  def serialize_null_element(buf, key)
  163
+    buf.put(NULL)
  164
+    self.class.serialize_cstr(buf, key)
  165
+  end
  166
+
  167
+  def serialize_boolean_element(buf, key, val)
  168
+    buf.put(BOOLEAN)
  169
+    self.class.serialize_cstr(buf, key)
  170
+    buf.put(val ? 1 : 0)
  171
+  end
  172
+
  173
+  def serialize_date_element(buf, key, val)
  174
+    buf.put(DATE)
  175
+    self.class.serialize_cstr(buf, key)
  176
+    buf.put_long(val.to_i)
  177
+  end
  178
+
  179
+  def serialize_number_element(buf, key, val, type)
  180
+    buf.put(type)
  181
+    self.class.serialize_cstr(buf, key)
  182
+    if type == NUMBER
  183
+      buf.put_double(val)
  184
+    else
  185
+      buf.put_int(val)
  186
+    end
  187
+  end
  188
+
  189
+  def serialize_object_element(buf, key, val)
  190
+    buf.put(OBJECT)
  191
+    self.class.serialize_cstr(buf, key)
  192
+    BSON.new(buf).serialize(val)
  193
+  end
  194
+
  195
+  def serialize_oid_element(buf, key, val)
  196
+    buf.put(OID)
  197
+    self.class.serialize_cstr(buf, key)
  198
+    buf.put_array(val.to_a)
  199
+  end
  200
+
  201
+  def serialize_string_element(buf, key, val, type)
  202
+    buf.put(type)
  203
+    self.class.serialize_cstr(buf, key)
  204
+
  205
+    # Make a hole for the length
  206
+    len_pos = buf.position
  207
+    buf.put_int(0)
  208
+
  209
+    # Save the string
  210
+    start_pos = buf.position
  211
+    self.class.serialize_cstr(buf, val)
  212
+    end_pos = buf.position
  213
+
  214
+    # Put the string size in front
  215
+    buf.put_int(end_pos - start_pos - 1, len_pos)
  216
+
  217
+    # Go back to where we were
  218
+    buf.position = end_pos
  219
+  end
  220
+
  221
+  def deserialize_element_name(buf)
  222
+    chars = ""
  223
+    while 1
  224
+      b = buf.get
  225
+      break if b == 0
  226
+      chars << b.chr
  227
+    end
  228
+    chars
  229
+  end
  230
+
  231
+  def bson_type(o, key)
  232
+    case o
  233
+    when nil
  234
+      NULL
  235
+    when Integer
  236
+      NUMBER_INT
  237
+    when Numeric
  238
+      NUMBER
  239
+    when String
  240
+      # magic awful stuff - the DB requires that a where clause is sent as CODE
  241
+      key == "$where" ? CODE : STRING
  242
+    when Array
  243
+      ARRAY
  244
+    when ObjectID
  245
+      OID
  246
+    when true, false
  247
+      Boolean
  248
+    when Time
  249
+      DATE
  250
+    when Hash
  251
+      OBJECT
  252
+    else
  253
+      raise "Unknown type of object: #{o.class.name}"
  254
+    end
  255
+  end
  256
+
  257
+end
132  lib/mongo/util/byte_buffer.rb
... ...
@@ -0,0 +1,132 @@
  1
+class ByteBuffer
  2
+
  3
+  attr_reader :order
  4
+
  5
+  def initialize(initial_data=[])
  6
+    @buf = initial_data
  7
+    @cursor = 0
  8
+    self.order = :little_endian
  9
+  end
  10
+
  11
+  # +endianness+ should be :little_endian or :big_endian. Default is :little_endian
  12
+  def order=(endianness)
  13
+    @order = endianness
  14
+    @int_pack_order = endianness == :little_endian ? 'V' : 'N'
  15
+    @double_pack_order = endianness == :little_endian ? 'E' : 'G'
  16
+  end
  17
+
  18
+  def rewind
  19
+    @cursor = 0
  20
+  end
  21
+
  22
+  def position
  23
+    @cursor
  24
+  end
  25
+
  26
+  def position=(val)
  27
+    @cursor = val
  28
+  end
  29
+
  30
+  def clear
  31
+    @buf = []
  32
+    rewind
  33
+  end
  34
+
  35
+  def size
  36
+    @buf.size
  37
+  end
  38
+  alias_method :length, :size
  39
+
  40
+  def put(byte, offset=nil)
  41
+    @cursor = offset if offset
  42
+    @buf[@cursor] = byte
  43
+    @cursor += 1
  44
+  end
  45
+
  46
+  def put_array(array, offset=nil)
  47
+    @cursor = offset if offset
  48
+    @buf[@cursor, array.length] = array
  49
+    @cursor += array.length
  50
+  end
  51
+
  52
+  def put_int(i, offset=nil)
  53
+    put_array([i].pack(@int_pack_order).split(//).collect{|c| c[0]}, offset)
  54
+  end
  55
+
  56
+  def put_long(i, offset=nil)
  57
+    offset = @cursor unless offset
  58
+    if @int_pack_order == 'N'
  59
+      put_int(i >> 32, offset)
  60
+      put_int(i & 0xffffffff, offset + 4)
  61
+    else
  62
+      put_int(i & 0xffffffff, offset)
  63
+      put_int(i >> 32, offset + 4)
  64
+    end
  65
+  end
  66
+
  67
+  def put_double(d, offset=nil)
  68
+    put_array([d].pack(@double_pack_order).split(//), offset)
  69
+  end
  70
+
  71
+  # If +size+ == 1, returns one byte. Else returns array of bytes of length
  72
+  # +size+.
  73
+  def get(len=1)
  74
+    check_read_length(len)
  75
+    start = @cursor
  76
+    @cursor += len
  77
+    if len == 1
  78
+      @buf[start]
  79
+    else
  80
+      @buf[start, len]
  81
+    end
  82
+  end
  83
+
  84
+  def get_int
  85
+    check_read_length(4)
  86
+    vals = ""
  87
+    (@cursor..@cursor+3).each { |i| vals << @buf[i].chr }
  88
+    @cursor += 4
  89
+    vals.unpack(@int_pack_order)[0]
  90
+  end
  91
+
  92
+  def get_long
  93
+    i1 = get_int
  94
+    i2 = get_int
  95
+    if @int_pack_order == 'N'
  96
+      (i1 << 32) + i2
  97
+    else
  98
+      (i2 << 32) + i1
  99
+    end
  100
+  end
  101
+
  102
+  def get_double