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
@@ -1,4 +1,4 @@
1
- module Monga::Requests
1
+ module Monga::Protocol
2
2
  class Update < Monga::Request
3
3
  op_name :update
4
4
 
data/lib/monga/request.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Monga
2
2
  class Request
3
- attr_reader :request_id
3
+ attr_reader :request_id, :connection
4
4
 
5
5
  OP_CODES = {
6
6
  reply: 1,
@@ -14,12 +14,13 @@ module Monga
14
14
  kill_cursors: 2007,
15
15
  }
16
16
 
17
- def initialize(db, collection_name, options = {})
18
- @db = db
17
+ def initialize(connection, db_name, collection_name, options = {})
18
+ @connection = connection
19
+ @db_name = db_name
19
20
  @collection_name = collection_name
20
21
  @options = options
22
+
21
23
  @request_id = self.class.request_id
22
- @connection = @db.client.aquire_connection
23
24
  end
24
25
 
25
26
  def command
@@ -37,23 +38,25 @@ module Monga
37
38
 
38
39
  # Fire and Forget
39
40
  def perform
40
- @connection.send_command(command)
41
- @request_id
41
+ @connection.send_command(command, @request_id)
42
+ self
42
43
  end
43
44
 
44
45
  # Fire and wait
45
46
  def callback_perform
46
- Monga::Response.surround do |response|
47
- @connection.send_command(command, @request_id) do |data|
48
- resp = parse_response(data)
49
- Exception === resp ? response.fail(resp) : response.succeed(resp)
47
+ @connection.send_command(command, @request_id) do |data|
48
+ err, resp = parse_response(data)
49
+ if block_given?
50
+ yield(err, resp)
51
+ else
52
+ err ? raise(err) : resp
50
53
  end
51
54
  end
52
55
  end
53
56
 
54
57
  def parse_response(data)
55
58
  if Exception === data
56
- data
59
+ [data, nil]
57
60
  else
58
61
  flags = data[4]
59
62
  number = data[7]
@@ -66,7 +69,7 @@ module Monga
66
69
  elsif docs.first && (docs.first["err"] || docs.first["errmsg"])
67
70
  Monga::Exceptions::QueryFailure.new(docs.first)
68
71
  else
69
- data
72
+ [nil, data]
70
73
  end
71
74
  end
72
75
  end
@@ -90,7 +93,7 @@ module Monga
90
93
  end
91
94
 
92
95
  def full_name
93
- [@db.name, @collection_name] * "."
96
+ [@db_name, @collection_name] * "."
94
97
  end
95
98
 
96
99
  def op_code
@@ -98,13 +101,14 @@ module Monga
98
101
  end
99
102
 
100
103
  def command_length
101
- HEADER_SIZE + body.size
104
+ Monga::HEADER_SIZE + body.size
102
105
  end
103
106
 
104
107
  def self.request_id
105
- @request_id ||= 0
106
- @request_id += 1
107
- @request_id >= 2**32 ? @request_id = 1 : @request_id
108
+ @@request_id ||= 0
109
+ @@request_id += 1
110
+ @@request_id >= 2**32 ? @@request_id = 1 : @@request_id
111
+ @@request_id
108
112
  end
109
113
 
110
114
  def self.op_name(op = nil)
@@ -114,9 +118,9 @@ module Monga
114
118
  end
115
119
 
116
120
 
