pahagon-mongo-abd 0.14.1

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.
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