animehunter-mongo 0.9

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 (79) hide show
  1. data/README.rdoc +311 -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 +40 -0
  19. data/lib/mongo.rb +19 -0
  20. data/lib/mongo/admin.rb +87 -0
  21. data/lib/mongo/collection.rb +235 -0
  22. data/lib/mongo/cursor.rb +227 -0
  23. data/lib/mongo/db.rb +538 -0
  24. data/lib/mongo/gridfs.rb +16 -0
  25. data/lib/mongo/gridfs/chunk.rb +96 -0
  26. data/lib/mongo/gridfs/grid_store.rb +468 -0
  27. data/lib/mongo/message.rb +20 -0
  28. data/lib/mongo/message/get_more_message.rb +37 -0
  29. data/lib/mongo/message/insert_message.rb +35 -0
  30. data/lib/mongo/message/kill_cursors_message.rb +36 -0
  31. data/lib/mongo/message/message.rb +84 -0
  32. data/lib/mongo/message/message_header.rb +50 -0
  33. data/lib/mongo/message/msg_message.rb +33 -0
  34. data/lib/mongo/message/opcodes.rb +32 -0
  35. data/lib/mongo/message/query_message.rb +77 -0
  36. data/lib/mongo/message/remove_message.rb +36 -0
  37. data/lib/mongo/message/update_message.rb +37 -0
  38. data/lib/mongo/mongo.rb +164 -0
  39. data/lib/mongo/query.rb +119 -0
  40. data/lib/mongo/types/binary.rb +42 -0
  41. data/lib/mongo/types/code.rb +34 -0
  42. data/lib/mongo/types/dbref.rb +37 -0
  43. data/lib/mongo/types/objectid.rb +137 -0
  44. data/lib/mongo/types/regexp_of_holding.rb +44 -0
  45. data/lib/mongo/types/undefined.rb +31 -0
  46. data/lib/mongo/util/bson.rb +534 -0
  47. data/lib/mongo/util/byte_buffer.rb +167 -0
  48. data/lib/mongo/util/ordered_hash.rb +96 -0
  49. data/lib/mongo/util/xml_to_ruby.rb +107 -0
  50. data/mongo-ruby-driver.gemspec +99 -0
  51. data/tests/mongo-qa/_common.rb +8 -0
  52. data/tests/mongo-qa/admin +26 -0
  53. data/tests/mongo-qa/capped +22 -0
  54. data/tests/mongo-qa/count1 +18 -0
  55. data/tests/mongo-qa/dbs +22 -0
  56. data/tests/mongo-qa/find +10 -0
  57. data/tests/mongo-qa/find1 +15 -0
  58. data/tests/mongo-qa/gridfs_in +16 -0
  59. data/tests/mongo-qa/gridfs_out +17 -0
  60. data/tests/mongo-qa/indices +49 -0
  61. data/tests/mongo-qa/remove +25 -0
  62. data/tests/mongo-qa/stress1 +35 -0
  63. data/tests/mongo-qa/test1 +11 -0
  64. data/tests/mongo-qa/update +18 -0
  65. data/tests/test_admin.rb +69 -0
  66. data/tests/test_bson.rb +246 -0
  67. data/tests/test_byte_buffer.rb +69 -0
  68. data/tests/test_chunk.rb +84 -0
  69. data/tests/test_cursor.rb +121 -0
  70. data/tests/test_db.rb +160 -0
  71. data/tests/test_db_api.rb +701 -0
  72. data/tests/test_db_connection.rb +18 -0
  73. data/tests/test_grid_store.rb +284 -0
  74. data/tests/test_message.rb +35 -0
  75. data/tests/test_mongo.rb +78 -0
  76. data/tests/test_objectid.rb +98 -0
  77. data/tests/test_ordered_hash.rb +129 -0
  78. data/tests/test_round_trip.rb +116 -0
  79. metadata +133 -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 XGen::Mongo::Driver
