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/lib/monga/cursor.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
module Monga
|
2
|
+
class Cursor < EM::DefaultDeferrable
|
3
|
+
attr_reader :cursor_id
|
4
|
+
|
5
|
+
CURSORS = {}
|
6
|
+
CLOSED_CURSOR = 0
|
7
|
+
# Batch kill cursors marked to be killed each CLOSE_TIMEOUT seconds
|
8
|
+
CLOSE_TIMEOUT = 1
|
9
|
+
|
10
|
+
def initialize(db, collection_name, options = {}, flags = {})
|
11
|
+
@keep_alive = true if flags.delete :keep_alive
|
12
|
+
|
13
|
+
@db = db
|
14
|
+
@connection = @db.client.aquire_connection
|
15
|
+
@collection_name = collection_name
|
16
|
+
@options = options
|
17
|
+
@options.merge!(flags)
|
18
|
+
|
19
|
+
@fetched_docs = []
|
20
|
+
@count = 0
|
21
|
+
@total_count = 0
|
22
|
+
@limit = @options[:limit] ||= 0
|
23
|
+
@batch_size = @options[:batch_size]
|
24
|
+
end
|
25
|
+
|
26
|
+
def next_document
|
27
|
+
Monga::Response.surround do |resp|
|
28
|
+
if doc = @fetched_docs.shift
|
29
|
+
resp.succeed doc
|
30
|
+
else
|
31
|
+
req = next_batch
|
32
|
+
req.callback do |docs|
|
33
|
+
@fetched_docs = docs
|
34
|
+
if doc = @fetched_docs.shift
|
35
|
+
@count =+ 1
|
36
|
+
resp.succeed doc
|
37
|
+
end
|
38
|
+
end
|
39
|
+
req.errback{ |err| resp.fail err }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def each_doc(&blk)
|
45
|
+
if more?
|
46
|
+
req = next_batch
|
47
|
+
req.callback do |batch|
|
48
|
+
if batch.any?
|
49
|
+
batch.each do |doc|
|
50
|
+
@count += 1
|
51
|
+
blk.call(doc)
|
52
|
+
end
|
53
|
+
each_doc(&blk)
|
54
|
+
else
|
55
|
+
succeed
|
56
|
+
end
|
57
|
+
end
|
58
|
+
req.errback{ |err| fail err }
|
59
|
+
else
|
60
|
+
succeed
|
61
|
+
end
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def kill
|
66
|
+
return unless @cursor_id > 0
|
67
|
+
self.class.kill_cursors(@connection, @cursor_id)
|
68
|
+
CURSORS.delete @cursor_id
|
69
|
+
@cursor_id = 0
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.batch_kill(conn)
|
73
|
+
cursors = CURSORS.select{ |k,v| v }
|
74
|
+
cursor_ids = cursors.keys
|
75
|
+
if cursor_ids.any?
|
76
|
+
Monga.logger.debug("Following cursors are going to be deleted: #{cursor_ids}")
|
77
|
+
kill_cursors(conn, cursor_ids)
|
78
|
+
CURSORS.delete_if{|k,v| cursor_ids.include?(k) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Sometime in future all marked cursors will be killed in batch
|
83
|
+
def mark_to_kill
|
84
|
+
CURSORS[@cursor_id] = true if @cursor_id && alive?
|
85
|
+
@cursor_id = 0
|
86
|
+
end
|
87
|
+
|
88
|
+
# Cursor is alive and we need more minerals
|
89
|
+
def more?
|
90
|
+
alive? && !satisfied?
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def get_more(batch_size)
|
96
|
+
Monga::Response.surround do |resp|
|
97
|
+
req = if @cursor_id
|
98
|
+
opts = { cursor_id: @cursor_id, batch_size: batch_size }
|
99
|
+
Monga::Requests::GetMore.new(@db, @collection_name, opts).callback_perform
|
100
|
+
else
|
101
|
+
Monga::Requests::Query.new(@db, @collection_name, @options).callback_perform
|
102
|
+
end
|
103
|
+
req.callback do |data|
|
104
|
+
@cursor_id = data[5]
|
105
|
+
fetched_docs = data.last
|
106
|
+
@total_count += fetched_docs.count
|
107
|
+
mark_to_kill unless cursor_more?
|
108
|
+
|
109
|
+
resp.succeed fetched_docs
|
110
|
+
end
|
111
|
+
req.errback do |err|
|
112
|
+
mark_to_kill
|
113
|
+
resp.fail err
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def next_batch
|
119
|
+
Monga::Response.surround do |resp|
|
120
|
+
if more?
|
121
|
+
batch_size = get_batch_size
|
122
|
+
req = get_more(batch_size)
|
123
|
+
req.callback{ |res| resp.succeed res }
|
124
|
+
req.errback{ |err| resp.fail err }
|
125
|
+
else
|
126
|
+
mark_to_kill
|
127
|
+
if !alive?
|
128
|
+
resp.fail Monga::Exceptions::CursorIsClosed.new("Cursor is already closed. Check `cursor.more?` before calling cursor")
|
129
|
+
elsif satisfied?
|
130
|
+
resp.fail Monga::Exceptions::CursorLimit.new("You've already fetched #{@limit} docs you asked. Check `cursor.more?` before calling cursor")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def cursor_more?
|
137
|
+
alive? && !cursor_satisfied?
|
138
|
+
end
|
139
|
+
|
140
|
+
# If cursor_id is not setted, or if isn't CLOSED_CURSOR - cursor is alive
|
141
|
+
def alive?
|
142
|
+
@cursor_id != CLOSED_CURSOR
|
143
|
+
end
|
144
|
+
|
145
|
+
# If global limit is setted
|
146
|
+
# we will be satisfied when we will get limit amount of documents.
|
147
|
+
# Otherwise we are not satisfied untill crsor is alive
|
148
|
+
def satisfied?
|
149
|
+
@limit > 0 && @count >= @limit
|
150
|
+
end
|
151
|
+
|
152
|
+
def cursor_satisfied?
|
153
|
+
@limit > 0 && @total_count >= @limit
|
154
|
+
end
|
155
|
+
|
156
|
+
# How many docs should be returned
|
157
|
+
def rest
|
158
|
+
@limit - @count if @limit > 0
|
159
|
+
end
|
160
|
+
|
161
|
+
# Cursor will get exact amount of docs as user passed with `limit` opr
|
162
|
+
def get_batch_size
|
163
|
+
if @limit > 0 && @batch_size
|
164
|
+
rest < @batch_size ? rest : @batch_size
|
165
|
+
else @batch_size
|
166
|
+
@batch_size
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.kill_cursors(connection, cursor_ids)
|
171
|
+
Monga::Requests::KillCursors.new(connection, cursor_ids: [*cursor_ids]).perform
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Monga
|
2
|
+
class Database
|
3
|
+
attr_reader :client, :name
|
4
|
+
|
5
|
+
def initialize(client, name)
|
6
|
+
@client = client
|
7
|
+
@name = name
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](collection_name)
|
11
|
+
Monga::Collection.new(self, collection_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def cmd(cmd, opts={})
|
15
|
+
options = {}
|
16
|
+
options[:query] = cmd
|
17
|
+
options.merge! opts
|
18
|
+
Monga::Miner.new(self, "$cmd", options).limit(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
def eval(js)
|
22
|
+
with_response do
|
23
|
+
cmd(eval: js)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Be carefull with using get_last_error with connection pool.
|
28
|
+
# In most cases you need to use #safe methods
|
29
|
+
# and don't access to #get_last_error directky
|
30
|
+
def get_last_error
|
31
|
+
with_response do
|
32
|
+
cmd(getLastError: 1)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def drop_collection(collection_name)
|
37
|
+
with_response do
|
38
|
+
cmd(drop: collection_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_collection(collection_name, opts = {})
|
43
|
+
with_response do
|
44
|
+
cmd({create: collection_name}.merge(opts))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def count(collection_name)
|
49
|
+
Monga::Response.surround do |resp|
|
50
|
+
req = with_response do
|
51
|
+
cmd(count: collection_name)
|
52
|
+
end
|
53
|
+
req.callback do |data|
|
54
|
+
cnt = data.first["n"].to_i
|
55
|
+
resp.succeed cnt
|
56
|
+
end
|
57
|
+
req.errback{ |err| resp.fail err }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def drop_indexes(collection_name, indexes)
|
62
|
+
with_response do
|
63
|
+
cmd(dropIndexes: collection_name, index: indexes)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Just helper
|
68
|
+
def list_collections
|
69
|
+
Monga::Response.surround do |resp|
|
70
|
+
req = eval("db.getCollectionNames()")
|
71
|
+
req.callback do |data|
|
72
|
+
resp.succeed(data.first["retval"])
|
73
|
+
end
|
74
|
+
req.errback do |err|
|
75
|
+
resp.fail(err)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def with_response
|
83
|
+
Monga::Response.surround do |resp|
|
84
|
+
req = yield(resp)
|
85
|
+
req.callback do |data|
|
86
|
+
if data.any?
|
87
|
+
resp.succeed(data)
|
88
|
+
else
|
89
|
+
exception = Monga::Exceptions::QueryFailure.new("Nothing was returned for your query: #{req.options[:query]}")
|
90
|
+
resp.fail(exception)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
req.errback{ |err| resp.fail err }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Monga::Exceptions
|
2
|
+
class LostConnection < StandardError; end
|
3
|
+
class CursorNotFound < StandardError; end
|
4
|
+
class CursorIsClosed < StandardError; end
|
5
|
+
class CursorLimit < StandardError; end
|
6
|
+
class QueryFailure < StandardError; end
|
7
|
+
class UndefinedIndexVersion < StandardError; end
|
8
|
+
class NoAvailableServers < StandardError; end
|
9
|
+
end
|
data/lib/monga/miner.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Miner is a "proxy" object to Cursor.
|
2
|
+
# It dinamically stores Cursor options and at any moment can return cursor.
|
3
|
+
# Also it hides Deferrable that could return all objects that cursor do.
|
4
|
+
module Monga
|
5
|
+
class Miner < EM::DefaultDeferrable
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(db, collection_name, options={})
|
9
|
+
@db = db
|
10
|
+
@collection_name = collection_name
|
11
|
+
@options = options
|
12
|
+
|
13
|
+
# Defaults
|
14
|
+
@options[:query] ||= {}
|
15
|
+
@options[:limit] ||= 0
|
16
|
+
@options[:skip] ||= 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def cursor(flags = {})
|
20
|
+
@cursor = Monga::Cursor.new(@db, @collection_name, @options, flags)
|
21
|
+
end
|
22
|
+
|
23
|
+
def explain
|
24
|
+
@options[:explain] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def hint
|
28
|
+
@options[:hint] = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def sort(val)
|
32
|
+
@options[:sort] = val
|
33
|
+
end
|
34
|
+
|
35
|
+
def limit(count)
|
36
|
+
@options[:limit] = count and self
|
37
|
+
end
|
38
|
+
|
39
|
+
def skip(count)
|
40
|
+
@options[:skip] = count and self
|
41
|
+
end
|
42
|
+
|
43
|
+
def batch_size(count)
|
44
|
+
@options[:batch_size] = count and self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Lazy operation execution
|
48
|
+
[:callback, :errback, :timeout].each do |meth|
|
49
|
+
class_eval <<-EOS
|
50
|
+
def #{meth}(*args)
|
51
|
+
mine! && @deferred = true unless @deferred
|
52
|
+
super
|
53
|
+
end
|
54
|
+
EOS
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def mine!
|
60
|
+
docs = []
|
61
|
+
itrator = cursor.each_doc do |doc|
|
62
|
+
docs << doc
|
63
|
+
end
|
64
|
+
itrator.callback do |resp|
|
65
|
+
succeed docs
|
66
|
+
end
|
67
|
+
itrator.errback do |err|
|
68
|
+
fail err
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Monga
|
2
|
+
class Request
|
3
|
+
attr_reader :request_id
|
4
|
+
|
5
|
+
OP_CODES = {
|
6
|
+
reply: 1,
|
7
|
+
msg: 1000,
|
8
|
+
update: 2001,
|
9
|
+
insert: 2002,
|
10
|
+
reserved: 2003,
|
11
|
+
query: 2004,
|
12
|
+
get_more: 2005,
|
13
|
+
delete: 2006,
|
14
|
+
kill_cursors: 2007,
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(db, collection_name, options = {})
|
18
|
+
@db = db
|
19
|
+
@collection_name = collection_name
|
20
|
+
@options = options
|
21
|
+
@request_id = self.class.request_id
|
22
|
+
@connection = @db.client.aquire_connection
|
23
|
+
end
|
24
|
+
|
25
|
+
def command
|
26
|
+
header.append!(body)
|
27
|
+
end
|
28
|
+
|
29
|
+
def header
|
30
|
+
headers = BSON::ByteBuffer.new
|
31
|
+
headers.put_int(command_length)
|
32
|
+
headers.put_int(@request_id)
|
33
|
+
headers.put_int(0)
|
34
|
+
headers.put_int(op_code)
|
35
|
+
headers
|
36
|
+
end
|
37
|
+
|
38
|
+
# Fire and Forget
|
39
|
+
def perform
|
40
|
+
@connection.send_command(command)
|
41
|
+
@request_id
|
42
|
+
end
|
43
|
+
|
44
|
+
# Fire and wait
|
45
|
+
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)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_response(data)
|
55
|
+
if Exception === data
|
56
|
+
data
|
57
|
+
else
|
58
|
+
flags = data[4]
|
59
|
+
number = data[7]
|
60
|
+
docs = unpack_docs(data.last, number)
|
61
|
+
data[-1] = docs
|
62
|
+
if flags & 2**0 > 0
|
63
|
+
Monga::Exceptions::CursorNotFound.new(docs.first)
|
64
|
+
elsif flags & 2**1 > 0
|
65
|
+
Monga::Exceptions::QueryFailure.new(docs.first)
|
66
|
+
elsif docs.first && (docs.first["err"] || docs.first["errmsg"])
|
67
|
+
Monga::Exceptions::QueryFailure.new(docs.first)
|
68
|
+
else
|
69
|
+
data
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def unpack_docs(data, number)
|
77
|
+
number.times.map do
|
78
|
+
size = data.slice(0, 4).unpack("L").first
|
79
|
+
d = data.slice!(0, size)
|
80
|
+
BSON.deserialize(d)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def flags
|
85
|
+
flags = 0
|
86
|
+
self.class::FLAGS.each do |k, byte|
|
87
|
+
flags = flags | 1 << byte if @options[k]
|
88
|
+
end
|
89
|
+
flags
|
90
|
+
end
|
91
|
+
|
92
|
+
def full_name
|
93
|
+
[@db.name, @collection_name] * "."
|
94
|
+
end
|
95
|
+
|
96
|
+
def op_code
|
97
|
+
OP_CODES[self.class.op_name]
|
98
|
+
end
|
99
|
+
|
100
|
+
def command_length
|
101
|
+
HEADER_SIZE + body.size
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.request_id
|
105
|
+
@request_id ||= 0
|
106
|
+
@request_id += 1
|
107
|
+
@request_id >= 2**32 ? @request_id = 1 : @request_id
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.op_name(op = nil)
|
111
|
+
op ? @op_name = op : @op_name
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
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__)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Monga::Requests
|
2
|
+
class Delete < Monga::Request
|
3
|
+
op_name :delete
|
4
|
+
|
5
|
+
FLAGS = {
|
6
|
+
single_remove: 0,
|
7
|
+
}
|
8
|
+
|
9
|
+
def body
|
10
|
+
@body ||= begin
|
11
|
+
query = @options[:query]
|
12
|
+
|
13
|
+
b = BSON::ByteBuffer.new
|
14
|
+
b.put_int(0)
|
15
|
+
BSON::BSON_RUBY.serialize_cstr(b, full_name)
|
16
|
+
b.put_int(flags)
|
17
|
+
b.append!(BSON::BSON_C.serialize(query).to_s)
|
18
|
+
b
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Monga::Requests
|
2
|
+
class GetMore < Monga::Request
|
3
|
+
op_name :get_more
|
4
|
+
|
5
|
+
def body
|
6
|
+
@body ||= begin
|
7
|
+
batch_size = @options[:batch_size] || 0
|
8
|
+
cursor_id = @options[:cursor_id]
|
9
|
+
|
10
|
+
b = BSON::ByteBuffer.new
|
11
|
+
b.put_int(0)
|
12
|
+
BSON::BSON_RUBY.serialize_cstr(b, full_name)
|
13
|
+
b.put_int(batch_size)
|
14
|
+
b.put_long(cursor_id)
|
15
|
+
b
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Monga::Requests
|
2
|
+
class Insert < Monga::Request
|
3
|
+
op_name :insert
|
4
|
+
|
5
|
+
FLAGS = {
|
6
|
+
continue_on_error: 0,
|
7
|
+
}
|
8
|
+
|
9
|
+
def body
|
10
|
+
@body ||= begin
|
11
|
+
documents = @options[:documents]
|
12
|
+
|
13
|
+
b = BSON::ByteBuffer.new
|
14
|
+
b.put_int(flags)
|
15
|
+
BSON::BSON_RUBY.serialize_cstr(b, full_name)
|
16
|
+
case documents
|
17
|
+
when Array
|
18
|
+
documents.each do |doc|
|
19
|
+
b.append!(BSON::BSON_C.serialize(doc).to_s)
|
20
|
+
end
|
21
|
+
when Hash
|
22
|
+
b.append!(BSON::BSON_C.serialize(documents).to_s)
|
23
|
+
end
|
24
|
+
b
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Monga::Requests
|
2
|
+
class KillCursors < Monga::Request
|
3
|
+
op_name :kill_cursors
|
4
|
+
|
5
|
+
def initialize(connection, options = {})
|
6
|
+
@options = options
|
7
|
+
@request_id = self.class.request_id
|
8
|
+
@connection = connection
|
9
|
+
end
|
10
|
+
|
11
|
+
def body
|
12
|
+
@body ||= begin
|
13
|
+
cursor_ids = @options[:cursor_ids]
|
14
|
+
|
15
|
+
b = BSON::ByteBuffer.new
|
16
|
+
b.put_int(0)
|
17
|
+
b.put_int(cursor_ids.size)
|
18
|
+
cursor_ids.each do |cursor_id|
|
19
|
+
b.put_long(cursor_id)
|
20
|
+
end
|
21
|
+
b
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Monga::Requests
|
2
|
+
class Query < Monga::Request
|
3
|
+
op_name :query
|
4
|
+
|
5
|
+
FLAGS = {
|
6
|
+
tailable_cursor: 1,
|
7
|
+
slave_ok: 2,
|
8
|
+
no_cursor_timeout: 4,
|
9
|
+
await_data: 5,
|
10
|
+
exhaust: 6,
|
11
|
+
partial: 7,
|
12
|
+
}
|
13
|
+
|
14
|
+
def body
|
15
|
+
@body ||= begin
|
16
|
+
skip = @options[:skip] || 0
|
17
|
+
limit = get_limit
|
18
|
+
fields = @options[:fields] || {}
|
19
|
+
|
20
|
+
query = {}
|
21
|
+
query["$query"] = @options[:query] || {}
|
22
|
+
query["$hint"] = @options[:hint] if @options[:hint]
|
23
|
+
query["$orderby"] = @options[:sort] if @options[:sort]
|
24
|
+
query["$explain"] = @options[:explain] if @options[:explain]
|
25
|
+
|
26
|
+
b = BSON::ByteBuffer.new
|
27
|
+
b.put_int(flags)
|
28
|
+
BSON::BSON_RUBY.serialize_cstr(b, full_name)
|
29
|
+
b.put_int(skip)
|
30
|
+
b.put_int(limit)
|
31
|
+
b.append!(BSON::BSON_C.serialize(query).to_s)
|
32
|
+
b.append!(BSON::BSON_C.serialize(fields).to_s) if fields.any?
|
33
|
+
b
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def get_limit
|
40
|
+
if @options[:batch_size]
|
41
|
+
@options[:batch_size]
|
42
|
+
elsif @options[:limit]
|
43
|
+
-@options[:limit]
|
44
|
+
else
|
45
|
+
0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Monga::Requests
|
2
|
+
class Update < Monga::Request
|
3
|
+
op_name :update
|
4
|
+
|
5
|
+
FLAGS = {
|
6
|
+
upsert: 0,
|
7
|
+
multi_update: 1,
|
8
|
+
}
|
9
|
+
|
10
|
+
def body
|
11
|
+
@body ||= begin
|
12
|
+
query = @options[:query]
|
13
|
+
update = @options[:update]
|
14
|
+
|
15
|
+
b = BSON::ByteBuffer.new
|
16
|
+
b.put_int(0)
|
17
|
+
BSON::BSON_RUBY.serialize_cstr(b, full_name)
|
18
|
+
b.put_int(flags)
|
19
|
+
b.append!(BSON::BSON_C.serialize(query).to_s)
|
20
|
+
b.append!(BSON::BSON_C.serialize(update).to_s)
|
21
|
+
b
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/monga.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
require "bson"
|
3
|
+
require "logger"
|
4
|
+
|
5
|
+
module Monga
|
6
|
+
DEFAULT_HOST = "127.0.0.1"
|
7
|
+
DEFAULT_PORT = 27017
|
8
|
+
HEADER_SIZE = 16
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def logger
|
13
|
+
@logger ||= begin
|
14
|
+
l = Logger.new(STDOUT)
|
15
|
+
l.level = Logger::DEBUG
|
16
|
+
l
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require File.expand_path("../monga/connection", __FILE__)
|
22
|
+
require File.expand_path("../monga/connection_pool", __FILE__)
|
23
|
+
require File.expand_path("../monga/client", __FILE__)
|
24
|
+
require File.expand_path("../monga/database", __FILE__)
|
25
|
+
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__)
|