monga 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/.gitignore +17 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +110 -0
- data/Rakefile +13 -0
- data/lib/monga/client.rb +8 -0
- data/lib/monga/clients/client.rb +24 -0
- data/lib/monga/clients/master_slave_client.rb +5 -0
- data/lib/monga/clients/replica_set_client.rb +101 -0
- data/lib/monga/collection.rb +108 -0
- data/lib/monga/connection.rb +23 -0
- data/lib/monga/connection_pool.rb +38 -0
- data/lib/monga/connections/em_connection.rb +152 -0
- data/lib/monga/connections/primary.rb +46 -0
- data/lib/monga/connections/secondary.rb +13 -0
- data/lib/monga/cursor.rb +175 -0
- data/lib/monga/database.rb +97 -0
- data/lib/monga/exceptions.rb +9 -0
- data/lib/monga/miner.rb +72 -0
- data/lib/monga/request.rb +122 -0
- data/lib/monga/requests/delete.rb +23 -0
- data/lib/monga/requests/get_more.rb +19 -0
- data/lib/monga/requests/insert.rb +29 -0
- data/lib/monga/requests/kill_cursors.rb +25 -0
- data/lib/monga/requests/query.rb +49 -0
- data/lib/monga/requests/update.rb +25 -0
- data/lib/monga/response.rb +11 -0
- data/lib/monga.rb +30 -0
- data/monga.gemspec +26 -0
- data/spec/helpers/mongodb.rb +59 -0
- data/spec/helpers/truncate.rb +15 -0
- data/spec/monga/collection_spec.rb +448 -0
- data/spec/monga/connection_pool_spec.rb +50 -0
- data/spec/monga/connection_spec.rb +64 -0
- data/spec/monga/cursor_spec.rb +186 -0
- data/spec/monga/database_spec.rb +67 -0
- data/spec/monga/replica_set_client_spec.rb +46 -0
- data/spec/monga/requests/delete_spec.rb +0 -0
- data/spec/monga/requests/insert_spec.rb +0 -0
- data/spec/monga/requests/query_spec.rb +28 -0
- data/spec/spec_helper.rb +29 -0
- metadata +185 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2013 Petr Yanovich
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            MIT License
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 6 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 7 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 8 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 9 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 10 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 11 | 
            +
            the following conditions:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 14 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 17 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 18 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 19 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 20 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 21 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 22 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,110 @@ | |