117
- require File.expand_path("../requests/query", __FILE__)
118
- require File.expand_path("../requests/insert", __FILE__)
119
- require File.expand_path("../requests/delete", __FILE__)
120
- require File.expand_path("../requests/update", __FILE__)
121
- require File.expand_path("../requests/get_more", __FILE__)
122
- require File.expand_path("../requests/kill_cursors", __FILE__)
121
+ require File.expand_path("../protocol/query", __FILE__)
122
+ require File.expand_path("../protocol/insert", __FILE__)
123
+ require File.expand_path("../protocol/delete", __FILE__)
124
+ require File.expand_path("../protocol/update", __FILE__)
125
+ require File.expand_path("../protocol/get_more", __FILE__)
126
+ require File.expand_path("../protocol/kill_cursors", __FILE__)
@@ -0,0 +1,5 @@
1
+ module Monga
2
+ DEFAULT_HOST = "127.0.0.1"
3
+ DEFAULT_PORT = 27017
4
+ HEADER_SIZE = 16
5
+ end
@@ -0,0 +1,11 @@
1
+ module Monga::Exceptions
2
+ class InvalidClientOption < StandardError; end
3
+ class UndefinedSocketType < StandardError; end
4
+ class WrongConnectionType < StandardError; end
5
+ class Disconnected < StandardError; end
6
+ class CouldNotConnect < StandardError; end
7
+ class CouldNotReconnect < StandardError; end
8
+ class QueryFailure < StandardError; end
9
+ class CursorNotFound < StandardError; end
10
+ class ClosedCursor < StandardError; end
11
+ end
data/lib/monga.rb CHANGED
@@ -1,12 +1,9 @@
1
- require "eventmachine"
1
+ require "em-synchrony"
2
2
  require "bson"
3
3
  require "logger"
4
+ require "forwardable"
4
5
 
5
6
  module Monga
6
- DEFAULT_HOST = "127.0.0.1"
7
- DEFAULT_PORT = 27017
8
- HEADER_SIZE = 16
9
-
10
7
  extend self
11
8
 
12
9
  def logger
@@ -18,13 +15,24 @@ module Monga
18
15
  end
19
16
  end
20
17
 
18
+ # It is strange, but cursor should be required befor fibered_connection
19
+ require File.expand_path("../monga/cursor", __FILE__)
20
+
21
+ require File.expand_path("../monga/clients/single_instance_client", __FILE__)
22
+ require File.expand_path("../monga/clients/replica_set_client", __FILE__)
23
+ require File.expand_path("../monga/connections/em_connection", __FILE__)
24
+ require File.expand_path("../monga/connections/fibered_connection", __FILE__)
25
+ require File.expand_path("../monga/connections/tcp_connection", __FILE__)
26
+ require File.expand_path("../monga/connections/em_proxy_connection", __FILE__)
27
+ require File.expand_path("../monga/connections/fibered_proxy_connection", __FILE__)
28
+ require File.expand_path("../monga/connections/proxy_connection", __FILE__)
29
+ require File.expand_path("../monga/connections/buffer", __FILE__)
30
+
31
+ require File.expand_path("../monga/client", __FILE__)
21
32
  require File.expand_path("../monga/connection", __FILE__)
22
33
  require File.expand_path("../monga/connection_pool", __FILE__)
23
- require File.expand_path("../monga/client", __FILE__)
24
34
  require File.expand_path("../monga/database", __FILE__)
25
35
  require File.expand_path("../monga/collection", __FILE__)
26
- require File.expand_path("../monga/miner", __FILE__)
27
- require File.expand_path("../monga/cursor", __FILE__)
28
- require File.expand_path("../monga/exceptions", __FILE__)
29
- require File.expand_path("../monga/response", __FILE__)
30
- require File.expand_path("../monga/request", __FILE__)
36
+ require File.expand_path("../monga/request", __FILE__)
37
+ require File.expand_path("../monga/utils/exceptions", __FILE__)
38
+ require File.expand_path("../monga/utils/constants", __FILE__)
data/monga.gemspec CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "monga"
7
- spec.version = "0.0.2"
7
+ spec.version = "0.0.3"
8
8
  spec.authors = ["Petr Yanovich"]
9
9
  spec.email = ["fl00r@yandex.ru"]
10
10
  spec.description = %q{MongoDB Ruby Evented Driver on EventMachine}
@@ -19,8 +19,8 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.add_development_dependency "bundler", "~> 1.3"
21
21
  spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "em-synchrony"
22
23
 
23
- spec.add_dependency "eventmachine"
24
24
  spec.add_dependency "bson"
25
25
  spec.add_dependency "bson_ext"
26
26
  end
