monga 0.0.2 → 0.0.3

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.
Files changed (63) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +1 -0
  3. data/README.md +59 -3
  4. data/lib/monga/client.rb +51 -6
  5. data/lib/monga/clients/master_slave_client.rb +0 -5
  6. data/lib/monga/clients/replica_set_client.rb +32 -71
  7. data/lib/monga/clients/single_instance_client.rb +53 -0
  8. data/lib/monga/collection.rb +102 -41
  9. data/lib/monga/connection.rb +38 -13
  10. data/lib/monga/connection_pool.rb +6 -17
  11. data/lib/monga/connections/buffer.rb +33 -0
  12. data/lib/monga/connections/em_connection.rb +25 -56
  13. data/lib/monga/connections/em_proxy_connection.rb +80 -0
  14. data/lib/monga/connections/fibered_connection.rb +26 -0
  15. data/lib/monga/connections/fibered_proxy_connection.rb +23 -0
  16. data/lib/monga/connections/proxy_connection.rb +4 -0
  17. data/lib/monga/connections/tcp_connection.rb +57 -0
  18. data/lib/monga/cursor.rb +197 -95
  19. data/lib/monga/database.rb +175 -60
  20. data/lib/monga/{requests → protocol}/delete.rb +1 -2
  21. data/lib/monga/{requests → protocol}/get_more.rb +1 -1
  22. data/lib/monga/{requests → protocol}/insert.rb +1 -2
  23. data/lib/monga/{requests → protocol}/kill_cursors.rb +1 -1
  24. data/lib/monga/{requests → protocol}/query.rb +3 -3
  25. data/lib/monga/{requests → protocol}/update.rb +1 -1
  26. data/lib/monga/request.rb +27 -23
  27. data/lib/monga/utils/constants.rb +5 -0
  28. data/lib/monga/utils/exceptions.rb +11 -0
  29. data/lib/monga.rb +19 -11
  30. data/monga.gemspec +2 -2
  31. data/spec/helpers/mongodb.rb +115 -38
  32. data/spec/monga/block/collection_spec.rb +172 -0
  33. data/spec/monga/block/cursor_spec.rb +160 -0
  34. data/spec/monga/block/database_spec.rb +80 -0
  35. data/spec/monga/block/single_instance_client_spec.rb +31 -0
  36. data/spec/monga/em/collection_spec.rb +308 -0
  37. data/spec/monga/em/cursor_spec.rb +256 -0
  38. data/spec/monga/em/database_spec.rb +140 -0
  39. data/spec/monga/em/replica_set_client_spec.rb +86 -0
  40. data/spec/monga/em/single_instance_client_spec.rb +28 -0
  41. data/spec/monga/sync/collection_spec.rb +247 -0
  42. data/spec/monga/sync/cursor_spec.rb +211 -0
  43. data/spec/monga/sync/database_spec.rb +110 -0
  44. data/spec/monga/sync/replica_set_client_spec.rb +54 -0
  45. data/spec/monga/sync/single_instance_client_spec.rb +25 -0
  46. data/spec/spec_helper.rb +2 -20
  47. metadata +50 -38
  48. data/lib/monga/clients/client.rb +0 -24
  49. data/lib/monga/connections/primary.rb +0 -46
  50. data/lib/monga/connections/secondary.rb +0 -13
  51. data/lib/monga/exceptions.rb +0 -9
  52. data/lib/monga/miner.rb +0 -72
  53. data/lib/monga/response.rb +0 -11
  54. data/spec/helpers/truncate.rb +0 -15
  55. data/spec/monga/collection_spec.rb +0 -448
  56. data/spec/monga/connection_pool_spec.rb +0 -50
  57. data/spec/monga/connection_spec.rb +0 -64
  58. data/spec/monga/cursor_spec.rb +0 -186
  59. data/spec/monga/database_spec.rb +0 -67
  60. data/spec/monga/replica_set_client_spec.rb +0 -46
  61. data/spec/monga/requests/delete_spec.rb +0 -0
  62. data/spec/monga/requests/insert_spec.rb +0 -0
  63. data/spec/monga/requests/query_spec.rb +0 -28
@@ -0,0 +1,33 @@
1
+ module Monga::Connections
2
+ class Buffer
3
+ include Enumerable
4
+
5
+ attr_reader :buffer
6
+
7
+ def initialize
8
+ @buffer = ""
9
+ @positon = 0
10
+ end
11
+
12
+ def append(data)
13
+ @buffer += data
14
+ end
15
+
16
+ def each
17
+ while true
18
+ size = @buffer.size
19
+ if size > Monga::HEADER_SIZE
20
+ msg_length = @buffer[0, 4].unpack("L").first
21
+ if msg_length && size >= msg_length
22
+ data = @buffer.slice!(0, msg_length)
23
+ yield data.unpack("LLLLLQLLa*")
24
+ else
25
+ break
26
+ end
27
+ else
28
+ break
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,61 +2,29 @@ module Monga::Connections
2
2
  class EMConnection < EM::Connection
