pahagon-mongo-abd 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/README.rdoc +353 -0
  2. data/Rakefile +62 -0
  3. data/bin/bson_benchmark.rb +59 -0
  4. data/bin/mongo_console +21 -0
  5. data/bin/run_test_script +19 -0
  6. data/bin/standard_benchmark +109 -0
  7. data/examples/admin.rb +41 -0
  8. data/examples/benchmarks.rb +42 -0
  9. data/examples/blog.rb +76 -0
  10. data/examples/capped.rb +23 -0
  11. data/examples/cursor.rb +47 -0
  12. data/examples/gridfs.rb +87 -0
  13. data/examples/index_test.rb +125 -0
  14. data/examples/info.rb +30 -0
  15. data/examples/queries.rb +69 -0
  16. data/examples/simple.rb +23 -0
  17. data/examples/strict.rb +34 -0
  18. data/examples/types.rb +35 -0
  19. data/lib/mongo.rb +19 -0
  20. data/lib/mongo/admin.rb +83 -0
  21. data/lib/mongo/collection.rb +415 -0
  22. data/lib/mongo/connection.rb +151 -0
  23. data/lib/mongo/cursor.rb +279 -0
  24. data/lib/mongo/db.rb +560 -0
  25. data/lib/mongo/errors.rb +26 -0
  26. data/lib/mongo/gridfs.rb +16 -0
  27. data/lib/mongo/gridfs/chunk.rb +92 -0
  28. data/lib/mongo/gridfs/grid_store.rb +464 -0
  29. data/lib/mongo/message.rb +20 -0
  30. data/lib/mongo/message/get_more_message.rb +32 -0
  31. data/lib/mongo/message/insert_message.rb +37 -0
  32. data/lib/mongo/message/kill_cursors_message.rb +31 -0
  33. data/lib/mongo/message/message.rb +80 -0
  34. data/lib/mongo/message/message_header.rb +45 -0
  35. data/lib/mongo/message/msg_message.rb +29 -0
  36. data/lib/mongo/message/opcodes.rb +27 -0
  37. data/lib/mongo/message/query_message.rb +78 -0
  38. data/lib/mongo/message/remove_message.rb +37 -0
  39. data/lib/mongo/message/update_message.rb +38 -0
  40. data/lib/mongo/query.rb +118 -0
  41. data/lib/mongo/types/binary.rb +38 -0
  42. data/lib/mongo/types/code.rb +30 -0
  43. data/lib/mongo/types/dbref.rb +33 -0
  44. data/lib/mongo/types/objectid.rb +143 -0
  45. data/lib/mongo/types/regexp_of_holding.rb +40 -0
  46. data/lib/mongo/util/bson.rb +546 -0
  47. data/lib/mongo/util/byte_buffer.rb +167 -0
  48. data/lib/mongo/util/ordered_hash.rb +113 -0
  49. data/lib/mongo/util/xml_to_ruby.rb +105 -0
  50. data/mongo-ruby-driver.gemspec +103 -0
  51. data/test/mongo-qa/_common.rb +8 -0
  52. data/test/mongo-qa/admin +26 -0
  53. data/test/mongo-qa/capped +22 -0
  54. data/test/mongo-qa/count1 +18 -0
  55. data/test/mongo-qa/dbs +22 -0
  56. data/test/mongo-qa/find +10 -0
  57. data/test/mongo-qa/find1 +15 -0
  58. data/test/mongo-qa/gridfs_in +16 -0
  59. data/test/mongo-qa/gridfs_out +17 -0
  60. data/test/mongo-qa/indices +49 -0
  61. data/test/mongo-qa/remove +25 -0
  62. data/test/mongo-qa/stress1 +35 -0
  63. data/test/mongo-qa/test1 +11 -0
  64. data/test/mongo-qa/update +18 -0
  65. data/test/test_admin.rb +69 -0
  66. data/test/test_bson.rb +268 -0
  67. data/test/test_byte_buffer.rb +69 -0
  68. data/test/test_chunk.rb +84 -0
  69. data/test/test_collection.rb +249 -0
  70. data/test/test_connection.rb +101 -0
  71. data/test/test_cursor.rb +331 -0
  72. data/test/test_db.rb +185 -0
  73. data/test/test_db_api.rb +798 -0
  74. data/test/test_db_connection.rb +18 -0
  75. data/test/test_grid_store.rb +284 -0
  76. data/test/test_message.rb +35 -0
  77. data/test/test_objectid.rb +105 -0
  78. data/test/test_ordered_hash.rb +138 -0
  79. data/test/test_round_trip.rb +120 -0
  80. data/test/test_threading.rb +37 -0
  81. metadata +135 -0