@@ -1,59 +1,136 @@
1
- module Mongodb
2
- MONGOD_PATH = "mongod"
3
-
4
- class Instance
5
- def initialize(opts)
6
- @opts = opts
7
- @cmd = [MONGOD_PATH]
8
- opts.each do |k,v|
9
- @cmd << "--#{k}"
10
- @cmd << v.to_s
11
- end
12
- clean_lock
13
- shtdwn
14
- start
1
+ module Fake
2
+
3
+ # Fake Response.
4
+ # It could be `ok`, or `primary?` reply.
5
+ class Response
6
+ def initialize(data, primary)
7
+ @data = data
8
+ @primary = primary
9
+ end
10
+
11
+ def ok(doc = nil)
12
+ document = doc || { ok: 1.0 }
13
+ [flags, cursor_id, starting_from, number_returned].pack("LQLL")
14
+ b = BSON::ByteBuffer.new
15
+ b.put_int(flags)
16
+ b.put_long(cursor_id)
17
+ b.put_int(starting_from)
18
+ b.put_int(number_returned)
19
+ b.append!(BSON::BSON_C.serialize(document).to_s)
20
+
21
+ header(b) + b.to_s
22
+ end
23
+
24
+ def primary?
25
+ doc = { ismaster: @primary }
26
+ ok(doc)
27
+ end
28
+
29
+ private
30
+
31
+ def header(body)
32
+ length = 16 + body.to_s.bytesize
33
+ request_id = 0
34
+ op_code = 0
35
+
36
+ h = BSON::ByteBuffer.new
37
+ h.put_int(length)
38
+ h.put_int(request_id)
39
+ h.put_int(response_to)
40
+ h.put_int(op_code)
41
+ h.to_s
42
+ end
43
+
44
+ def response_to
45
+ @data.unpack("LLLL")[1]
15
46
  end
16
47
 
17
- def clean_lock
18
- lock = @opts[:dbpath] + "/mongod.lock"
19
- File.rm(lock)
20
- rescue
21
- # ok
48
+ def flags; 0; end
49
+ def cursor_id; 0; end
50
+ def starting_from; 0; end
51
+ def number_returned; 1; end
52
+ end
53
+
54
+ # Fake MongoDB server.
55
+ # Replies with `ok` message for all 2004 op queries instead `isMaster`.
56
+ # For other ops it sends no answer.
57
+ class Node < EM::Connection
58
+ def initialize(si)
59
+ @si = si
60
+ @si.server = self
22
61
  end
23
62
 
24
- def shtdwn
25
- system "#{@cmd * ' '} --shutdown"
63
+ def primary
64
+ @si.rs.primary == @si if @si.rs
65
+ end
66
+
67
+ def receive_data(data)
68
+ begin
69
+ length, req_id, resp_to, op_code = data.unpack("LLLL")
70
+ piece = data.slice!(0, length)
71
+ if op_code == 2004
72
+ if piece["isMaster"]
73
+ send_data Fake::Response.new(piece, primary).primary?
74
+ else
75
+ send_data Fake::Response.new(piece, primary).ok
76
+ end
77
+ end
78
+ end while data != ""
79
+ end
80
+ end
81
+
82
+ # Single instance binded on one port.
83
+ # Could be stopped or started.
84
+ class SingleInstance
85
+ attr_reader :rs
86
+ attr_accessor :server
87
+
88
+ def initialize(port, rs=nil)
89
+ @rs = rs
90
+ @port = port
26
91
  end
27
92
 
28
93
  def start
29
- return @pid if @pid
30
- @pid = spawn(*@cmd, out: "/dev/null")
31
- sleep(1)
94
+ @connected = true
95
+ @sign = EM.start_server '127.0.0.1', @port, Fake::Node, self
32
96
  end
33
97
 
34
98
  def stop
35
- return unless @pid
36
- Process.kill("SIGINT", @pid)
37
- Process.waitpid(@pid)
38
- @pid = nil
99
+ @server.close_connection
100
+ EM.stop_server @sign
101
+ @connected = false
102
+ end
103
+
104
+ def connected?
105
+ @connected
39
106
  end
40
107
  end
41
108
 
109
+ # Fake Replica set with a number of Single Instances.
110
+ # You could stop/start primary/secondary.
42
111
  class ReplicaSet
