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.

Files changed (50) hide show
  1. data/MIT_LICENSE +19 -0
  2. data/README.md +323 -0
  3. data/lib/moped.rb +19 -0
  4. data/lib/moped/bson.rb +25 -0
  5. data/lib/moped/bson/binary.rb +68 -0
  6. data/lib/moped/bson/code.rb +61 -0
  7. data/lib/moped/bson/document.rb +16 -0
  8. data/lib/moped/bson/extensions.rb +81 -0
  9. data/lib/moped/bson/extensions/array.rb +44 -0
  10. data/lib/moped/bson/extensions/boolean.rb +14 -0
  11. data/lib/moped/bson/extensions/false_class.rb +15 -0
  12. data/lib/moped/bson/extensions/float.rb +23 -0
  13. data/lib/moped/bson/extensions/hash.rb +49 -0
  14. data/lib/moped/bson/extensions/integer.rb +37 -0
  15. data/lib/moped/bson/extensions/nil_class.rb +20 -0
  16. data/lib/moped/bson/extensions/regexp.rb +40 -0
  17. data/lib/moped/bson/extensions/string.rb +35 -0
  18. data/lib/moped/bson/extensions/symbol.rb +25 -0
  19. data/lib/moped/bson/extensions/time.rb +21 -0
  20. data/lib/moped/bson/extensions/true_class.rb +15 -0
  21. data/lib/moped/bson/max_key.rb +21 -0
  22. data/lib/moped/bson/min_key.rb +21 -0
  23. data/lib/moped/bson/object_id.rb +123 -0
  24. data/lib/moped/bson/timestamp.rb +15 -0
  25. data/lib/moped/bson/types.rb +67 -0
  26. data/lib/moped/cluster.rb +193 -0
  27. data/lib/moped/collection.rb +67 -0
  28. data/lib/moped/cursor.rb +60 -0
  29. data/lib/moped/database.rb +76 -0
  30. data/lib/moped/errors.rb +61 -0
  31. data/lib/moped/indexes.rb +93 -0
  32. data/lib/moped/logging.rb +25 -0
  33. data/lib/moped/protocol.rb +20 -0
  34. data/lib/moped/protocol/command.rb +27 -0
  35. data/lib/moped/protocol/commands.rb +11 -0
  36. data/lib/moped/protocol/commands/authenticate.rb +54 -0
  37. data/lib/moped/protocol/delete.rb +92 -0
  38. data/lib/moped/protocol/get_more.rb +79 -0
  39. data/lib/moped/protocol/insert.rb +92 -0
  40. data/lib/moped/protocol/kill_cursors.rb +61 -0
  41. data/lib/moped/protocol/message.rb +320 -0
  42. data/lib/moped/protocol/query.rb +131 -0
  43. data/lib/moped/protocol/reply.rb +90 -0
  44. data/lib/moped/protocol/update.rb +107 -0
  45. data/lib/moped/query.rb +230 -0
  46. data/lib/moped/server.rb +73 -0
  47. data/lib/moped/session.rb +253 -0
  48. data/lib/moped/socket.rb +201 -0
  49. data/lib/moped/version.rb +4 -0
  50. metadata +108 -46