| 1 | 
            +
            # Monga
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            [MongoDB](http://www.mongodb.org/) Ruby Client on [EventMachine](https://github.com/eventmachine/eventmachine).
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            This client is under development. You can try [em-mongo](https://github.com/bcg/em-mongo).
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Client supports MongoDB 2.4. Some features won't work in lower versions.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ## To Do List
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * [ ] Write a Wiki
         | 
| 12 | 
            +
            * [ ] Write comments
         | 
| 13 | 
            +
            * [ ] Grammar improvement ;)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            ### Clients
         | 
| 16 | 
            +
            * [x] Client (Single instance connection)
         | 
| 17 | 
            +
            * [IN PROCESS] ReplicaSetClient
         | 
| 18 | 
            +
            * [ ] MasterSlaveClient
         | 
| 19 | 
            +
            * [ ] ReadPref
         | 
| 20 | 
            +
            * [ ] Sharding Support
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            ### Connection
         | 
| 23 | 
            +
            * [x] Connection
         | 
| 24 | 
            +
            * [x] Autoreconnect
         | 
| 25 | 
            +
            * [x] Connection Pool
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ### Protocol
         | 
| 28 | 
            +
            * [x] OP_QUERY
         | 
| 29 | 
            +
            * [x] OP_GET_MORE
         | 
| 30 | 
            +
            * [x] OP_KILL_CURSORS
         | 
| 31 | 
            +
            * [x] OP_INSERT
         | 
| 32 | 
            +
            * [x] OP_UPDATE
         | 
| 33 | 
            +
            * [x] OP_DELETE
         | 
| 34 | 
            +
            * [x] OP_REPLY
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            ### Database
         | 
| 37 | 
            +
            * [x] create_collection
         | 
| 38 | 
            +
            * [x] drop_collection
         | 
| 39 | 
            +
            * [x] get_last_error
         | 
| 40 | 
            +
            * [x] drop_indexes
         | 
| 41 | 
            +
            * [x] get_indexes
         | 
| 42 | 
            +
            * Authentication
         | 
| 43 | 
            +
                * [ ] login
         | 
| 44 | 
            +
                * [ ] logout
         | 
| 45 | 
            +
                * [ ] add_user
         | 
| 46 | 
            +
            * [ ] check maxBsonSize / validate
         | 
| 47 | 
            +
            * [x] cmd
         | 
| 48 | 
            +
            * [x] eval
         | 
| 49 | 
            +
            * [ ] where
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            ### Collection
         | 
| 52 | 
            +
            * QUERY_OP
         | 
| 53 | 
            +
                * [x] find
         | 
| 54 | 
            +
                * [x] find_one (first)
         | 
| 55 | 
            +
                * [ ] sorting
         | 
| 56 | 
            +
            * INSERT_OP
         | 
| 57 | 
            +
                * [x] insert (single)
         | 
| 58 | 
            +
                * [x] insert (batch)
         | 
| 59 | 
            +
                * [x] safe_insert
         | 
| 60 | 
            +
                * FLAGS
         | 
| 61 | 
            +
                    * [x] continue_on_error
         | 
| 62 | 
            +
            * UPDATE_OP
         | 
| 63 | 
            +
                * [x] update
         | 
| 64 | 
            +
                * [x] safe_update
         | 
| 65 | 
            +
                * FLAGS
         | 
| 66 | 
            +
                    * [x] upsert
         | 
| 67 | 
            +
                    * [x] multi_update
         | 
| 68 | 
            +
            * DELETE_OP
         | 
| 69 | 
            +
                * [x] delete
         | 
| 70 | 
            +
                * [x] safe_delete
         | 
| 71 | 
            +
                * FLAGS
         | 
| 72 | 
            +
                    * [x] single_remove
         | 
| 73 | 
            +
            * INDEXES
         | 
| 74 | 
            +
                * [x] ensure_index
         | 
| 75 | 
            +
                * [x] drop_index
         | 
| 76 | 
            +
                * [x] drop_indexes
         | 
| 77 | 
            +
                * [x] get_indexes
         | 
| 78 | 
            +
            * [x] count
         | 
| 79 | 
            +
            * [x] all
         | 
| 80 | 
            +
            * [x] cursor
         | 
| 81 | 
            +
            * [ ] DBRef
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            ### Cursor
         | 
| 84 | 
            +
            * [x] limit
         | 
| 85 | 
            +
            * [x] skip
         | 
| 86 | 
            +
            * [x] batch_size
         | 
| 87 | 
            +
            * [x] get_more
         | 
| 88 | 
            +
            * [x] next_document
         | 
| 89 | 
            +
            * [x] next_batch
         | 
| 90 | 
            +
            * [x] each_doc
         | 
| 91 | 
            +
            * [x] kill
         | 
| 92 | 
            +
            * [x] mark_to_kill
         | 
| 93 | 
            +
            * [x] batch_kill
         | 
| 94 | 
            +
            * [x] explain
         | 
| 95 | 
            +
            * [x] hint
         | 
| 96 | 
            +
            * Flags
         | 
| 97 | 
            +
                * [x] tailable_cursor
         | 
| 98 | 
            +
                * [x] slave_ok
         | 
| 99 | 
            +
                * [x] no_cursor_timeout
         | 
| 100 | 
            +
                * [x] await_data
         | 
| 101 | 
            +
                * [x] exhaust
         | 
| 102 | 
            +
                * [x] partial
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            # ISSUES handled with
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            Some commands, such as `db.getLastError`, `db.count` and other `db.commands` requires `numberToReturn` in OP_QUERY to be setted as `-1`. Also this commands should return a response. If nothing returned it should be interpreted as an Exception. Also, in contrast to the other queries it could return `errmsg` which should be interpreted as an Exception too. Other query methods could return `err` response.
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            To create index you can't call any `db.ensureIndex` command but you should insert a document into `sytem.indexes` collection manually. To get list of indexes you should fetch all documents from this collection. But to drop index you should call specific `db.dropIndexes` command.
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            `multi_update` flag works only with `$` commands (i.e. `$set: { title: "blahblah" }`)
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            require "bundler/gem_tasks"
         | 
| 2 | 
            +
            require "rake/testtask"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            task :default => :spec
         | 
| 5 | 
            +
            Rake::TestTask.new(:spec) do |t|
         | 
| 6 | 
            +
              t.libs << 'spec'
         | 
| 7 | 
            +
              if spec = ENV['spec']
         | 
| 8 | 
            +
                t.pattern = "spec/**/#{spec}*_spec.rb"
         | 
| 9 | 
            +
              else
         | 
| 10 | 
            +
                t.pattern = 'spec/**/*_spec.rb'
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
              t.verbose = false
         | 
| 13 | 
            +
            end
         | 
    
        data/lib/monga/client.rb
    ADDED
    
    | @@ -0,0 +1,8 @@ | |
| 1 | 
            +
            require File.expand_path("../clients/client", __FILE__)
         | 
| 2 | 
            +
            require File.expand_path("../clients/replica_set_client", __FILE__)
         | 
| 3 | 
            +
            require File.expand_path("../clients/master_slave_client", __FILE__)
         | 
| 4 | 
            +
            module Monga
         | 
| 5 | 
            +
              Client = Monga::Clients::Client
         | 
| 6 | 
            +
              ReplicaSetClient = Monga::Clients::ReplicaSetClient
         | 
| 7 | 
            +
              MasterSlaveClient = Monga::Clients::MasterSlaveClient
         | 
| 8 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            module Monga::Clients
         | 
| 2 | 
            +
              class Client
         | 
| 3 | 
            +
                extend Forwardable
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def_delegators :@connection_pool, :aquire_connection, :send_command, :primary?, :connected?
         | 
| 6 | 
            +
                
         | 
| 7 | 
            +
                attr_reader :connection_pool
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(opts={})
         | 
| 10 | 
            +
                  opts[:pool_size] ||= 1
         | 
| 11 | 
            +
                  @connection_pool = Monga::ConnectionPool.new(opts)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def [](db_name)
         | 
| 15 | 
            +
                  Monga::Database.new(self, db_name)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def find_primary!
         | 
| 19 | 
            +
                  @connection_pool.connections.each do |conn|
         | 
| 20 | 
            +
                    conn.is_master?(self)
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,101 @@ | |
| 1 | 
            +
            # How it works
         | 
| 2 | 
            +
            # Replica Set tries to establish connections to all passed servers.
         | 
| 3 | 
            +
            # Till no connection established it queues al queries inside it's
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Monga::Clients
         | 
| 6 | 
            +
              class ReplicaSetClient
         | 
| 7 | 
            +
                class ProxyConnection
         | 
| 8 | 
            +
                  include EM::Deferrable
         | 
| 9 | 
            +
                  def initialize(client)
         | 
| 10 | 
            +
                    @client = client
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def send_command(msg, request_id=nil, &cb)
         | 
| 14 | 
            +
                    callback do
         | 
| 15 | 
            +
                      connection = @client.aquire_connection
         | 
| 16 | 
            +
                      connection.send_command(msg, request_id, &cb)
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                include EM::Deferrable
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                attr_reader :servers, :clients
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def initialize(opts = {})
         | 
| 26 | 
            +
                  @read_pref = opts.delete(:read_pref) || :primary
         | 
| 27 | 
            +
                  @servers = opts.delete(:servers)
         | 
| 28 | 
            +
                  raise ArgumentError, "servers option is not passed or empty" if @servers.empty?
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  @clients = @servers.map do |server|
         | 
| 31 | 
            +
                    Monga::Client.new(server.merge(opts))
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  @proxy_connection = ProxyConnection.new(self)
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def [](db_name)
         | 
| 38 | 
            +
                  Monga::Database.new(self, db_name)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def aquire_connection
         | 
| 42 | 
            +
                  server ||= case @read_pref
         | 
| 43 | 
            +
                  when :primary
         | 
| 44 | 
            +
                    primary
         | 
| 45 | 
            +
                  when :secondary
         | 
| 46 | 
            +
                    secondary
         | 
| 47 | 
            +
                  when :primary_preferred
         | 
| 48 | 
            +
                    primary || secondary
         | 
| 49 | 
            +
                  when :secondary_preferred
         | 
| 50 | 
            +
                    secondary || primary
         | 
| 51 | 
            +
                  when :nearest
         | 
| 52 | 
            +
                    fail "unimplemented read_pref mode"
         | 
| 53 | 
            +
                  else
         | 
| 54 | 
            +
                    fail "read_pref is undefined"
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
             | 
| 58 | 
            +
                  if server
         | 
| 59 | 
            +
                    if @deferred_status != :succeeded
         | 
| 60 | 
            +
                      set_deferred_status :succeeded 
         | 
| 61 | 
            +
                      @proxy_connection.set_deferred_status :succeeded
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  else
         | 
| 64 | 
            +
                    if @deferred_status == :succeeded
         | 
| 65 | 
            +
                      set_deferred_status nil
         | 
| 66 | 
            +
                      @proxy_connection.set_deferred_status nil
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  server || @proxy_connection
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def primary
         | 
| 74 | 
            +
                  prim = @clients.detect{ |c| c.primary? && c.connected? }
         | 
| 75 | 
            +
                  unless prim
         | 
| 76 | 
            +
                    find_primary!
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                  prim
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def secondary
         | 
| 82 | 
            +
                  @clients.select{ |c| !c.primary? && c.connected? }.sample
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def find_primary!
         | 
| 86 | 
            +
                  unless @pending_primary
         | 
| 87 | 
            +
                    @pending_primary = true
         | 
| 88 | 
            +
                    @clients.each{ |c| c.find_primary! }
         | 
| 89 | 
            +
                    EM.add_timer(0.1) do
         | 
| 90 | 
            +
                      if primary
         | 
| 91 | 
            +
                        aquire_connection
         | 
| 92 | 
            +
                        @pending_primary = false
         | 
| 93 | 
            +
                      else
         | 
| 94 | 
            +
                        @pending_primary = false
         | 
| 95 | 
            +
                        find_primary!
         | 
| 96 | 
            +
                      end
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
            end
         | 
| @@ -0,0 +1,108 @@ | |
| 1 | 
            +
            module Monga
         | 
| 2 | 
            +
              class Collection
         | 
| 3 | 
            +
                attr_reader :name, :db
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(db, name)
         | 
| 6 | 
            +
                  @db = db
         | 
| 7 | 
            +
                  @name = name
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def query(query = {}, fields = {}, opts = {})
         | 
| 11 | 
            +
                  options = {}
         | 
| 12 | 
            +
                  options[:query] = query
         | 
| 13 | 
            +
                  options[:fields] = fields
         | 
| 14 | 
            +
                  options.merge! opts
         | 
| 15 | 
            +
                  Monga::Miner.new(db, name, options)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
                alias :find :query
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def find_one(query = {}, fields = {}, opts = {})
         | 
| 20 | 
            +
                  options = {}
         | 
| 21 | 
            +
                  options[:query] = query
         | 
| 22 | 
            +
                  options[:fields] = fields
         | 
| 23 | 
            +
                  options.merge! opts
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  Monga::Response.surround do |resp|
         | 
| 26 | 
            +
                    req = Monga::Miner.new(db, name, options).limit(1)
         | 
| 27 | 
            +
                    req.callback{ |data| resp.succeed data.first }
         | 
| 28 | 
            +
                    req.errback{ |err| resp.fail err }
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
                alias :first :find_one
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def insert(documents, opts = {})
         | 
| 34 | 
            +
                  options = {}
         | 
| 35 | 
            +
                  options[:documents] = documents
         | 
| 36 | 
            +
                  options.merge!(opts)
         | 
| 37 | 
            +
                  Monga::Requests::Insert.new(@db, @name, options).perform
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def update(query = {}, update = {}, flags = {})
         | 
| 41 | 
            +
                  options = {}
         | 
| 42 | 
            +
                  options[:query] = query
         | 
| 43 | 
            +
                  options[:update] = update
         | 
| 44 | 
            +
                  options.merge!(flags)
         | 
| 45 | 
            +
                  Monga::Requests::Update.new(@db, @name, options).perform
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def delete(query = {}, opts = {})
         | 
| 49 | 
            +
                  options = {}
         | 
| 50 | 
            +
                  options[:query] = query
         | 
| 51 | 
            +
                  options.merge!(opts)
         | 
| 52 | 
            +
                  Monga::Requests::Delete.new(@db, @name, options).perform
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
                alias :remove :delete
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def ensure_index(keys, opts={})
         | 
| 57 | 
            +
                  doc = {}
         | 
| 58 | 
            +
                  doc.merge!(opts)
         | 
| 59 | 
            +
                  # Read docs about naming
         | 
| 60 | 
            +
                  doc[:name] ||= keys.to_a.flatten * "_"
         | 
| 61 | 
            +
                  doc[:key] = keys
         | 
| 62 | 
            +
                  doc[:ns] = "#{db.name}.#{name}"
         | 
| 63 | 
            +
                  Monga::Requests::Insert.new(@db, "system.indexes", {documents: doc}).perform
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def drop_index(indexes)
         | 
| 67 | 
            +
                  @db.drop_indexes(@name, indexes)
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def drop_indexes
         | 
| 71 | 
            +
                  @db.drop_indexes(@name, "*")
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def get_indexes
         | 
| 75 | 
            +
                  Monga::Miner.new(@db, "system.indexes")
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                def drop
         | 
| 79 | 
            +
                  @db.drop_collection(@name)
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def count
         | 
| 83 | 
            +
                  @db.count(@name)
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                # Safe methods
         | 
| 87 | 
            +
                [:update, :insert, :delete, :remove, :ensure_index].each do |meth|
         | 
| 88 | 
            +
                  class_eval <<-EOS
         | 
| 89 | 
            +
                    def safe_#{meth}(*args)
         | 
| 90 | 
            +
                      safe do
         | 
| 91 | 
            +
                        #{meth}(*args)
         | 
| 92 | 
            +
                      end
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
                  EOS
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                def safe
         | 
| 98 | 
            +
                  response = Monga::Response.new
         | 
| 99 | 
            +
                  request_id = yield
         | 
| 100 | 
            +
                  req = @db.get_last_error
         | 
| 101 | 
            +
                  req.callback do |data|
         | 
| 102 | 
            +
                    response.succeed(request_id)
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
                  req.errback{ |err| response.fail(err) }
         | 
| 105 | 
            +
                  response
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            require File.expand_path("../connections/em_connection", __FILE__)
         | 
| 2 | 
            +
            require File.expand_path("../connections/primary", __FILE__)
         | 
| 3 | 
            +
            require File.expand_path("../connections/secondary", __FILE__)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Monga
         | 
| 6 | 
            +
              class Connection
         | 
| 7 | 
            +
                extend Forwardable
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def_delegators :@connection, :connected?, :reconnect, :responses, :send_command, :master?, :is_master?, :host, :port
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                CONNECTION_TYPES = {
         | 
| 12 | 
            +
                  default: Monga::Connections::EMConnection,
         | 
| 13 | 
            +
                  primary: Monga::Connections::Primary,
         | 
| 14 | 
            +
                  secondary: Monga::Connections::Secondary,
         | 
| 15 | 
            +
                }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def initialize(opts={})
         | 
| 18 | 
            +
                  conn_type = opts.delete(:connection_type) || :default
         | 
| 19 | 
            +
                  conn_class = CONNECTION_TYPES[conn_type]
         | 
| 20 | 
            +
                  @connection = conn_class.connect(opts)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            module Monga
         | 
| 2 | 
            +
              class ConnectionPool < Monga::Connection
         | 
| 3 | 
            +
                extend Forwardable
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def_delegators :aquire_connection, :send_command
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                attr_reader :connections
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(opts={})
         | 
| 10 | 
            +
                  @connections = []
         | 
| 11 | 
            +
                  pool_size = opts.delete :pool_size
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  pool_size.times do
         | 
| 14 | 
            +
                    @connections << Monga::Connection.new(opts)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def aquire_connection
         | 
| 19 | 
            +
                  connected = @connections.select(&:connected?)
         | 
| 20 | 
            +
                  if connected.any?
         | 
| 21 | 
            +
                    min = connected.min_by{ |c| c.responses.size }.responses.size
         | 
| 22 | 
            +
                    conns = connected.select{ |c| c.responses.size == min }
         | 
| 23 | 
            +
                    conns.sample
         | 
| 24 | 
            +
                  else
         | 
| 25 | 
            +
                    @connections.sample
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def primary?
         | 
| 30 | 
            +
                  conn = aquire_connection
         | 
| 31 | 
            +
                  conn ? conn.master? : false
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def connected?
         | 
| 35 | 
            +
                  @connections.any?(&:connected?)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
| @@ -0,0 +1,152 @@ | |
| 1 | 
            +
            module Monga::Connections
         | 
| 2 | 
            +
              class EMConnection < EM::Connection
         | 
| 3 | 
            +
                include EM::Deferrable
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                class Buffer
         | 
| 6 | 
            +
                  include Enumerable
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  attr_reader :buffer
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize
         | 
| 11 | 
            +
                    @buffer = ""
         | 
| 12 | 
            +
                    @positon = 0
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def append(data)
         | 
| 16 | 
            +
                    @buffer += data
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def each
         | 
| 20 | 
            +
                    while true
         | 
| 21 | 
            +
                      size = @buffer.size
         | 
| 22 | 
            +
                      if size > Monga::HEADER_SIZE
         | 
| 23 | 
            +
                        msg_length = @buffer[0, 4].unpack("L").first
         | 
| 24 | 
            +
                        if msg_length && size >= msg_length
         | 
| 25 | 
            +
                          data = @buffer.slice!(0, size)
         | 
| 26 | 
            +
                          yield data.unpack("LLLLLQLLa*")
         | 
| 27 | 
            +
                        else
         | 
| 28 | 
            +
                          break
         | 
| 29 | 
            +
                        end
         | 
| 30 | 
            +
                      else
         | 
| 31 | 
            +
                        break
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                attr_reader :responses, :host, :port
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def initialize(opts = {})
         | 
| 40 | 
            +
                  @host = opts[:host]
         | 
| 41 | 
            +
                  @port = opts[:port]
         | 
| 42 | 
            +
                  @reactor_running = true
         | 
| 43 | 
            +
                  @responses = {}
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def self.connect(opts = {})
         | 
| 47 | 
            +
                  host = opts[:host] ||= Monga::DEFAULT_HOST
         | 
| 48 | 
            +
                  port = opts[:port] ||= Monga::DEFAULT_PORT
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  EM.connect(host, port, self, opts)
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def send_command(msg, request_id=nil, &cb)
         | 
| 54 | 
            +
                  reconnect unless @connected
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  callback do
         | 
| 57 | 
            +
                    send_data msg
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  @responses[request_id] = cb if cb
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def receive_data(data)
         | 
| 64 | 
            +
                  @buffer.append(data)
         | 
| 65 | 
            +
                  @buffer.each do |message|
         | 
| 66 | 
            +
                    request_id = message[2]
         | 
| 67 | 
            +
                    cb = @responses.delete request_id
         | 
| 68 | 
            +
                    cb.call(message) if cb
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def connection_completed
         | 
| 73 | 
            +
                  Monga.logger.debug("Connection is established #{@host}:#{@port}")
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  EM.add_shutdown_hook do
         | 
| 76 | 
            +
                    close
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  unless @reactor_running
         | 
| 80 | 
            +
                    EM.add_periodic_timer(Monga::Cursor::CLOSE_TIMEOUT){ Monga::Cursor.batch_kill(self) }
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  @connected = true
         | 
| 84 | 
            +
                  @pending_for_reconnect = false
         | 
| 85 | 
            +
                  @buffer = Buffer.new
         | 
| 86 | 
            +
                  @reactor_running = true
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  succeed
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def reconnect
         | 
| 92 | 
            +
                  unless @connected && @pending_for_reconnect
         | 
| 93 | 
            +
                    if @reactor_running
         | 
| 94 | 
            +
                      super(@host, @port)
         | 
| 95 | 
            +
                    else
         | 
| 96 | 
            +
                      EM.schedule{ super(@host, @port) }
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
                    @pending_for_reconnect = true
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def force_reconnect(host, port)
         | 
| 103 | 
            +
                  @connected = false
         | 
| 104 | 
            +
                  @host = host
         | 
| 105 | 
            +
                  @port = port
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def connected?
         | 
| 109 | 
            +
                  EM.schedule { reconnect } unless @reactor_running
         | 
| 110 | 
            +
                  @connected || false
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                def unbind
         | 
| 114 | 
            +
                  Monga.logger.debug("Lost connection #{@host}:#{@port}")
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  @responses.each{ |k, cb| cb.call(Monga::Exceptions::LostConnection.new("Mongo has lost connection"))}
         | 
| 117 | 
            +
                  @connected = false
         | 
| 118 | 
            +
                  @primary = false
         | 
| 119 | 
            +
                  @pending_for_reconnect = false
         | 
| 120 | 
            +
                  set_deferred_status(nil)
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                  if @reactor_running
         | 
| 123 | 
            +
                    EM.add_timer(0.1){ reconnect }
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                def close
         | 
| 128 | 
            +
                  Monga.logger.debug("EventMachine is stopped, closing connection")
         | 
| 129 | 
            +
                  @reactor_running = false
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                def master?
         | 
| 133 | 
            +
                  @primary || false
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def is_master?(client)
         | 
| 137 | 
            +
                  db = client["admin"]
         | 
| 138 | 
            +
                  req = Monga::Requests::Query.new(db, "$cmd", query: {"isMaster" => 1}, limit: 1)
         | 
| 139 | 
            +
                  command = req.command
         | 
| 140 | 
            +
                  request_id = req.request_id
         | 
| 141 | 
            +
                  @responses[request_id] = proc do |data|
         | 
| 142 | 
            +
                    resp = req.parse_response(data)
         | 
| 143 | 
            +
                    if Exception === resp
         | 
| 144 | 
            +
                      @primary = false
         | 
| 145 | 
            +
                    else
         | 
| 146 | 
            +
                      @primary = resp.last.first["ismaster"]
         | 
| 147 | 
            +
                    end
         | 
| 148 | 
            +
                  end
         | 
| 149 | 
            +
                  send_data command
         | 
| 150 | 
            +
                end
         | 
| 151 | 
            +
              end
         | 
| 152 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            module Monga::Connections
         | 
| 2 | 
            +
              class Primary < Monga::Connections::EMConnection
         | 
| 3 | 
            +
                def initialize(opts)
         | 
| 4 | 
            +
                  @client = opts.delete :client
         | 
| 5 | 
            +
                  @ser
         | 
| 6 | 
            +
                  super
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def connection_completed
         | 
| 10 | 
            +
                  check_master do |master|
         | 
| 11 | 
            +
                    if master
         | 
| 12 | 
            +
                      super
         | 
| 13 | 
            +
                    else
         | 
| 14 | 
            +
                      reconnect
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def check_master
         | 
| 20 | 
            +
                  db = @client["admin"]
         | 
| 21 | 
            +
                  request = Monga::Request.new(db, "$cmd", query: { "isMaster" => 1 })
         | 
| 22 | 
            +
                  request_id = request.request_id
         | 
| 23 | 
            +
                  @responses[request_id] = proc do |data|
         | 
| 24 | 
            +
                    request.parse_response(data)
         | 
| 25 | 
            +
                    if Exception === data
         | 
| 26 | 
            +
                      Monga.logger.debug("Error on connecting Primary to #{@host}:#{@port}, #{data.class}, #{data.message}")
         | 
| 27 | 
            +
                      @host, @port = @client.next_addr(@host, @port)
         | 
| 28 | 
            +
                      yield false
         | 
| 29 | 
            +
                    else
         | 
| 30 | 
            +
                      doc = data.last.first
         | 
| 31 | 
            +
                      if doc["ismaster"]
         | 
| 32 | 
            +
                        Monga.logger.debug("Primary has connected to #{@host}:#{@port}")
         | 
| 33 | 
            +
                        @client.inform(:primary, @host, @port)
         | 
| 34 | 
            +
                        yield true
         | 
| 35 | 
            +
                      else
         | 
| 36 | 
            +
                        Monga.logger.debug("#{@host}:#{@port} is not a primary")
         | 
| 37 | 
            +
                        @host, @port = @client.select_addr(doc)
         | 
| 38 | 
            +
                        yield false
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                  command = request.command
         | 
| 43 | 
            +
                  send_data(command)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         |