pahagon-mongo-abd 0.14.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +353 -0
- data/Rakefile +62 -0
- data/bin/bson_benchmark.rb +59 -0
- data/bin/mongo_console +21 -0
- data/bin/run_test_script +19 -0
- data/bin/standard_benchmark +109 -0
- data/examples/admin.rb +41 -0
- data/examples/benchmarks.rb +42 -0
- data/examples/blog.rb +76 -0
- data/examples/capped.rb +23 -0
- data/examples/cursor.rb +47 -0
- data/examples/gridfs.rb +87 -0
- data/examples/index_test.rb +125 -0
- data/examples/info.rb +30 -0
- data/examples/queries.rb +69 -0
- data/examples/simple.rb +23 -0
- data/examples/strict.rb +34 -0
- data/examples/types.rb +35 -0
- data/lib/mongo.rb +19 -0
- data/lib/mongo/admin.rb +83 -0
- data/lib/mongo/collection.rb +415 -0
- data/lib/mongo/connection.rb +151 -0
- data/lib/mongo/cursor.rb +279 -0
- data/lib/mongo/db.rb +560 -0
- data/lib/mongo/errors.rb +26 -0
- data/lib/mongo/gridfs.rb +16 -0
- data/lib/mongo/gridfs/chunk.rb +92 -0
- data/lib/mongo/gridfs/grid_store.rb +464 -0
- data/lib/mongo/message.rb +20 -0
- data/lib/mongo/message/get_more_message.rb +32 -0
- data/lib/mongo/message/insert_message.rb +37 -0
- data/lib/mongo/message/kill_cursors_message.rb +31 -0
- data/lib/mongo/message/message.rb +80 -0
- data/lib/mongo/message/message_header.rb +45 -0
- data/lib/mongo/message/msg_message.rb +29 -0
- data/lib/mongo/message/opcodes.rb +27 -0
- data/lib/mongo/message/query_message.rb +78 -0
- data/lib/mongo/message/remove_message.rb +37 -0
- data/lib/mongo/message/update_message.rb +38 -0
- data/lib/mongo/query.rb +118 -0
- data/lib/mongo/types/binary.rb +38 -0
- data/lib/mongo/types/code.rb +30 -0
- data/lib/mongo/types/dbref.rb +33 -0
- data/lib/mongo/types/objectid.rb +143 -0
- data/lib/mongo/types/regexp_of_holding.rb +40 -0
- data/lib/mongo/util/bson.rb +546 -0
- data/lib/mongo/util/byte_buffer.rb +167 -0
- data/lib/mongo/util/ordered_hash.rb +113 -0
- data/lib/mongo/util/xml_to_ruby.rb +105 -0
- data/mongo-ruby-driver.gemspec +103 -0
- data/test/mongo-qa/_common.rb +8 -0
- data/test/mongo-qa/admin +26 -0
- data/test/mongo-qa/capped +22 -0
- data/test/mongo-qa/count1 +18 -0
- data/test/mongo-qa/dbs +22 -0
- data/test/mongo-qa/find +10 -0
- data/test/mongo-qa/find1 +15 -0
- data/test/mongo-qa/gridfs_in +16 -0
- data/test/mongo-qa/gridfs_out +17 -0
- data/test/mongo-qa/indices +49 -0
- data/test/mongo-qa/remove +25 -0
- data/test/mongo-qa/stress1 +35 -0
- data/test/mongo-qa/test1 +11 -0
- data/test/mongo-qa/update +18 -0
- data/test/test_admin.rb +69 -0
- data/test/test_bson.rb +268 -0
- data/test/test_byte_buffer.rb +69 -0
- data/test/test_chunk.rb +84 -0
- data/test/test_collection.rb +249 -0
- data/test/test_connection.rb +101 -0
- data/test/test_cursor.rb +331 -0
- data/test/test_db.rb +185 -0
- data/test/test_db_api.rb +798 -0
- data/test/test_db_connection.rb +18 -0
- data/test/test_grid_store.rb +284 -0
- data/test/test_message.rb +35 -0
- data/test/test_objectid.rb +105 -0
- data/test/test_ordered_hash.rb +138 -0
- data/test/test_round_trip.rb +120 -0
- data/test/test_threading.rb +37 -0
- 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"
|
data/examples/info.rb
ADDED
@@ -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
|
data/examples/queries.rb
ADDED
@@ -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')
|
data/examples/simple.rb
ADDED
@@ -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
|
data/examples/strict.rb
ADDED
@@ -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')
|
data/examples/types.rb
ADDED
@@ -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
|
data/lib/mongo.rb
ADDED
@@ -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
|
data/lib/mongo/admin.rb
ADDED
@@ -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
|