11
+
12
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
13
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT
14
+
15
+ puts ">> Connecting to #{host}:#{port}"
16
+ db = Mongo.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"
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 XGen::Mongo::Driver
5
+
6
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
7
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT
8
+
9
+ puts "Connecting to #{host}:#{port}"
10
+ db = Mongo.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 XGen::Mongo::Driver
6
+
7
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
8
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT
9
+
10
+ puts "Connecting to #{host}:#{port}"
11
+ db = Mongo.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', offset 1, limit 2 records.
42
+ # Sort can be single name, array, or hash.
43
+ coll.find({}, {:offset => 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 XGen::Mongo::Driver
5
+
6
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
7
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT
8
+
9
+ puts "Connecting to #{host}:#{port}"
10
+ db = Mongo.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 XGen::Mongo::Driver
5
+
6
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
7
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT
8
+
9
+ puts "Connecting to #{host}:#{port}"
10
+ db = Mongo.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,40 @@
1
+ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'mongo'
3
+ require 'pp'
4
+
5
+ include XGen::Mongo::Driver
6
+
7
+ host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
8
+ port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT
9
+
10
+ puts "Connecting to #{host}:#{port}"
11
+ db = Mongo.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
+
31
+ # NOTE: the undefined type is not saved to the database properly. This is a
32
+ # Mongo bug. However, the undefined type may go away completely.
33
+ # 'undef' => Undefined.new,
34
+
35
+ 'null' => nil,
36
+ 'symbol' => :zildjian)
37
+
38
+ pp coll.find().next_object
39
+
40
+ 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
+ require 'mongo/types/undefined'
6
+
7
+ require 'mongo/mongo'
8
+ require 'mongo/message'
9
+ require 'mongo/db'
10
+ require 'mongo/cursor'
11
+ require 'mongo/collection'
12
+ require 'mongo/admin'
13
+
14
+ module XGen
15
+ module Mongo
16
+ ASCENDING = 1
17
+ DESCENDING = -1
18
+ end
19
+ end
@@ -0,0 +1,87 @@
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 XGen
20
+ module Mongo
21
+ module Driver
22
+
23
+ # Provide administrative database methods: those having to do with
24
+ # profiling and validation.
25
+ class Admin
26
+
27
+ def initialize(db)
28
+ @db = db
29
+ end
30
+
31
+ # Return the current database profiling level.
32
+ def profiling_level
33
+ oh = OrderedHash.new
34
+ oh[:profile] = -1
35
+ doc = @db.db_command(oh)
36
+ raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc) && doc['was'].kind_of?(Numeric)
37
+ case doc['was'].to_i
38
+ when 0
39
+ :off
40
+ when 1
41
+ :slow_only
42
+ when 2
43
+ :all
44
+ else
45
+ raise "Error: illegal profiling level value #{doc['was']}"
46
+ end
47
+ end
48
+
49
+ # Set database profiling level to :off, :slow_only, or :all.
50
+ def profiling_level=(level)
51
+ oh = OrderedHash.new
52
+ oh[:profile] = case level
53
+ when :off
54
+ 0
55
+ when :slow_only
56
+ 1
57
+ when :all
58
+ 2
59
+ else
60
+ raise "Error: illegal profiling level value #{level}"
61
+ end
62
+ doc = @db.db_command(oh)
63
+ raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc)
64
+ end
65
+
66
+ # Return an array contining current profiling information from the
67
+ # database.
68
+ def profiling_info
69
+ @db.query(Collection.new(@db, DB::SYSTEM_PROFILE_COLLECTION), Query.new({})).to_a
70
+ end
71
+
72
+ # Validate a named collection by raising an exception if there is a
73
+ # problem or returning an interesting hash (see especially the
74
+ # 'result' string value) if all is well.
75
+ def validate_collection(name)
76
+ doc = @db.db_command(:validate => name)
77
+ raise "Error with validate command: #{doc.inspect}" unless @db.ok?(doc)
78
+ result = doc['result']
79
+ raise "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
80
+ raise "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
81
+ doc
82
+ end
83
+
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,235 @@
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 XGen
20
+ module Mongo
21
+ module Driver
22
+
23
+ # A named collection of records in a database.
24
+ class Collection
25
+
26
+ attr_reader :db, :name, :hint
27
+
28
+ def initialize(db, name)
29
+ @db, @name = db, name
30
+ @hint = nil
31
+ end
32
+
33
+ # Set hint fields to use and return +self+. hint may be a single field
34
+ # name, array of field names, or a hash (preferably an OrderedHash).
35
+ # May be +nil+.
36
+ def hint=(hint)
37
+ @hint = normalize_hint_fields(hint)
38
+ self
39
+ end
40
+
41
+ # Return records that match a +selector+ hash. See Mongo docs for
42
+ # details.
43
+ #
44
+ # Options:
45
+ # :fields :: Array of collection field names; only those will be returned (plus _id if defined)
46
+ # :offset :: Start at this record when returning records
47
+ # :limit :: Maximum number of records to return
48
+ # :sort :: Either hash of field names as keys and 1/-1 as values; 1 ==
49
+ # ascending, -1 == descending, or array of field names (all
50
+ # assumed to be sorted in ascending order).
51
+ # :hint :: See #hint. This option overrides the collection-wide value.
52
+ def find(selector={}, options={})
53
+ fields = options.delete(:fields)
54
+ fields = nil if fields && fields.empty?
55
+ offset = options.delete(:offset) || 0
56
+ limit = options.delete(:limit) || 0
57
+ sort = options.delete(:sort)
58
+ hint = options.delete(:hint)
59
+ if hint
60
+ hint = normalize_hint_fields(hint)
61
+ else
62
+ hint = @hint # assumed to be normalized already
63
+ end
64
+ raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty?
65
+ @db.query(self, Query.new(selector, fields, offset, limit, sort, hint))
66
+ end
67
+
68
+ # Find the first record that matches +selector+. See #find.
69
+ def find_first(selector={}, options={})
70
+ h = options.dup
71
+ h[:limit] = 1
72
+ cursor = find(selector, h)
73
+ cursor.next_object # don't need to explicitly close b/c of limit
74
+ end
75
+
76
+ # Save an updated +object+ to the collection, or insert it if it doesn't exist already.
77
+ def save(object)
78
+ if id = object[:_id] || object['_id']
79
+ repsert({:_id => id}, object)
80
+ else
81
+ insert(object)
82
+ end
83
+ end
84
+
85
+ # Insert +objects+, which are hashes. "<<" is aliased to this method.
86
+ # Returns either the single inserted object or a new array containing
87
+ # +objects+. The object(s) may have been modified by the database's PK
88
+ # factory, if it has one.
89
+ def insert(*objects)
90
+ objects = objects.first if objects.size == 1 && objects.first.is_a?(Array)
91
+ res = @db.insert_into_db(@name, objects)
92
+ res.size > 1 ? res : res.first
93
+ end
94
+ alias_method :<<, :insert
95
+
96
+ # Remove the records that match +selector+.
97
+ def remove(selector={})
98
+ @db.remove_from_db(@name, selector)
99
+ end
100
+
101
+ # Remove all records.
102
+ def clear
103
+ remove({})
104
+ end
105
+
106
+ # Update records that match +selector+ by applying +obj+ as an update.
107
+ # If no match, inserts (???).
108
+ def repsert(selector, obj)
109
+ @db.repsert_in_db(@name, selector, obj)
110
+ end
111
+
112
+ # Update records that match +selector+ by applying +obj+ as an update.
113
+ def replace(selector, obj)
114
+ @db.replace_in_db(@name, selector, obj)
115
+ end
116
+
117
+ # Update records that match +selector+ by applying +obj+ as an update.
118
+ # Both +selector+ and +modifier_obj+ are required.
119
+ def modify(selector, modifier_obj)
120
+ raise "no object" unless modifier_obj
121
+ raise "no selector" unless selector
122
+ @db.modify_in_db(@name, selector, modifier_obj)
123
+ end
124
+
125
+ # Create a new index. +field_or_spec+
126
+ # should be either a single field name or a Array of [field name,
127
+ # direction] pairs. Directions should be specified as
128
+ # XGen::Mongo::ASCENDING or XGen::Mongo::DESCENDING.
129
+ # +unique+ is an optional boolean indicating whether this index
130
+ # should enforce a uniqueness constraint.
131
+ def create_index(field_or_spec, unique=false)
132
+ @db.create_index(@name, field_or_spec, unique)
133
+ end
134
+
135
+ # Drop index +name+.
136
+ def drop_index(name)
137
+ @db.drop_index(@name, name)
138
+ end
139
+
140
+ # Drop all indexes.
141
+ def drop_indexes
142
+ # just need to call drop indexes with no args; will drop them all
143
+ @db.drop_index(@name, '*')
144
+ end
145
+
146
+ # Drop the entire collection. USE WITH CAUTION.
147
+ def drop
148
+ @db.drop_collection(@name)
149
+ end
150
+
151
+ # Perform a query similar to an SQL group by operation.
152
+ #
153
+ # Returns an array of grouped items.
154
+ #
155
+ # :keys :: list of fields to group by
156
+ # :condition :: specification of rows to be considered (as a 'find'
157
+ # query specification)
158
+ # :initial :: initial value of the aggregation counter object
159
+ # :reduce :: aggregation function as a JavaScript string
160
+ def group(keys, condition, initial, reduce)
161
+ group_function = <<EOS
162
+ function () {
163
+ var c = db[ns].find(condition);
164
+ var map = new Map();
165
+ var reduce_function = #{reduce};
166
+ while (c.hasNext()) {
167
+ var obj = c.next();
168
+
169
+ var key = {};
170
+ for (var i in keys) {
171
+ key[keys[i]] = obj[keys[i]];
172
+ }
173
+
174
+ var aggObj = map.get(key);
175
+ if (aggObj == null) {
176
+ var newObj = Object.extend({}, key);
177
+ aggObj = Object.extend(newObj, initial);
178
+ map.put(key, aggObj);
179
+ }
180
+ reduce_function(obj, aggObj);
181
+ }
182
+ return {"result": map.values()};
183
+ }
184
+ EOS
185
+ return @db.eval(Code.new(group_function,
186
+ {
187
+ "ns" => @name,
188
+ "keys" => keys,
189
+ "condition" => condition,
190
+ "initial" => initial
191
+ }))["result"]
192
+ end
193
+
194
+ # Get information on the indexes for the collection +collection_name+.
195
+ # Returns a hash where the keys are index names (as returned by
196
+ # Collection#create_index and the values are lists of [key, direction]
197
+ # pairs specifying the index (as passed to Collection#create_index).
198
+ def index_information
199
+ @db.index_information(@name)
200
+ end
201
+
202
+ # Return a hash containing options that apply to this collection.
203
+ # 'create' will be the collection name. For the other possible keys
204
+ # and values, see DB#create_collection.
205
+ def options
206
+ @db.collections_info(@name).next_object()['options']
207
+ end
208
+
209
+ # Return the number of records that match +selector+. If +selector+ is
210
+ # +nil+ or an empty hash, returns the count of all records.
211
+ def count(selector={})
212
+ @db.count(@name, selector || {})
213
+ end
214
+
215
+ protected
216
+
217
+ def normalize_hint_fields(hint)
218
+ case hint
219
+ when String
220
+ {hint => 1}
221
+ when Hash
222
+ hint
223
+ when nil
224
+ nil
225
+ else
226
+ h = OrderedHash.new
227
+ hint.to_a.each { |k| h[k] = 1 }
228
+ h
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+