monga 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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__)
|