@@ -0,0 +1,125 @@
1
+ class Exception
2
+ def errmsg
3
+ "%s: %s\n%s" % [self.class, message, (backtrace || []).join("\n") << "\n"]
4
+ end
5
+ end
6
+
7
+ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
8
+ require 'mongo'
9
+
10
+ include Mongo
11
+
12
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
13
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT
14
+
15
+ puts ">> Connecting to #{host}:#{port}"
16
+ db = Connection.new(host, port).db('ruby-mongo-index_test')
17
+
18
+ puts ">> Dropping collection test"
19
+ begin
20
+ res = db.drop_collection('test')
21
+ puts "dropped : #{res.inspect}"
22
+ rescue => e
23
+ puts "Error: #{e.errmsg}"
24
+ end
25
+
26
+ puts ">> Creating collection test"
27
+ begin
28
+ coll = db.collection('test')
29
+ puts "created : #{coll.inspect}"
30
+ rescue => e
31
+ puts "Error: #{e.errmsg}"
32
+ end
33
+
34
+ OBJS_COUNT = 100
35
+
36
+ puts ">> Generating test data"
37
+ msgs = %w{hola hello aloha ciao}
38
+ arr = (0...OBJS_COUNT).collect {|x| { :number => x, :rndm => (rand(5)+1), :msg => msgs[rand(4)] }}
39
+ puts "generated"
40
+
41
+ puts ">> Inserting data (#{arr.size})"
42
+ coll.insert(arr)
43
+ puts "inserted"
44
+
45
+ puts ">> Creating index"
46
+ res = coll.create_index "all", :_id => 1, :number => 1, :rndm => 1, :msg => 1
47
+ # res = coll.create_index "all", '_id' => 1, 'number' => 1, 'rndm' => 1, 'msg' => 1
48
+ puts "created index: #{res.inspect}"
49
+ # ============================ Mongo Log ============================
50
+ # Fri Dec 5 14:45:02 Adding all existing records for ruby-mongo-console.test to new index
51
+ # ***
52
+ # Bad data or size in BSONElement::size()
53
+ # bad type:30
54
+ # totalsize:11 fieldnamesize:4
55
+ # lastrec:
56
+ # Fri Dec 5 14:45:02 ruby-mongo-console.system.indexes Assertion failure false jsobj.cpp a0
57
+ # Fri Dec 5 14:45:02 database: ruby-mongo-console op:7d2 0
58
+ # Fri Dec 5 14:45:02 ns: ruby-mongo-console.system.indexes
59
+
60
+ puts ">> Gathering index information"
61
+ begin
62
+ res = coll.index_information
63
+ puts "index_information : #{res.inspect}"
64
+ rescue => e
65
+ puts "Error: #{e.errmsg}"
66
+ end
67
+ # ============================ Console Output ============================
68
+ # RuntimeError: Keys for index on return from db was nil. Coll = ruby-mongo-console.test
69
+ # from ./bin/../lib/mongo/db.rb:135:in `index_information'
70
+ # from (irb):11:in `collect'
71
+ # from ./bin/../lib/mongo/cursor.rb:47:in `each'
72
+ # from ./bin/../lib/mongo/db.rb:130:in `collect'
73
+ # from ./bin/../lib/mongo/db.rb:130:in `index_information'
74
+ # from ./bin/../lib/mongo/collection.rb:74:in `index_information'
75
+ # from (irb):11
76
+
77
+ puts ">> Dropping index"
78
+ begin
79
+ res = coll.drop_index "all_1"
80
+ puts "dropped : #{res.inspect}"
81
+ rescue => e
82
+ puts "Error: #{e.errmsg}"
83
+ end
84
+
85
+ # ============================ Console Output ============================
86
+ # => {"nIndexesWas"=>2.0, "ok"=>1.0}
87
+ # ============================ Mongo Log ============================
88
+ # 0x41802a 0x411549 0x42bac6 0x42c1f6 0x42c55b 0x42e6f7 0x41631e 0x41a89d 0x41ade2 0x41b448 0x4650d2 0x4695ad
89
+ # db/db(_Z12sayDbContextPKc+0x17a) [0x41802a]
90
+ # db/db(_Z8assertedPKcS0_j+0x9) [0x411549]
91
+ # db/db(_ZNK11BSONElement4sizeEv+0x1f6) [0x42bac6]
92
+ # db/db(_ZN7BSONObj8getFieldEPKc+0xa6) [0x42c1f6]
93
+ # db/db(_ZN7BSONObj14getFieldDottedEPKc+0x11b) [0x42c55b]
94
+ # db/db(_ZN7BSONObj19extractFieldsDottedES_R14BSONObjBuilder+0x87) [0x42e6f7]
95
+ # db/db(_ZN12IndexDetails17getKeysFromObjectER7BSONObjRSt3setIS0_St4lessIS0_ESaIS0_EE+0x24e) [0x41631e]
96
+ # db/db(_Z12_indexRecordR12IndexDetailsR7BSONObj7DiskLoc+0x5d) [0x41a89d]
97
+ # db/db(_Z18addExistingToIndexPKcR12IndexDetails+0xb2) [0x41ade2]
98
+ # db/db(_ZN11DataFileMgr6insertEPKcPKvib+0x508) [0x41b448]
99
+ # db/db(_Z14receivedInsertR7MessageRSt18basic_stringstreamIcSt11char_traitsIcESaIcEE+0x112) [0x4650d2]
100
+ # db/db(_Z10connThreadv+0xb4d) [0x4695ad]
101
+ # Fri Dec 5 14:45:02 ruby-mongo-console.system.indexes Caught Assertion insert, continuing
102
+ # Fri Dec 5 14:47:59 CMD: deleteIndexes ruby-mongo-console.test
103
+ # d->nIndexes was 2
104
+ # alpha implementation, space not reclaimed
105
+
106
+ puts ">> Gathering index information"
107
+ begin
108
+ res = coll.index_information
109
+ puts "index_information : #{res.inspect}"
110
+ rescue => e
111
+ puts "Error: #{e.errmsg}"
112
+ end
113
+ # ============================ Console Output ============================
114
+ # RuntimeError: Keys for index on return from db was nil. Coll = ruby-mongo-console.test
115
+ # from ./bin/../lib/mongo/db.rb:135:in `index_information'
116
+ # from (irb):15:in `collect'
117
+ # from ./bin/../lib/mongo/cursor.rb:47:in `each'
118
+ # from ./bin/../lib/mongo/db.rb:130:in `collect'
119
+ # from ./bin/../lib/mongo/db.rb:130:in `index_information'
120
+ # from ./bin/../lib/mongo/collection.rb:74:in `index_information'
121
+ # from (irb):15
122
+
123
+ puts ">> Closing connection"
124
+ db.close
125
+ puts "closed"
@@ -0,0 +1,30 @@
1
+ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'mongo'
3
+
4
+ include Mongo
5
+
6
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
7
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT
8
+
9
+ puts "Connecting to #{host}:#{port}"
10
+ db = Connection.new(host, port).db('ruby-mongo-examples')
11
+ coll = db.collection('test')
12
+
13
+ # Erase all records from collection, if any
14
+ coll.clear
15
+
16
+ # Insert 3 records
17
+ 3.times { |i| coll.insert({'a' => i+1}) }
18
+
19
+ # Collection names in database
20
+ p db.collection_names
21
+
22
+ # More information about each collection
23
+ p db.collections_info
24
+
25
+ # Index information
26
+ db.create_index('test', 'a')
27
+ p db.index_information('test')
28
+
29
+ # Destroy the collection
30
+ coll.drop
@@ -0,0 +1,69 @@
1
+ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'mongo'
3
+ require 'pp'
4
+
5
+ include Mongo
6
+
7
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
8
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT
9
+
10
+ puts "Connecting to #{host}:#{port}"
11
+ db = Connection.new(host, port).db('ruby-mongo-examples')
12
+ coll = db.collection('test')
13
+
14
+ # Remove all records, if any
15
+ coll.clear
16
+
17
+ # Insert three records
18
+ coll.insert('a' => 1)
19
+ coll.insert('a' => 2)
20
+ coll.insert('b' => 3)
21
+
22
+ # Count.
23
+ puts "There are #{coll.count()} records."
24
+
25
+ # Find all records. find() returns a Cursor.
26
+ cursor = coll.find()
27
+
28
+ # Print them. Note that all records have an _id automatically added by the
29
+ # database. See pk.rb for an example of how to use a primary key factory to
30
+ # generate your own values for _id.
31
+ cursor.each { |row| pp row }
32
+
33
+ # Cursor has a to_a method that slurps all records into memory.
34
+ rows = coll.find().to_a
35
+ rows.each { |row| pp row }
36
+
37
+ # See Collection#find. From now on in this file, we won't be printing the
38
+ # records we find.
39
+ coll.find('a' => 1)
40
+
41
+ # Find records sort by 'a', skip 1, limit 2 records.
42
+ # Sort can be single name, array, or hash.
43
+ coll.find({}, {:skip => 1, :limit => 2, :sort => 'a'})
44
+
45
+ # Find all records with 'a' > 1. There is also $lt, $gte, and $lte.
46
+ coll.find({'a' => {'$gt' => 1}})
47
+ coll.find({'a' => {'$gt' => 1, '$lte' => 3}})
48
+
49
+ # Find all records with 'a' in a set of values.
50
+ coll.find('a' => {'$in' => [1,2]})
51
+
52
+ # Find by regexp
53
+ coll.find('a' => /[1|2]/)
54
+
55
+ # Print query explanation
56
+ pp coll.find('a' => /[1|2]/).explain()
57
+
58
+ # Use a hint with a query. Need an index. Hints can be stored with the
59
+ # collection, in which case they will be used with all queries, or they can be
60
+ # specified per query, in which case that hint overrides the hint associated
61
+ # with the collection if any.
62
+ coll.create_index('a')
63
+ coll.hint = 'a'
64
+
65
+ # You will see a different explanation now that the hint is in place
66
+ pp coll.find('a' => /[1|2]/).explain()
67
+
68
+ # Override hint for single query
69
+ coll.find({'a' => 1}, :hint => 'b')
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'mongo'
3
+
4
+ include Mongo
5
+
6
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
7
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT
8
+
9
+ puts "Connecting to #{host}:#{port}"
10
+ db = Connection.new(host, port).db('ruby-mongo-examples')
11
+ coll = db.collection('test')
12
+
13
+ # Erase all records from collection, if any
14
+ coll.clear
15
+
16
+ # Insert 3 records
17
+ 3.times { |i| coll.insert({'a' => i+1}) }
18
+
19
+ puts "There are #{coll.count()} records in the test collection. Here they are:"
20
+ coll.find().each { |doc| puts doc.inspect }
21
+
22
+ # Destroy the collection
23
+ coll.drop
@@ -0,0 +1,34 @@
1
+ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'mongo'
3
+
4
+ include Mongo
5
+
6
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
7
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT
8
+
9
+ puts "Connecting to #{host}:#{port}"
10
+ db = Connection.new(host, port).db('ruby-mongo-examples')
11
+
12
+ db.drop_collection('does-not-exist')
13
+ db.create_collection('test')
14
+
15
+ db.strict = true
16
+
17
+ begin
18
+ # Can't reference collection that does not exist
19
+ db.collection('does-not-exist')
20
+ puts "error: expected exception"
21
+ rescue => ex
22
+ puts "expected exception: #{ex}"
23
+ end
24
+
25
+ begin
26
+ # Can't create collection that already exists
27
+ db.create_collection('test')
28
+ puts "error: expected exception"
29
+ rescue => ex
30
+ puts "expected exception: #{ex}"
31
+ end
32
+
33
+ db.strict = false
34
+ db.drop_collection('test')
@@ -0,0 +1,35 @@
1
+ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'mongo'
3
+ require 'pp'
4
+
5
+ include Mongo
6
+
7
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
8
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT
9
+
10
+ puts "Connecting to #{host}:#{port}"
11
+ db = Connection.new(host, port).db('ruby-mongo-examples')
12
+ coll = db.collection('test')
13
+
14
+ # Remove all records, if any
15
+ coll.clear
16
+
17
+ # Insert record with all sorts of values
18
+ coll.insert('array' => [1, 2, 3],
19
+ 'string' => 'hello',
20
+ 'hash' => {'a' => 1, 'b' => 2},
21
+ 'date' => Time.now, # milliseconds only; microseconds are not stored
22
+ 'oid' => ObjectID.new,
23
+ 'binary' => Binary.new([1, 2, 3]),
24
+ 'int' => 42,
25
+ 'float' => 33.33333,
26
+ 'regex' => /foobar/i,
27
+ 'boolean' => true,
28
+ 'where' => Code.new('this.x == 3'),
29
+ 'dbref' => DBRef.new(coll.name, ObjectID.new),
30
+ 'null' => nil,
31
+ 'symbol' => :zildjian)
32
+
33
+ pp coll.find().next_object
34
+
35
+ coll.clear
@@ -0,0 +1,19 @@
1
+ require 'mongo/types/binary'
2
+ require 'mongo/types/dbref'
3
+ require 'mongo/types/objectid'
4
+ require 'mongo/types/regexp_of_holding'
5
+
6
+ require 'mongo/errors'
7
+ require 'mongo/connection'
8
+ require 'mongo/message'
9
+ require 'mongo/db'
10
+ require 'mongo/cursor'
11
+ require 'mongo/collection'
12
+ require 'mongo/admin'
13
+
14
+ module Mongo
15
+ ASCENDING = 1
16
+ DESCENDING = -1
17
+
18
+ VERSION = "0.14.1"
19
+ end
@@ -0,0 +1,83 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/util/ordered_hash'
18
+
19
+ module Mongo
20
+
21
+ # Provide administrative database methods: those having to do with
22
+ # profiling and validation.
23
+ class Admin
24
+
25
+ def initialize(db)
26
+ @db = db
27
+ end
28
+
29
+ # Return the current database profiling level.
30
+ def profiling_level
31
+ oh = OrderedHash.new
32
+ oh[:profile] = -1
33
+ doc = @db.db_command(oh)
34
+ raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc) && doc['was'].kind_of?(Numeric)
35
+ case doc['was'].to_i
36
+ when 0
37
+ :off
38
+ when 1
39
+ :slow_only
40
+ when 2
41
+ :all
42
+ else
43
+ raise "Error: illegal profiling level value #{doc['was']}"
44
+ end
45
+ end
46
+
47
+ # Set database profiling level to :off, :slow_only, or :all.
48
+ def profiling_level=(level)
49
+ oh = OrderedHash.new
50
+ oh[:profile] = case level
51
+ when :off
52
+ 0
53
+ when :slow_only
54
+ 1
55
+ when :all
56
+ 2
57
+ else
58
+ raise "Error: illegal profiling level value #{level}"
59
+ end
60
+ doc = @db.db_command(oh)
61
+ raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc)
62
+ end
63
+
64
+ # Return an array contining current profiling information from the
65
+ # database.
66
+ def profiling_info
67
+ @db.query(Collection.new(@db, DB::SYSTEM_PROFILE_COLLECTION), Query.new({})).to_a
68
+ end
69
+
70
+ # Validate a named collection by raising an exception if there is a
71
+ # problem or returning an interesting hash (see especially the
72
+ # 'result' string value) if all is well.
73
+ def validate_collection(name)
74
+ doc = @db.db_command(:validate => name)
75
+ raise "Error with validate command: #{doc.inspect}" unless @db.ok?(doc)
76
+ result = doc['result']
77
+ raise "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
78
+ raise "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
79
+ doc
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,415 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/query'
18
+
19
+ module Mongo
20
+
21
+ # A named collection of records in a database.
22
+ class Collection
23
+
24
+ attr_reader :db, :name, :hint
25
+
26
+ def initialize(db, name)
27
+ case name
28
+ when Symbol, String
29
+ else
30
+ raise TypeError, "new_name must be a string or symbol"
31
+ end
32
+
33
+ name = name.to_s
34
+
35
+ if name.empty? or name.include? ".."
36
+ raise InvalidName, "collection names cannot be empty"
37
+ end
38
+ if name.include? "$" and not name.match(/^\$cmd/)
39
+ raise InvalidName, "collection names must not contain '$'"
40
+ end
41
+ if name.match(/^\./) or name.match(/\.$/)
42
+ raise InvalidName, "collection names must not start or end with '.'"
43
+ end
44
+
45
+ @db, @name = db, name
46
+ @hint = nil
47
+ end
48
+
49
+ # Get a sub-collection of this collection by name.
50
+ #
51
+ # Raises InvalidName if an invalid collection name is used.
52
+ #
53
+ # :name :: the name of the collection to get
54
+ def [](name)
55
+ name = "#{self.name}.#{name}"
56
+ return Collection.new(db, name) if !db.strict? || db.collection_names.include?(name)
57
+ raise "Collection #{name} doesn't exist. Currently in strict mode."
58
+ end
59
+
60
+ # Set hint fields to use and return +self+. hint may be a single field
61
+ # name, array of field names, or a hash (preferably an OrderedHash).
62
+ # May be +nil+.
63
+ def hint=(hint)
64
+ @hint = normalize_hint_fields(hint)
65
+ self
66
+ end
67
+
68
+ # Query the database.
69
+ #
70
+ # The +selector+ argument is a prototype document that all results must
71
+ # match. For example:
72
+ #
73
+ # collection.find({"hello" => "world"})
74
+ #
75
+ # only matches documents that have a key "hello" with value "world".
76
+ # Matches can have other keys *in addition* to "hello".
77
+ #
78
+ # If given an optional block +find+ will yield a Cursor to that block,
79
+ # close the cursor, and then return nil. This guarantees that partially
80
+ # evaluated cursors will be closed. If given no block +find+ returns a
81
+ # cursor.
82
+ #
83
+ # :selector :: A document (hash) specifying elements which must be
84
+ # present for a document to be included in the result set.
85
+ #
86
+ # Options:
87
+ # :fields :: Array of field names that should be returned in the result
88
+ # set ("_id" will always be included). By limiting results
89
+ # to a certain subset of fields you can cut down on network
90
+ # traffic and decoding time.
91
+ # :skip :: Number of documents to omit (from the start of the result set)
92
+ # when returning the results
93
+ # :limit :: Maximum number of records to return
94
+ # :sort :: Either hash of field names as keys and 1/-1 as values; 1 ==
95
+ # ascending, -1 == descending, or array of field names (all
96
+ # assumed to be sorted in ascending order).
97
+ # :hint :: See #hint. This option overrides the collection-wide value.
98
+ # :snapshot :: If true, snapshot mode will be used for this query.
99
+ # Snapshot mode assures no duplicates are returned, or
100
+ # objects missed, which were preset at both the start and
101
+ # end of the query's execution. For details see
102
+ # http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
103
+ def find(selector={}, options={})
104
+ fields = options.delete(:fields)
105
+ fields = ["_id"] if fields && fields.empty?
106
+ skip = options.delete(:offset) || nil
107
+ if !skip.nil?
108
+ warn "the :offset option to find is deprecated and will be removed. please use :skip instead"
109
+ end
110
+ skip = options.delete(:skip) || skip || 0
111
+ limit = options.delete(:limit) || 0
112
+ sort = options.delete(:sort)
113
+ hint = options.delete(:hint)
114
+ snapshot = options.delete(:snapshot)
115
+ if hint
116
+ hint = normalize_hint_fields(hint)
117
+ else
118
+ hint = @hint # assumed to be normalized already
119
+ end
120
+ raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty?
121
+
122
+ cursor = @db.query(self, Query.new(selector, fields, skip, limit, sort, hint, snapshot))
123
+ if block_given?
124
+ yield cursor
125
+ cursor.close()
126
+ nil
127
+ else
128
+ cursor
129
+ end
130
+ end
131
+
132
+ # Get a single object from the database.
133
+ #
134
+ # Raises TypeError if the argument is of an improper type. Returns a
135
+ # single document (hash), or nil if no result is found.
136
+ #
137
+ # :spec_or_object_id :: a hash specifying elements which must be
138
+ # present for a document to be included in the result set OR an
139
+ # instance of ObjectID to be used as the value for an _id query.
140
+ # if nil an empty spec, {}, will be used.
141
+ # :options :: options, as passed to Collection#find
142
+ def find_one(spec_or_object_id=nil, options={})
143
+ spec = case spec_or_object_id
144
+ when nil
145
+ {}
146
+ when ObjectID
147
+ {:_id => spec_or_object_id}
148
+ when Hash
149
+ spec_or_object_id
150
+ else
151
+ raise TypeError, "spec_or_object_id must be an instance of ObjectID or Hash, or nil"
152
+ end
153
+ find(spec, options.merge(:limit => -1)).next_object
154
+ end
155
+
156
+ # Save a document in this collection.
157
+ #
158
+ # If +to_save+ already has an '_id' then an update (upsert) operation
159
+ # is performed and any existing document with that _id is overwritten.
160
+ # Otherwise an insert operation is performed. Returns the _id of the
161
+ # saved document.
162
+ #
163
+ # :to_save :: the document (a hash) to be saved
164
+ #
165
+ # Options:
166
+ # :safe :: if true, check that the save succeeded. OperationFailure
167
+ # will be raised on an error. Checking for safety requires an extra
168
+ # round-trip to the database
169
+ def save(to_save, options={})
170
+ if id = to_save[:_id] || to_save['_id']
171
+ update({:_id => id}, to_save, :upsert => true, :safe => options.delete(:safe))
172
+ id
173
+ else
174
+ insert(to_save, :safe => options.delete(:safe))
175
+ end
176
+ end
177
+
178
+ # Insert a document(s) into this collection.
179
+ #
180
+ # "<<" is aliased to this method. Returns the _id of the inserted
181
+ # document or a list of _ids of the inserted documents. The object(s)
182
+ # may have been modified by the database's PK factory, if it has one.
183
+ #
184
+ # :doc_or_docs :: a document (as a hash) or Array of documents to be
185
+ # inserted
186
+ #
187
+ # Options:
188
+ # :safe :: if true, check that the insert succeeded. OperationFailure
189
+ # will be raised on an error. Checking for safety requires an extra
190
+ # round-trip to the database
191
+ def insert(doc_or_docs, options={})
192
+ doc_or_docs = [doc_or_docs] if !doc_or_docs.is_a?(Array)
193
+ res = @db.insert_into_db(@name, doc_or_docs)
194
+ if options.delete(:safe)
195
+ error = @db.error
196
+ if error
197
+ raise OperationFailure, error
198
+ end
199
+ end
200
+ res.size > 1 ? res : res.first
201
+ end
202
+ alias_method :<<, :insert
203
+
204
+ # Remove the records that match +selector+.
205
+ def remove(selector={})
206
+ @db.remove_from_db(@name, selector)
207
+ end
208
+
209
+ # Remove all records.
210
+ def clear
211
+ remove({})
212
+ end
213
+
214
+ # Update a single document in this collection.
215
+ #
216
+ # :spec :: a hash specifying elements which must be present for
217
+ # a document to be updated
218
+ # :document :: a hash specifying the fields to be changed in the
219
+ # selected document, or (in the case of an upsert) the document to
220
+ # be inserted
221
+ #
222
+ # Options:
223
+ # :upsert :: if true, perform an upsert operation
224
+ # :safe :: if true, check that the update succeeded. OperationFailure
225
+ # will be raised on an error. Checking for safety requires an extra
226
+ # round-trip to the database
227
+ def update(spec, document, options={})
228
+ upsert = options.delete(:upsert)
229
+ safe = options.delete(:safe)
230
+
231
+ if upsert
232
+ @db.repsert_in_db(@name, spec, document)
233
+ else
234
+ @db.replace_in_db(@name, spec, document)
235
+ end
236
+ if safe
237
+ error = @db.error
238
+ if error
239
+ raise OperationFailure, error
240
+ end
241
+ end
242
+ end
243
+
244
+ # Create a new index. +field_or_spec+
245
+ # should be either a single field name or a Array of [field name,
246
+ # direction] pairs. Directions should be specified as
247
+ # Mongo::ASCENDING or Mongo::DESCENDING.
248
+ # +unique+ is an optional boolean indicating whether this index
249
+ # should enforce a uniqueness constraint.
250
+ def create_index(field_or_spec, unique=false)
251
+ @db.create_index(@name, field_or_spec, unique)
252
+ end
253
+
254
+ # Drop index +name+.
255
+ def drop_index(name)
256
+ @db.drop_index(@name, name)
257
+ end
258
+
259
+ # Drop all indexes.
260
+ def drop_indexes
261
+ # just need to call drop indexes with no args; will drop them all
262
+ @db.drop_index(@name, '*')
263
+ end
264
+
265
+ # Drop the entire collection. USE WITH CAUTION.
266
+ def drop
267
+ @db.drop_collection(@name)
268
+ end
269
+
270
+ # Perform a query similar to an SQL group by operation.
271
+ #
272
+ # Returns an array of grouped items.
273
+ #
274
+ # :keys :: Array of fields to group by
275
+ # :condition :: specification of rows to be considered (as a 'find'
276
+ # query specification)
277
+ # :initial :: initial value of the aggregation counter object
278
+ # :reduce :: aggregation function as a JavaScript string
279
+ # :command :: if true, run the group as a command instead of in an
280
+ # eval - it is likely that this option will eventually be
281
+ # deprecated and all groups will be run as commands
282
+ def group(keys, condition, initial, reduce, command=false)
283
+ if command
284
+ hash = {}
285
+ keys.each do |k|
286
+ hash[k] = 1
287
+ end
288
+
289
+ case reduce
290
+ when Code
291
+ else
292
+ reduce = Code.new(reduce)
293
+ end
294
+
295
+ result = @db.db_command({"group" =>
296
+ {
297
+ "ns" => @name,
298
+ "$reduce" => reduce,
299
+ "key" => hash,
300
+ "cond" => condition,
301
+ "initial" => initial}})
302
+ if result["ok"] == 1
303
+ return result["retval"]
304
+ else
305
+ raise OperationFailure, "group command failed: #{result['errmsg']}"
306
+ end
307
+ end
308
+
309
+ case reduce
310
+ when Code
311
+ scope = reduce.scope
312
+ else
313
+ scope = {}
314
+ end
315
+ scope.merge!({
316
+ "ns" => @name,
317
+ "keys" => keys,
318
+ "condition" => condition,
319
+ "initial" => initial })
320
+
321
+ group_function = <<EOS
322
+ function () {
323
+ var c = db[ns].find(condition);
324
+ var map = new Map();
325
+ var reduce_function = #{reduce};
326
+ while (c.hasNext()) {
327
+ var obj = c.next();
328
+
329
+ var key = {};
330
+ for (var i = 0; i < keys.length; i++) {
331
+ var k = keys[i];
332
+ key[k] = obj[k];
333
+ }
334
+
335
+ var aggObj = map.get(key);
336
+ if (aggObj == null) {
337
+ var newObj = Object.extend({}, key);
338
+ aggObj = Object.extend(newObj, initial);
339
+ map.put(key, aggObj);
340
+ }
341
+ reduce_function(obj, aggObj);
342
+ }
343
+ return {"result": map.values()};
344
+ }
345
+ EOS
346
+ return @db.eval(Code.new(group_function, scope))["result"]
347
+ end
348
+
349
+ # Rename this collection.
350
+ #
351
+ # If operating in auth mode, client must be authorized as an admin to
352
+ # perform this operation. Raises +InvalidName+ if +new_name+ is an invalid
353
+ # collection name.
354
+ #
355
+ # :new_name :: new name for this collection
356
+ def rename(new_name)
357
+ case new_name
358
+ when Symbol, String
359
+ else
360
+ raise TypeError, "new_name must be a string or symbol"
361
+ end
362
+
363
+ new_name = new_name.to_s
364
+
365
+ if new_name.empty? or new_name.include? ".."
366
+ raise InvalidName, "collection names cannot be empty"
367
+ end
368
+ if new_name.include? "$"
369
+ raise InvalidName, "collection names must not contain '$'"
370
+ end
371
+ if new_name.match(/^\./) or new_name.match(/\.$/)
372
+ raise InvalidName, "collection names must not start or end with '.'"
373
+ end
374
+
375
+ @db.rename_collection(@name, new_name)
376
+ end
377
+
378
+ # Get information on the indexes for the collection +collection_name+.
379
+ # Returns a hash where the keys are index names (as returned by
380
+ # Collection#create_index and the values are lists of [key, direction]
381
+ # pairs specifying the index (as passed to Collection#create_index).
382
+ def index_information
383
+ @db.index_information(@name)
384
+ end
385
+
386
+ # Return a hash containing options that apply to this collection.
387
+ # 'create' will be the collection name. For the other possible keys
388
+ # and values, see DB#create_collection.
389
+ def options
390
+ @db.collections_info(@name).next_object()['options']
391
+ end
392
+
393
+ # Get the number of documents in this collection.
394
+ def count()
395
+ find().count()
396
+ end
397
+
398
+ protected
399
+
400
+ def normalize_hint_fields(hint)
401
+ case hint
402
+ when String
403
+ {hint => 1}
404
+ when Hash
405
+ hint
406
+ when nil
407
+ nil
408
+ else
409
+ h = OrderedHash.new
410
+ hint.to_a.each { |k| h[k] = 1 }
411
+ h
412
+ end
413
+ end
414
+ end
415
+ end