43
- def initialize(ports, opts={})
44
- opts[:replSet] ||= "rs1"
45
- opts[:dbpath] ||= "/tmp/mongodb/rs0"
46
- @instances = {}
47
- ports.each.with_index do |prt, i|
48
- dbpath = opts[:dbpath] + "-#{i}"
49
- o = opts.merge({ port: prt[:port], dbpath: dbpath })
50
- @instances[prt[:port]] = Instance.new(o)
112
+ def initialize(ports)
113
+ @instances = ports.map do |port|
114
+ SingleInstance.new(port, self)
51
115
  end
52
116
  end
53
117
 
118
+ def start_all
119
+ @instances.each(&:start)
120
+ end
121
+
122
+ def vote
123
+ @primary = @instances.select{|inst| inst.connected? }.sample
124
+ end
125
+
54
126
  def primary
55
- pr = RS_CLIENT.primary
56
- @instances[pr.aquire_connection.port] if pr
127
+ @primary ||= begin
128
+ @instances.select{|inst| inst.connected? }.sample
129
+ end
130
+ end
131
+
132
+ def secondaries
133
+ @instances - [@primary]
57
134
  end
58
135
  end
59
136
  end
@@ -0,0 +1,172 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monga::Collection do
4
+ before do
5
+ @client = Monga::Client.new(type: :block, pool_size: 10)
6
+ @db = @client["dbTest"]
7
+ @collection = @db["testCollection"]
8
+ @collection.safe_remove
9
+ docs = []
10
+ 10.times do |i|
11
+ docs << { artist: "Madonna", title: "Track #{i+1}" }
12
+ docs << { artist: "Radiohead", title: "Track #{i+1}" }
13
+ end
14
+ @collection.safe_insert(docs)
15
+ end
16
+
17
+ # QUERY
18
+
19
+ describe "query" do
20
+ it "should fetch all documents" do
21
+ docs = @collection.find.all
22
+ docs.size.must_equal 20
23
+ end
24
+
25
+ it "should fetch all docs with skip and limit" do
26
+ docs = @collection.find.skip(10).limit(4).all
27
+ docs.size.must_equal 4
28
+ end
29
+
30
+ it "should fetch first" do
31
+ doc = @collection.first
32
+ doc.keys.must_equal ["_id", "artist", "title"]
33
+ end
34
+ end
35
+
36
+ # INSERT
37
+
38
+ describe "insert" do
39
+ before do
40
+ @collection.safe_ensure_index({ "personal_id" => 1 }, { unique: true, sparse: true })
41
+ end
42
+
43
+ after do
44
+ @collection.drop_index( personal_id: 1 )
45
+ end
46
+
47
+ it "should insert single doc" do
48
+ doc = { name: "Peter", age: 18 }
49
+ @collection.safe_insert(doc)
50
+ resp = @collection.find(name: "Peter").all
51
+ resp.size.must_equal 1
52
+ resp.first["age"].must_equal 18
53
+ end
54
+
55
+ it "should insert batch of docs" do
56
+ docs = [{ name: "Peter", age: 18 }, {name: "Jhon", age: 18}]
57
+ @collection.safe_insert(docs)
58
+ resp = @collection.find(age: 18).all
59
+ resp.size.must_equal 2
60
+ end
61
+
62
+ it "should fail on uniq index" do
63
+ docs = [{ name: "Peter", age: 18, personal_id: 20 }, {name: "Jhon", age: 18, personal_id: 20}, {name: "Rebeca", age: 21, personal_id: 5}]
64
+ proc{ @collection.safe_insert(docs) }.must_raise Monga::Exceptions::QueryFailure
65
+ @collection.count.must_equal 21
66
+ end
67
+
68
+ it "should continue_on_error" do
69
+ docs = [{ name: "Peter", age: 18, personal_id: 20 }, {name: "Jhon", age: 18, personal_id: 20}, {name: "Rebeca", age: 21, personal_id: 5}]
70
+ proc{ @collection.safe_insert(docs, continue_on_error: true) }.must_raise Monga::Exceptions::QueryFailure
71
+ @collection.count.must_equal 22
72
+ end
73
+ end
74
+
75
+ # UPDATE
76
+
77
+ describe "update" do
78
+ it "should make simple update (first matching)" do
79
+ @collection.safe_update({ artist: "Madonna" }, { "$set" => { country: "USA" } })
80
+ @collection.count( query: { artist: "Madonna", country: "USA" }).must_equal 1
81
+ end
82
+
83
+ it "should create non existing item (upsert)" do
84
+ @collection.safe_update({ artist: "Bjork" }, { "$set" => { country: "Iceland" } }, { upsert: true })
85
+ @collection.count(query: { artist: "Bjork" }).must_equal 1
86
+ end
87
+
88
+ it "should update all matching data (multi_update)" do
89
+ @collection.safe_update({ artist: "Madonna" }, { "$set" => { country: "USA" } }, {multi_update: true})
90
+ docs = @collection.find(artist: "Madonna").all
91
+ docs.each{ |d| d["country"].must_equal "USA" }
92
+ end
93
+ end
94
+
95
+ # REMOVE
96
+
97
+ describe "remove" do
98
+ it "should delete all matching docs" do
99
+ @collection.safe_delete(artist: "Madonna")
100
+ @collection.count(query: { artist: "Madonna" }).must_equal 0
101
+ end
102
+
103
+ it "should delete first matching doc (single_remove)" do
104
+ @collection.safe_delete({ artist: "Madonna" }, single_remove: true)
105
+ @collection.count(query: { artist: "Madonna" }).must_equal 9
106
+ end
107
+ end
108
+
109
+ # COUNT
110
+
111
+ describe "count" do
112
+ it "should count all docs" do
113
+ @collection.count.must_equal 20
114
+ end
115
+
116
+ it "should count all docs with query" do
117
+ @collection.count(query: { artist: "Madonna" }).must_equal 10
118
+ end
119
+
120
+ it "should count all docs with limit" do
121
+ @collection.count(query: { artist: "Madonna" }, limit: 5).must_equal 5
122
+ end
123
+
124
+ it "should count all docs with limit and skip" do
125
+ @collection.count(query: { artist: "Madonna" }, limit: 5, skip: 6).must_equal 4
126
+ end
127
+ end
128
+
129
+ # ENSURE/DROP INDEX
130
+
131
+ describe "ensure_index" do
132
+ before do
133
+ @collection.drop_indexes
134
+ end
135
+
136
+ it "should create index" do
137
+ @collection.safe_ensure_index(title: 1)
138
+ docs = @collection.get_indexes
139
+ docs.any?{ |doc| doc["key"] == {"title" => 1}}.must_equal true
140
+ end
141
+
142
+ it "should create sparse index" do
143
+ @collection.safe_ensure_index({ title: 1 }, sparse: true)
144
+ docs = @collection.get_indexes
145
+ docs.any?{ |doc| doc["key"] == {"title" => 1} && doc["sparse"] == true }.must_equal true
146
+ end
147
+
148
+ it "should create unique index" do
149
+ @collection.safe_ensure_index({ some_field: 1 }, unique: true, sparse: true)
150
+ docs = @collection.get_indexes
151
+ docs.any?{ |doc| doc["key"] == {"some_field" => 1} && doc["unique"] == true }.must_equal true
152
+ end
153
+
154
+ it "should drop single index" do
155
+ @collection.safe_ensure_index(title: 1)
156
+ docs = @collection.get_indexes
157
+ docs.any?{ |doc| doc["key"] == {"title" => 1}}.must_equal true
158
+ @collection.drop_index(title: 1)
159
+ docs = @collection.get_indexes
160
+ docs.any?{ |doc| doc["key"] == {"title" => 1}}.must_equal false
161
+ end
162
+
163
+ it "should drop all indexes (except primary on _id)" do
164
+ @collection.safe_ensure_index(title: 1)
165
+ docs = @collection.get_indexes
166
+ docs.any?{ |doc| doc["key"] == {"title" => 1}}.must_equal true
167
+ @collection.drop_indexes
168
+ docs = @collection.get_indexes
169
+ docs.select{ |d| d["ns"] == "dbTest.testCollection" }.size.must_equal 1
170
+ end
171
+ end
172
+ end