@@ -0,0 +1,131 @@
1
+ module Moped
2
+ module Protocol
3
+ # The Protocol class for querying a collection.
4
+ #
5
+ # @example Find all users named John
6
+ # Query.new "moped", "users", { name: "John" }
7
+ #
8
+ # @example Find all users named John skipping 5 and returning 10
9
+ # Query.new "moped", "users", { name: "John" },
10
+ # skip: 5, limit: 10
11
+ #
12
+ # @example Find all users on slave node
13
+ # Query.new "moped", "users", {}, flags: [:slave_ok]
14
+ #
15
+ # @example Find all user ids
16
+ # Query.new "moped", "users", {}, fields: { _id: 1 }
17
+ #
18
+ class Query
19
+ include Message
20
+
21
+ # @attribute
22
+ # @return [Number] the length of the message
23
+ int32 :length
24
+
25
+ # @attribute
26
+ # @return [Number] the request id of the message
27
+ int32 :request_id
28
+
29
+ int32 :response_to
30
+
31
+ # @attribute
32
+ # @return [Number] the operation code of this message
33
+ int32 :op_code
34
+
35
+ # @attribute
36
+ # The flags for the query. Supported flags are: +:tailable+, +:slave_ok+,
37
+ # +:no_cursor_timeout+, +:await_data+, +:exhaust+.
38
+ #
39
+ # @param [Array] flags the flags for this message
40
+ # @return [Array] the flags for this message
41
+ flags :flags, tailable: 2 ** 1,
42
+ slave_ok: 2 ** 2,
43
+ no_cursor_timeout: 2 ** 4,
44
+ await_data: 2 ** 5,
45
+ exhaust: 2 ** 6
46
+
47
+ # @attribute
48
+ # @return [String] the namespaced collection name
49
+ cstring :full_collection_name
50
+
51
+ # @attribute
52
+ # @return [Number] the number of documents to skip
53
+ int32 :skip
54
+
55
+ # @attribute
56
+ # @return [Number] the number of documents to return
57
+ int32 :limit
58
+
59
+ # @attribute
60
+ # @return [Hash] the selector for this query
61
+ document :selector
62
+
63
+ # @attribute
64
+ # @return [Hash, nil] the fields to include in the reply
65
+ document :fields, :optional => true
66
+
67
+ # @return [Number] OP_QUERY operation code (2004)
68
+ def op_code
69
+ 2004
70
+ end
71
+
72
+ # @return [String, Symbol] the database to query
73
+ attr_reader :database
74
+
75
+ # @return [String, Symbol] the collection to query
76
+ attr_reader :collection
77
+
78
+ # Create a new query command.
79
+ #
80
+ # @example
81
+ # Query.new "moped", "users", { name: "John" },
82
+ # skip: 5,
83
+ # limit: 10,
84
+ # request_id: 12930,
85
+ # fields: { _id: -1, name: 1 }
86
+ #
87
+ # @param [String, Symbol] database the database to insert into
88
+ # @param [String, Symbol] collection the collection to insert into
89
+ # @param [Hash] selector the query
90
+ # @param [Hash] options additional options
91
+ # @option options [Number] :request_id the command's request id
92
+ # @option options [Number] :skip the number of documents to skip
93
+ # @option options [Number] :limit the number of documents to return
94
+ # @option options [Hash] :fields the fields to return
95
+ # @option options [Array] :flags the flags for querying. Supported
96
+ # flags: +:tailable+, +:slave_ok+, +:no_cursor_timeout+, +:await_data+,
97
+ # +:exhaust+.
98
+ def initialize(database, collection, selector, options = {})
99
+ @database = database
100
+ @collection = collection
101
+
102
+ @full_collection_name = "#{database}.#{collection}"
103
+ @selector = selector
104
+ @request_id = options[:request_id]
105
+ @flags = options[:flags]
106
+ @limit = options[:limit]
107
+ @skip = options[:skip]
108
+ @fields = options[:fields]
109
+ end
110
+
111
+ def log_inspect
112
+ type = "QUERY"
113
+
114
+ fields = []
115
+ fields << ["%-12s", type]
116
+ fields << ["database=%s", database]
117
+ fields << ["collection=%s", collection]
118
+ fields << ["selector=%s", selector.inspect]
119
+ fields << ["flags=%s", flags.inspect]
120
+ fields << ["limit=%s", limit.inspect]
121
+ fields << ["skip=%s", skip.inspect]
122
+ fields << ["fields=%s", self.fields.inspect]
123
+
124
+ f, v = fields.transpose
125
+
126
+ f.join(" ") % v
127
+ end
128
+
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,90 @@
1
+ module Moped
2
+ module Protocol
3
+
4
+ # The Protocol class representing messages received from a mongo
5
+ # connection.
6
+ #
7
+ # @example
8
+ # socket = TCPSocket.new "127.0.0.1", 27017
9
+ # command = Moped::Protocol::Command.new "admin", buildinfo: 1
10
+ # socket.write command.serialize
11
+ # reply = Moped::Protocol::Reply.deserialize(socket)
12
+ # reply.documents[0]["version"] # => "2.0.0"
13
+ class Reply
14
+ include Message
15
+
16
+ # @attribute
17
+ # @return [Number] the length of the message
18
+ int32 :length
19
+
20
+ # @attribute
21
+ # @return [Number] the request id of the message
22
+ int32 :request_id
23
+
24
+ # @attribute
25
+ # @return [Number] the id that generated the message
26
+ int32 :response_to
27
+
28
+ # @attribute
29
+ # @return [Number] the operation code of this message (always 1)
30
+ int32 :op_code
31
+
32
+ # @attribute
33
+ # @return [Array<Symbol>] the flags for this reply
34
+ flags :flags, cursor_not_found: 2 ** 0,
35
+ query_failure: 2 ** 1,
36
+ await_capable: 2 ** 3
37
+
38
+ # @attribute
39
+ # @return [Number] the id of the cursor on the server
40
+ int64 :cursor_id
41
+
42
+ # @attribute
43
+ # @return [Number] the starting position within the cursor
44
+ int32 :offset
45
+
46
+ # @attribute
47
+ # @return [Number] the number of documents returned
48
+ int32 :count
49
+
50
+ # @attribute
51
+ # @return [Array] the returned documents
52
+ document :documents, type: :array
53
+
54
+ class << self
55
+
56
+ # Consumes a buffer, returning the deserialized Reply message.
57
+ #
58
+ # @example
59
+ # socket = TCPSocket.new "localhost", 27017
60
+ # socket.write Moped::Protocol::Command.new(:admin, ismaster: 1).serialize
61
+ # reply = Moped::Protocol::Reply.deserialize(socket)
62
+ # reply.documents[0]['ismaster'] # => 1
63
+ #
64
+ # @param [#read] buffer an IO or IO-like resource to deserialize the
65
+ # reply from.
66
+ # @return [Reply] the deserialized reply
67
+ def deserialize(buffer)
68
+ allocate.tap do |reply|
69
+ fields.each do |field|
70
+ reply.__send__ :"deserialize_#{field}", buffer
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def deserialize_documents(buffer)
79
+ documents = []
80
+
81
+ count.times do
82
+ documents << BSON::Document.deserialize(buffer)
83
+ end
84
+
85
+ @documents = documents
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,107 @@
1
+ module Moped
2
+ module Protocol
3
+
4
+ # The Protocol class for updating documents in a collection.
5
+ #
6
+ # @example Rename a user
7
+ # Update.new "moped", "users", { _id: "123" }, { name: "Bob" }
8
+ #
9
+ # @example Rename all users named John
10
+ # Update.new "moped", "users", { name: "John" }, { name: "Bob" },
11
+ # flags: [:multi]
12
+ #
13
+ # @example Upsert
14
+ # Update.new "moped", "users", { name: "John" }, { name: "John" },
15
+ # flags: [:upsert]
16
+ #
17
+ # @example Setting the request id
18
+ # Update.new "moped", "users", {}, { name: "Bob" },
19
+ # request_id: 123
20
+ class Update
21
+ include Message
22
+
23
+ # @attribute
24
+ # @return [Number] the length of the message
25
+ int32 :length
26
+
27
+ # @attribute
28
+ # @return [Number] the request id of the message
29
+ int32 :request_id
30
+
31
+ int32 :response_to
32
+
33
+ # @attribute
34
+ # @return [Number] the operation code of this message
35
+ int32 :op_code
36
+
37
+ int32 :reserved # reserved for future use
38
+
39
+ # @attribute
40
+ # @return [String] the namespaced collection name
41
+ cstring :full_collection_name
42
+
43
+ # @attribute
44
+ # The flags for the update message. Supported flags are +:upsert+ and
45
+ # +:multi+.
46
+ # @param [Array] flags the flags for this message
47
+ # @return [Array] the flags for this message
48
+ flags :flags, upsert: 2 ** 0,
49
+ multi: 2 ** 1
50
+
51
+ # @attribute
52
+ # @return [Hash] the selector for the update
53
+ document :selector
54
+
55
+ # @attribute
56
+ # @return [Hash] the updates to apply
57
+ document :update
58
+
59
+ # @return [Number] OP_UPDATE operation code (2001)
60
+ def op_code
61
+ 2001
62
+ end
63
+
64
+ # @return [String, Symbol] the database this insert targets
65
+ attr_reader :database
66
+
67
+ # @return [String, Symbol] the collection this insert targets
68
+ attr_reader :collection
69
+
70
+ # Create a new update command. The +database+ and +collection+ arguments
71
+ # are joined together to set the +full_collection_name+.
72
+ #
73
+ # @example
74
+ # Update.new "moped", "users", { name: "John" }, { name: "Bob" },
75
+ # flags: [:upsert],
76
+ # request_id: 123
77
+ #
78
+ # @param [String, Symbol] database the database to insert into
79
+ # @param [String, Symbol] collection the collection to insert into
80
+ # @param [Hash] selector the selector
81
+ # @param [Hash] update the update to perform
82
+ # @param [Hash] options additional options
83
+ # @option options [Number] :request_id the command's request id
84
+ # @option options [Array] :flags the flags for insertion. Supported
85
+ # flags: +:upsert+, +:multi+.
86
+ def initialize(database, collection, selector, update, options = {})
87
+ @database = database
88
+ @collection = collection
89
+
90
+ @full_collection_name = "#{database}.#{collection}"
91
+ @selector = selector
92
+ @update = update
93
+ @request_id = options[:request_id]
94
+ @flags = options[:flags]
95
+ end
96
+
97
+ def log_inspect
98
+ type = "UPDATE"
99
+
100
+ "%-12s database=%s collection=%s selector=%s update=%s flags=%s" % [type, database, collection,
101
+ selector.inspect,
102
+ update.inspect,
103
+ flags.inspect]
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,230 @@
1
+ module Moped
2
+
3
+ # The +Query+ class encapsulates all of the logic related to building
4
+ # selectors for querying, updating, or removing documents in a collection.
5
+ #
6
+ # @example
7
+ # people = db[:people]
8
+ # people.find.entries # => [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}]
9
+ # people.find.skip(2).first # => { id: 3 }
10
+ # people.find.skip(2).update(name: "John")
11
+ # people.find.skip(2).first # => { id: 3, name: "John" }
12
+ #
13
+ # people.find(name: nil).update_all(name: "Unknown")
14
+ # people.find.one # => { id: 5, name: "Unknown" }
15
+ # people.find.first # => { id: 5, name: "Unknown" }
16
+ # people.find.select(name: 0).first # => { id: 5 }
17
+ # people.find(name: "Unknown").remove_all
18
+ # people.find.count # => 1
19
+ class Query
20
+ include Enumerable
21
+
22
+ # @return [Collection] the query's collection
23
+ attr_reader :collection
24
+
25
+ # @return [Hash] the query's selector
26
+ attr_reader :selector
27
+
28
+ # @api private
29
+ attr_reader :operation
30
+
31
+ # @param [Collection] collection the query's collection
32
+ # @param [Hash] selector the query's selector
33
+ def initialize(collection, selector)
34
+ @collection = collection
35
+ @selector = selector
36
+
37
+ @operation = Protocol::Query.new(
38
+ collection.database.name,
39
+ collection.name,
40
+ selector
41
+ )
42
+ end
43
+
44
+ # Set the query's limit.
45
+ #
46
+ # @param [Numeric] limit
47
+ # @return [Query] self
48
+ def limit(limit)
49
+ operation.limit = limit
50
+ self
51
+ end
52
+
53
+ # Set the number of documents to skip.
54
+ #
55
+ # @param [Numeric] skip
56
+ # @return [Query] self
57
+ def skip(skip)
58
+ operation.skip = skip
59
+ self
60
+ end
61
+
62
+ # Set the sort order for the query.
63
+ #
64
+ # @example
65
+ # db[:people].find.sort(name: 1, age: -1).one
66
+ #
67
+ # @param [Hash] sort
68
+ # @return [Query] self
69
+ def sort(sort)
70
+ operation.selector = { "$query" => selector, "$orderby" => sort }
71
+ self
72
+ end
73
+
74
+ # Explain the current query.
75
+ #
76
+ # @example Explain the query.
77
+ # db[:people].find.explain
78
+ #
79
+ # @return [ Hash ] The explain document.
80
+ def explain
81
+ operation.selector = {
82
+ "$query" => selector,
83
+ "$orderby" => operation.selector.fetch("$orderby", {}),
84
+ "$explain" => true
85
+ } and first
86
+ end
87
+
88
+ # Set the fields to return from the query.
89
+ #
90
+ # @example
91
+ # db[:people].find.select(name: 1).one # => { name: "John" }
92
+ #
93
+ # @param [Hash] select
94
+ # @return [Query] self
95
+ def select(select)
96
+ operation.fields = select
97
+ self
98
+ end
99
+
100
+ # @return [Hash] the first document that matches the selector.
101
+ def first
102
+ session.simple_query(operation)
103
+ end
104
+ alias one first
105
+
106
+ # Iterate through documents matching the query's selector.
107
+ #
108
+ # @yieldparam [Hash] document each matching document
109
+ def each
110
+ cursor = Cursor.new(session.with(retain_socket: true), operation)
111
+ cursor.to_enum.tap do |enum|
112
+ enum.each do |document|
113
+ yield document
114
+ end if block_given?
115
+ end
116
+ end
117
+
118
+ # Get the distinct values in the collection for the provided key.
119
+ #
120
+ # @example Get the distinct values.
121
+ # query.distinct(:name)
122
+ #
123
+ # @param [ Symbol, String ] key The name of the field.
124
+ #
125
+ # @return [ Array<Object ] The distinct values.
126
+ def distinct(key)
127
+ result = collection.database.command(
128
+ distinct: collection.name,
129
+ key: key.to_s,
130
+ query: selector
131
+ )
132
+ result["values"]
133
+ end
134
+
135
+ # @return [Numeric] the number of documents that match the selector.
136
+ def count
137
+ result = collection.database.command(
138
+ count: collection.name,
139
+ query: selector
140
+ )
141
+
142
+ result["n"]
143
+ end
144
+
145
+ # Update a single document matching the query's selector.
146
+ #
147
+ # @example
148
+ # db[:people].find(_id: 1).update(name: "John")
149
+ #
150
+ # @param [Hash] change the changes to make to the document
151
+ # @param [Array] flags an array of operation flags. Valid values are:
152
+ # +:multi+ and +:upsert+
153
+ def update(change, flags = nil)
154
+ update = Protocol::Update.new(
155
+ operation.database,
156
+ operation.collection,
157
+ operation.selector,
158
+ change,
159
+ flags: flags
160
+ )
161
+
162
+ session.with(consistency: :strong) do |session|
163
+ session.execute update
164
+ end
165
+ end
166
+
167
+ # Update multiple documents matching the query's selector.
168
+ #
169
+ # @example
170
+ # db[:people].find(name: "John").update_all(name: "Mary")
171
+ #
172
+ # @param [Hash] change the changes to make to the documents
173
+ def update_all(change)
174
+ update change, [:multi]
175
+ end
176
+
177
+ # Update an existing document with +change+, otherwise create one.
178
+ #
179
+ # @example
180
+ # db[:people].find.entries # => { name: "John" }
181
+ # db[:people].find(name: "John").upsert(name: "James")
182
+ # db[:people].find.entries # => { name: "James" }
183
+ # db[:people].find(name: "John").upsert(name: "Mary")
184
+ # db[:people].find.entries # => [{ name: "James" }, { name: "Mary" }]
185
+ #
186
+ # @param [Hash] change the changes to make to the the document
187
+ def upsert(change)
188
+ update change, [:upsert]
189
+ end
190
+
191
+ # Remove a single document matching the query's selector.
192
+ #
193
+ # @example
194
+ # db[:people].find(name: "John").remove
195
+ def remove
196
+ delete = Protocol::Delete.new(
197
+ operation.database,
198
+ operation.collection,
199
+ operation.selector,
200
+ flags: [:remove_first]
201
+ )
202
+
203
+ session.with(consistency: :strong) do |session|
204
+ session.execute delete
205
+ end
206
+ end
207
+
208
+ # Remove multiple documents matching the query's selector.
209
+ #
210
+ # @example
211
+ # db[:people].find(name: "John").remove_all
212
+ def remove_all
213
+ delete = Protocol::Delete.new(
214
+ operation.database,
215
+ operation.collection,
216
+ operation.selector
217
+ )
218
+
219
+ session.with(consistency: :strong) do |session|
220
+ session.execute delete
221
+ end
222
+ end
223
+
224
+ private
225
+
226
+ def session
227
+ collection.database.session
228
+ end
229
+ end
230
+ end