moped 0.0.0.beta → 1.0.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of moped might be problematic. Click here for more details.
- data/MIT_LICENSE +19 -0
- data/README.md +323 -0
- data/lib/moped.rb +19 -0
- data/lib/moped/bson.rb +25 -0
- data/lib/moped/bson/binary.rb +68 -0
- data/lib/moped/bson/code.rb +61 -0
- data/lib/moped/bson/document.rb +16 -0
- data/lib/moped/bson/extensions.rb +81 -0
- data/lib/moped/bson/extensions/array.rb +44 -0
- data/lib/moped/bson/extensions/boolean.rb +14 -0
- data/lib/moped/bson/extensions/false_class.rb +15 -0
- data/lib/moped/bson/extensions/float.rb +23 -0
- data/lib/moped/bson/extensions/hash.rb +49 -0
- data/lib/moped/bson/extensions/integer.rb +37 -0
- data/lib/moped/bson/extensions/nil_class.rb +20 -0
- data/lib/moped/bson/extensions/regexp.rb +40 -0
- data/lib/moped/bson/extensions/string.rb +35 -0
- data/lib/moped/bson/extensions/symbol.rb +25 -0
- data/lib/moped/bson/extensions/time.rb +21 -0
- data/lib/moped/bson/extensions/true_class.rb +15 -0
- data/lib/moped/bson/max_key.rb +21 -0
- data/lib/moped/bson/min_key.rb +21 -0
- data/lib/moped/bson/object_id.rb +123 -0
- data/lib/moped/bson/timestamp.rb +15 -0
- data/lib/moped/bson/types.rb +67 -0
- data/lib/moped/cluster.rb +193 -0
- data/lib/moped/collection.rb +67 -0
- data/lib/moped/cursor.rb +60 -0
- data/lib/moped/database.rb +76 -0
- data/lib/moped/errors.rb +61 -0
- data/lib/moped/indexes.rb +93 -0
- data/lib/moped/logging.rb +25 -0
- data/lib/moped/protocol.rb +20 -0
- data/lib/moped/protocol/command.rb +27 -0
- data/lib/moped/protocol/commands.rb +11 -0
- data/lib/moped/protocol/commands/authenticate.rb +54 -0
- data/lib/moped/protocol/delete.rb +92 -0
- data/lib/moped/protocol/get_more.rb +79 -0
- data/lib/moped/protocol/insert.rb +92 -0
- data/lib/moped/protocol/kill_cursors.rb +61 -0
- data/lib/moped/protocol/message.rb +320 -0
- data/lib/moped/protocol/query.rb +131 -0
- data/lib/moped/protocol/reply.rb +90 -0
- data/lib/moped/protocol/update.rb +107 -0
- data/lib/moped/query.rb +230 -0
- data/lib/moped/server.rb +73 -0
- data/lib/moped/session.rb +253 -0
- data/lib/moped/socket.rb +201 -0
- data/lib/moped/version.rb +4 -0
- metadata +108 -46
@@ -0,0 +1,54 @@
|
|
1
|
+
module Moped
|
2
|
+
module Protocol
|
3
|
+
module Commands
|
4
|
+
|
5
|
+
# Implementation of the authentication command for Mongo. See:
|
6
|
+
# http://www.mongodb.org/display/DOCS/Implementing+Authentication+in+a+Driver
|
7
|
+
# for details.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# socket.write Command.new :admin, getnonce: 1
|
11
|
+
# reply = Reply.deserialize socket
|
12
|
+
# socket.write Authenticate.new :admin, "username", "password",
|
13
|
+
# reply.documents[0]["nonce"]
|
14
|
+
# Reply.deserialize(socket).documents[0]["ok"] # => 1.0
|
15
|
+
class Authenticate < Command
|
16
|
+
|
17
|
+
# Create a new authentication command.
|
18
|
+
#
|
19
|
+
# @param [String] database the database to authenticate against
|
20
|
+
# @param [String] username
|
21
|
+
# @param [String] password
|
22
|
+
# @param [String] nonce the nonce returned from running the getnonce
|
23
|
+
# command.
|
24
|
+
def initialize(database, username, password, nonce)
|
25
|
+
super database, build_auth_command(username, password, nonce)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [String] username
|
29
|
+
# @param [String] password
|
30
|
+
# @param [String] nonce
|
31
|
+
# @return [String] the mongo digest of the username, password, and
|
32
|
+
# nonce.
|
33
|
+
def digest(username, password, nonce)
|
34
|
+
Digest::MD5.hexdigest(
|
35
|
+
nonce + username + Digest::MD5.hexdigest(username + ":mongo:" + password)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [String] username
|
40
|
+
# @param [String] password
|
41
|
+
# @param [String] nonce
|
42
|
+
def build_auth_command(username, password, nonce)
|
43
|
+
{
|
44
|
+
authenticate: 1,
|
45
|
+
user: username,
|
46
|
+
nonce: nonce,
|
47
|
+
key: digest(username, password, nonce)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Moped
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
# The Protocol class for deleting documents from a collection.
|
5
|
+
#
|
6
|
+
# @example Delete all people named John
|
7
|
+
# delete = Delete.new "moped", "people", { name: "John" }
|
8
|
+
#
|
9
|
+
# @example Delete the first person named John
|
10
|
+
# delete = Delete.new "moped", "people", { name: "John" },
|
11
|
+
# flags: [:remove_first]
|
12
|
+
#
|
13
|
+
# @example Setting the request id
|
14
|
+
# delete = Delete.new "moped", "people", { name: "John" },
|
15
|
+
# request_id: 123
|
16
|
+
class Delete
|
17
|
+
include Message
|
18
|
+
|
19
|
+
# @attribute
|
20
|
+
# @return [Number] the length of the message
|
21
|
+
int32 :length
|
22
|
+
|
23
|
+
# @attribute
|
24
|
+
# @return [Number] the request id of the message
|
25
|
+
int32 :request_id
|
26
|
+
|
27
|
+
int32 :response_to
|
28
|
+
|
29
|
+
# @attribute
|
30
|
+
# @return [Number] the operation code of this message
|
31
|
+
int32 :op_code
|
32
|
+
|
33
|
+
int32 :reserved # reserved for future use
|
34
|
+
|
35
|
+
# @attribute
|
36
|
+
# @return [String] the full collection name
|
37
|
+
cstring :full_collection_name
|
38
|
+
|
39
|
+
# @attribute
|
40
|
+
# @param [Array] the flags for the message
|
41
|
+
# @return [Array] the flags for the message
|
42
|
+
flags :flags, remove_first: 2 ** 0
|
43
|
+
|
44
|
+
# @attribute
|
45
|
+
# @return [Hash] the query to use when deleting documents
|
46
|
+
document :selector
|
47
|
+
|
48
|
+
# @return [String, Symbol] the database to delete from
|
49
|
+
attr_reader :database
|
50
|
+
|
51
|
+
# @return [String, Symbol] the collection to delete from
|
52
|
+
attr_reader :collection
|
53
|
+
|
54
|
+
# Create a new delete command. The +database+ and +collection+ arguments
|
55
|
+
# are joined together to set the +full_collection_name+.
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# Delete.new "moped", "users", { condition: true },
|
59
|
+
# flags: [:remove_first],
|
60
|
+
# request_id: 123
|
61
|
+
#
|
62
|
+
# @param [String, Symbol] database the database to delete from
|
63
|
+
# @param [String, Symbol] collection the collection to delete from
|
64
|
+
# @param [Hash] selector the selector for which documents to delete
|
65
|
+
# @param [Hash] options additional options
|
66
|
+
# @option options [Number] :request_id the command's request id
|
67
|
+
# @option options [Array] :flags the flags for insertion. Supported
|
68
|
+
# flags: +:remove_first+
|
69
|
+
def initialize(database, collection, selector, options = {})
|
70
|
+
@database = database
|
71
|
+
@collection = collection
|
72
|
+
|
73
|
+
@full_collection_name = "#{database}.#{collection}"
|
74
|
+
@selector = selector
|
75
|
+
@request_id = options[:request_id]
|
76
|
+
@flags = options[:flags]
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Number] OP_DELETE operation code (2006)
|
80
|
+
def op_code
|
81
|
+
2006
|
82
|
+
end
|
83
|
+
|
84
|
+
def log_inspect
|
85
|
+
type = "DELETE"
|
86
|
+
|
87
|
+
"%-12s database=%s collection=%s selector=%s flags=%s" % [type, database, collection, selector.inspect, flags.inspect]
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Moped
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
# The Protocol class for retrieving more documents from a cursor.
|
5
|
+
#
|
6
|
+
# @example Get more results using database default limit
|
7
|
+
# insert = GetMore.new "moped", "people", 29301021, 0
|
8
|
+
#
|
9
|
+
# @example Get more results using custom limit
|
10
|
+
# insert = Insert.new "moped", "people", 29301021, 10
|
11
|
+
#
|
12
|
+
# @example Setting the request id
|
13
|
+
# insert = Insert.new "moped", "people", 29301021, 10,
|
14
|
+
# request_id: 123
|
15
|
+
class GetMore
|
16
|
+
include Message
|
17
|
+
|
18
|
+
# @attribute
|
19
|
+
# @return [Number] the length of the message
|
20
|
+
int32 :length
|
21
|
+
|
22
|
+
# @attribute
|
23
|
+
# @return [Number] the request id of the message
|
24
|
+
int32 :request_id
|
25
|
+
|
26
|
+
int32 :response_to
|
27
|
+
|
28
|
+
# @attribute
|
29
|
+
# @return [Number] the operation code of this message
|
30
|
+
int32 :op_code
|
31
|
+
|
32
|
+
int32 :reserved # reserved for future use
|
33
|
+
|
34
|
+
# @attribute
|
35
|
+
# @return [String] the namespaced collection name
|
36
|
+
cstring :full_collection_name
|
37
|
+
|
38
|
+
# @attribute
|
39
|
+
# @return [Number] the number of documents to return
|
40
|
+
int32 :limit
|
41
|
+
|
42
|
+
# @attribute
|
43
|
+
# @return [Number] the id of the cursor to get more documents from
|
44
|
+
int64 :cursor_id
|
45
|
+
|
46
|
+
# @return [Number] OP_GETMORE operation code (2005)
|
47
|
+
def op_code
|
48
|
+
2005
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String, Symbol] the database this insert targets
|
52
|
+
attr_reader :database
|
53
|
+
|
54
|
+
# @return [String, Symbol] the collection this insert targets
|
55
|
+
attr_reader :collection
|
56
|
+
|
57
|
+
# Create a new +GetMore+ command. The +database+ and +collection+ arguments
|
58
|
+
# are joined together to set the +full_collection_name+.
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# GetMore.new "moped", "users", 29301021, 10, request_id: 123
|
62
|
+
def initialize(database, collection, cursor_id, limit, options = {})
|
63
|
+
@database = database
|
64
|
+
@collection = collection
|
65
|
+
|
66
|
+
@full_collection_name = "#{database}.#{collection}"
|
67
|
+
@cursor_id = cursor_id
|
68
|
+
@limit = limit
|
69
|
+
@request_id = options[:request_id]
|
70
|
+
end
|
71
|
+
|
72
|
+
def log_inspect
|
73
|
+
type = "GET_MORE"
|
74
|
+
|
75
|
+
"%-12s database=%s collection=%s limit=%s cursor_id=%s" % [type, database, collection, limit, cursor_id]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Moped
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
# The Protocol class for inserting documents into a collection.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# insert = Insert.new "moped", "people", [{ name: "John" }]
|
8
|
+
#
|
9
|
+
# @example Continuing after an error on batch insert
|
10
|
+
# insert = Insert.new "moped", "people",
|
11
|
+
# [{ unique_field: 1 }, { unique_field: 1 }, { unique_field: 2 }],
|
12
|
+
# flags: [:continue_on_error]
|
13
|
+
#
|
14
|
+
# @example Setting the request id
|
15
|
+
# insert = Insert.new "moped", "people", [{ name: "John" }],
|
16
|
+
# request_id: 123
|
17
|
+
class Insert
|
18
|
+
include Message
|
19
|
+
|
20
|
+
# @attribute
|
21
|
+
# @return [Number] the length of the message
|
22
|
+
int32 :length
|
23
|
+
|
24
|
+
# @attribute
|
25
|
+
# @return [Number] the request id of the message
|
26
|
+
int32 :request_id
|
27
|
+
|
28
|
+
int32 :response_to
|
29
|
+
|
30
|
+
# @attribute
|
31
|
+
# @return [Number] the operation code of this message
|
32
|
+
int32 :op_code
|
33
|
+
|
34
|
+
# @attribute
|
35
|
+
# The flags for the query. Supported flags are: +:continue_on_error+.
|
36
|
+
#
|
37
|
+
# @param [Array] flags the flags for this message
|
38
|
+
# @return [Array] the flags for this message
|
39
|
+
flags :flags, continue_on_error: 2 ** 0
|
40
|
+
|
41
|
+
# @attribute
|
42
|
+
# @return [String] the namespaced collection name
|
43
|
+
cstring :full_collection_name
|
44
|
+
|
45
|
+
# @attribute
|
46
|
+
# @return [Array<Hash>] the documents to insert
|
47
|
+
document :documents, type: :array
|
48
|
+
|
49
|
+
# @return [Number] OP_INSERT operation code (2002)
|
50
|
+
def op_code
|
51
|
+
2002
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [String, Symbol] the database this insert targets
|
55
|
+
attr_reader :database
|
56
|
+
|
57
|
+
# @return [String, Symbol] the collection this insert targets
|
58
|
+
attr_reader :collection
|
59
|
+
|
60
|
+
# Create a new insert command. The +database+ and +collection+ arguments
|
61
|
+
# are joined together to set the +full_collection_name+.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# Insert.new "moped", "users", [{ name: "John" }],
|
65
|
+
# flags: [:continue_on_error],
|
66
|
+
# request_id: 123
|
67
|
+
#
|
68
|
+
# @param [String, Symbol] database the database to insert into
|
69
|
+
# @param [String, Symbol] collection the collection to insert into
|
70
|
+
# @param [Array<Hash>] documents the documents to insert
|
71
|
+
# @param [Hash] options additional options
|
72
|
+
# @option options [Number] :request_id the command's request id
|
73
|
+
# @option options [Array] :flags the flags for insertion. Supported
|
74
|
+
# flags: +:continue_on_error+
|
75
|
+
def initialize(database, collection, documents, options = {})
|
76
|
+
@database = database
|
77
|
+
@collection = collection
|
78
|
+
|
79
|
+
@full_collection_name = "#{database}.#{collection}"
|
80
|
+
@documents = documents
|
81
|
+
@request_id = options[:request_id]
|
82
|
+
@flags = options[:flags]
|
83
|
+
end
|
84
|
+
|
85
|
+
def log_inspect
|
86
|
+
type = "INSERT"
|
87
|
+
|
88
|
+
"%-12s database=%s collection=%s documents=%s flags=%s" % [type, database, collection, documents.inspect, flags.inspect]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Moped
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
# The Protocol class for killing active cursors.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# command = KillCursors.new [123, 124, 125]
|
8
|
+
#
|
9
|
+
# @example Setting the request id
|
10
|
+
# command = KillCursors.new [123, 124, 125], request_id: 456
|
11
|
+
class KillCursors
|
12
|
+
include Message
|
13
|
+
|
14
|
+
# @attribute
|
15
|
+
# @return [Number] the length of the message
|
16
|
+
int32 :length
|
17
|
+
|
18
|
+
# @attribute
|
19
|
+
# @return [Number] the request id of the message
|
20
|
+
int32 :request_id
|
21
|
+
|
22
|
+
int32 :response_to
|
23
|
+
|
24
|
+
# @attribute
|
25
|
+
# @return [Number] the operation code of this message
|
26
|
+
int32 :op_code
|
27
|
+
|
28
|
+
int32 :reserved # reserved for future use
|
29
|
+
|
30
|
+
# @attribute
|
31
|
+
# @return [Number] the number of cursor ids
|
32
|
+
int32 :number_of_cursor_ids
|
33
|
+
|
34
|
+
# @attribute
|
35
|
+
# @return [Array] the cursor ids to kill
|
36
|
+
int64 :cursor_ids, type: :array
|
37
|
+
|
38
|
+
# @return [Number] OP_KILL_CURSORS operation code (2007)
|
39
|
+
def op_code
|
40
|
+
2007
|
41
|
+
end
|
42
|
+
|
43
|
+
# Create a new command to kill cursors.
|
44
|
+
#
|
45
|
+
# @param [Array] cursor_ids an array of cursor ids to kill
|
46
|
+
# @param [Hash] options additional options
|
47
|
+
# @option options [Number] :request_id the command's request id
|
48
|
+
def initialize(cursor_ids, options = {})
|
49
|
+
@cursor_ids = cursor_ids
|
50
|
+
@number_of_cursor_ids = cursor_ids.length
|
51
|
+
@request_id = options[:request_id]
|
52
|
+
end
|
53
|
+
|
54
|
+
def log_inspect
|
55
|
+
type = "KILL_CURSORS"
|
56
|
+
|
57
|
+
"%-12s cursor_ids=%s" % [type, cursor_ids.inspect]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
module Moped
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
# The base class for building all messages needed to implement the Mongo
|
5
|
+
# Wire Protocol. It provides a minimal DSL for defining typed fields for
|
6
|
+
# serialization and deserialization over the wire.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# class KillCursors < Moped::Protocol::Message
|
11
|
+
# # header fields
|
12
|
+
# int32 :length
|
13
|
+
# int32 :request_id
|
14
|
+
# int32 :response_to
|
15
|
+
# int32 :op_code
|
16
|
+
#
|
17
|
+
# # message fields
|
18
|
+
# int32 :reserved
|
19
|
+
# int32 :number_of_cursors
|
20
|
+
# int64 :cursor_ids, type: :array
|
21
|
+
#
|
22
|
+
# # Customize field reader
|
23
|
+
# def number_of_cursors
|
24
|
+
# cursor_ids.length
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# Note that all messages *must* implement the header fields required by the
|
29
|
+
# Mongo Wire Protocol, namely:
|
30
|
+
#
|
31
|
+
# int32 :length
|
32
|
+
# int32 :request_id
|
33
|
+
# int32 :response_to
|
34
|
+
# int32 :op_code
|
35
|
+
#
|
36
|
+
module Message
|
37
|
+
|
38
|
+
class << self
|
39
|
+
|
40
|
+
# Extends the including class with +ClassMethods+.
|
41
|
+
#
|
42
|
+
# @param [Class] subclass the inheriting class
|
43
|
+
def included(base)
|
44
|
+
super
|
45
|
+
|
46
|
+
base.extend ClassMethods
|
47
|
+
end
|
48
|
+
|
49
|
+
private :included
|
50
|
+
end
|
51
|
+
|
52
|
+
# Provides a DSL for defining struct-like fields for building messages
|
53
|
+
# for the Mongo Wire.
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# class Command
|
57
|
+
# extend Message::ClassMethods
|
58
|
+
#
|
59
|
+
# int32 :length
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Command.fields # => [:length]
|
63
|
+
# command = Command.new
|
64
|
+
# command.length = 12
|
65
|
+
# command.serialize_length("") # => "\f\x00\x00\x00"
|
66
|
+
module ClassMethods
|
67
|
+
|
68
|
+
# @return [Array] the fields defined for this message
|
69
|
+
def fields
|
70
|
+
@fields ||= []
|
71
|
+
end
|
72
|
+
|
73
|
+
# Declare a null terminated string field.
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
# class Query < Message
|
77
|
+
# cstring :collection
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# @param [String] name the name of this field
|
81
|
+
def cstring(name)
|
82
|
+
attr_accessor name
|
83
|
+
|
84
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
85
|
+
def serialize_#{name}(buffer)
|
86
|
+
buffer << #{name}
|
87
|
+
buffer << 0
|
88
|
+
end
|
89
|
+
RUBY
|
90
|
+
|
91
|
+
fields << name
|
92
|
+
end
|
93
|
+
|
94
|
+
# Declare a BSON Document field.
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# class Update < Message
|
98
|
+
# document :selector
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# @example optional document field
|
102
|
+
# class Query < Message
|
103
|
+
# document :selector
|
104
|
+
# document :fields, optional: true
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# @example array of documents
|
108
|
+
# class Reply < Message
|
109
|
+
# document :documents, type: :array
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# @param [String] name the name of this field
|
113
|
+
# @param [Hash] options the options for this field
|
114
|
+
# @option options [:array] :type specify an array of documents
|
115
|
+
# @option options [Boolean] :optional specify this field as optional
|
116
|
+
def document(name, options = {})
|
117
|
+
attr_accessor name
|
118
|
+
|
119
|
+
if options[:optional]
|
120
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
121
|
+
def serialize_#{name}(buffer)
|
122
|
+
BSON::Document.serialize(#{name}, buffer) if #{name}
|
123
|
+
end
|
124
|
+
RUBY
|
125
|
+
elsif options[:type] == :array
|
126
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
127
|
+
def serialize_#{name}(buffer)
|
128
|
+
#{name}.each do |document|
|
129
|
+
BSON::Document.serialize(document, buffer)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
RUBY
|
133
|
+
else
|
134
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
135
|
+
def serialize_#{name}(buffer)
|
136
|
+
BSON::Document.serialize(#{name}, buffer)
|
137
|
+
end
|
138
|
+
RUBY
|
139
|
+
end
|
140
|
+
|
141
|
+
fields << name
|
142
|
+
end
|
143
|
+
|
144
|
+
# Declare a flag field (32 bit signed integer)
|
145
|
+
#
|
146
|
+
# @example
|
147
|
+
# class Update < Message
|
148
|
+
# flags :flags, upsert: 2 ** 0,
|
149
|
+
# multi: 2 ** 1
|
150
|
+
# end
|
151
|
+
#
|
152
|
+
# @param [String] name the name of this field
|
153
|
+
# @param [Hash{Symbol => Number}] flags the flags for this flag field
|
154
|
+
def flags(name, flag_map = {})
|
155
|
+
attr_writer name
|
156
|
+
|
157
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
158
|
+
def #{name}
|
159
|
+
@#{name} ||= []
|
160
|
+
end
|
161
|
+
|
162
|
+
def #{name}=(flags)
|
163
|
+
if flags.is_a? Numeric
|
164
|
+
@#{name} = #{name}_from_int(flags)
|
165
|
+
else
|
166
|
+
@#{name} = flags
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def #{name}_as_int
|
171
|
+
bits = 0
|
172
|
+
flags = self.#{name}
|
173
|
+
#{flag_map.map { |flag, value| "bits |= #{value} if flags.include? #{flag.inspect}" }.join "\n"}
|
174
|
+
bits
|
175
|
+
end
|
176
|
+
|
177
|
+
def #{name}_from_int(bits)
|
178
|
+
flags = []
|
179
|
+
#{flag_map.map { |flag, value| "flags << #{flag.inspect} if #{value} & bits == #{value}" }.join "\n"}
|
180
|
+
flags
|
181
|
+
end
|
182
|
+
|
183
|
+
def serialize_#{name}(buffer)
|
184
|
+
buffer << [#{name}_as_int].pack('l<')
|
185
|
+
end
|
186
|
+
|
187
|
+
def deserialize_#{name}(buffer)
|
188
|
+
bits, = buffer.read(4).unpack('l<')
|
189
|
+
|
190
|
+
self.#{name} = bits
|
191
|
+
end
|
192
|
+
RUBY
|
193
|
+
|
194
|
+
fields << name
|
195
|
+
end
|
196
|
+
|
197
|
+
# Declare a 32 bit signed integer field.
|
198
|
+
#
|
199
|
+
# @example
|
200
|
+
# class Query < Message
|
201
|
+
# int32 :length
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
# @param [String] name the name of this field
|
205
|
+
def int32(name)
|
206
|
+
attr_writer name
|
207
|
+
|
208
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
209
|
+
def #{name}
|
210
|
+
@#{name} ||= 0
|
211
|
+
end
|
212
|
+
|
213
|
+
def serialize_#{name}(buffer)
|
214
|
+
buffer << [#{name}].pack('l<')
|
215
|
+
end
|
216
|
+
|
217
|
+
def deserialize_#{name}(buffer)
|
218
|
+
self.#{name}, = buffer.read(4).unpack('l<')
|
219
|
+
end
|
220
|
+
RUBY
|
221
|
+
|
222
|
+
fields << name
|
223
|
+
end
|
224
|
+
|
225
|
+
# Declare a 64 bit signed integer field.
|
226
|
+
#
|
227
|
+
# @example
|
228
|
+
# class Query < Message
|
229
|
+
# int64 :cursor_id
|
230
|
+
# end
|
231
|
+
#
|
232
|
+
# @example with array type
|
233
|
+
# class KillCursors < Message
|
234
|
+
# int64 :cursor_ids, type: :array
|
235
|
+
# end
|
236
|
+
#
|
237
|
+
# @param [String] name the name of this field
|
238
|
+
# @param [Hash] options the options for this field
|
239
|
+
# @option options [:array] :type specify an array of 64 bit ints
|
240
|
+
def int64(name, options = {})
|
241
|
+
attr_writer name
|
242
|
+
|
243
|
+
if options[:type] == :array
|
244
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
245
|
+
def #{name}
|
246
|
+
@#{name} ||= []
|
247
|
+
end
|
248
|
+
|
249
|
+
def serialize_#{name}(buffer)
|
250
|
+
buffer << #{name}.pack('q*<')
|
251
|
+
end
|
252
|
+
|
253
|
+
def deserialize_#{name}(buffer)
|
254
|
+
raise NotImplementedError
|
255
|
+
end
|
256
|
+
RUBY
|
257
|
+
else
|
258
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
259
|
+
def #{name}
|
260
|
+
@#{name} ||= 0
|
261
|
+
end
|
262
|
+
|
263
|
+
def serialize_#{name}(buffer)
|
264
|
+
buffer << [#{name}].pack('q<')
|
265
|
+
end
|
266
|
+
|
267
|
+
def deserialize_#{name}(buffer)
|
268
|
+
self.#{name}, = buffer.read(8).unpack('q<')
|
269
|
+
end
|
270
|
+
RUBY
|
271
|
+
end
|
272
|
+
|
273
|
+
fields << name
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
# This ensures that subclasses of the primary wire message classes have
|
279
|
+
# identical fields.
|
280
|
+
def inherited(subclass)
|
281
|
+
super
|
282
|
+
|
283
|
+
subclass.fields.replace fields
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
# Serializes the message and all of its fields to a new buffer or to the
|
289
|
+
# provided buffer.
|
290
|
+
#
|
291
|
+
# @param [String] buffer a buffer to serialize to
|
292
|
+
# @return [String] the result of serliazing this message
|
293
|
+
def serialize(buffer = "")
|
294
|
+
buffer.tap do
|
295
|
+
start = buffer.length
|
296
|
+
|
297
|
+
self.class.fields.each do |field|
|
298
|
+
__send__ :"serialize_#{field}", buffer
|
299
|
+
end
|
300
|
+
|
301
|
+
self.length = buffer.length - start
|
302
|
+
|
303
|
+
buffer[start, 4] = serialize_length("")
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
alias to_s serialize
|
308
|
+
|
309
|
+
# @return [String] the nicely formatted version of the message
|
310
|
+
def inspect
|
311
|
+
fields = self.class.fields.map do |field|
|
312
|
+
"@#{field}=" + __send__(field).inspect
|
313
|
+
end
|
314
|
+
"#<#{self.class.name}\n" <<
|
315
|
+
" #{fields * "\n "}>"
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|