mongo 1.9.1 → 1.9.2
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/VERSION +1 -1
- data/lib/mongo/collection.rb +23 -13
- data/lib/mongo/cursor.rb +23 -3
- data/lib/mongo/mongo_client.rb +5 -10
- data/lib/mongo/networking.rb +1 -1
- data/lib/mongo/util/node.rb +8 -1
- data/lib/mongo/util/uri_parser.rb +14 -7
- data/test/functional/collection_test.rb +30 -1
- data/test/functional/connection_test.rb +13 -1
- data/test/functional/cursor_test.rb +28 -0
- data/test/functional/uri_test.rb +12 -0
- metadata +5 -5
- metadata.gz.sig +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0373b5b1e6c7a4e81fa022f379ed2ee17cffde80
|
4
|
+
data.tar.gz: 681a86c1b120260262b2f77f6133c7a7497a0f34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e192fa80088c0151f75b40fbb0649b356c3ac781be003985d753b9fa2e6af08371bcda2061c2a1a3e088fcfdd4896987845edcdca9a5af59ce01ef604311c749
|
7
|
+
data.tar.gz: 67cff259cc85729e4c16ae706ae4cef3e3e5a747895deae699ea0cdace0eb416b5c522e3dc41fd8e92aab4b39b3cb4c30c8b3e5c6c99be4b25b965922aabf95f
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.9.
|
1
|
+
1.9.2
|
data/lib/mongo/collection.rb
CHANGED
@@ -616,25 +616,36 @@ module Mongo
|
|
616
616
|
|
617
617
|
# Atomically update and return a document using MongoDB's findAndModify command. (MongoDB > 1.3.0)
|
618
618
|
#
|
619
|
-
# @option opts [Hash] :query ({}) a query selector document for matching
|
620
|
-
#
|
621
|
-
# @option opts [
|
622
|
-
#
|
623
|
-
#
|
624
|
-
#
|
625
|
-
#
|
626
|
-
#
|
619
|
+
# @option opts [Hash] :query ({}) a query selector document for matching
|
620
|
+
# the desired document.
|
621
|
+
# @option opts [Hash] :update (nil) the update operation to perform on the
|
622
|
+
# matched document.
|
623
|
+
# @option opts [Array, String, OrderedHash] :sort ({}) specify a sort
|
624
|
+
# option for the query using any
|
625
|
+
# of the sort options available for Cursor#sort. Sort order is important
|
626
|
+
# if the query will be matching multiple documents since only the first
|
627
|
+
# matching document will be updated and returned.
|
628
|
+
# @option opts [Boolean] :remove (false) If true, removes the the returned
|
629
|
+
# document from the collection.
|
630
|
+
# @option opts [Boolean] :new (false) If true, returns the updated
|
631
|
+
# document; otherwise, returns the document prior to update.
|
632
|
+
# @option opts [Boolean] :full_response (false) If true, returns the entire
|
633
|
+
# response object from the server including 'ok' and 'lastErrorObject'.
|
627
634
|
#
|
628
635
|
# @return [Hash] the matched document.
|
629
636
|
#
|
630
637
|
# @core findandmodify find_and_modify-instance_method
|
631
638
|
def find_and_modify(opts={})
|
639
|
+
full_response = opts.delete(:full_response)
|
640
|
+
|
632
641
|
cmd = BSON::OrderedHash.new
|
633
642
|
cmd[:findandmodify] = @name
|
634
643
|
cmd.merge!(opts)
|
635
|
-
cmd[:sort] = Mongo::Support.format_order_clause(opts[:sort]) if opts[:sort]
|
636
644
|
|
637
|
-
|
645
|
+
cmd[:sort] =
|
646
|
+
Mongo::Support.format_order_clause(opts[:sort]) if opts[:sort]
|
647
|
+
|
648
|
+
full_response ? @db.command(cmd) : @db.command(cmd)['value']
|
638
649
|
end
|
639
650
|
|
640
651
|
# Perform an aggregation using the aggregation framework on the current collection.
|
@@ -1098,8 +1109,7 @@ module Mongo
|
|
1098
1109
|
def insert_batch(message, documents, write_concern, continue_on_error, errors, collection_name=@name)
|
1099
1110
|
begin
|
1100
1111
|
send_insert_message(message, documents, collection_name, write_concern)
|
1101
|
-
rescue
|
1102
|
-
NoMemoryError, SystemCallError => ex
|
1112
|
+
rescue OperationFailure => ex
|
1103
1113
|
raise ex unless continue_on_error
|
1104
1114
|
errors << ex
|
1105
1115
|
end
|
@@ -1159,7 +1169,7 @@ module Mongo
|
|
1159
1169
|
insert_batch(message, docs_to_insert, write_concern, continue_on_error, errors, collection_name)
|
1160
1170
|
# insert_batch collects errors if w > 0 and continue_on_error is true,
|
1161
1171
|
# so raise the error here, as this is the last or only msg sent
|
1162
|
-
raise errors.
|
1172
|
+
raise errors.last unless errors.empty?
|
1163
1173
|
end
|
1164
1174
|
|
1165
1175
|
collect_on_error ? [inserted_ids, error_docs] : inserted_ids
|
data/lib/mongo/cursor.rb
CHANGED
@@ -117,7 +117,7 @@ module Mongo
|
|
117
117
|
# @return [Hash, Nil] the next document or Nil if no documents remain.
|
118
118
|
def next
|
119
119
|
if @cache.length == 0
|
120
|
-
if @query_run &&
|
120
|
+
if @query_run && exhaust?
|
121
121
|
close
|
122
122
|
return nil
|
123
123
|
else
|
@@ -224,6 +224,11 @@ module Mongo
|
|
224
224
|
def limit(number_to_return=nil)
|
225
225
|
return @limit unless number_to_return
|
226
226
|
check_modifiable
|
227
|
+
|
228
|
+
if (number_to_return != 0) && exhaust?
|
229
|
+
raise MongoArgumentError, "Limit is incompatible with exhaust option."
|
230
|
+
end
|
231
|
+
|
227
232
|
@limit = number_to_return
|
228
233
|
self
|
229
234
|
end
|
@@ -381,6 +386,14 @@ module Mongo
|
|
381
386
|
def add_option(opt)
|
382
387
|
check_modifiable
|
383
388
|
|
389
|
+
if exhaust?(opt)
|
390
|
+
if @limit != 0
|
391
|
+
raise MongoArgumentError, "Exhaust is incompatible with limit."
|
392
|
+
elsif @connection.mongos?
|
393
|
+
raise MongoArgumentError, "Exhaust is incompatible with mongos."
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
384
397
|
@options |= opt
|
385
398
|
@options
|
386
399
|
end
|
@@ -447,7 +460,7 @@ module Mongo
|
|
447
460
|
# Return the number of documents remaining for this cursor.
|
448
461
|
def num_remaining
|
449
462
|
if @cache.length == 0
|
450
|
-
if @query_run &&
|
463
|
+
if @query_run && exhaust?
|
451
464
|
close
|
452
465
|
return 0
|
453
466
|
else
|
@@ -483,7 +496,7 @@ module Mongo
|
|
483
496
|
socket = @socket || checkout_socket_from_connection
|
484
497
|
results, @n_received, @cursor_id = @connection.receive_message(
|
485
498
|
Mongo::Constants::OP_QUERY, message, nil, socket, @command,
|
486
|
-
nil,
|
499
|
+
nil, exhaust?)
|
487
500
|
rescue ConnectionFailure => ex
|
488
501
|
socket.close if socket
|
489
502
|
@pool = nil
|
@@ -621,6 +634,13 @@ module Mongo
|
|
621
634
|
end
|
622
635
|
end
|
623
636
|
|
637
|
+
# Check whether the exhaust option is set
|
638
|
+
#
|
639
|
+
# @return [true, false] The state of the exhaust flag.
|
640
|
+
def exhaust?(opts = options)
|
641
|
+
!(opts & OP_QUERY_EXHAUST).zero?
|
642
|
+
end
|
643
|
+
|
624
644
|
def check_modifiable
|
625
645
|
if @query_run || @closed
|
626
646
|
raise InvalidOperation, "Cannot modify the query once it has been run or closed."
|
data/lib/mongo/mongo_client.rb
CHANGED
@@ -30,7 +30,7 @@ module Mongo
|
|
30
30
|
DEFAULT_HOST = 'localhost'
|
31
31
|
DEFAULT_PORT = 27017
|
32
32
|
DEFAULT_DB_NAME = 'test'
|
33
|
-
GENERIC_OPTS = [:auths, :logger, :connect]
|
33
|
+
GENERIC_OPTS = [:auths, :logger, :connect, :default_db]
|
34
34
|
TIMEOUT_OPTS = [:timeout, :op_timeout, :connect_timeout]
|
35
35
|
SSL_OPTS = [:ssl, :ssl_key, :ssl_cert, :ssl_verify, :ssl_ca_cert]
|
36
36
|
POOL_OPTS = [:pool_size, :pool_timeout]
|
@@ -199,7 +199,7 @@ module Mongo
|
|
199
199
|
#
|
200
200
|
# @return [Mongo::MongoClient, Mongo::MongoReplicaSetClient]
|
201
201
|
def self.from_uri(uri = ENV['MONGODB_URI'], extra_opts={})
|
202
|
-
parser = URIParser.new
|
202
|
+
parser = URIParser.new(uri)
|
203
203
|
parser.connection(extra_opts)
|
204
204
|
end
|
205
205
|
|
@@ -210,7 +210,7 @@ module Mongo
|
|
210
210
|
raise MongoArgumentError,
|
211
211
|
"ENV['MONGODB_URI'] implies a replica set."
|
212
212
|
end
|
213
|
-
opts.merge!
|
213
|
+
opts.merge!(parser.connection_options)
|
214
214
|
[parser.host, parser.port]
|
215
215
|
else
|
216
216
|
[host || DEFAULT_HOST, port || DEFAULT_PORT]
|
@@ -358,13 +358,7 @@ module Mongo
|
|
358
358
|
# @return [Mongo::DB]
|
359
359
|
#
|
360
360
|
# @core databases db-instance_method
|
361
|
-
def db(db_name=
|
362
|
-
if !db_name && uri = ENV['MONGODB_URI']
|
363
|
-
db_name = uri[%r{/([^/\?]+)(\?|$)}, 1]
|
364
|
-
end
|
365
|
-
|
366
|
-
db_name ||= DEFAULT_DB_NAME
|
367
|
-
|
361
|
+
def db(db_name = @default_db, opts = {})
|
368
362
|
DB.new(db_name, self, opts)
|
369
363
|
end
|
370
364
|
|
@@ -689,6 +683,7 @@ module Mongo
|
|
689
683
|
end
|
690
684
|
Mongo::ReadPreference::validate(@read)
|
691
685
|
|
686
|
+
@default_db = opts.delete(:default_db) || DEFAULT_DB_NAME
|
692
687
|
@tag_sets = opts.delete(:tag_sets) || []
|
693
688
|
@acceptable_latency = opts.delete(:secondary_acceptable_latency_ms) || 15
|
694
689
|
|
data/lib/mongo/networking.rb
CHANGED
@@ -208,7 +208,7 @@ module Mongo
|
|
208
208
|
raise Mongo::OperationFailure, "Query response returned CURSOR_NOT_FOUND. " +
|
209
209
|
"Either an invalid cursor was specified, or the cursor may have timed out on the server."
|
210
210
|
elsif flags & Mongo::Constants::REPLY_QUERY_FAILURE != 0
|
211
|
-
#
|
211
|
+
# Mongo query reply failures are handled in Cursor#next.
|
212
212
|
end
|
213
213
|
end
|
214
214
|
|
data/lib/mongo/util/node.rb
CHANGED
@@ -105,7 +105,14 @@ module Mongo
|
|
105
105
|
@last_state = @config['ismaster'] ? :primary : :other
|
106
106
|
end
|
107
107
|
|
108
|
-
|
108
|
+
if @client.connect_timeout
|
109
|
+
Timeout::timeout(@client.connect_timeout, OperationTimeout) do
|
110
|
+
@config = @client['admin'].command({:ismaster => 1}, :socket => @socket)
|
111
|
+
end
|
112
|
+
else
|
113
|
+
@config = @client['admin'].command({:ismaster => 1}, :socket => @socket)
|
114
|
+
end
|
115
|
+
|
109
116
|
update_max_sizes
|
110
117
|
|
111
118
|
if @config['msg']
|
@@ -13,11 +13,12 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
require 'cgi'
|
16
|
+
require 'uri'
|
16
17
|
|
17
18
|
module Mongo
|
18
19
|
class URIParser
|
19
20
|
|
20
|
-
USER_REGEX = /(
|
21
|
+
USER_REGEX = /(.+)/
|
21
22
|
PASS_REGEX = /([^@,]+)/
|
22
23
|
AUTH_REGEX = /(#{USER_REGEX}:#{PASS_REGEX}@)?/
|
23
24
|
|
@@ -108,7 +109,7 @@ module Mongo
|
|
108
109
|
:w => lambda { |arg| Mongo::Support.is_i?(arg) ? arg.to_i : arg.to_sym },
|
109
110
|
:wtimeout => lambda { |arg| arg.to_i },
|
110
111
|
:wtimeoutms => lambda { |arg| arg.to_i }
|
111
|
-
|
112
|
+
}
|
112
113
|
|
113
114
|
attr_reader :auths,
|
114
115
|
:connect,
|
@@ -253,6 +254,7 @@ module Mongo
|
|
253
254
|
opts[:name] = replicaset
|
254
255
|
end
|
255
256
|
|
257
|
+
opts[:default_db] = @db
|
256
258
|
opts[:connect] = connect?
|
257
259
|
|
258
260
|
opts
|
@@ -277,7 +279,7 @@ module Mongo
|
|
277
279
|
uname = matches[2]
|
278
280
|
pwd = matches[3]
|
279
281
|
hosturis = matches[4].split(',')
|
280
|
-
db
|
282
|
+
@db = matches[8]
|
281
283
|
|
282
284
|
hosturis.each do |hosturi|
|
283
285
|
# If port is present, use it, otherwise use default port
|
@@ -295,11 +297,16 @@ module Mongo
|
|
295
297
|
raise MongoArgumentError, "No nodes specified. Please ensure that you've provided at least one node."
|
296
298
|
end
|
297
299
|
|
298
|
-
if uname && pwd && db
|
299
|
-
auths << {
|
300
|
+
if uname && pwd && @db
|
301
|
+
auths << {
|
302
|
+
:db_name => @db,
|
303
|
+
:username => URI.unescape(uname),
|
304
|
+
:password => URI.unescape(pwd)
|
305
|
+
}
|
300
306
|
elsif uname || pwd
|
301
|
-
raise MongoArgumentError,
|
302
|
-
|
307
|
+
raise MongoArgumentError, 'MongoDB URI must include username, ' +
|
308
|
+
'password, and db if username and ' +
|
309
|
+
'password are specified.'
|
303
310
|
end
|
304
311
|
end
|
305
312
|
|
@@ -292,6 +292,18 @@ class TestCollection < Test::Unit::TestCase
|
|
292
292
|
return conn.db(MONGO_TEST_DB)["test"]
|
293
293
|
end
|
294
294
|
|
295
|
+
def test_non_operation_failure_halts_insertion_with_continue_on_error
|
296
|
+
coll = limited_collection
|
297
|
+
coll.stubs(:send_insert_message).raises(OperationTimeout).times(1)
|
298
|
+
docs = []
|
299
|
+
10.times do
|
300
|
+
docs << {'foo' => 'a' * 950}
|
301
|
+
end
|
302
|
+
assert_raise OperationTimeout do
|
303
|
+
coll.insert(docs, :continue_on_error => true)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
295
307
|
def test_chunking_batch_insert
|
296
308
|
docs = []
|
297
309
|
10.times do
|
@@ -955,7 +967,9 @@ class TestCollection < Test::Unit::TestCase
|
|
955
967
|
@@test << { :a => 2, :processed => false }
|
956
968
|
@@test << { :a => 3, :processed => false }
|
957
969
|
|
958
|
-
@@test.find_and_modify(:query => {},
|
970
|
+
@@test.find_and_modify(:query => {},
|
971
|
+
:sort => [['a', -1]],
|
972
|
+
:update => {"$set" => {:processed => true}})
|
959
973
|
|
960
974
|
assert @@test.find_one({:a => 3})['processed']
|
961
975
|
end
|
@@ -969,6 +983,21 @@ class TestCollection < Test::Unit::TestCase
|
|
969
983
|
@@test.find_and_modify(:blimey => {})
|
970
984
|
end
|
971
985
|
end
|
986
|
+
|
987
|
+
def test_find_and_modify_with_full_response
|
988
|
+
@@test << { :a => 1, :processed => false }
|
989
|
+
@@test << { :a => 2, :processed => false }
|
990
|
+
@@test << { :a => 3, :processed => false }
|
991
|
+
|
992
|
+
doc = @@test.find_and_modify(:query => {},
|
993
|
+
:sort => [['a', -1]],
|
994
|
+
:update => {"$set" => {:processed => true}},
|
995
|
+
:full_response => true,
|
996
|
+
:new => true)
|
997
|
+
|
998
|
+
assert doc['value']['processed']
|
999
|
+
assert ['ok', 'value', 'lastErrorObject'].all? { |key| doc.key?(key) }
|
1000
|
+
end
|
972
1001
|
end
|
973
1002
|
|
974
1003
|
if @@version >= "1.3.5"
|
@@ -122,12 +122,24 @@ class TestConnection < Test::Unit::TestCase
|
|
122
122
|
ENV['MONGODB_URI'] = "mongodb://#{host_port}/"
|
123
123
|
con = MongoClient.from_uri
|
124
124
|
db = con.db
|
125
|
-
assert_equal db.name,
|
125
|
+
assert_equal db.name, MongoClient::DEFAULT_DB_NAME
|
126
126
|
ensure
|
127
127
|
ENV['MONGODB_URI'] = old_mongodb_uri
|
128
128
|
end
|
129
129
|
end
|
130
130
|
|
131
|
+
def test_db_from_uri_from_string_param
|
132
|
+
db_name = "_database"
|
133
|
+
db = MongoClient.from_uri("mongodb://#{host_port}/#{db_name}").db
|
134
|
+
assert_equal db.name, db_name
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_db_from_uri_from_string_param_no_db_name
|
138
|
+
db = MongoClient.from_uri("mongodb://#{host_port}").db
|
139
|
+
assert_equal db.name, MongoClient::DEFAULT_DB_NAME
|
140
|
+
end
|
141
|
+
|
142
|
+
|
131
143
|
def test_server_version
|
132
144
|
assert_match(/\d\.\d+(\.\d+)?/, @client.server_version.to_s)
|
133
145
|
end
|
@@ -91,6 +91,34 @@ class CursorTest < Test::Unit::TestCase
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
+
def test_exhaust_after_limit_error
|
95
|
+
c = Cursor.new(@@coll, :limit => 17)
|
96
|
+
assert_raise MongoArgumentError do
|
97
|
+
c.add_option(OP_QUERY_EXHAUST)
|
98
|
+
end
|
99
|
+
|
100
|
+
assert_raise MongoArgumentError do
|
101
|
+
c.add_option(OP_QUERY_EXHAUST + OP_QUERY_SLAVE_OK)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_limit_after_exhaust_error
|
106
|
+
c = Cursor.new(@@coll)
|
107
|
+
c.add_option(OP_QUERY_EXHAUST)
|
108
|
+
assert_raise MongoArgumentError do
|
109
|
+
c.limit(17)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_exhaust_with_mongos
|
114
|
+
@@connection.expects(:mongos?).returns(:true)
|
115
|
+
c = Cursor.new(@@coll)
|
116
|
+
|
117
|
+
assert_raise MongoArgumentError do
|
118
|
+
c.add_option(OP_QUERY_EXHAUST)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
94
122
|
def test_inspect
|
95
123
|
selector = {:a => 1}
|
96
124
|
cursor = @@coll.find(selector)
|
data/test/functional/uri_test.rb
CHANGED
@@ -53,6 +53,18 @@ class URITest < Test::Unit::TestCase
|
|
53
53
|
assert_equal "b:ob", parser.auths[0][:username]
|
54
54
|
end
|
55
55
|
|
56
|
+
def test_username_with_encoded_symbol
|
57
|
+
parser = Mongo::URIParser.new('mongodb://f%40o:bar@localhost/admin')
|
58
|
+
username = parser.auths.first[:username]
|
59
|
+
assert_equal 'f@o', username
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_password_with_encoded_symbol
|
63
|
+
parser = Mongo::URIParser.new('mongodb://foo:b%40r@localhost/admin')
|
64
|
+
password = parser.auths.first[:password]
|
65
|
+
assert_equal 'b@r', password
|
66
|
+
end
|
67
|
+
|
56
68
|
def test_passwords_contain_no_commas
|
57
69
|
assert_raise MongoArgumentError do
|
58
70
|
Mongo::URIParser.new('mongodb://bob:a,b@a.example.com:27018/test')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.9.
|
4
|
+
version: 1.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tyler Brock
|
@@ -33,7 +33,7 @@ cert_chain:
|
|
33
33
|
8v7zLF2XliYbfurYIwkcXs8yPn8ggApBIy9bX6VJxRs/l2+UvqzaHIFaFy/F8/GP
|
34
34
|
RNTuXsVG5NDACo7Q
|
35
35
|
-----END CERTIFICATE-----
|
36
|
-
date: 2013-
|
36
|
+
date: 2013-08-21 00:00:00.000000000 Z
|
37
37
|
dependencies:
|
38
38
|
- !ruby/object:Gem::Dependency
|
39
39
|
name: bson
|
@@ -41,14 +41,14 @@ dependencies:
|
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
version: 1.9.
|
44
|
+
version: 1.9.2
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
47
|
version_requirements: !ruby/object:Gem::Requirement
|
48
48
|
requirements:
|
49
49
|
- - ~>
|
50
50
|
- !ruby/object:Gem::Version
|
51
|
-
version: 1.9.
|
51
|
+
version: 1.9.2
|
52
52
|
description: A Ruby driver for MongoDB. For more information about Mongo, see http://www.mongodb.org.
|
53
53
|
email: mongodb-dev@googlegroups.com
|
54
54
|
executables:
|
@@ -171,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
171
|
version: '0'
|
172
172
|
requirements: []
|
173
173
|
rubyforge_project: mongo
|
174
|
-
rubygems_version: 2.0.
|
174
|
+
rubygems_version: 2.0.7
|
175
175
|
signing_key:
|
176
176
|
specification_version: 4
|
177
177
|
summary: Ruby driver for MongoDB
|
metadata.gz.sig
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
|
1
|
+
����d��
|
2
|
+
;5����ʼn_�]d�e��41E�.HR�g��v�鸁\
|