2008-12-02 01:20:00 +00:00
|
|
|
= Introduction
|
|
|
|
|
2009-11-24 20:20:51 +00:00
|
|
|
This is the 10gen-supported Ruby driver for MongoDB[http://www.mongodb.org].
|
2009-01-08 16:48:59 +00:00
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
Here is a quick code sample. See the MongoDB Ruby Tutorial
|
|
|
|
(http://www.mongodb.org/display/DOCS/Ruby+Tutorial) for much more.
|
2009-01-08 16:48:59 +00:00
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
require 'rubygems'
|
2009-01-08 16:48:59 +00:00
|
|
|
require 'mongo'
|
2009-08-20 14:50:48 +00:00
|
|
|
include Mongo
|
2009-01-08 16:48:59 +00:00
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
@db = Connection.new.db('sample-db')
|
|
|
|
@coll = db.collection('test')
|
2008-12-02 01:21:22 +00:00
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
@coll.remove
|
|
|
|
3.times do |i|
|
|
|
|
@coll.insert({'a' => i+1})
|
|
|
|
end
|
|
|
|
puts "There are #{@coll.count()} records. Here they are:"
|
|
|
|
@coll.find().each { |doc| puts doc.inspect }
|
2009-01-29 16:23:50 +00:00
|
|
|
|
2009-01-08 16:42:52 +00:00
|
|
|
= Installation
|
|
|
|
|
2009-10-05 14:43:57 +00:00
|
|
|
The driver's gems are hosted on Gemcutter[http://gemcutter.org]. If you haven't
|
2009-11-24 22:39:44 +00:00
|
|
|
installed a gem from Gemcutter before, you'll need to set up Gemcutter first:
|
2009-01-08 16:42:52 +00:00
|
|
|
|
2009-10-05 14:43:57 +00:00
|
|
|
$ gem install gemcutter
|
|
|
|
$ gem tumble
|
2009-01-08 16:42:52 +00:00
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
Once you've installed Gemcutter, install the mongo gem as follows:
|
2009-10-05 14:43:57 +00:00
|
|
|
|
|
|
|
$ gem install mongo
|
2009-01-23 13:53:48 +00:00
|
|
|
|
2009-11-16 21:45:37 +00:00
|
|
|
For a significant performance boost, you should also install the driver's C
|
|
|
|
extensions:
|
2009-11-16 21:10:12 +00:00
|
|
|
|
|
|
|
$ gem install mongo_ext
|
|
|
|
|
2009-01-23 13:53:48 +00:00
|
|
|
=== From the GitHub source
|
|
|
|
|
2009-01-15 21:05:56 +00:00
|
|
|
The source code is available at http://github.com/mongodb/mongo-ruby-driver.
|
|
|
|
You can either clone the git repository or download a tarball or zip file.
|
|
|
|
Once you have the source, you can use it from wherever you downloaded it or
|
|
|
|
you can install it as a gem from the source by typing
|
2009-01-08 16:42:52 +00:00
|
|
|
|
|
|
|
$ rake gem:install
|
|
|
|
|
2009-11-16 21:45:37 +00:00
|
|
|
To install the C extensions from source, type this instead:
|
2009-03-19 18:56:54 +00:00
|
|
|
|
|
|
|
$ rake gem:install_extensions
|
|
|
|
|
|
|
|
That's all there is to it!
|
2009-01-08 16:42:52 +00:00
|
|
|
|
2009-02-03 17:16:33 +00:00
|
|
|
= Examples
|
2008-12-02 01:20:00 +00:00
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
For extensive examples, see the MongoDB Ruby Tutorial
|
|
|
|
(http://www.mongodb.org/display/DOCS/Ruby+Tutorial).
|
|
|
|
|
|
|
|
Bundled with the dirver are many examples in the "examples" subdirectory. Samples include using
|
|
|
|
the driver and using the GridFS class GridStore. MongoDB must be running for
|
2009-02-03 17:16:33 +00:00
|
|
|
these examples to work, of course.
|
2009-01-07 21:07:22 +00:00
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
Here's how to start MongoDB and run the "simple.rb" example:
|
2009-02-03 17:16:33 +00:00
|
|
|
|
|
|
|
$ cd path/to/mongo
|
|
|
|
$ ./mongod run
|
|
|
|
... then in another window ...
|
|
|
|
$ cd path/to/mongo-ruby-driver
|
2008-12-17 16:43:08 +00:00
|
|
|
$ ruby examples/simple.rb
|
2008-12-02 01:20:00 +00:00
|
|
|
|
2009-08-18 14:07:01 +00:00
|
|
|
See also the test code, especially test/test_db_api.rb.
|
2008-12-02 01:20:00 +00:00
|
|
|
|
2009-01-29 16:23:50 +00:00
|
|
|
= GridStore
|
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
The GridStore class is a Ruby implementation of MongoDB's GridFS file storage
|
|
|
|
system. An instance of GridStore is like an IO object. See the RDocs for
|
2009-02-11 19:33:19 +00:00
|
|
|
details, and see examples/gridfs.rb for code that uses many of the GridStore
|
2009-11-24 22:39:44 +00:00
|
|
|
features (metadata, content type, rewind/seek/tell, etc).
|
2009-01-29 16:23:50 +00:00
|
|
|
|
2009-01-29 16:31:45 +00:00
|
|
|
Note that the GridStore class is not automatically required when you require
|
2009-11-24 22:39:44 +00:00
|
|
|
'mongo'. You also need to require 'mongo/gridfs'
|
2009-01-29 16:31:45 +00:00
|
|
|
|
|
|
|
Example code:
|
|
|
|
|
|
|
|
GridStore.open(database, 'filename', 'w') { |f|
|
|
|
|
f.puts "Hello, world!"
|
|
|
|
}
|
|
|
|
GridStore.open(database, 'filename, 'r') { |f|
|
|
|
|
puts f.read # => Hello, world!\n
|
|
|
|
}
|
|
|
|
GridStore.open(database, 'filename', 'w+') { |f|
|
|
|
|
f.puts "But wait, there's more!"
|
|
|
|
}
|
|
|
|
GridStore.open(database, 'filename, 'r') { |f|
|
|
|
|
puts f.read # => Hello, world!\nBut wait, there's more!\n
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-01-13 17:53:55 +00:00
|
|
|
= Notes
|
|
|
|
|
2009-10-09 04:33:35 +00:00
|
|
|
== Thread Safety
|
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
The driver is thread safe.
|
|
|
|
|
|
|
|
== Connection Pooling
|
|
|
|
|
|
|
|
As of 0.18, the driver implements connection pooling. By default, only one
|
|
|
|
socket connection will be opened to MongoDB. However, if you're running a
|
|
|
|
multi-threaded application, you can specify a maximum pool size and a maximum
|
|
|
|
timeout for waiting for old connections to be released to the pool.
|
|
|
|
|
|
|
|
To set up a pooled connection to a single MongoDB instance:
|
|
|
|
|
|
|
|
@conn = Connection.new("localhost", 27017, :pool_size => 5, :timeout => 5)
|
|
|
|
|
|
|
|
A pooled connection to a paired instance would look like this:
|
|
|
|
|
|
|
|
@conn = Connection.new({:left => ["db1.example.com", 27017],
|
|
|
|
:right => ["db2.example.com", 27017]}, nil,
|
|
|
|
:pool_size => 20, :timeout => 5)
|
|
|
|
|
|
|
|
Though the pooling architecure will undoubtedly evolve, it owes much credit
|
|
|
|
to the connection pooling implementations in ActiveRecord and PyMongo.
|
2009-10-09 04:33:35 +00:00
|
|
|
|
2009-09-14 19:22:41 +00:00
|
|
|
== Using with Phusion Passenger
|
|
|
|
|
|
|
|
When passenger is in smart spawning mode you need to be sure that child
|
|
|
|
processes forked by passenger will create a new connection to the database.
|
|
|
|
activerecord-mongo-adapter handles this for you, so if you are using that
|
|
|
|
you shouldn't need to worry about it. Otherwise you'll either need to use
|
|
|
|
conservative spawning[http://www.modrails.org/documentation/Users%20guide.html#RailsSpawnMethod]
|
|
|
|
or handle reconnecting when passenger forks a new process:
|
|
|
|
|
|
|
|
if defined?(PhusionPassenger)
|
|
|
|
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
|
|
|
if forked
|
|
|
|
# Call db.connect_to_master to reconnect here
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
The above code should be put in _environment.rb_ or an initialization
|
|
|
|
script.
|
|
|
|
|
|
|
|
See this thread[http://groups.google.com/group/mongodb-user/browse_thread/thread/f31e2d23de38136a]
|
|
|
|
for more details on this issue.
|
|
|
|
|
2009-01-13 17:53:55 +00:00
|
|
|
== String Encoding
|
|
|
|
|
|
|
|
The BSON ("Binary JSON") format used to communicate with Mongo requires that
|
|
|
|
strings be UTF-8 (http://en.wikipedia.org/wiki/UTF-8).
|
|
|
|
|
|
|
|
Ruby 1.9 has built-in character encoding support. All strings sent to Mongo
|
|
|
|
and received from Mongo are converted to UTF-8 when necessary, and strings
|
|
|
|
read from Mongo will have their character encodings set to UTF-8.
|
|
|
|
|
|
|
|
When used with Ruby 1.8, the bytes in each string are written to and read from
|
|
|
|
Mongo as-is. If the string is ASCII all is well, because ASCII is a subset of
|
2009-01-16 19:41:53 +00:00
|
|
|
UTF-8. If the string is not ASCII then it may not be a well-formed UTF-8
|
|
|
|
string.
|
|
|
|
|
2009-02-01 14:14:21 +00:00
|
|
|
== Primary Keys
|
|
|
|
|
|
|
|
The field _id is a primary key. It is treated specially by the database, and
|
2009-07-13 16:18:05 +00:00
|
|
|
its use makes many operations more efficient. The value of an _id may be of
|
|
|
|
any type. The database itself inserts an _id value if none is specified when
|
|
|
|
a record is inserted.
|
2009-02-01 15:25:50 +00:00
|
|
|
|
2009-01-16 19:41:53 +00:00
|
|
|
=== Primary Key Factories
|
|
|
|
|
2009-02-01 14:14:21 +00:00
|
|
|
A primary key factory is a class you supply to a DB object that knows how to
|
2009-07-13 16:18:05 +00:00
|
|
|
generate _id values. If you want to control _id values or even their types,
|
|
|
|
using a PK factory lets you do so.
|
2009-02-01 14:14:21 +00:00
|
|
|
|
|
|
|
You can tell the Ruby Mongo driver how to create primary keys by passing in
|
2009-08-20 22:48:09 +00:00
|
|
|
the :pk option to the Connection#db method.
|
2009-01-16 19:41:53 +00:00
|
|
|
|
2009-08-20 14:50:48 +00:00
|
|
|
include Mongo
|
2009-08-20 22:48:09 +00:00
|
|
|
db = Connection.new.db('dbname', :pk => MyPKFactory.new)
|
2009-01-16 19:41:53 +00:00
|
|
|
|
|
|
|
A primary key factory object must respond to :create_pk, which should take a
|
|
|
|
hash and return a hash which merges the original hash with any primary key
|
|
|
|
fields the factory wishes to inject. NOTE: if the object already has a primary
|
|
|
|
key, the factory should not inject a new key; this means that the object is
|
2009-07-13 16:18:05 +00:00
|
|
|
being used in a repsert but it already exists. The idea here is that whenever
|
2009-01-16 19:41:53 +00:00
|
|
|
a record is inserted, the :pk object's +create_pk+ method will be called and
|
|
|
|
the new hash returned will be inserted.
|
|
|
|
|
|
|
|
Here is a sample primary key factory, taken from the tests:
|
|
|
|
|
|
|
|
class TestPKFactory
|
|
|
|
def create_pk(row)
|
2009-08-20 14:50:48 +00:00
|
|
|
row['_id'] ||= Mongo::ObjectID.new
|
2009-01-16 19:41:53 +00:00
|
|
|
row
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-01-29 16:31:45 +00:00
|
|
|
Here's a slightly more sophisticated one that handles both symbol and string
|
|
|
|
keys. This is the PKFactory that comes with the MongoRecord code (an
|
|
|
|
ActiveRecord-like framework for non-Rails apps) and the AR Mongo adapter code
|
|
|
|
(for Rails):
|
|
|
|
|
|
|
|
class PKFactory
|
|
|
|
def create_pk(row)
|
|
|
|
return row if row[:_id]
|
|
|
|
row.delete(:_id) # in case it exists but the value is nil
|
2009-08-20 14:50:48 +00:00
|
|
|
row['_id'] ||= Mongo::ObjectID.new
|
2009-01-29 16:31:45 +00:00
|
|
|
row
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-02-01 14:14:21 +00:00
|
|
|
A database's PK factory object may be set either when a DB object is created
|
|
|
|
or immediately after you obtain it, but only once. The only reason it is
|
|
|
|
changeable at all is so that libraries such as MongoRecord that use this
|
|
|
|
driver can set the PK factory after obtaining the database but before using it
|
|
|
|
for the first time.
|
|
|
|
|
|
|
|
== The DB Class
|
|
|
|
|
|
|
|
=== Primary Key factories
|
|
|
|
|
|
|
|
See the section on "Primary Keys" above.
|
2009-01-16 19:41:53 +00:00
|
|
|
|
|
|
|
=== Strict mode
|
|
|
|
|
|
|
|
Each database has an optional strict mode. If strict mode is on, then asking
|
|
|
|
for a collection that does not exist will raise an error, as will asking to
|
|
|
|
create a collection that already exists. Note that both these operations are
|
|
|
|
completely harmless; strict mode is a programmer convenience only.
|
|
|
|
|
|
|
|
To turn on strict mode, either pass in :strict => true when obtaining a DB
|
|
|
|
object or call the :strict= method:
|
|
|
|
|
2009-08-20 22:48:09 +00:00
|
|
|
db = Connection.new.db('dbname', :strict => true)
|
2009-01-16 19:41:53 +00:00
|
|
|
# I'm feeling lax
|
|
|
|
db.strict = false
|
|
|
|
# No, I'm not!
|
|
|
|
db.strict = true
|
|
|
|
|
|
|
|
The method DB#strict? returns the current value of that flag.
|
|
|
|
|
2009-02-03 17:43:22 +00:00
|
|
|
== Cursors
|
|
|
|
|
|
|
|
Random cursor fun facts:
|
|
|
|
|
|
|
|
- Cursors are enumerable.
|
|
|
|
|
|
|
|
- The query doesn't get run until you actually attempt to retrieve data from a
|
|
|
|
cursor.
|
|
|
|
|
|
|
|
- Cursors have a to_a method.
|
|
|
|
|
|
|
|
|
2009-01-13 17:53:55 +00:00
|
|
|
|
2008-12-02 01:20:00 +00:00
|
|
|
= Testing
|
|
|
|
|
2009-01-08 16:42:52 +00:00
|
|
|
If you have the source code, you can run the tests.
|
|
|
|
|
2008-12-02 01:20:00 +00:00
|
|
|
$ rake test
|
|
|
|
|
2009-11-23 18:13:14 +00:00
|
|
|
This will run both unit and functional tests. If you want to run these
|
|
|
|
individually:
|
|
|
|
|
|
|
|
$ rake test:unit
|
|
|
|
$ rake test:functional
|
|
|
|
|
|
|
|
|
|
|
|
If you want to test replica pairs, you can run the following tests
|
|
|
|
individually:
|
|
|
|
|
|
|
|
$ rake test:pair_count
|
|
|
|
$ rake test:pair_insert
|
|
|
|
$ rake test:pair_query
|
|
|
|
|
2009-11-24 20:20:51 +00:00
|
|
|
It's also possible to test replica pairs with connection pooling:
|
|
|
|
|
|
|
|
$ rake test:pooled_pair_insert
|
|
|
|
|
|
|
|
|
2009-11-23 18:13:14 +00:00
|
|
|
All tests now require shoulda and mocha. You can install these gems as
|
2009-10-20 15:31:07 +00:00
|
|
|
follows:
|
|
|
|
|
|
|
|
$ gem install shoulda
|
|
|
|
$ gem install mocha
|
|
|
|
|
2009-01-29 15:42:20 +00:00
|
|
|
The tests assume that the Mongo database is running on the default port. You
|
2009-08-20 22:48:09 +00:00
|
|
|
can override the default host (localhost) and port (Connection::DEFAULT_PORT) by
|
2009-01-29 15:42:20 +00:00
|
|
|
using the environment variables MONGO_RUBY_DRIVER_HOST and
|
|
|
|
MONGO_RUBY_DRIVER_PORT.
|
2008-12-02 01:20:00 +00:00
|
|
|
|
2009-01-12 14:48:24 +00:00
|
|
|
The project mongo-qa (http://github.com/mongodb/mongo-qa) contains many more
|
|
|
|
Mongo driver tests that are language independent. To run thoses tests as part
|
2009-01-28 19:15:21 +00:00
|
|
|
of the "rake test" task, download the code "next to" this directory. So, after
|
|
|
|
installing the mongo-qa code you would have these two directories next to each
|
|
|
|
other:
|
2009-01-12 14:48:24 +00:00
|
|
|
|
2009-01-28 19:15:21 +00:00
|
|
|
$ ls
|
|
|
|
mongo-qa
|
|
|
|
mongo-ruby-driver
|
2009-01-12 14:48:24 +00:00
|
|
|
$ rake test
|
|
|
|
|
2009-01-28 19:15:21 +00:00
|
|
|
The tests run just fine if the mongo-qa directory is not there.
|
2009-01-12 14:48:24 +00:00
|
|
|
|
2009-01-12 16:23:07 +00:00
|
|
|
Additionally, the script bin/validate is used by the mongo-qa project's
|
|
|
|
validator script.
|
|
|
|
|
2008-12-02 01:21:22 +00:00
|
|
|
|
2008-12-04 22:02:19 +00:00
|
|
|
= Documentation
|
|
|
|
|
2009-09-03 15:30:43 +00:00
|
|
|
This documentation is available online at http://api.mongodb.org/ruby. You can
|
2009-01-08 16:42:52 +00:00
|
|
|
generate the documentation if you have the source by typing
|
|
|
|
|
2008-12-04 22:02:19 +00:00
|
|
|
$ rake rdoc
|
|
|
|
|
2009-01-08 16:42:52 +00:00
|
|
|
Then open the file html/index.html.
|
2008-12-02 15:45:02 +00:00
|
|
|
|
|
|
|
|
2009-01-07 21:07:22 +00:00
|
|
|
= Release Notes
|
2008-12-02 15:45:02 +00:00
|
|
|
|
2009-11-24 22:39:44 +00:00
|
|
|
See HISTORY.
|
2008-12-02 15:45:02 +00:00
|
|
|
|
2008-12-05 21:39:00 +00:00
|
|
|
= Credits
|
|
|
|
|
|
|
|
Adrian Madrid, aemadrid@gmail.com
|
2009-01-06 15:49:18 +00:00
|
|
|
* bin/mongo_console
|
2008-12-05 21:39:00 +00:00
|
|
|
* examples/benchmarks.rb
|
|
|
|
* examples/irb.rb
|
2008-12-08 20:08:14 +00:00
|
|
|
* Modifications to examples/simple.rb
|
|
|
|
* Found plenty of bugs and missing features.
|
2009-01-06 15:49:18 +00:00
|
|
|
* Ruby 1.9 support.
|
|
|
|
* Gem support.
|
2008-12-08 20:08:14 +00:00
|
|
|
* Many other code suggestions and improvements.
|
2008-12-05 21:39:00 +00:00
|
|
|
|
2009-05-18 14:02:10 +00:00
|
|
|
Aman Gupta, aman@tmm1.net
|
|
|
|
* Collection#save
|
2008-12-05 21:39:00 +00:00
|
|
|
|
2009-06-01 13:21:59 +00:00
|
|
|
Jon Crosby, jon@joncrosby.me
|
|
|
|
* Some code clean-up
|
|
|
|
|
2009-06-02 18:49:49 +00:00
|
|
|
John Nunemaker, http://railstips.org
|
|
|
|
* Collection#create_index takes symbols as well as strings
|
|
|
|
* Fix for Collection#save
|
2009-10-08 13:15:15 +00:00
|
|
|
* Add logger convenience methods to connection and database
|
2009-06-02 18:49:49 +00:00
|
|
|
|
2009-07-16 20:32:57 +00:00
|
|
|
David James, djames@sunlightfoundation.com
|
|
|
|
* Fix dates to return as UTC
|
|
|
|
|
2009-07-28 13:56:45 +00:00
|
|
|
Paul Dlug, paul.dlug@gmail.com
|
|
|
|
* Generate _id on the client side if not provided
|
|
|
|
* Collection#insert and Collection#save return _id
|
|
|
|
|
2009-10-08 14:02:54 +00:00
|
|
|
Durran Jordan, durran@gmail.com
|
2009-08-27 20:29:41 +00:00
|
|
|
* DB#collections
|
2009-10-08 14:02:54 +00:00
|
|
|
* Support for specifying sort order as array of [key, direction] pairs
|
2009-08-27 20:29:41 +00:00
|
|
|
|
2009-09-11 16:13:41 +00:00
|
|
|
Cyril Mougel, cyril.mougel@gmail.com
|
|
|
|
* Initial logging support
|
2009-10-26 18:58:39 +00:00
|
|
|
* Test case
|
2009-09-11 16:13:41 +00:00
|
|
|
|
2009-09-16 20:19:35 +00:00
|
|
|
Jack Chen, chendo on github
|
|
|
|
* Test case + fix for deserializing pre-epoch Time instances
|
|
|
|
|
2009-09-17 18:54:30 +00:00
|
|
|
Michael Bernstein, mrb on github
|
|
|
|
* #sort method for Cursor instances
|
|
|
|
|
2009-09-30 14:49:08 +00:00
|
|
|
Paulo Ahahgon, pahagon on github
|
|
|
|
* removed hard limit
|
|
|
|
|
2009-10-05 14:01:56 +00:00
|
|
|
Les Hill, leshill on github
|
|
|
|
* OrderedHash#each returns self
|
|
|
|
|
2009-10-19 15:05:42 +00:00
|
|
|
Sean Cribbs, seancribbs on github
|
|
|
|
* Modify standard_benchmark to allow profiling
|
2009-10-05 14:01:56 +00:00
|
|
|
|
2009-11-11 15:47:42 +00:00
|
|
|
Sunny Hirai
|
|
|
|
* Suggested hashcode fix for Mongo::ObjectID
|
2009-11-17 18:20:57 +00:00
|
|
|
* Noted index ordering bug.
|
2009-11-11 15:47:42 +00:00
|
|
|
|
2009-11-25 16:25:50 +00:00
|
|
|
Christos Trochalakis
|
|
|
|
* Added map/reduce helper
|
|
|
|
|
2008-12-02 01:20:00 +00:00
|
|
|
= License
|
|
|
|
|
2009-02-15 13:24:14 +00:00
|
|
|
Copyright 2008-2009 10gen Inc.
|
2008-12-02 01:20:00 +00:00
|
|
|
|
2009-02-15 13:24:14 +00:00
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
2008-12-02 01:20:00 +00:00
|
|
|
|
2009-02-15 13:24:14 +00:00
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
2008-12-02 01:20:00 +00:00
|
|
|
|