3
3
  include EM::Deferrable
4
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, msg_length)
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
5
  attr_reader :responses, :host, :port
38
6
 
39
- def initialize(opts = {})
40
- @host = opts[:host]
41
- @port = opts[:port]
7
+ def initialize(host, port, timeout)
8
+ @host = host
9
+ @port = port
10
+ @timeout = timeout
42
11
  @reactor_running = true
43
12
  @responses = {}
44
13
  end
45
14
 
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)
15
+ def self.connect(host, port, timeout)
16
+ EM.connect(host, port, self, host, port, timeout)
51
17
  end
52
18
 
53
19
  def send_command(msg, request_id=nil, &cb)
20
+ # Reconnect is a hack for testing.
21
+ # We are stopping EvenMachine for each test.
22
+ # This hack reconnects to Mongo on first query
54
23
  reconnect unless @connected
55
24
 
56
25
  callback do
57
26
  send_data msg
58
27
  end
59
-
60
28
  @responses[request_id] = cb if cb
61
29
  end
62
30
 
@@ -99,22 +67,21 @@ module Monga::Connections
99
67
  end
100
68
  end
101
69
 
102
- def force_reconnect(host, port)
103
- @connected = false
104
- @host = host
105
- @port = port
106
- end
107
-
108
70
  def connected?
109
- EM.schedule { reconnect } unless @reactor_running
71
+ reconnect unless @reactor_running
110
72
  @connected || false
111
73
  end
112
74
 
113
75
  def unbind
76
+ @connected = false
114
77
  Monga.logger.debug("Lost connection #{@host}:#{@port}")
115
78
 
116
- @responses.each{ |k, cb| cb.call(Monga::Exceptions::LostConnection.new("Mongo has lost connection"))}
117
- @connected = false
79
+ @responses.keys.each do |k|
80
+ cb = @responses.delete k
81
+ err = Monga::Exceptions::Disconnected.new("Disconnected from #{@host}:#{@port}")
82
+ cb.call(err)
83
+ end
84
+
118
85
  @primary = false
119
86
  @pending_for_reconnect = false
120
87
  set_deferred_status(nil)
@@ -129,21 +96,23 @@ module Monga::Connections
129
96
  @reactor_running = false
130
97
  end
131
98
 
132
- def master?
99
+ def primary?
133
100
  @primary || false
134
101
  end
135
102
 
136
- def is_master?(client)
137
- db = client["admin"]
138
- req = Monga::Requests::Query.new(db, "$cmd", query: {"isMaster" => 1}, limit: 1)
103
+ def is_master?
104
+ reconnect unless @connected
105
+ req = Monga::Protocol::Query.new(self, "admin", "$cmd", query: {"isMaster" => 1}, limit: 1)
139
106
  command = req.command
140
107
  request_id = req.request_id
141
108
  @responses[request_id] = proc do |data|
142
- resp = req.parse_response(data)
143
- if Exception === resp
109
+ err, resp = req.parse_response(data)
110
+ if Exception === err
144
111
  @primary = false
112
+ yield nil
145
113
  else
146
114
  @primary = resp.last.first["ismaster"]
115
+ yield @primary ? :primary : :secondary
147
116
  end
148
117
  end
149
118
  send_data command
@@ -0,0 +1,80 @@
1
+ module Monga::Connections
2
+ # ProxyConnection accepts requests when ReplicaSetClient didn't know where to send requests.
3
+ # I.E. when client is just initialized here is no any established connections,
4
+ # so client waits for the connection ready to accept requests.
5
+ # Also, when primary is down it will collect request while nodes are voting.
6
+ # Importaint to say, that requests will be stored in this object only for `timeout` period.
7
+ class EMProxyConnection
8
+ # Pause while searching server in seconds
9
+ WAIT = 0.1
10
+
11
+ def initialize(client)
12
+ @client = client
13
+ @timeout = @client.timeout
14
+ @requests = {}
15
+ end
16
+
17
+ # If timeout is defined then collect request and start timout.
18
+ # If timout is not defined or zero then return exception.
19
+ def send_command(msg, request_id = nil, &cb)
20
+ if @timeout && @timeout > 0
21
+ @requests[request_id] = [msg, cb] if cb
22
+ set_timeout
23
+ find_server!
24
+ else
25
+ error = Monga::Exceptions::Disconnected.new "Can't find appropriate server (all disconnected)"
26
+ cb.call(error) if cb
27
+ end
28
+ end
29
+
30
+ # If timeout happend send exception to all collected requests.
31
+ def set_timeout
32
+ unless @pending_timeout
33
+ @pending_timeout = true
34
+ EM.add_timer(@timeout) do
35
+ @pending_timeout = false
36
+ @requests.keys.each do |request_id|
37
+ msg, cb = @requests.delete request_id
38
+ error = Monga::Exceptions::Disconnected.new "Can't find appropriate server (all disconnected)"
39
+ cb.call(error)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ # Find server unless server is found
46
+ def find_server!
47
+ if !@pending_server && @pending_timeout
48
+ @pending_server = true
49
+ _count = 0
50
+ @client.clients.each do |client|
51
+ client.force_status! do |status|
52
+ if status == :primary && [:primary, :primary_preferred, :secondary_preferred].include?(@client.read_pref)
53
+ @pending_server = false
54
+ server_found!
55
+ elsif status == :secondary && [:secondary, :primary_preferred, :secondary_preferred].include?(@client.read_pref)
56
+ @pending_server = false
57
+ server_found!
58
+ else
59
+ EM.add_timer(WAIT) do
60
+ EM.next_tick do
61
+ @pending_server = false if (_count +=1) == @client.clients.size
62
+ find_server!
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # YEEEHA! Send all collected requests back to client
71
+ def server_found!
72
+ @pending_timeout = false
73
+ @requests.keys.each do |request_id|
74
+ msg, blk = @requests.delete request_id
75
+ @client.aquire_connection.send_command(msg, request_id, &blk)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,26 @@
1
+ require 'fiber'
2
+
3
+ class Fiber
4
+ alias :call :resume
5
+ end
6
+
7
+ module Monga::Connections
8
+ class FiberedConnection < EMConnection
9
+ def send_command(msg, request_id=nil, &cb)
10
+ fib = Fiber.current
11
+ reconnect unless @connected
12
+
13
+ callback do
14
+ send_data msg
15
+ end
16
+
17
+ if cb
18
+ reconnect unless @connected
19
+ @responses[request_id] = fib
20
+ res = Fiber.yield
21
+ raise res if Exception === res
22
+ cb.call(res)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ module Monga::Connections
2
+ class FiberedProxyConnection < EMProxyConnection
3
+ def send_command(msg, request_id = nil, &cb)
4
+ if @timeout && @timeout > 0
5
+ @requests[request_id] = [msg, @fib]
6
+ @fib = Fiber.current
7
+ set_timeout
8
+ find_server!
9
+ res = Fiber.yield
10
+ @requests.delete(request_id)
11
+ raise res if Exception === res
12
+ @client.aquire_connection.send_command(msg, request_id, &cb)
13
+ else
14
+ error = Monga::Exceptions::Disconnected.new "Can't find appropriate server (all disconnected)"
15
+ cb.call(error) if cb
16
+ end
17
+ end
18
+
19
+ def server_found!
20
+ @fib.resume
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ module Monga::Connections
2
+ class ProxyConnection
3
+ end
4
+ end
@@ -0,0 +1,57 @@
1
+ require 'socket'
2
+
3
+ # Currently blocking mode is very poor.
4
+ # It is working as is.
5
+ # Going to support reconnecting and timouts later.
6
+ # Use it for tests and prototyping. Not the best choice for production.
7
+
8
+ module Monga::Connections
9
+ class TCPConnection
10
+ def self.connect(host, port, timeout)
11
+ new(host, port, timeout)
12
+ end
13
+
14
+ def initialize(host, port, timeout)
15
+ @host, @port, @timout = host, port, timeout
16
+ @connected = true
17
+ @buffer = Buffer.new
18
+ end
19
+
20
+ def connected?
21
+ @connected
22
+ end
23
+
24
+ def socket
25
+ @socket ||= TCPSocket.new(@host, @port)
26
+ end
27
+
28
+ # Fake answer, as far as we are blocking
29
+ def responses
30
+ 0
31
+ end
32
+
33
+ def send_command(msg, request_id=nil, &cb)
34
+ socket.send msg.to_s, 0
35
+ if cb
36
+ length = socket.read(4)
37
+ raise Errno::ECONNREFUSED, "Socket returns nothing like it would be closed." unless length
38
+ @buffer.append(length)
39
+ l = length.unpack("L").first
40
+ rest = socket.read(l-4)
41
+ @buffer.append(rest)
42
+ @buffer.each do |message|
43
+ rid = message[2]
44
+ fail "Returned Request Id is not equal to sended one" if rid != request_id
45
+ cb.call(message)
46
+ end
47
+ end
48
+ rescue Errno::ECONNREFUSED, Errno::EPIPE => e
49
+ @connected = false
50
+ @socket = nil
51
+ if cb
52
+ err = Monga::Exceptions::Disconnected.new("Disconnected from #{@host}:#{@port}, #{e.message}")
53
+ cb.call(err)
54
+ end
55
+ end
56
+ end
57
+ end